3

When I read postgres date types, I want them to be coerced into joda time LocalDates (or the new java.time localdates). This is because postgres dates does not have time zone info, so I don't want my java or clojure object to add it somehow.

This is the IResultSetReadColumn protocol extension I've set up for this:

(extend-protocol clojure.java.jdbc/IResultSetReadColumn
  java.sql.Date
  (result-set-read-column [v _rsmeta _idx]
    (tc/to-local-date v)))

However, when I try to use it, I get the wrong result:

(DateTimeZone/getDefault)
=> #object[org.joda.time.tz.CachedDateTimeZone 0x60470ff "Europe/Stockholm"]

(jdbc/query db/db ["SHOW TIMEZONE"])
=> ({:timezone "Europe/Stockholm"})

(jdbc/query db/db ["SELECT '2020-01-01'::date"])
=> ({:date #object[org.joda.time.LocalDate 0x75081795 "2019-12-31"]}) ; ARGH!! It changed date!

I suspect this is caused by the dates being converted into java.sql.date, which has timezone info in it somehow. Is there perhaps a way to read the postgres dates directly to avoid this? I've found documentation on the postgres JDBC driver that seems promising but I can't figure out how to implement it in clojure.java.jdbc.

Basically: Is there any way to get my postgres dates out of the database without messing them up, if my default timezone is not UTC?

rutchkiwi
  • 427
  • 3
  • 14
  • Does this answer your question? [Insert & fetch java.time.LocalDate objects to/from an SQL database such as H2](https://stackoverflow.com/questions/43039614/insert-fetch-java-time-localdate-objects-to-from-an-sql-database-such-as-h2) – Ole V.V. Jan 11 '21 at 23:24

1 Answers1

1

I figured out how to do it now:

(defn sql-date->LocalDate [v]
      ; Convert a java.sql.Date into a LocalDate.
      ; LocalDates does NOT have any timezone info - bit un-intuitive perhaps.
      ; Neither does the postgres date type.
      ;
      ; Unfortunately, java.sql.Date has a time component, 
      ; which would causes lots of issues if we were to pass it along as is.
      ; How it works is:
      ; 1. The postgres JDBC driver reads the postgres date as a java.sql.Date,
      ;    by setting the jav.sql.Date timestamp to 00.00
      ;    the date of the postgres date in the 
      ;    JVM default timezone (DateTimeZone/getDefault).
      ;
      ; 2. .toLocalDate converts the java.sql.Date to a java.time.LocalDate 
      ;     in the JVM default timezone (DateTimeZone/getDefault).
      ;
      ; 3. Then we convert the java.time.LocalDate to a joda-time LocalDate, 
      ;    as that is what clj-time uses.
      ;
      ; So because we convert both date -> timestamp -> date in the same
      ; timezone, it all evens out.
      ;
      (let [java-time-localdate (.toLocalDate v)]
        (time/local-date (.getYear java-time-localdate)
                         (.getValue (.getMonth java-time-localdate))
                         (.getDayOfMonth java-time-localdate))))

The most important thing is NOT to use clj-time.coerce's to-localtime function. I'm not sure what it does but anyways it messes the day up when there are timezones involved.

rutchkiwi
  • 427
  • 3
  • 14