parse-timestamp
Parse a string containing an RFC3339-like like timestamp.
The function new-instant is called with the following arguments.
min max default
--- ------------ -------
years 0 9999 N/A (s must provide years)
months 1 12 1
days 1 31 1 (actual max days depends
hours 0 23 0 on month and year)
minutes 0 59 0
seconds 0 60 0 (though 60 is only valid
nanoseconds 0 999999999 0 when minutes is 59)
offset-sign -1 1 0
offset-hours 0 23 0
offset-minutes 0 59 0
These are all integers and will be non-nil. (The listed defaults
will be passed if the corresponding field is not present in s.)
Grammar (of s):
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
time-hour = 2DIGIT ; 00-23
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
; rules
time-secfrac = '.' 1*DIGIT
time-numoffset = ('+' / '-') time-hour ':' time-minute
time-offset = 'Z' / time-numoffset
time-part = time-hour [ ':' time-minute [ ':' time-second
[time-secfrac] [time-offset] ] ]
timestamp = date-year [ '-' date-month [ '-' date-mday
[ 'T' time-part ] ] ]
Unlike RFC3339:
- we only parse the timestamp format
- timestamp can elide trailing components
- time-offset is optional (defaults to +00:00)
Though time-offset is syntactically optional, a missing time-offset
will be treated as if the time-offset zero (+00:00) had been
specified.
Source
(def parse-timestamp
"Parse a string containing an RFC3339-like like timestamp.
The function new-instant is called with the following arguments.
min max default
--- ------------ -------
years 0 9999 N/A (s must provide years)
months 1 12 1
days 1 31 1 (actual max days depends
hours 0 23 0 on month and year)
minutes 0 59 0
seconds 0 60 0 (though 60 is only valid
nanoseconds 0 999999999 0 when minutes is 59)
offset-sign -1 1 0
offset-hours 0 23 0
offset-minutes 0 59 0
These are all integers and will be non-nil. (The listed defaults
will be passed if the corresponding field is not present in s.)
Grammar (of s):
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
time-hour = 2DIGIT ; 00-23
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
; rules
time-secfrac = '.' 1*DIGIT
time-numoffset = ('+' / '-') time-hour ':' time-minute
time-offset = 'Z' / time-numoffset
time-part = time-hour [ ':' time-minute [ ':' time-second
[time-secfrac] [time-offset] ] ]
timestamp = date-year [ '-' date-month [ '-' date-mday
[ 'T' time-part ] ] ]
Unlike RFC3339:
- we only parse the timestamp format
- timestamp can elide trailing components
- time-offset is optional (defaults to +00:00)
Though time-offset is syntactically optional, a missing time-offset
will be treated as if the time-offset zero (+00:00) had been
specified.
"
(let [timestamp #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?"]
(fn [new-instant ^CharSequence cs]
(if-let [[_ years months days hours minutes seconds fraction
offset-sign offset-hours offset-minutes]
(re-matches timestamp cs)]
(new-instant
(parse-int years)
(if-not months 1 (parse-int months))
(if-not days 1 (parse-int days))
(if-not hours 0 (parse-int hours))
(if-not minutes 0 (parse-int minutes))
(if-not seconds 0 (parse-int seconds))
(if-not fraction 0 (parse-int (zero-fill-right fraction 9)))
(cond (= "-" offset-sign) -1
(= "+" offset-sign) 1
:else 0)
(if-not offset-hours 0 (parse-int offset-hours))
(if-not offset-minutes 0 (parse-int offset-minutes)))
(fail (str "Unrecognized date/time syntax: " cs))))))