Epoch fail, Clojure edition

A while back, I wrote about doing epoch calculations in Elixir. Recently, I tried the same sort of thing in Clojure. Since Clojure sits on top of Java, I was not optimistic. Historically, Java has not had a good story to tell when it comes to date-time manipulation. But Java 1.8 added a new java.time package, so maybe it’s better now?

Clojure has clj-time, which wraps joda-time. Joda Time is the third-party library that most Java folks used before version 1.8 (and still do, I guess, if they’re stuck on 1.7 or 1.6 for some reason). Perhaps clj-time will move to java.time eventually, but for now at least they are still on joda-time. Maybe it would have been more sensible for me to just have used clj-time, but I wanted to use the java.time package.

I discovered there is a Clojure java-time (with a dash) that wraps java.time (with a period) and I started out doing that, but eventually abandoned it. When I got to the google-calendar calculation, I needed the .toInstant method, and java-time didn’t seem to supply it.

So, I changed to using java.time directly (if you look back in the git history of Epochs-clojure, you can see where I switched). Since Clojure’s Java interop is so seamless, it turned out pretty nice! Here is that google-calendar portion:

(defn google-calendar
  "Google Calendar time seems to count 32-day months from the day
  before the Unix epoch. @noppers worked out how to do this."
  (let [seconds-per-day (* 24 60 60)
        utc (java.time.ZoneOffset/UTC)
        n-days (quot n seconds-per-day)
        n-seconds (rem n seconds-per-day)
        g-months (quot n-days 32)
        g-days (rem n-days 32)]
    (-> (java.time.LocalDateTime/ofEpochSecond (- seconds-per-day) 0 utc)
        (.plusDays g-days)
        (.plusMonths g-months)
        (.toInstant utc)
        (.plusSeconds n-seconds))))

Java time has Instants, which are the naive date-times that I want to do everything with. Similar to Elixir, java.time won’t let me add days or months to an Instant. So I create a LocalDateTime, add the days and months, then convert to an Instant and add the seconds.

Not bad. Not as nice as the Perl version, but better than the Elixir version. At least java.time gives me the .plusMonths method I need, even if it does make me choose a timezone to get it. And, remarkably, it’s just Java. I usually can’t bear to look at Java code, but Clojure makes it almost pretty.

I look forward to clj-time or java-time supplying more Clojure-like access to java.time in the future. But in the meantime, accessing it directly isn’t that bad!

(Update: 2017-08-06) I went to see if I could add .toInstant support for LocalDateTimes in java-time. I figured rather than just complain, I’d send a pull request. In so doing, I discovered it is already there! If I have a LocalDateTime in ldt, then this Java interop code

(.toInstant ldt java.time.ZoneOffset/UTC)

can be written as this java-time code

(instant ldt (zone-offset 0))

I think I’m going to leave Epochs-clojure as Java interop for now, but maybe I’ll switch back to java-time in the future if things start to get too Java-y.

Epoch fail, Clojure edition