An open API service indexing awesome lists of open source software.

https://github.com/dm3/clojure.joda-time

An idiomatic Clojure wrapper for Joda-Time
https://github.com/dm3/clojure.joda-time

clojure interop java joda-time library

Last synced: about 2 months ago
JSON representation

An idiomatic Clojure wrapper for Joda-Time

Awesome Lists containing this project

README

        

# Clojure.Joda-Time

## For Java 8 Users

If you are on Java 8 and don't need to support earlier Java versions, consider
using [Clojure.Java-Time](https://github.com/dm3/clojure.java-time)!

## For Java 7-and-below Users

[![Build Status](https://travis-ci.org/dm3/clojure.joda-time.png?branch=master)](https://travis-ci.org/dm3/clojure.joda-time)

An idiomatic Clojure wrapper for Joda-Time.

Main goals:

* Provide a consistent API for common operations with
instants, date-times, periods, partials and intervals.
* Provide an escape hatch from Joda types to clojure datastructures
and back where possible.
* Avoid reflective calls (this is a problem, because many types in Joda-Time
have similar functionality hidden under similarly named and overloaded
methods with no common interfaces).
* Provide an entry point into Joda-Time by freeing the user from importing most
of the Joda-Time classes.

Why use Clojure.Joda-Time over [clj-time](https://github.com/clj-time/clj-time)?

* You don't want to treat `DateTime` differently from other dates
* You need to operate on arbitrary `Periods` and `Partials`
* You want to have everything handy under a single namespace (two, if you want some sugar)
* You know the Joda-Time library and want to stay close to the original API
* You need to perform complicated operations on dates/periods/intervals/durations

This library employs a structured and comprehensive approach to exposing the
Joda-Time API to the Clojure world. It can help in the 10% of cases when
functionality provided by *clj-time* isn't enough. Try it out and see if it
sticks!

## Usage

Add the following dependency to your `project.clj`:

```clj
[clojure.joda-time "0.7.0"]
```

[API](http://dm3.github.io/clojure.joda-time/) of the Clojure.Joda-Time
consists of one namespace, namely: `joda-time`. For the purposes of this
guide, we will `use` the main namespace:

```clj
(refer-clojure :exclude [merge partial iterate format print contains? max min])
(use 'joda-time)
```

### An appetizer

First, a quick run through common use cases.

What is the current date and time in our time zone?

```clj
(def now (date-time))
=> #
```

In UTC?

```clj
(with-zone now (timezone :UTC))
=> #
```

In UTC but with the current timezone's time?

```clj
(in-zone now (timezone :UTC))
=> #
```

Without the time zone?

```clj
(def now-local (local-date-time))
=> #
```

Now, how would we go about a date five years and six months from now? First,
we would need to represent this period:

```clj
(period {:years 5, :months 6})
=> #
```

or as a sum:

```clj
(def five-years-and-some (plus (years 5) (months 6)))
=> #
```

Now for the date:

```clj
(def in-five-years (plus now five-years-and-some))
=> #

(def in-five-years-local (plus now-local five-years-and-some))
=> #
```

How many hours to the point five years and six months from now?

```clj
(hours-in now in-five-years)
=> 48191

(hours-in now-local in-five-years-local)
=> 48191
```

What if we want a specific date?

```clj
(def in-two-years (date-time "2015-12-10"))
=> #

(def in-two-years-local (local-date-time "2015-12-10"))
=> #
```

Same with positional arguments:

```clj
(def in-two-years-positional (date-time 2015 12 10))
=> #

(def in-two-years-local-positional (local-date-time 2015 12 10))
=> #
```

Does the interval from `now` to `in-five-years` contain this date?

```clj
(after? in-five-years in-two-years now)
=> true

(after? in-five-years-local in-two-years-local now-local)
=> true
```

Another way, actually using the interval type:

```clj
(contains? (interval now in-five-years) in-two-years)
=> true

(contains? (partial-interval now-local in-five-years-local) in-two-years-local)
=> true
```

What's the largest/smallest date in the list?

```clj
(max now in-five-years in-two-years)
=> #

(min now in-five-years in-two-years)
=> #
```

What about the current day of month?

```clj
(-> now (property :dayOfMonth) value)
=> 10

(-> now-local (property :dayOfMonth) value)
=> 10
```

The date at the last day of month?

```clj
(-> now (property :dayOfMonth) with-max-value)
=> #

(def new-years-eve (-> now-local (property :dayOfMonth) with-max-value)
=> #
```

We can also do this using the `accessors` namespace:

```clj
(require '[joda-time.accessors :as ja])

(value (ja/day-of-month-prop now))
=> #

(ja/day-of-month now)
=> 10

(ja/min-day-of-month now)
=> 1

(ja/max-day-of-month now)
=> 31

(ja/with-max-day-of-month now)
=> #

(ja/with-day-of-month now 20)
=> #
```

Every date at the last day of month from now?

```clj
(iterate plus new-years-eve (months 1))
=> (#
# ...)
```

In case we want to print the dates, we'll need a formatter:

```clj
(def our-formatter (formatter "yyyy/MM/dd"))
=> #

(print our-formatter now)
=> "2013/12/10"

(print our-formatter now-local)
=> "2013/12/10"
```

And what about parsing?

```clj
(parse-date-time our-formatter "2013/12/10")
=> #

(parse-local-date our-formatter "2013/12/10")
=> #
```

How should we convert between Joda dates and java.util/sql Dates?

```clj
(local-date now)
=> #

(date-time now-local)
=> #

(local-time now)
=> #

(date-time (local-time now))
=> #

(to-java-date now)
=> #inst "2013-12-10T11:07:16.000-00:00"

(to-java-date local-now)
=> #inst "2013-12-10T11:07:16.000-00:00"

(to-millis-from-epoch now)
=> 1386673636000
```

I hope you're interested. However, we've barely scratched the surface of the
API. Please, continue reading for a deeper look.

### Joda-Time entities

Clojure.Joda-Time provides a way to construct most of the time entities
provided by the Joda-Time. For example, given a `LocalDate` type in Joda-Time,
the corresponding construction function (I'll hijack the name "constructor" to
define construction functions) in Clojure.Joda-Time will be called
`local-date`.

A call to a constructor with a single argument goes through the following
pattern:

* given a `nil`, return a `nil` (**important**: this is different from the
default Joda-Time behaviour which usually has a default value for `nil`)
* given a number, convert it to `Long` and invoke the next rule,
* given a map, try to reconstruct a time entity from its map representation
(see **Properties** section) or invoke one of the constructors on the
corresponding Java class.
* given any object, pass it to the Joda-Time `ConverterManager`,

Mostly single-argument constructors are supported (except for a several cases
which we will look at later on) to avoid confusion with overloading.

By convention, a call to a constructor without arguments will return a time
entity constructed at the current date and time.

#### Instants

In Joda-Time [instants](http://www.joda.org/joda-time/key_instant.html) are
represented by `DateTime` and `Instant` types.

```clj
(date-time)
=> #

(instant)
=> #
```

You might have noticed that `DateMidnight` is not supported. This is because
the type is deprecated in the recent versions of Joda-Time. If you need a
midnight date, you should be use:

```clj
(.withTimeAtStartOfDay (date-time))
=> #
```

#### Partials

[Partials](http://www.joda.org/joda-time/key_partial.html) are represented by
`Partial`, `LocalDate`, `LocalDateTime`, `LocalTime`, `YearMonth` and `MonthDay`.

```clj
(partial)
=> #

(partial {:year 2013, :monthOfYear 12})
=> #

(local-date)
=> #

(local-date-time)
=> #

(local-time)
=> #

(year-month)
=> #

(month-day)
=> #
```

Multi-arity versions of the constructors are also supported. The fields not
provided will default to minimum values (0 for hours, minutes, seconds, millis;
1 for days).

#### Periods

Joda types for [periods](http://www.joda.org/joda-time/key_period.html) are
`Period`, `MutablePeriod`, `Years`, `Months`, `Weeks`, `Days`, `Hours`,
`Minutes` and `Seconds`.

Multi-field period accepts a map of several possible shapes. The first shape is
a map representation of a period, e.g.:

```clj
(period {:years 10, :months 10})
=> #
```

the second shape delegates to an appropriate Joda `Period` constructor, such
as:

```clj
(period {:start 0, :end 1000})
=> #
```

Period constructor can be called with two arguments, where the second argument
is the type of the period - either a `PeriodType` or a vector of duration field
name keywords:

```clj
(period 1000 [:millis])
=> #

(period {:start 0, :end 1000} (period-type :millis))
=> #
```

All of the single-field periods are constructed the same way:

```clj
(years 10)
=> #

(months 5)
=> #
```

Single-field period constructors can also be used to extract duration component
out of the multi-field period:

```clj
(years (period {:years 20, :months 10}))
=> #
```

When called on an interval, single-field period constructor will calculate the
duration (same as `Years.yearsIn`, `Months.monthsIn`, etc. in Joda-Time):

```clj
(minutes (interval (date-time "2008") (date-time "2010")))
=> #
```

You can get the value of the single-field period using the helper functions in
`joda-time.accessors`:

```clj
(ja/minutes (period {:hours 20, :minutes 10}))
=> 10
```

To get the standard duration of the period, use one of the `-in` functions:

```clj
(minutes-in (period {:hours 20, :minutes 10}))
=> 1210
```

Be aware that standard duration doesn't work for periods containing months or
years as they are variable length.

You can also query the type of the period:

```clj
(period-type (period))
=> #

(period-type (years 5))
=> #
```

`period-type` can also construct a `PeriodType` out of the duration field type
names:

```clj
(period-type :years)
=> #

(period-type :years :months :weeks :days)
=> #
```

You can also convert the `PeriodType` back to a seq of keywords:

```clj
(period-type->seq (period-type (years 10)))
=> [:years]
```

#### Durations

[Duration](http://www.joda.org/joda-time/key_duration.html) is the most
boring time entity. It can be constructed in the following way:

```clj
(duration 1000)
=> #
```

Duration constructor also accepts a map (you can find the whole set of options
in the docstring):

```clj
(duration {:start (date-time), :period (years 5)})
=> #
```

#### Intervals

[Intervals](http://www.joda.org/joda-time/key_interval.html) consist of a
single type inherited from Joda Time:

```clj
(interval 0 1000)
=> #
```

and one additional type defined in this library:

```clj
(partial-interval (partial {:year 0}) (partial {:year 2010}))
=> #joda_time.interval.PartialInterval{:start #, :end #}

(partial-interval (local-date "2010") (local-date "2013"))
=> #joda_time.interval.PartialInterval{:start #,
:end #}
```

record representation of the partial interval is an implementation detail and
should not be relied upon.

As you can see, the interval constructor accepts start and end arguments -
either milliseconds from epoch, instants or date-times. Constructor also
accepts a map with different combinations of `start`, `end`, `duration` and
`period` parameters, same as Joda-Time `Interval` constructors:

```clj
(interval {:start 0, :end 1000})
=> #

(interval {:start 0, :duration 1000})
=> #

(interval {:start 0, :period (seconds 1)})
=> #
```

Both instant and partial intervals support a common set of operations on their
start/end (string representation of the interval is shortened for readability):

```clj
(def i (interval 0 10000))
=> #

(move-start-to i (instant 5000))
=> #

(move-end-to i (instant 5000))
=> #

(move-start-by i (seconds 5))
=> #

(move-end-by i (seconds 5))
=> #

(move-end-by i (seconds 5))
=> #
```

intervals can also be queried for several properties:

```clj
(start i)
=> #

(end i)
=> #

(contains? i (interval 2000 5000))
=> true

(contains? i (interval 0 15000))
=> false

(contains? (interval (date-time "2010") (date-time "2012"))
(date-time "2011"))
=> true

(overlaps? (interval (date-time "2010") (date-time "2012"))
(interval (date-time "2011") (date-time "2013")))
=> true

(abuts? (interval (date-time "2010") (date-time "2012"))
(interval (date-time "2012") (date-time "2013")))
=> true
```

we can also calculate interval operations present in the Joda-Time:

```clj
(overlap (interval (date-time "2010") (date-time "2012"))
(interval (date-time "2011") (date-time "2013")))
=> #

(gap (interval (date-time "2010") (date-time "2012"))
(interval (date-time "2013") (date-time "2015")))
=> #
```

All of the above functions work with partial intervals the same way.

#### Timezones and Chronologies

Timezones can be constructed through the `timezone` function given the
(case-sensitive) timezone ID:

```clj
(timezone)
=> #

(timezone "Europe/Vilnius")
=> #

(timezone :UTC)
=> #
```

Chronologies are constructed using `chronology` with a lower-case chronology
type and an optional timezone argument:

```clj
(chronology :coptic)
=> #

(chronology :coptic :UTC)
=> #

(chronology :iso (timezone :UTC))
=> #
```

#### Formatters

Formatters (printers and parsers) are defined through the `formatter` function:

```clj
(formatter "yyyy-MM-dd")
=> #
```

All of the ISO formatter defined by Joda-Time in the `ISODateTimeFormat` class
can be referenced by the appropriate keywords:

```clj
(formatter :date-time)
=> #
```

Formatters may also be composed out of multiple patterns and other formatters:

```clj
(def fmt (formatter "yyyy/MM/dd" :date-time (formatter :date)))
=> #
```

the resulting formatter will print according to the first pattern:

```clj
(print fmt (date-time "2010"))
=> "2010/01/01"
```

and parse all of the provided formats. Dates can be parsed from strings using
a family of `parse` functions:

```clj
(parse-date-time fmt "2010/01/01")
=> #

(parse-mutable-date-time fmt "2010/01/01")
=> #

(parse-local-date fmt "2010/01/01")
=> #

(parse-local-date-time fmt "2010/01/01")
=> #

(parse-local-time fmt "2010/01/01")
=> #
```

### Conversions

Joda-Time partials, instants and date-times can be converted back and forth
using the corresponding constructors:

```clj
(def now (date-time))
=> #

(local-date now)
=> #

(local-date-time now)
=> #

(date-time (local-date now))
=> #

(instant (local-date now))
=> #

(date-time (partial {:hourOfDay 12}))
=> #
```

As you can see, conversions to date-time do not force the UTC timezone and set
the missing fields to the unix epoch. If we want to construct a date-time out
of a partial and fill the missing fields in another way, we could use the map
constructor:

```clj
(date-time {:partial (partial {:millisOfDay 1000}), :base now})
=> #
```

You can customize date-time construction from partials by registering a custom
`InstantConverter` in the Joda `ConverterManager`.

We can also convert Joda date entities to native Java types:

```clj
(to-java-date now)
=> #inst "2013-12-10T11:07:16.000-00:00"

(type (to-sql-date now))
=> java.sql.Date

(to-sql-timestamp now)
=> #inst "2013-12-10T11:07:16.000000000-00:00"

(to-millis-from-epoch now)
=> 1386673636000
```

Of course, native Java types can be converted between themselves:

```clj
(to-java-date 1386673636000)
=> #inst "2013-12-10T11:07:16.000-00:00"

(to-java-date "2013-12-10")
=> #inst "2013-12-09T22:00:00.000-00:00"
```

Don't worry about the seemingly incorrect java date in the last example. We get
an `2013-12-09` *inst* out of a `2013-12-10` string because *inst* is printed
in the UTC timezone. We can check that everything is OK by converting back to
the Joda date-time:

```clj
(= (date-time 2013 12 10) (date-time (to-java-date "2013-12-10")))
=> true
```

Same with local dates:

```clj
(= (local-date-time 2013 12 10) (local-date-time (to-java-date "2013-12-10")))
=> true
```

Even more conversions:

```clj
(= now (date-time (local-date-time (to-java-date now))))
=> true
```

### Properties

Properties allow us to query and act on separate fields of date-times,
instants, partials and periods.

We can query single properties by using the `property` function:

```clj
(value (property (date-time "2010") :monthOfYear))
=> 1

(max-value (property (instant "2010") :monthOfYear))
=> 12

(min-value (property (partial {:monthOfYear 10}) :monthOfYear))
=> 1

(with-value (property (period {:years 10, :months 5}) :years) 15)
=> #
```

Property expressions read better when chained with threading macros:

```clj
(-> (date-time "2010") (property :monthOfYear) value)
=> 1
```

Clojure loves maps, so I've tried to produce a map interface to the most
commonly used Joda-time entities. Date-times, instants, partials and periods
can be converted into maps using the `properties` function which uses
`property` under the hood. For example, a `DateTime` contains a whole bunch of
properties - one for every `DateTimeFieldType`:

```clj
(def props (properties (date-time)))
=> {:centuryOfEra #, ...}

(keys props)
=> (:centuryOfEra :clockhourOfDay :clockhourOfHalfday
:dayOfMonth :dayOfWeek :dayOfYear
:era :halfdayOfDay :hourOfDay :hourOfHalfday
:millisOfDay :millisOfSecond :minuteOfDay
:minuteOfHour :monthOfYear :secondOfDay :secondOfMinute
:weekOfWeekyear :weekyear :weekyearOfCentury
:year :yearOfCentury :yearOfEra)
```

Now we can get the values for all of the fields (we'll cheat and use
`flatland.useful.map/map-vals`):

```clj
(useful/map-vals props value)
=> {:year 2013, :monthOfYear 12, :dayOfMonth 8,
:yearOfCentury 13, :hourOfHalfday 9, :minuteOfDay 1305, ...}
```

although this is better achieved by calling `as-map` convenience function.

As you can see, map representations allow us to plug into the rich set of
operations on maps provided by Clojure and free us from using
`DateTimeFieldType` or `DurationFieldType` classes directly.

Partials contain a smaller set of properties, for example:

```clj
(-> (partial {:year 2013, :monthOfYear 12})
properties
(useful/map-vals value))
=> {:year 2013, :monthOfYear 12}
```

Properties allow us to perform a bunch of useful calculations, such as getting
the date for the last day of the current month:

```clj
(-> (date-time) (property :dayOfMonth) with-max-value)
```

or get the date for the first day:

```clj
(-> (date-time) (properties :dayOfMonth) with-min-value)
```

The above can also be done using the `joda-time.accessors` namespace which
defines a function for every possible date-time field supported by Joda-Time.

```clj
(ja/with-max-day-of-month (date-time))
(ja/with-min-day-of-month (date-time))
```

We can also solve a common problem of getting a sequence of dates for the last
day of month:

```clj
(iterate #(ja/with-max-day-of-month (plus %1 %2)) (local-date) (months 1))
=> (# # ...)
```

Note that `iterate` is defined in the `joda-time` namespace.

### Operations

One of the most useful parts of the Joda-Time library is it's rich set of
arithmetic operations allowed on the various time entities. You can sum periods
and durations together or add them to date-times, instants or partials. You
can compute the difference of durations and periods or subtract them from
dates. You can also negate and compute absolute values of durations and
periods.

Here's an example of using a `plus` operation on a date-time:

```clj
(def now (date-time "2010-01-01"))
=> #

(plus now (years 11))
=> #

(def millis-10sec (* 10 1000))
=> 10000

(def duration-10sec (duration millis-10sec)
=> #

(plus now (years 11) (months 10) (days 20) duration-10sec millis-10sec)
=> #
```

same with instants:

```clj
(plus (instant "2010-01-01") (years 11))
=> #
```

with partials:

```clj
(def now (local-date 2010 1 1))
=> #

(plus now (years 11) (months 10) (days 20))
=> #
```

or with periods:

```clj
(def p (plus (years 10) (years 10) (months 10)))
=> #

(period-type p)
=> #
```

or with durations:

```clj
(plus (duration 1000) (duration 1000) 1000)
=> #
```

Obviously, you can `minus` all the same things you can `plus`:

```clj
(minus now (years 11) (months 10))
=> #

(minus (duration 1000) (duration 1000) 1000)
=> #

(minus (years 10) (years 10) (months 10))
=> #
```

As you can see, durations and periods can become negative. Actually, we can
turn a positive period into a negative one by using `negate`:

```clj
(negate (years 10))
=> #

(negate (duration 1000))
=> #
```

and we can take an absolute value of a period or a duration:

```clj
(abs (days -20))
=> #

(abs (days 20))
=> #

(abs (duration -1000))
=> #
```

There is also a `merge` operation which is supported by periods and partials.
In case of a period, `merge` works like `plus`, only the values get overwritten
like when merging maps with `clojure.core/merge`:

```clj
(merge (period {:years 10, :months 6}) (years 20) (days 10))
=> #

(merge (local-date) (local-time))
=> #

(merge (local-date) (local-time) (partial {:era 0})
=> #
```

Essentially, merging several partials or periods together is the same as
converting them to their map representations with `as-map`, merging maps and
converting the result back into a period/partial, only in a more efficient way.

It's important to note that operations on mutable Joda-Time entities aren't
supported. You are expected to chain methods through java interop.

## License

Copyright © 2013 Vadim Platonov

Distributed under the MIT License.