#time/date "2039-01-01"A Clojure(Script) library which provides tagged literals for date-time objects
There are two artifacts:
. on js runtimes the objects are those of a java.time clone, called 'js-joda'
on js-runtimes the objects are from the platform Temporal API - see Chronos lib for a cross-platform date-time library based on java.time and Temporal.
This talk provides some more background.
Note : To use this from Clojurescript, you must have at least version 1.11.51. If using shadow-cljs, it must be at least version 2.19.3
Reader Literals were a headline new feature in Clojure 1.4 and with that came built-in support for the #inst tag. The #inst tag is a part of the edn spec, where it is defined as representing an instant in time, which means a point in time relative to UTC that is given to (at least) millisecond precision.
In Clojure(script), #inst is read as a legacy platform Date object by default, but as is made clear by the edn spec and by this talk from Rich Hickey the default implementation is just that: #inst may be read to whatever internal representation is useful to a consuming program. For example a program running on the jvm could read #inst tags to java.time.Instant (or java.time.OffsetDateTime if wishing to preserve the UTC offset information). It seems to me unfortunate that Clojure(script) provided defaults for #inst because users may not realise it is 'just a default', but that's just my opinion. My guess is that Clojure is trying to be both simple and easy in this case.
When conveying data using edn format, built-in tagged elements are preferred to user defined elements.
There are many kinds of things relating to date and time that are not an instant in time, so #inst would not be an appropriate way to tag them. For example the month of a particular year such as 'January 1990' or a calendar date such as 'the first of June, 3030'. There are no built-in edn tags for these.
Note that the default Clojure reader behaviour is to accept partially specified instants, such as #inst "2020" (and read that to a Date with millisecond precision) - but this is specific to the Clojure implementation and not valid edn (AFAIK).
Clojure provides two mechanisms for printing objects - abstract and concrete as this code printing the same object shows:
(let [h (java.util.HashMap.)]
{:abstract (pr-str h)
:concrete (binding [*print-dup* true]
(pr-str h))})
=> {:abstract "{}", :concrete "#=(java.util.HashMap. {})"}When at the REPL and using say, persistent datastructures, the concrete representation is rarely useful to know, but when dealing with date-time objects it is always useful. Also, the string output can be passed back to the reader to recreate the same internal representation again, which is known as round-tripping.
The default readers and printers of platform date objects don't allow round-tripping, the reason for which is unknown.
This is relevant to the java.time types which logically correspond to #inst (java.time.Instant and java.time.OffsetDateTime). This library contains specific readers and printers for those objects so that they do round-trip. When conveying these objects out of process in edn format, they should be tagged as #inst of course. To do that, simply provide your own implementation of clojure.core/print-method for those types. With *print-dup* true, the concrete type will still show.
Chronos is a cross-platform date-time library based on java.time and Temporal.
cljc.java-time is a one for one mapping of the classes and methods from java.time into a Clojure(Script) library
The tick library is an intuitive Clojure(Script) library for dealing with time, intended as a replacement for clj-time. It bundles this library and enables time-literals printing
by default.
Note: IMHO one should avoid putting tag literals in source code because a tag can be bound to different readers in different contexts, but code will be expecting some specific API. Additionally one has to add a side-effecting require of the tag-reader-namespace to make sure the reader function (ie the one you hope is bound to the tag) exists. tl;dr - it is too magical.
The library includes the magic file data_readers.cljc which Clojure and the Clojurescript compilers will look for.
Supported literals:
#time/date "2039-01-01"
#time/date-time "2018-07-25T08:08:44.026"
#time/zoned-date-time "2018-07-25T08:09:11.227+01:00[Europe/London]"
#time/instant "2018-07-25T07:10:05.861Z"
#time/time "08:12:13.366"
#time/year-month "3030-01"
#time/duration "PT1S"
; the following are only available in the java.time/js-joda version
#time/month "JUNE"
#time/period "P1D"
#time/offset-date-time "2018-07-25T08:11:54.453+01:00"
#time/year "3030"
#time/zone "Europe/London"
#time/day-of-week "TUESDAY"
For example, in a Clojure repl:
;In a cljs repl
(require '[java.time])
(println #time/duration "PT1S")
; => #object[Duration PT1S]
; Now, include printing and edn reading
(require '[time-literals.read-write])
(time-literals.read-write/print-time-literals-cljs!)
(println #time/duration "PT1S")
; => #time/duration "PT1S"
As with any non-core tagged literal, the tag reader functions referred to from a data_readers file must be loaded before the forms can be read.
(require '[time-literals.read-write]) ;; For printing/writing
(time-literals.read-write/print-time-literals-clj!)
(println #time/duration "PT1S")
(require '[time-literals.read-write])
(time-literals.read-write/print-time-literals-clj!)
Printing will now automatically change, for example re run the println above
Read edn like this:
(clojure.edn/read-string {:readers time-literals.read-write/tags} "#time/date \"2011-01-01\"")
If you only need Instant from java.time/jsr-310, you could just rebind the tag readers and printer fns for
#inst. Note
however that Clojure's inst format is based on RFC3339 and so is actually closer to the default format
for java.time.OffsetDateTime
(for example to
read an inst tag, OffsetDateTime/parse will work ok, but Instant/parse will not). But... I think in most use
cases Instant is preferred over OffsetDateTime as a representation of an absolute point in time. Rebinding inst
reader and printer might
also lead
to problems where a programmer needs to work with both java.util.Date (or js/Date) and java.time.Instant objects - for example if using
Datomic - it only works with java.util.Date objects.
There is a similar library, java-time-literals but this currently only works
on the jvm, and also doesn't provide a way to read edn with the literals (via clojure.edn/read-string or cljs.reader). The naming of tags
in this library (time-literals) follows the tick convention, for example
#time/date for LocalDate, instead of #time/ld as in java-time-literals.
TL;DR it is sufficiently ambiguous.
This library reads/writes objects from java.time, js-joda and Temporal. It would be feasible to use the same set of tags with different time libraries, either on the jvm or other elsewhere. If the namespace were 'jsr310' or 'java.time' that would be too implementaion specific.
A set of literals for the ISO-8601 specification would probably be the ideal for date interchange, with literals such as `#iso8601/ordinal-date"1981-095"``
However, although the Java.time domain overlaps significantly with concepts in ISO-8601, there are differences. For example, the ISO 'Duration' is roughly a combination of java.time.Duration and java.time.Period, and the IANA time zone names (such are you see in the literal representation of ZonedDateTime) are not part of ISO.
Copyright © 2025 Widd Industries
Distributed under the MIT License