Skip to content
Permalink
Browse files

CLJS-454: Allow nanoseconds in fractions part of instant literals

This patch mimics the clojure reader which truncates the fraction part to 9 digits (i.e. nanoseconds). This patch truncates to
milliseconds (3 digits). Note that this patch also fixes three other bugs:

* CLJS-564
* The clojure reader reads "#inst \"2003-12-10T00:00:00.4\"" as #inst "2003-12-10T00:00:00.400" but the clojurescript reader (prior to
this patch) reads the same string as #inst "2003-12-10T00:00:00.004"
* #inst tests generated bad test data
  • Loading branch information...
jonase authored and swannodette committed Jul 31, 2013
1 parent 9d26766 commit a749ab6c04baa6bd4c890e860adf43b3d702932b
Showing with 54 additions and 30 deletions.
  1. +39 −28 src/cljs/cljs/reader.cljs
  2. +15 −2 test/cljs/cljs/reader_test.cljs
@@ -440,12 +440,12 @@ nil if the end of stream has been reached")

;; read instances

(defn ^:private zero-fill-right [s width]
(defn ^:private zero-fill-right-and-truncate [s width]
(cond (= width (count s)) s
(< width (count s)) (.substring s 0 width)
(< width (count s)) (subs s 0 width)
:else (loop [b (gstring/StringBuffer. s)]
(if (< (.getLength b) width)
(recur (.append b \0))
(recur (.append b "0"))
(.toString b)))))

(defn ^:private divisible?
@@ -468,31 +468,42 @@ nil if the end of stream has been reached")
(fn [month leap-year?]
(get (if leap-year? dim-leap dim-norm) month))))

(def ^:private parse-and-validate-timestamp
(let [timestamp #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?"
check (fn [low n high msg]
(assert (<= low n high) (str msg " Failed: " low "<=" n "<=" high))
n)]
(fn [ts]
(when-let [[[_ years months days hours minutes seconds milliseconds] [_ _ _] :as V]
(->> ts
(re-matches timestamp)
(split-at 8)
(map vec))]
(let [[[_ y mo d h m s ms] [offset-sign offset-hours offset-minutes]]
(->> V
(map #(update-in %2 [0] %)
[(constantly nil) #(if (= % "-") "-1" "1")])
(map (fn [v] (map #(js/parseInt % 10) v))))
offset (* offset-sign (+ (* offset-hours 60) offset-minutes))]
[(if-not years 1970 y)
(if-not months 1 (check 1 mo 12 "timestamp month field must be in range 1..12"))
(if-not days 1 (check 1 d (days-in-month mo (leap-year? y)) "timestamp day field must be in range 1..last day in month"))
(if-not hours 0 (check 0 h 23 "timestamp hour field must be in range 0..23"))
(if-not minutes 0 (check 0 m 59 "timestamp minute field must be in range 0..59"))
(if-not seconds 0 (check 0 s (if (= m 59) 60 59) "timestamp second field must be in range 0..60"))
(if-not milliseconds 0 (check 0 ms 999 "timestamp millisecond field must be in range 0..999"))
offset])))))
(def ^:private timestamp-regex #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?")

(defn ^:private parse-int [s]
(let [n (js/parseInt s)]
(if-not (js/isNaN n)
n)))

(defn ^:private check [low n high msg]
(when-not (<= low n high)
(reader-error nil (str msg " Failed: " low "<=" n "<=" high)))
n)

(defn parse-and-validate-timestamp [s]
(let [[_ years months days hours minutes seconds fraction offset-sign offset-hours offset-minutes :as v]
(re-matches timestamp-regex s)]
(if-not v
(reader-error nil (str "Unrecognized date/time syntax: " s))
(let [years (parse-int years)
months (or (parse-int months) 1)
days (or (parse-int days) 1)
hours (or (parse-int hours) 0)
minutes (or (parse-int minutes) 0)
seconds (or (parse-int seconds) 0)
fraction (or (parse-int (zero-fill-right-and-truncate fraction 3)) 0)
offset-sign (if (= offset-sign "-") -1 1)
offset-hours (or (parse-int offset-hours) 0)
offset-minutes (or (parse-int offset-minutes) 0)
offset (* offset-sign (+ (* offset-hours 60) offset-minutes))]
[years
(check 1 months 12 "timestamp month field must be in range 1..12")
(check 1 days (days-in-month months (leap-year? years)) "timestamp day field must be in range 1..last day in month")
(check 0 hours 23 "timestamp hour field must be in range 0..23")
(check 0 minutes 59 "timestamp minute field must be in range 0..59")
(check 0 seconds (if (= minutes 59) 60 59) "timestamp second field must be in range 0..60")
(check 0 fraction 999 "timestamp millisecond field must be in range 0..999")
offset]))))

(defn parse-timestamp
[ts]
@@ -43,7 +43,11 @@

;; inst
(let [est-inst (reader/read-string "#inst \"2010-11-12T13:14:15.666-05:00\"")
utc-inst (reader/read-string "#inst \"2010-11-12T18:14:15.666-00:00\"")]
utc-inst (reader/read-string "#inst \"2010-11-12T18:14:15.666-00:00\"")
pad (fn [n]
(if (< n 10)
(str "0" n)
n))]

(assert (= (.valueOf (js/Date. "2010-11-12T13:14:15.666-05:00"))
(.valueOf est-inst)))
@@ -55,10 +59,19 @@
(.valueOf utc-inst)))

(doseq [month (range 1 13) day (range 1 29) hour (range 1 23)]
(let [s (str "#inst \"2010-" month "-" day "T" hour ":14:15.666-06:00\"")]
(let [s (str "#inst \"2010-" (pad month) "-" (pad day) "T" (pad hour) ":14:15.666-06:00\"")]
(assert (= (-> s reader/read-string .valueOf)
(-> s reader/read-string pr-str reader/read-string .valueOf))))))

(let [insts [(reader/read-string "#inst \"2012\"")
(reader/read-string "#inst \"2012-01\"")
(reader/read-string "#inst \"2012-01-01\"")
(reader/read-string "#inst \"2012-01-01T00\"")
(reader/read-string "#inst \"2012-01-01T00:00:00.000\"")
(reader/read-string "#inst \"2012-01-01T00:00:00.000123456\"")
(reader/read-string "#inst \"2012-01-01T00:00:00.000123456789+00:00\"")]]
(assert (apply = (map #(.valueOf %) insts))))

;; uuid literals
(let [u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")]
(assert (= u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")))

0 comments on commit a749ab6

Please sign in to comment.
You can’t perform that action at this time.