【问题标题】:how do I get a date stored in a database as a string in Clojure?如何获取存储在数据库中的日期作为 Clojure 中的字符串?
【发布时间】:2019-02-09 17:46:20
【问题描述】:

当使用 clojure.java.jdbc 从 Postgres 数据库中取回值时,我最终得到了

{:id 1, :name "example", :created_at #object[java.time.LocalDateTime 0x15e0aadc "2019-02-08T12:52:11.438633"]}

当我想要时

{:id 1, :name "example", :created_at :created_at "2019-02-08T12:52:11.438633"]}

我希望日期以 JSON 格式返回并可由 JavaScript 使用。第二个示例可能不是理想的格式,因此我对其他人持开放态度,但 Java 对象无法正常工作。

我尝试了几种方法,但由于日期 #object,编译器产生了许多“括号不匹配”错误。

关于如何做到这一点的想法?我是对原始 SQL 查询做些什么,我是对返回的数据做些什么,还是做其他事情?

【问题讨论】:

  • 您使用什么将地图序列化为 JSON?它不能处理日期时间吗?
  • 我使用了 clojure.data.json write-str 函数,它产生了以下错误:1. Unhandled java.lang.Exception Don't know how to write JSON of class java.time.LocalDateTime

标签: json clojure clojure-java-interop


【解决方案1】:

我想 cheshire.core/encode 会为您提供来自该地图的漂亮 JSON。

https://github.com/dakrone/cheshire了解更多关于柴郡的信息

【讨论】:

  • 我试试看!
【解决方案2】:

我尝试使用 H2 db 重新创建您的结果,但它给了我 Clojure #inst 结果而不是对象引用。


好的,问题来了。正如您的打印输出所说,您有一个 java.time.LocalDateTime(Clojure #inst 是一个 java.util.Date 对象)。

如果你想要一个字符串版本,你需要做的就是在它上面调用成员函数toString

  (let [ldt (LocalDateTime/parse "2019-02-01T00:00:00" )]

    (.toString ldt) => <#java.lang.String "2019-02-01T00:00">

但是,您没有附加时区信息。因此,您可能需要 ZonedDateTime。 我们将假定 UTC 时区低于(请验证您的数据库服务器配置):

  (let [jud      (Date.)
        ldt      (LocalDateTime/parse "2019-02-01T00:00:00")
        zdt      (.atZone ldt (ZoneId/of "UTC"))  ; *** big assumption here! ***
        inst     (.toInstant zdt)
        inst-str (.toString inst) ]

    ; a Clojure #inst is just a java.util.Date object
    jud      =>  #inst "2019-02-09T19:38:30.088-00:00"   

    ; a ZonedDateTime can print in 2 ways
    zdt              => #object[java.time.ZonedDateTime 0x780af31 "2019-02-01T00:00Z[UTC]"]
    (.toString zdt)  => "2019-02-01T00:00Z[UTC]"

    ; a java.time.Instant also prints in 2 ways:
    instant          => #object[java.time.Instant 0x48301adb "2019-02-01T00:00:00Z"]
    instant-str      => "2019-02-01T00:00:00Z"

请注意,ZDT 有一个像 [UTC] 这样的后缀,因此您可能希望将其转换为 Instant,然后使用 .toString 方法来获得它的更简单的字符串表示形式 (ISO-8601) .


如果您不介意使用外部库来简化此操作,tupelo.java-time 库有一个非常方便的辅助函数:

(ns xyz
  (require [tupelo.java-time :as tjt] ... ))

(tjt/string-date-time-iso zdt) => "2019-02-01T00:00:00Z"

还有许多其他的辅助函数可用。请参阅the API Docsthe unit tests for examples


更新

我终于修复了我的 Postgres 安装(必须重置密码才能使 Hikari 正常工作)。这是我的测试代码:

(ns tst.demo.jdbc-pool
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [clojure.java.jdbc :as jdbc]
    [hikari-cp.core :as pool]
    [tupelo.java-time :as tjt] ) )

(def datasource-options-pg
  {:adapter       "postgresql"
   :database-name "alan"
   :server-name   "localhost"
   :port-number   5433
   :username      "alan"
   :password      "secret" } )

(def ^:dynamic db-conn nil)

(defn with-connection-pool
  "Creates and uses a connection for test function"
  [tst-fn]
  (let [datasource (pool/make-datasource datasource-options-pg)]
    (binding [db-conn {:datasource datasource}]
      (tst-fn)
      (pool/close-datasource datasource)))) ; close the connection - also closes/destroys the in-memory database

(use-fixtures
  :once with-connection-pool) ; use the same db connection pool for all tests

以上只是配置的东西。这是验证行为的单元测试:

(dotest
  (jdbc/db-do-commands db-conn ["drop table if exists langs"])
  (jdbc/db-do-commands db-conn
    [(jdbc/create-table-ddl :langs [[:id :serial]
                                    [:lang "varchar not null"]
                                    [:creation :timestamptz]])])
  (jdbc/insert-multi! db-conn :langs
    [{:lang "Clojure" :creation (tjt/iso-str->timestamp "2008-01-01T12:34:56Z")}
     {:lang "Java"    :creation (tjt/iso-str->timestamp "1995-06-01T07:08:09Z")}])

  (let [result (jdbc/query db-conn ["select * from langs"])]
    (is=  (vec result)
      [{:id       1, :lang "Clojure",
        :creation #inst "2008-01-01T12:34:56.000000000-00:00"}
       {:id       2, :lang "Java",
        :creation #inst "1995-06-01T07:08:09.000000000-00:00"}])))

所以你可以看到我仍然得到java.util.Date 结果,Clojure 以#inst 格式打印。我不确定您是如何让 JDBC 输出 LocalDateTime 格式的。

【讨论】:

  • 感谢您的完整回答! .toString 有效。 UTC 需求是尚未考虑的事情。我也试试图珀洛。
【解决方案3】:

在您的 db.core.clj 文件中扩展 java.time.Instant。有关扩展类型和协议的示例,请参阅 https://rundis.github.io/blog/2015/clojure_dates.html

(extend-type java.time.Instant
  jdbc/ISQLParameter
  (set-parameter [v ^PreparedStatement stmt ^long idx]
    (.setTimestamp stmt idx (Timestamp. (.toEpochMilli v)))))

(extend-protocol cheshire.generate/JSONable
  java.time.Instant
  (to-json [dt gen]
    (cheshire.generate/write-string gen (str dt))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-07
    • 1970-01-01
    相关资源
    最近更新 更多