Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert clojure's #inst literals to equivalent java.time types?

I used next.jdbc library to persist a record to a PostgreSQL table. As you can see below, the values are a LocalDate instance and an Instant instance. The SQL columns are of type DATE and TIMESTAMPTZ respectively.

Required the next.jdbc.date-time namespace to map from Java -> SQL, as mentioned in the docs.

{:id 7, :trade_date #object[java.time.LocalDate 0x6b751cb1 2020-12-22], :created_at #object[java.time.Instant 0x3add5e17 2020-12-22T11:41:07.557790Z]}

When I read the record back (SQL -> Clojure), the representation is as follows

{:id 7, :trade_date #inst "2020-12-21T18:30:00.000-00:00", :created_at #inst "2020-12-22T11:41:07.557790000-00:00"}

So they are read back as #inst literals? I'd like them back as java.time types. Also note that the LocalDate seems to be converted to a UTC timestamp. Here's what I dug up so far..

; inst -> Instant
=> (.toInstant #inst "2020-12-22T11:41:07.557790000-00:00")
#object[java.time.Instant 0x312cc79e "2020-12-22T11:41:07.557Z"]

; inst -> LocalDate
=> (-> #inst "2020-12-21T18:30:00.000-00:00"
            #_=> .toInstant
            #_=> (ZonedDateTime/ofInstant (ZoneId/of "UTC"))
            #_=> (.withZoneSameInstant (ZoneId/systemDefault))
            #_=> (.toLocalDate))
#object[java.time.LocalDate 0x44048462 "2020-12-22"]

Doesn't seem simple / the Clojure way. Is there a better way?

References: Pieced from the answers by Alan Thompson and Basil Bourque

like image 700
Gishu Avatar asked Mar 02 '23 20:03

Gishu


2 Answers

1 hr later. Well that didn't work.

Turns out next.jdbc reconstructs the record with java.sql.* types, which happen to be printed out as #inst literals at the REPL.

Calling (.toInstant sql-date-instance) blows up with an Exception (this is as designed). However armed with this bit of info, it was easy to achieve the end result with. Run the returned vector of maps through this mapper.

(defn mapSqlToTimeTypes [map-with-sql-types]
  (reduce (fn [return-map [k, v]]
            
            (cond
              (instance? java.sql.Timestamp v) (assoc return-map k (.toInstant v))
              (instance? java.sql.Date v) (assoc return-map k (.toLocalDate v))
              :else (assoc return-map k v)))
          {} 
          map-with-sql-types))
like image 145
Gishu Avatar answered Mar 09 '23 00:03

Gishu


So #inst is just a tagged literal, by default its underlying type is java.util.Date.

(def my-inst #inst "2018-03-28T10:48:00.000")
(= java.util.Date (class my-inst))
;=> true

But you or any library can change the default reader for #inst, since data-readers is a dynamic var. So at any point in time, depending on the lib you are using #inst may produce different underlying type.

I haven't used nex.jdbc yet myself, but I think that what you need is to call read-as-instant or read-as-local function. See here Also checkout clj-time Clojure library. Also checkout clojure.java-time wrapper around java.time

like image 31
Simon Polak Avatar answered Mar 09 '23 01:03

Simon Polak