如何在 Clojure 中将日期作为字符串存储在数据库中?
how do I get a date stored in a database as a string in Clojure?
当使用 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.
,编译器产生了一些 'unmatched parenthesis' 错误
关于如何做到这一点的想法?我是对原始 SQL 查询做些什么,我是对返回的数据做些什么,还是做其他事情?
我想柴郡 core/encode 会从那张地图上给你很好的 JSON。
阅读更多关于柴郡的信息
我尝试使用 H2 数据库重新创建您的结果,但它给我的是 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 Docs and the 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
格式。
扩展 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))))
当使用 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.
,编译器产生了一些 'unmatched parenthesis' 错误关于如何做到这一点的想法?我是对原始 SQL 查询做些什么,我是对返回的数据做些什么,还是做其他事情?
我想柴郡 core/encode 会从那张地图上给你很好的 JSON。
阅读更多关于柴郡的信息我尝试使用 H2 数据库重新创建您的结果,但它给我的是 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 Docs and the 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
格式。
扩展 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))))