Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Java 8 Date-Time API for Clojure

License

NotificationsYou must be signed in to change notification settings

dm3/clojure.java-time

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Clojars Project

A Clojure wrapper for Java 8 Date-Time API.

Note: This library has no relation to Clojure's (or Java's) core team.It's naming is legacy and preserved for backwards compatibility reasons.

Rationale

Main goals:

  • Provide a consistent API for common operations withinstants, date-times, zones and periods.
  • Provide an escape hatch from Java types to clojure data structures.
  • Avoid reflective calls.
  • Provide an entry point into Java-Time by freeing the user from importing mostof the Java-Time classes.

Why use Clojure.Java-Time overclj-timeorClojure.Joda-Time?

  • You don't want to have a dependency on the Joda-Time library
  • You already use Java 8
  • You prefer as little Java interop code as possible

This library employs a structured and comprehensive approach to exposing theJava 8 Date-Time API to the Clojure world. It's very similar toClojure.Joda-Time in its design goals and overall feeling, so if you ever usedthat you will feel at home!

Why use Clojure.Java-Time overcljc.java-time withtick?

  • You only plan on running on the JVM
  • You prefer a singlerequire over multiple ones

I don't see any reasons except for aesthetical pleasure and existing knowledge to choose oneover the other. However, I have neither used or benchmarked Cljc.Java-Time and Tick so my endorsementis purely on the merits of a broader feature set.

Documentation

What's different in Java Time API?

If you already used Joda Time before you might think: "What in the world couldthey do better?". After all, Joda-Time already provides a pretty comprehensiveset of tools for dealing with time-related concepts. Turns out, it's a tad morecomplicated than it has to be. Also, a few concepts have faulty designs whichlead to hard to fix bugs and misuse. You can see the birds-eye view of changesand some of the rationale on the author's (Stephen Colebourne) blog:

You can also take a look at acomprehensive comparison by theTime4J authors.

Usage

Add the following dependency to yourdeps.edn:

clojure.java-time/clojure.java-time {:mvn/version"1.4.3"}

or to yourproject.clj orbuild.boot:

[clojure.java-time"1.4.3"]

TheAPI of the Clojure.Java-Timeconsists of one namespace, namelyjava-time.api. For the purposes of this guide,we willrequire the main namespace:

(require '[java-time.api:as jt];; for REPL experimentation         'java-time.repl)

Concept run-through

The Java Time API may seem daunting. Instead of a singlejava.util.Date you haveaZonedDateTime,OffsetDateTime,LocalDateTime,Instant, and othertypes. You would be well served by reading the official documentation for theJava Time API,but we'll also do a quick run-through here.

Local Dates and/or Times

LocalDate,LocalTime andLocalDateTime are used to represent a date, timeand date-time respectively without an offset or a time zone. The local time entitiesare used to represent human-based dates/times. They are a good fit for representingthe time of various events:

Example usage:

(jt/local-date201510);=> #<java.time.LocalDate 2015-10-01>(jt/local-time10);=> #<java.time.LocalTime 10:00>(jt/local-date-time201510);=> #<java.time.LocalDateTime 2015-10-01T00:00>

Zoned Dates

There are two types which deal with zones:

They do pretty much what you would expect from their name.You can think of theOffset time as a more concrete version of theZonedtime. For example, the same time zone can have different offsets throughout theyear due to DST or governmental regulations.

(jt/offset-time10);=> #<java.time.OffsetTime 10:00+01:00>(jt/offset-date-time201510);=> #<java.time.OffsetDateTime 2015-10-01T10:00+01:00>(jt/zoned-date-time201510);=> #<java.time.ZonedDateTime 2015-10-01T10:00+01:00[Europe/London]>

Offset/Zone times only take the offset/zone as the last arguments for themaximum arity constructor. You can influence the zone/offset by using thejt/with-zoneorjt/with-offset functions, like so:

(jt/with-zone (jt/zoned-date-time201510)"UTC");=> #<java.time.ZonedDateTime 2015-10-01T00:00Z[UTC]>(jt/with-zone-same-instant (jt/zoned-date-time201510)"UTC");=> #<java.time.ZonedDateTime 2015-09-30T23:00Z[UTC]>(jt/with-clock (jt/system-clock"UTC")  (jt/zoned-date-time201510));=> #<java.time.ZonedDateTime 2015-10-01T00:00Z[UTC]>

Instant

AnInstant is used to generate a time stamp representing machine time. Itdoesn't have an offset or a time zone. You can think of it as of a number ofmilliseconds since epoch (1970-01-01T00:00:00Z). An instant is directlyanalogous tojava.util.Date:

(jt/instant);=> #<java.time.Instant "2015-09-26T05:25:48.667Z">(java.util.Date.);=> #inst "2015-09-26T05:25:50.118-00:00"

Every other date entity can be converted to an instant (local ones will requirean additional zone information).

Period and Duration

Java Time Period entities are considerably simpler than the Joda-Time periods.They are fixed containers of years, months and days. You can use them torepresent any period of time with a granularity larger or equal to a single day.Duration, on the other hand, represents a standard duration less than or equalto a single standard (24-hour) day.

Caution

The current incarnation of the library is relatively slow while calling the 2-3arityzoned-date-time/offset-time/offset-date-time constructors for thefirst time in a given Clojure runtime. If you need predictable latency at thetime of the first call in your business logic, please warm theconstructors you are going to use up by calling them beforehand, e.g.:

(defnwarm-up []  (jt/zoned-date-time201511)  (jt/zoned-date-time20151)  (jt/zoned-date-time2015))

The "constructor" here refers to an arity of a function together with its typesignature. For example, a(jt/zoned-date-time 2015) and(jt/zoned-date-time (jt/system-clock))are different constructors.

An appetizer

First, let's do a quick run through common use cases.

What is the current date?

(defnow (jt/local-date));=> #object[java.time.LocalDate "2015-09-27"]

What's the next day?

(jt/plus now (jt/days1));=> #object[java.time.LocalDate "2015-09-28"]

The previous day?

(jt/minus now (jt/days1));=> #object[java.time.LocalDate "2015-09-26"]

Three days starting atnow?

(take3 (jt/iterate jt/plus now (jt/days1)));=> (#object[java.time.LocalDate "2015-09-27"];    #object[java.time.LocalDate "2015-09-28"];    #object[java.time.LocalDate "2015-09-29"])

When is the first Monday in month?

(jt/adjust now:first-in-month:monday);=> #object[java.time.LocalDate "2015-09-07"]

Date with some of its fields truncated:

(jt/truncate-to (jt/local-date-time20159281015):days);=> #object[java.time.LocalDateTime "2015-09-28T00:00"]

Date-time adjusted to the given hour:

(jt/adjust (jt/local-date-time20159281015) (jt/local-time6));=> #object[java.time.LocalDateTime "2015-09-28T06:00"]

The latest of the given dates?

(jt/max (jt/local-date2015920) (jt/local-date2015928) (jt/local-date201591));=> #object[java.time.LocalDate "2015-09-28"]

The shortest of the given durations?

(jt/min (jt/duration10:seconds) (jt/duration5:hours) (jt/duration3000:millis));=> #object[java.time.Duration "PT3S"]

Get the year field out of the date:

(jt/as (jt/local-date2015928):year);=> 2015

Get multiple fields:

(jt/as (jt/local-date2015928):year:month-of-year:day-of-month);=> (2015 9 28)

Get the duration in a different unit:

(jt/plus (jt/hours3) (jt/minutes2));=> #object[java.time.Duration "PT3H2M"](jt/as *1:minutes);=> 182

Format a date:

(jt/format"MM/dd" (jt/zoned-date-time2015928));=> "09/28"

Parse a date:

(jt/local-date"MM/yyyy/dd""09/2015/28");=> #object[java.time.LocalDate "2015-09-28"]

Zoned date-times and offset date-times/times always take the zone/offset as thelast argument. Offsets can be specified as float values:

(jt/zone-offset+1.5);=> #<java.time.ZoneOffset +01:30>(jt/zone-offset-1.5);=> #<java.time.ZoneOffset -01:30>

Compare dates:

(jt/before? (jt/year2020) (jt/year2021));=> true(jt/after? (jt/year2021) (jt/year2021));=> false(let [expiration-date (jt/year2010)      purchase-date (jt/year2010)]  (jt/not-before? expiration-date purchase-date));=> true(let [start-date (jt/year2011)      cutoff-date (jt/year2010)]  (jt/not-after? start-date cutoff-date));=> false

Conversions

Time entities can be converted to other time entities if the target containsless information, e.g. (assuming we're in UTC time zone):

(jt/zoned-date-time (jt/offset-date-time20159281));=> #object[java.time.ZonedDateTime "2015-09-28T01:00Z"](jt/instant (jt/offset-date-time20159281));=> #object[java.time.Instant "2015-09-28T01:00:00Z"](jt/offset-time (jt/offset-date-time20159281));=> #object[java.time.OffsetTime "01:00Z"](jt/local-date-time (jt/offset-date-time20159281));=> #object[java.time.LocalDateTime "2015-09-28T01:00"](jt/local-time (jt/offset-time1));=> #object[java.time.LocalTime 0x3a3cd6d5 "01:00"]

Converting an Instant to ZonedDateTime requires a time zone:

(jt/zoned-date-time (jt/instant100)"UTC");=> #object[java.time.ZonedDateTime 0x291777c0 "1970-01-01T00:00:00.100Z[UTC]"]

Legacy Date-Time Types

Any date which can be converted to an instant, can also be converted to ajava.util.Date:

(jt/java-date (jt/zoned-date-time2015928));=> #inst "2015-09-27T22:00:00.000-00:00"(jt/java-date50000);=> #inst "1970-01-01T00:00:50.000-00:00"

An instance ofjava.util.Date serves the same purpose as the newjava.time.Instant. It's a machine timestamp which isn't aware of thetime zone. Please, do not get confused by the way it is printed by the Clojureprinter - the UTC time zone is applied during formatting.

Sometimes you'll have to work with the legacyjava.sql.{Date,Time,Timestamp}types. The correspondence between the legacy types and the new Date-Timeentities is as follows:

  • java.sql.Date <->java.time.LocalDate
  • java.sql.Timestamp <->java.time.LocalDateTime
  • java.sql.Time <->java.time.LocalTime
(jt/sql-date2015928);=> #inst "2015-09-27T22:00:00.000-00:00"(jt/sql-timestamp20159281020304000000);=> #inst "2015-09-28T09:20:30.004-00:00"(jt/sql-time102030);=> #inst "1970-01-01T09:20:30.000-00:00"

The results of the above calls get printed as#inst because all of thejava.sql.{Date,Time,Timestamp} are subtypes ofjava.util.Date.Coincidentally, this makes it impossible to plug thejava.sql.* types intothe Clojure.Java-Time conversion graph.

Conversions to the legacy types also go the other way around:

(jt/local-date (jt/sql-date2015928));=> #object[java.time.LocalDate "2015-09-28"](jt/local-date-time (jt/sql-timestamp20159281020304000000));=> #object[java.time.LocalDateTime "2015-09-28T10:20:30.004"](jt/local-time (jt/sql-time102030));=> #object[java.time.LocalTime "10:20:30"]

Three-Ten Extra

If you add an optional[org.threeten/threeten-extra "1.2"] dependency to theproject, you will get anInterval,AmPm,DayOfMonth,DayOfYear,Quarter andYearQuarter data types as well as a couple more adjusters.

An interval can be constructed from two entities that can be converted toinstants:

(jt/interval (jt/offset-date-time201511) (jt/zoned-date-time201611));=> #<org.threeten.extra.Interval 2015-01-01T00:00:00Z/2016-01-01T00:00:00Z>(jt/move-start-by *1 (jt/duration5:days));=> #<org.threeten.extra.Interval 2015-01-06T00:00:00Z/2016-01-01T00:00:00Z>(jt/move-end-by *1 (jt/duration5:days));=> #<org.threeten.extra.Interval 2015-01-06T00:00:00Z/2016-01-06T00:00:00Z>(jt/contains? *1 (jt/offset-date-time201511));=> false

Joda-Time

Bonus! If you have Joda Time on the classpath (either directly, or viaclj-time), you can seamlessly convert from Joda Time to Java Time types:

(java-time.repl/show-path org.joda.time.DateTime java.time.OffsetTime);=> {:cost 2.0,;    :path [[#<java_time.graph.Types@15e43c24 [org.joda.time.DateTime]>;            #<java_time.graph.Types@78a2235c [java.time.Instant java.time.ZoneId]>];           [#<java_time.graph.Types@6d8ded1a [java.time.Instant java.time.ZoneId]>;            #<java_time.graph.Types@5360f6ae [java.time.OffsetTime]>]]}(jt/offset-time (org.joda.time.DateTime/now));=> #<java.time.OffsetTime 22:00:00.000000000-00:00>

Clojure 1.9 added anInstprotocol which is implemented forjava.util.Date andjava.time.Instant bydefault. If you're stuck on Joda-Time, you can extend theorg.joda.time.ReadableInstant, which includes bothInstant andDateTimeusing the following:

(jt/when-joda-time-loaded  (extend-type org.joda.time.ReadableInstant    Inst (inst-ms* [inst] (.getMillis inst))))

This snippet isn't included in the Clojure.Java-Time code by default as boththeInst protocol and the Joda-Time types are external to the library.

Clocks

Java Time introduced a concept ofClock - a time entity which can seed thedates, times and zones. However, there's no built-in facility which would allowyou to influence the date-times created using default constructors à la Joda'sDateTimeUtils/setCurrentMillisSystem. Clojure.Java-Time tries to fix that withthewith-clock macro and the correspondingwith-clock-fn function:

(jt/zone-id);=> #<java.time.ZoneRegion Europe/London>(jt/with-clock (jt/system-clock"UTC")  (jt/zone-id));=> #<java.time.ZoneRegion UTC>

In addition to the built-injava.time clocks, we provide a Mock clock whichcan be very handy in testing:

(defclock (jt/mock-clock0"UTC"));=> #'user/clock(jt/with-clock clock  (jt/instant));=> #object[java.time.Instant "1970-01-01T00:00:00Z"](jt/advance-clock! clock (jt/plus (jt/hours5) (jt/minutes20)));=> nil(jt/with-clock clock  (jt/instant));=> #object[java.time.Instant "1970-01-01T05:20:00Z"](jt/set-clock! clock0);=> nil(jt/with-clock clock  (jt/instant));=> #object[java.time.Instant "1970-01-01T00:00:00Z"]

Clock overrides works for all of the date-time types.

Fields, Units and Properties

Date-Time entities are composed of date fields, while Duration entities arecomposed of time units. You can see all of the predefined fields and unitsvia thejava-time.repl ns:

(java-time.repl/show-fields);=> (:aligned-day-of-week-in-month;    :aligned-day-of-week-in-year;    :aligned-week-of-month;    :aligned-week-of-year;    :am-pm-of-day;    :clock-hour-of-am-pm;    ...)
(java-time.repl/show-units);=> (:centuries;    :days;    :decades;    :eras;    :forever;    :half-days;    ...)

You can obtain any field/unit like this:

(jt/field:year);=> #object[java.time.temporal.ChronoField "Year"](jt/unit:days);=> #object[java.time.temporal.ChronoUnit "Days"](jt/field (jt/local-date2015):year);=> #object[java.time.temporal.ChronoField "Year"]

You can obtain all of the fields/units of the temporal entity:

(jt/fields (jt/local-date));=> {:proleptic-month #object[java.time.temporal.ChronoField ...}(jt/units (jt/duration));=> {:seconds #object[java.time.temporal.ChronoUnit "Seconds"],;    :nanos #object[java.time.temporal.ChronoUnit "Nanos"]}

By themselves the fields and units aren't very interesting. You can get therange of valid values for a field and a duration between two dates, but that'sabout it:

(jt/range (jt/field:year));=> #object[java.time.temporal.ValueRange "-999999999 - 999999999"](jt/range (jt/field:day-of-month));=> #object[java.time.temporal.ValueRange "1 - 28/31"](jt/time-between (jt/local-date20159) (jt/local-date2015928):days);=> 27

Fields and units become interesting in conjunction with properties. Java-Timedoesn't support the concept of properties which is present in Joda-Time. Thereare reasons for that which I feel are only valid in a statically-typed API likeJava's. In Clojure, properties allow expressing time entity modifications andqueries uniformly across all of the entity types.

(defprop (jt/property (jt/local-date2015228):day-of-month));=> #java_time.temporal.TemporalFieldProperty{...}(jt/value prop);=> 28(jt/with-min-value prop);=> #object[java.time.LocalDate "2015-02-01"](jt/with-value prop20);=> #object[java.time.LocalDate "2015-02-20"](jt/with-max-value prop);=> #object[java.time.LocalDate "2015-02-28"](jt/properties (jt/local-date2015928));=> {:proleptic-month #java_time.temporal.TemporalFieldProperty{...}, ...}

Implementation Details

Most of the temporal entity constructors with arities 1 to 3 use the conversiongraph underneath. This provides for a very flexible way of defining theconversions while avoiding huge conditional statements and multiple definitionsof the identical conversion logic. However, the flexibility comes with a cost:

  1. The first call to a constructor will take along time as it will try tofind a path in the conversion graph. Subsequent calls will reuse the path.
  2. It's not trivial to evaluate the impact of adding and removing conversionsboth on the performance and the conversion path chosen for certain arguments.
  3. You might get nonsensical results for some of the paths in the graph thatyou might expect would make sense.

Hopefully, the performance issue will be resolved in the future...

You can play with the conversion graph using the following helpers:

(java-time.repl/show-path org.joda.time.DateTime java.time.OffsetTime);=> {:cost 2.0,;    :path [[#<java_time.graph.Types@15e43c24 [org.joda.time.DateTime]>;            #<java_time.graph.Types@78a2235c [java.time.Instant java.time.ZoneId]>];           [#<java_time.graph.Types@6d8ded1a [java.time.Instant java.time.ZoneId]>;            #<java_time.graph.Types@5360f6ae [java.time.OffsetTime]>]]}(java-time.repl/show-graph);=> {1;    {org.threeten.extra.DayOfYear;     [[#object[java_time.graph.Types "[java.lang.Number]"];       #object[java_time.graph.Conversion "Cost:1.0"]]],;     java.lang.Number;     [[#object[java_time.graph.Types "[java.time.Instant]"];       #object[java_time.graph.Conversion "Cost:1.0"]];     ...}}

About

Java 8 Date-Time API for Clojure

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors27


[8]ページ先頭

©2009-2025 Movatter.jp