Skip to content

Commit

Permalink
CLJS-3291: Incorrect #inst parsing with respect to Julian / Gregorian…
Browse files Browse the repository at this point in the history
… calendar transition
  • Loading branch information
mfikes committed Jan 2, 2021
1 parent 5e88d33 commit 017ce88
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 3 deletions.
10 changes: 9 additions & 1 deletion src/main/clojure/cljs/compiler.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#?(:clj (:import [cljs.tagged_literals JSValue]
java.lang.StringBuilder
[java.io File Writer]
[java.time Instant]
[java.util.concurrent Executors ExecutorService TimeUnit]
[java.util.concurrent.atomic AtomicLong])
:cljs (:import [goog.string StringBuffer])))
Expand Down Expand Up @@ -418,8 +419,15 @@

;; tagged literal support

(defn- emit-inst [inst-ms]
(emits "new Date(" inst-ms ")"))

(defmethod emit-constant* #?(:clj java.util.Date :cljs js/Date) [^java.util.Date date]
(emits "new Date(" (.getTime date) ")"))
(emit-inst (.getTime date)))

#?(:clj
(defmethod emit-constant* java.time.Instant [^java.time.Instant inst]
(emit-inst (.toEpochMilli inst))))

(defmethod emit-constant* #?(:clj java.util.UUID :cljs UUID) [^java.util.UUID uuid]
(let [uuid-str (.toString uuid)]
Expand Down
54 changes: 54 additions & 0 deletions src/main/clojure/cljs/instant.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
;; Copyright (c) Rich Hickey. All rights reserved.
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.

(ns cljs.instant
(:require [clojure.instant :as inst])
(:import [java.time Instant OffsetDateTime ZoneOffset]
[java.time.format DateTimeFormatter DateTimeFormatterBuilder]
[java.util Locale Locale$Category]))

(set! *warn-on-reflection* true)

(def ^:private ^java.time.format.DateTimeFormatter utc-format
(-> (DateTimeFormatterBuilder.)
(.appendInstant 9)
(.toFormatter (Locale/getDefault Locale$Category/FORMAT))))

(defn- remove-last-char ^String [s]
(subs s 0 (dec (count s))))

(defn- print-instant
"Print a java.time.Instant as RFC3339 timestamp, always in UTC."
[^java.time.Instant instant, ^java.io.Writer w]
(.write w "#inst \"")
(.write w (remove-last-char (.format utc-format instant)))
(.write w "-00:00\""))

(defmethod print-method java.time.Instant
[^java.time.Instant instant, ^java.io.Writer w]
(print-instant instant w))

(defmethod print-dup java.time.Instant
[^java.time.Instant instant, ^java.io.Writer w]
(print-instant instant w))

(defn- construct-instant
"Construct a java.time.Instant, which has nanosecond precision."
[years months days hours minutes seconds nanoseconds
offset-sign offset-hours offset-minutes]
(Instant/from
(OffsetDateTime/of years months days hours minutes seconds nanoseconds
(ZoneOffset/ofHoursMinutes (* offset-sign offset-hours) (* offset-sign offset-minutes)))))

(defn read-instant-instant
"To read an instant as a java.time.Instant, bind *data-readers* to a
map with this var as the value for the 'inst key. Instant preserves
fractional seconds with nanosecond precision. The timezone offset will
be used to convert into UTC."
[^CharSequence cs]
(inst/parse-timestamp (inst/validated construct-instant) cs))
4 changes: 2 additions & 2 deletions src/main/clojure/cljs/tagged_literals.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
;; You must not remove this notice, or any other, from this software.

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

(defn read-queue
Expand Down Expand Up @@ -46,7 +46,7 @@
(when-not (string? form)
(throw (RuntimeException. "Instance literal expects a string for its timestamp.")))
(try
(inst/read-instant-date form)
(inst/read-instant-instant form)
(catch Throwable e
(throw (RuntimeException. (.getMessage e)))))))

Expand Down
15 changes: 15 additions & 0 deletions src/test/cljs/cljs/reader_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,18 @@

(deftest testing-cljs-3278
(is (nil? (reader/read-string {:readers {'foo (constantly nil)}} "#foo 1"))))

(deftest testing-cljs-3291
(is (= "#inst \"1500-01-01T00:00:00.000-00:00\"" (pr-str #inst "1500")))
(is (= "#inst \"1582-10-04T00:00:00.000-00:00\"" (pr-str #inst "1582-10-04")))
(is (= "#inst \"1582-10-04T23:59:59.999-00:00\"" (pr-str #inst "1582-10-04T23:59:59.999")))
(is (= "#inst \"1582-10-05T00:00:00.000-00:00\"" (pr-str #inst "1582-10-05")))
(is (= "#inst \"1582-10-07T00:00:00.000-00:00\"" (pr-str #inst "1582-10-07")))
(is (= "#inst \"1582-10-14T23:59:59.999-00:00\"" (pr-str #inst "1582-10-14T23:59:59.999")))
(is (= "#inst \"1582-10-15T00:00:00.000-00:00\"" (pr-str #inst "1582-10-15")))
(is (= "#inst \"1582-10-17T00:00:00.000-00:00\"" (pr-str #inst "1582-10-17")))
(is (= "#inst \"1700-01-01T00:00:00.000-00:00\"" (pr-str #inst "1700")))
(is (= "#inst \"1850-01-01T00:00:00.000-00:00\"" (pr-str #inst "1850")))
(is (= "#inst \"1984-01-01T00:00:00.000-00:00\"" (pr-str #inst "1984")))
(is (= "#inst \"2000-01-01T00:00:10.123-00:00\"" (pr-str #inst "2000-01-01T00:00:10.123456789-00:00")))
(is (= "#inst \"2020-01-01T05:00:00.000-00:00\"" (pr-str #inst "2020-01-01T00:00:00.000-05:00"))))
22 changes: 22 additions & 0 deletions src/test/clojure/cljs/instant_tests.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
;; Copyright (c) Rich Hickey. All rights reserved.
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.

(ns cljs.instant-tests
(:require
[cljs.instant :as inst]
[clojure.test :refer [deftest is]]))

(deftest read-instant-instant-test
;; Clojure uses hybrid Julian / Gregorian, while Instant is proleptic Gregorian
(is (not= #inst "1500" (inst/read-instant-instant "1500")))
(is (not= (inst-ms #inst "1500") (inst-ms (inst/read-instant-instant "1500"))))
(is (= -14831769600000 (inst-ms (inst/read-instant-instant "1500"))))
(is (= "#inst \"1500-01-01T00:00:00.123456789-00:00\""
(pr-str (inst/read-instant-instant "1500-01-01T00:00:00.123456789-00:00"))))
(is (= "#inst \"2020-01-01T05:00:00.000000000-00:00\""
(pr-str (inst/read-instant-instant "2020-01-01T00:00:00.000-05:00")))))
2 changes: 2 additions & 0 deletions src/test/clojure/cljs/test_runner.clj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[cljs.compiler-tests]
[cljs.externs-infer-tests]
[cljs.externs-parsing-tests]
[cljs.instant-tests]
[cljs.module-graph-tests]
[cljs.module-processing-tests]
[cljs.source-map.base64-tests]
Expand All @@ -23,6 +24,7 @@
'cljs.compiler-tests
'cljs.externs-infer-tests
'cljs.externs-parsing-tests
'cljs.instant-tests
'cljs.module-graph-tests
'cljs.module-processing-tests
'cljs.source-map.base64-tests
Expand Down

0 comments on commit 017ce88

Please sign in to comment.