java.time: parse fractional seconds in formatter-based date parsing
parse-ms ignored sub-second fractions: a formatter parse of "2020-07-06T10:59:13.417Z" dropped the .417 (and the ISO_OFFSET_DATE_TIME pattern "…ssXXX" then mis-aligned, reading ".417Z" as the offset). java.time's ISO formatters accept an optional fractional second, so jolt should too. After parsing the seconds field, consume an optional .fff from the input (to millis) when the pattern carries no fraction field — which is how the ISO_* constants are modeled here (ss, no S). Also handle the S pattern letter for explicit .SSS patterns. Carry the fraction into the result. Fixes aws-api shape iso8601 parse (1 FAIL -> 0; cognitect.aws.util/parse-date now returns the right Date). Two JVM-certified corpus rows. make test green, 0 new divergences. Runtime .ss — no re-mint.
This commit is contained in:
parent
b3ffd357a2
commit
0278293bcc
2 changed files with 23 additions and 3 deletions
|
|
@ -179,7 +179,7 @@
|
|||
(else (loop (+ i 1)))))))
|
||||
(define (parse-ms pattern input)
|
||||
(let ((pn (string-length pattern)) (inn (string-length input))
|
||||
(y 1970) (mo 1) (d 1) (hh 0) (mi 0) (ss 0) (pm 'none))
|
||||
(y 1970) (mo 1) (d 1) (hh 0) (mi 0) (ss 0) (frac-ms 0) (pm 'none))
|
||||
;; a parse failure is a java.time.format.DateTimeParseException (typed, so a
|
||||
;; (catch DateTimeParseException …) over a bad date matches), like the JVM.
|
||||
(define (pfail)
|
||||
|
|
@ -208,7 +208,7 @@
|
|||
(begin
|
||||
(when (eq? pm 'pm) (when (< hh 12) (set! hh (+ hh 12))))
|
||||
(when (eq? pm 'am) (when (= hh 12) (set! hh 0)))
|
||||
(make-jinst (* 1000 (+ (* (days-from-civil y mo d) 86400) (* hh 3600) (* mi 60) ss))))
|
||||
(make-jinst (+ (* 1000 (+ (* (days-from-civil y mo d) 86400) (* hh 3600) (* mi 60) ss)) frac-ms)))
|
||||
(let ((c (string-ref pattern pi)))
|
||||
(cond
|
||||
((char-alphabetic? c)
|
||||
|
|
@ -225,7 +225,25 @@
|
|||
((char=? c #\d) (let ((r (read-digits-w ii (if (>= k 2) k #f)))) (set! d (car r)) (loop (+ pi k) (cdr r))))
|
||||
((or (char=? c #\H) (char=? c #\h)) (let ((r (read-digits-w ii (if (>= k 2) k #f)))) (set! hh (car r)) (loop (+ pi k) (cdr r))))
|
||||
((char=? c #\m) (let ((r (read-digits-w ii (if (>= k 2) k #f)))) (set! mi (car r)) (loop (+ pi k) (cdr r))))
|
||||
((char=? c #\s) (let ((r (read-digits-w ii (if (>= k 2) k #f)))) (set! ss (car r)) (loop (+ pi k) (cdr r))))
|
||||
((char=? c #\s) (let ((r (read-digits-w ii (if (>= k 2) k #f))))
|
||||
(set! ss (car r))
|
||||
;; an ISO formatter (modeled here as an ss-pattern with no S
|
||||
;; field) still accepts an optional fractional second; consume
|
||||
;; .fff -> millis from the input. Skip when the pattern carries
|
||||
;; the fraction itself (a following '.'/S handles it).
|
||||
(let ((j (cdr r)) (pnext (if (< (+ pi k) pn) (string-ref pattern (+ pi k)) #\nul)))
|
||||
(if (and (not (char=? pnext #\.)) (not (char=? pnext #\S))
|
||||
(< j inn) (char=? (string-ref input j) #\.)
|
||||
(< (+ j 1) inn) (digit? (string-ref input (+ j 1))))
|
||||
(let frac ((p (+ j 1)) (kk 0) (acc 0))
|
||||
(if (and (< p inn) (digit? (string-ref input p)))
|
||||
(frac (+ p 1) (+ kk 1) (if (< kk 3) (+ (* acc 10) (- (char->integer (string-ref input p)) 48)) acc))
|
||||
(begin (set! frac-ms (* acc (expt 10 (max 0 (- 3 kk))))) (loop (+ pi k) p))))
|
||||
(loop (+ pi k) j)))))
|
||||
((char=? c #\S) (let frac ((p ii) (kk 0) (acc 0))
|
||||
(if (and (< p inn) (< kk k) (digit? (string-ref input p)))
|
||||
(frac (+ p 1) (+ kk 1) (+ (* acc 10) (- (char->integer (string-ref input p)) 48)))
|
||||
(begin (set! frac-ms (* acc (expt 10 (max 0 (- 3 kk))))) (loop (+ pi k) p)))))
|
||||
((char=? c #\E) (loop (+ pi k) (cdr (read-alpha ii))))
|
||||
((char=? c #\a) (let ((r (read-alpha ii)))
|
||||
(set! pm (if (string=? (ascii-string-down (car r)) "pm") 'pm 'am))
|
||||
|
|
|
|||
|
|
@ -558,6 +558,8 @@
|
|||
{:suite "interop / java.time chrono-fields" :label "ALIGNED_WEEK_OF_MONTH" :expected "3" :actual "(.getLong (java.time.LocalDate/of 2020 3 17) java.time.temporal.ChronoField/ALIGNED_WEEK_OF_MONTH)"}
|
||||
{:suite "interop / java.time chrono-fields" :label "Year isSupported + getLong" :expected "2020" :actual "(.getLong (java.time.Year/of 2020) java.time.temporal.ChronoField/YEAR)"}
|
||||
{:suite "interop / java.time chrono-fields" :label "YearMonth PROLEPTIC_MONTH" :expected "24244" :actual "(.getLong (java.time.YearMonth/of 2020 5) java.time.temporal.ChronoField/PROLEPTIC_MONTH)"}
|
||||
{:suite "interop / java.time chrono-fields" :label "ISO_OFFSET_DATE_TIME keeps fractional seconds" :expected "1594033153417" :actual "(.toEpochMilli (.toInstant (java.time.ZonedDateTime/parse \"2020-07-06T10:59:13.417Z\" java.time.format.DateTimeFormatter/ISO_OFFSET_DATE_TIME)))"}
|
||||
{:suite "interop / java.time chrono-fields" :label "ISO_OFFSET_DATE_TIME without fraction" :expected "1594033153000" :actual "(.toEpochMilli (.toInstant (java.time.ZonedDateTime/parse \"2020-07-06T10:59:13Z\" java.time.format.DateTimeFormatter/ISO_OFFSET_DATE_TIME)))"}
|
||||
{:suite "interop / java.time shims" :label "Instant/now is current" :expected "true" :actual "(> (.toEpochMilli (Instant/now)) 1500000000000)"}
|
||||
{:suite "interop / java.time shims" :label "sql types are not" :expected "false" :actual "(instance? java.sql.Timestamp #inst \"2020-01-01T00:00:00Z\")"}
|
||||
{:suite "interop / StringReader & StringBuilder" :label "StringReader read" :expected "[97 98 -1]" :actual "(let [r (java.io.StringReader. \"ab\")] [(.read r) (.read r) (.read r)])"}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue