ode
ode

Reputation: 63

Clojure Datetime Arithmetic with #inst's

I have lots of dates in the #inst format like

(list #inst "2016-04-30T10:29:17.000-00:00" 
      #inst "2016-03-24T12:13:12.000-00:00" 
      #inst "2016-03-24T12:09:43.000-00:00" 
      #inst "2016-03-23T13:19:03.000-00:00" 
      #inst "2016-02-26T14:51:37.000-00:00" 
      #inst "2016-01-20T16:55:24.000-00:00")

I need to calculate time differences between them as integers of minutes.

I searched for Clojure time libraries but can't see much about #inst's other than how to convert to them.

How can I do time arithmetic with the seq's of #inst's to get the diffs and convert the diffs to ints of minutes?

Thanks for your help.

Upvotes: 3

Views: 1500

Answers (4)

Alan Thompson
Alan Thompson

Reputation: 29958

Yet another solution using the native java.time:

(ns tst.tupelo.java-time
  (:import [ java.time Duration ZoneId ZonedDateTime ]))

  ; note that instants are in DESCENDING order
  (let [instants         ["2016-04-30T10:29:17.000-00:00"
                          "2016-03-24T12:13:12.000-00:00"
                          "2016-03-24T12:09:43.000-00:00"
                          "2016-03-23T13:19:03.000-00:00"
                          "2016-02-26T14:51:37.000-00:00"
                          "2016-01-20T16:55:24.000-00:00"]

        zoned-date-times (mapv #(ZonedDateTime/parse %) instants)
        zdt-pairs        (partition 2 1 zoned-date-times)
        durations        (vec (for [[interval-stop interval-start] zdt-pairs]
                                (.toMinutes ; *** truncates ***
                                  (Duration/between interval-start interval-stop))))]

with result:

durations => [53176 3 1370 37347 53156]

PLEASE NOTE: these values are TRUNCATED to whole minutes, as the original problem stated.


Discussion:

Since the first 2 answers were wrong, this shows the value of using the built-in capabilities of java.time instead of attempting to quickly write a home-grown solution. In most instances, I find it easier to use the native Java classes & functions vs a Clojure wrapper like clj-time, which wraps the deprecated library Joda-Time.

Warning:

Beginning with Java 8, clj-time (and the Joda Time library it wraps) should be considered deprecated. From the clj-time homepage:

A date and time library for Clojure, wrapping the Joda Time library. The Joda Time website says:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Note also that the author of Joda-Time is also the author of the java.time package, which extends Joda-Time and corrects some shortcomings that were only apparent with age. The Joda-Time homepage itself says:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.


Update

Since the original timestamps were (mostly) strings, I thought the OP might fine that solution easier. If you want to use the Clojure #inst syntax, the answer is even easier:

  ; note that instants are in DESCENDING order
  (let [instants  [  #inst "2016-04-30T10:29:17.000-00:00"
                     #inst "2016-03-24T12:13:12.000-00:00"
                     #inst "2016-03-24T12:09:43.000-00:00"
                     #inst "2016-03-23T13:19:03.000-00:00"
                     #inst "2016-02-26T14:51:37.000-00:00"
                     #inst "2016-01-20T16:55:24.000-00:00"]

        instants         (mapv #(.toInstant %) instants)
        inst-pairs       (partition 2 1 instants)
        durations        (vec (for [[interval-stop interval-start] inst-pairs]
                                (.toMinutes ; *** truncates ***
                                  (Duration/between interval-start interval-stop))))]

Note that the new mapping function #(.toInstant %) is the only thing that needs to change.

Upvotes: 3

Ido Barkan
Ido Barkan

Reputation: 113

to slightly improve @akond answer (and use no external libraries):

(defn diff-minutes [[x y]]
  (quot (- x y) 60000))

(def l (list #inst "2016-04-30T10:29:17.000-00:00"
             #inst "2016-03-24T12:13:12.000-00:00"
             #inst "2016-03-24T12:09:43.000-00:00"
             #inst "2016-03-23T13:19:03.000-00:00"
             #inst "2016-02-26T14:51:37.000-00:00"
             #inst "2016-01-20T16:55:24.000-00:00"))

(->> l (map #(.getTime %))
       (partition 2 1)
       (map diff-minutes))

;; or, an anonymous version
(->> l (map #(.getTime %))
       (partition 2 1)
       (map (fn [[x y]] (quot (- x y) 60000))))

(53176 3 1370 37347 53156)

Upvotes: 1

dpassen
dpassen

Reputation: 1306

All answers so far seem to be off quite a bit in the first and last groupings. Also, this solution will return Integers as the question required.

Please note that it requires Java 8+.

(def l (list #inst "2016-04-30T10:29:17.000-00:00"
             #inst "2016-03-24T12:13:12.000-00:00"
             #inst "2016-03-24T12:09:43.000-00:00"
             #inst "2016-03-23T13:19:03.000-00:00"
             #inst "2016-02-26T14:51:37.000-00:00"
             #inst "2016-01-20T16:55:24.000-00:00"))

(import 'java.time.temporal.ChronoUnit)

(->> l
     (map #(.toInstant %))
     (partition 2 1)
     (map (fn [[a b]] (Math/abs (.between ChronoUnit/MINUTES b a)))))

will return the list:

(53176 3 1370 37347 53156)

Using a couple libraries clojure.math.numeric-tower and Clojure.Java-Time, we can do the following

(require '[java-time])
(require '[clojure.math.numeric-tower :refer [abs]])

(->> l 
     (map java-time/instant)
     (partition 2 1)
     (map (comp abs (partial apply java-time/time-between :minutes))))

which will return the same list.

Introducing xforms will allow us to use transducers.

(require '[java-time])
(require '[clojure.math.numeric-tower :refer [abs]])
(require '[net.cgrand.xforms :as x])

(sequence
 (comp
  (map java-time/instant)
  (x/partition 2 1)
  (map (partial apply java-time/time-between :minutes))
  (map abs))
 l)

and achieve the same end result.

Upvotes: 1

akond
akond

Reputation: 16035

#inst "..." is just a java.util.Date object, so what you can do is get its number of milliseconds since the epoch:

(->> (list #inst "2016-04-30T10:29:17.000-00:00"
           #inst "2016-03-24T12:13:12.000-00:00"
           #inst "2016-03-24T12:09:43.000-00:00"
           #inst "2016-03-23T13:19:03.000-00:00"
           #inst "2016-02-26T14:51:37.000-00:00"
           #inst "2016-01-20T16:55:24.000-00:00")
     (map #(.. %
               toInstant
               (atZone (ZoneId/systemDefault))
               toLocalDateTime))
     (partition 2 1)
     (map (fn [[a b]] (Math/abs (.until a b ChronoUnit/MINUTES)))))    
=>  (53236 3 1370 37347 53156)

Upvotes: 1

Related Questions