如何将Clojure的#inst字面量转换为等效的java.time类型?

4
我使用了 next.jdbc 库将一条记录持久化到 PostgreSQL 表。如下所示,这些值是 LocalDate 实例和 Instant 实例。SQL 列分别为 DATE 类型和 TIMESTAMPTZ 类型。
在文档中提到,需要使用 next.jdbc.date-time 命名空间来进行 Java -> SQL 的映射。
{: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]}

当我读回记录(SQL -> Clojure)时,其表示如下

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

所以它们被读取为 #inst 字面量吗? 我希望将它们作为 java.time 类型返回。还要注意,LocalDate 似乎被转换为 UTC 时间戳。以下是我迄今挖掘到的内容。

; 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"]

看起来并不简单/符合Clojure的方式。是否有更好的方法?

参考资料: 从Alan ThompsonBasil Bourque的答案中整理而来。

3个回答

3

#inst 只是一个带标签的字面量,其默认基础类型为 java.util.Date。

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

但你或任何库都可以更改 #inst 的默认 reader,因为 data-readers 是一个动态变量。因此,在任何时候,取决于您正在使用的库,#inst 可能会产生不同的基础类型。

我自己还没有使用过 nex.jdbc,但我认为你需要调用 read-as-instantread-as-local 函数。请参见这里同时也可以查看 Clojure 库 clj-time,也可以查看 java.time 的 clojure.java-time 包装器。


这些选项各自完成了一半的工作 - 我的表格有一个UTC时间戳和一个本地日期。因此,我将通过这些方法调用之一获取Instant或LocalDate。你能给我一些链接来更好地理解#inst字面量吗?我已经读了几本Clojure书,但不记得有涉及到这个。 - Gishu
1
@AlanThompson,哦谢谢。我还是老派的。Gishu,在这里阅读有关Clojure Reader的页面https://clojure.org/reference/reader。 - Simon Polak

3

1小时后。好吧,那没用。

事实证明,next.jdbc使用java.sql.*类型重构记录,在REPL中以#inst字面量形式打印出来。

调用(.toInstant sql-date-instance)将导致异常(这是按设计要求的)。但是有了这些信息,通过将返回的映射向量运行此映射器,很容易实现最终结果。

(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))

1
作为对@Gishu回答的小简化,对于这种数据转换,使用postwalk非常方便。这里有一个简单的函数,可以将所有关键字转换为普通字符串,例如:
(defn walk-keywords->strings
  "Recursively walks form, converting all keywords to strings. "
  [form]
  (walk/postwalk (fn [item]
                   (if (keyword? item)
                     (t/kw->str item)
                     item))
    form))

当处理通常是深度嵌套的映射和向量的网络请求和响应时,这特别有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接