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...
1 parent 9d26766 commit a749ab6c04baa6bd4c890e860adf43b3d702932b @jonase jonase committed with swannodette Jul 31, 2013
Showing with 54 additions and 30 deletions.
  1. +39 −28 src/cljs/cljs/reader.cljs
  2. +15 −2 test/cljs/cljs/reader_test.cljs
View
@@ -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.