TIL: Clojure #inst Reader Literal Shortcuts

I’m a big fan of the Today I Learned microblog that Hashrocket runs. The TIL format of simply sharing what is new and useful to the author encourages Hashrocket’s engineers to share practical knowledge when they might not otherwise find the time if they needed to write a wordy introduction or conduct a topical deep dive. Beyond that, it also gives license for the author to share something that is new to him or her–and likely many others–without any implicit suggestion of blazing new technological trails. I’d like to embrace that format to some degree on this blog, sharing some quick TIL posts alongside longer pieces that dig deeper.

At a recent Austin Clojure Meetup I picked up a tiny tip that has saved me quite a bit of typing when testing code involving dates. Because the java.util.Date class is not a lot of fun to work with from Clojure (you know what I mean if you’ve ever used it), I almost always end up pulling clj-time into my date/time-related tests when they require anything more complicated than the current datetime (Date.).

But because I often need to ultimately work with java.util.Dates (e.g. with Datomic), I then end up having to coerce. So I end up with a bunch of junk like this in my date-related tests:

(deftest old-records-excluded
  (let [old-date (time.coerce/to-date (time/date-time 2018 1))
        new-date (time.coerce/to-date (time/date-time 2018 2))]
    ;; ... test involving dates ...

It works, and I choose it because the dates are easier to understand at a glance than the (nearly1) equivalent…

(deftest old-records-excluded
  (let [old-date (Date. 118 0 1)
        new-date (Date. 118 1 1)]
    ;; ... test involving dates ...

But what I recently learned is that the familiar #inst literal reader (which you may think of as the format Clojure shows dates in at the REPL) actually supports eliding the later portions of the date string, meaning we can succinctly write common date literals. In other words, all of the following are valid and equivalent:

(= #inst "2018"
   #inst "2018-01"
   #inst "2018-01-01"
   #inst "2018-01-01T00:00:00.000-00:00")
;; => true

This reduces our test to the much more compact…

(deftest old-records-excluded
  (let [old-date #inst "2018-01"
        new-date #inst "2018-02"]
    ;; ... test involving dates ...

…and involves no requires. Much better!

  1. There are actually some timezone related differences, but for the purposes of such tests they tend to be irrelevant since they’re consistently offset.