Skip to content

Commit 017ce88

Browse files
committed
CLJS-3291: Incorrect #inst parsing with respect to Julian / Gregorian calendar transition
1 parent 5e88d33 commit 017ce88

File tree

6 files changed

+104
-3
lines changed

6 files changed

+104
-3
lines changed

src/main/clojure/cljs/compiler.cljc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#?(:clj (:import [cljs.tagged_literals JSValue]
3434
java.lang.StringBuilder
3535
[java.io File Writer]
36+
[java.time Instant]
3637
[java.util.concurrent Executors ExecutorService TimeUnit]
3738
[java.util.concurrent.atomic AtomicLong])
3839
:cljs (:import [goog.string StringBuffer])))
@@ -418,8 +419,15 @@
418419

419420
;; tagged literal support
420421

422+
(defn- emit-inst [inst-ms]
423+
(emits "new Date(" inst-ms ")"))
424+
421425
(defmethod emit-constant* #?(:clj java.util.Date :cljs js/Date) [^java.util.Date date]
422-
(emits "new Date(" (.getTime date) ")"))
426+
(emit-inst (.getTime date)))
427+
428+
#?(:clj
429+
(defmethod emit-constant* java.time.Instant [^java.time.Instant inst]
430+
(emit-inst (.toEpochMilli inst))))
423431

424432
(defmethod emit-constant* #?(:clj java.util.UUID :cljs UUID) [^java.util.UUID uuid]
425433
(let [uuid-str (.toString uuid)]

src/main/clojure/cljs/instant.clj

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
;; Copyright (c) Rich Hickey. All rights reserved.
2+
;; The use and distribution terms for this software are covered by the
3+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
;; which can be found in the file epl-v10.html at the root of this distribution.
5+
;; By using this software in any fashion, you are agreeing to be bound by
6+
;; the terms of this license.
7+
;; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.instant
10+
(:require [clojure.instant :as inst])
11+
(:import [java.time Instant OffsetDateTime ZoneOffset]
12+
[java.time.format DateTimeFormatter DateTimeFormatterBuilder]
13+
[java.util Locale Locale$Category]))
14+
15+
(set! *warn-on-reflection* true)
16+
17+
(def ^:private ^java.time.format.DateTimeFormatter utc-format
18+
(-> (DateTimeFormatterBuilder.)
19+
(.appendInstant 9)
20+
(.toFormatter (Locale/getDefault Locale$Category/FORMAT))))
21+
22+
(defn- remove-last-char ^String [s]
23+
(subs s 0 (dec (count s))))
24+
25+
(defn- print-instant
26+
"Print a java.time.Instant as RFC3339 timestamp, always in UTC."
27+
[^java.time.Instant instant, ^java.io.Writer w]
28+
(.write w "#inst \"")
29+
(.write w (remove-last-char (.format utc-format instant)))
30+
(.write w "-00:00\""))
31+
32+
(defmethod print-method java.time.Instant
33+
[^java.time.Instant instant, ^java.io.Writer w]
34+
(print-instant instant w))
35+
36+
(defmethod print-dup java.time.Instant
37+
[^java.time.Instant instant, ^java.io.Writer w]
38+
(print-instant instant w))
39+
40+
(defn- construct-instant
41+
"Construct a java.time.Instant, which has nanosecond precision."
42+
[years months days hours minutes seconds nanoseconds
43+
offset-sign offset-hours offset-minutes]
44+
(Instant/from
45+
(OffsetDateTime/of years months days hours minutes seconds nanoseconds
46+
(ZoneOffset/ofHoursMinutes (* offset-sign offset-hours) (* offset-sign offset-minutes)))))
47+
48+
(defn read-instant-instant
49+
"To read an instant as a java.time.Instant, bind *data-readers* to a
50+
map with this var as the value for the 'inst key. Instant preserves
51+
fractional seconds with nanosecond precision. The timezone offset will
52+
be used to convert into UTC."
53+
[^CharSequence cs]
54+
(inst/parse-timestamp (inst/validated construct-instant) cs))

src/main/clojure/cljs/tagged_literals.cljc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
;; You must not remove this notice, or any other, from this software.
88

99
(ns cljs.tagged-literals
10-
#?(:clj (:require [clojure.instant :as inst])
10+
#?(:clj (:require [cljs.instant :as inst])
1111
:cljs (:require [cljs.reader :as reader])))
1212

1313
(defn read-queue
@@ -46,7 +46,7 @@
4646
(when-not (string? form)
4747
(throw (RuntimeException. "Instance literal expects a string for its timestamp.")))
4848
(try
49-
(inst/read-instant-date form)
49+
(inst/read-instant-instant form)
5050
(catch Throwable e
5151
(throw (RuntimeException. (.getMessage e)))))))
5252

src/test/cljs/cljs/reader_test.cljs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,18 @@
239239

240240
(deftest testing-cljs-3278
241241
(is (nil? (reader/read-string {:readers {'foo (constantly nil)}} "#foo 1"))))
242+
243+
(deftest testing-cljs-3291
244+
(is (= "#inst \"1500-01-01T00:00:00.000-00:00\"" (pr-str #inst "1500")))
245+
(is (= "#inst \"1582-10-04T00:00:00.000-00:00\"" (pr-str #inst "1582-10-04")))
246+
(is (= "#inst \"1582-10-04T23:59:59.999-00:00\"" (pr-str #inst "1582-10-04T23:59:59.999")))
247+
(is (= "#inst \"1582-10-05T00:00:00.000-00:00\"" (pr-str #inst "1582-10-05")))
248+
(is (= "#inst \"1582-10-07T00:00:00.000-00:00\"" (pr-str #inst "1582-10-07")))
249+
(is (= "#inst \"1582-10-14T23:59:59.999-00:00\"" (pr-str #inst "1582-10-14T23:59:59.999")))
250+
(is (= "#inst \"1582-10-15T00:00:00.000-00:00\"" (pr-str #inst "1582-10-15")))
251+
(is (= "#inst \"1582-10-17T00:00:00.000-00:00\"" (pr-str #inst "1582-10-17")))
252+
(is (= "#inst \"1700-01-01T00:00:00.000-00:00\"" (pr-str #inst "1700")))
253+
(is (= "#inst \"1850-01-01T00:00:00.000-00:00\"" (pr-str #inst "1850")))
254+
(is (= "#inst \"1984-01-01T00:00:00.000-00:00\"" (pr-str #inst "1984")))
255+
(is (= "#inst \"2000-01-01T00:00:10.123-00:00\"" (pr-str #inst "2000-01-01T00:00:10.123456789-00:00")))
256+
(is (= "#inst \"2020-01-01T05:00:00.000-00:00\"" (pr-str #inst "2020-01-01T00:00:00.000-05:00"))))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
;; Copyright (c) Rich Hickey. All rights reserved.
2+
;; The use and distribution terms for this software are covered by the
3+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
;; which can be found in the file epl-v10.html at the root of this distribution.
5+
;; By using this software in any fashion, you are agreeing to be bound by
6+
;; the terms of this license.
7+
;; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.instant-tests
10+
(:require
11+
[cljs.instant :as inst]
12+
[clojure.test :refer [deftest is]]))
13+
14+
(deftest read-instant-instant-test
15+
;; Clojure uses hybrid Julian / Gregorian, while Instant is proleptic Gregorian
16+
(is (not= #inst "1500" (inst/read-instant-instant "1500")))
17+
(is (not= (inst-ms #inst "1500") (inst-ms (inst/read-instant-instant "1500"))))
18+
(is (= -14831769600000 (inst-ms (inst/read-instant-instant "1500"))))
19+
(is (= "#inst \"1500-01-01T00:00:00.123456789-00:00\""
20+
(pr-str (inst/read-instant-instant "1500-01-01T00:00:00.123456789-00:00"))))
21+
(is (= "#inst \"2020-01-01T05:00:00.000000000-00:00\""
22+
(pr-str (inst/read-instant-instant "2020-01-01T00:00:00.000-05:00")))))

src/test/clojure/cljs/test_runner.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
[cljs.compiler-tests]
77
[cljs.externs-infer-tests]
88
[cljs.externs-parsing-tests]
9+
[cljs.instant-tests]
910
[cljs.module-graph-tests]
1011
[cljs.module-processing-tests]
1112
[cljs.source-map.base64-tests]
@@ -23,6 +24,7 @@
2324
'cljs.compiler-tests
2425
'cljs.externs-infer-tests
2526
'cljs.externs-parsing-tests
27+
'cljs.instant-tests
2628
'cljs.module-graph-tests
2729
'cljs.module-processing-tests
2830
'cljs.source-map.base64-tests

0 commit comments

Comments
 (0)