diff options
author | Ori Bernstein <ori@eigenstate.org> | 2015-09-23 00:13:15 -0700 |
---|---|---|
committer | Ori Bernstein <ori@eigenstate.org> | 2015-09-23 00:13:15 -0700 |
commit | a51d1fcf9346ae3d9e6418f568fa6a06d4111ea8 (patch) | |
tree | 7b149597d2698c5e6acec8917197d25c017d01de /lib/date | |
parent | c376b622ebbff34ddb414b50a383339a9ef79b32 (diff) | |
download | mc-a51d1fcf9346ae3d9e6418f568fa6a06d4111ea8.tar.gz |
Fix and test parsing with timezones.
Diffstat (limited to 'lib/date')
-rw-r--r-- | lib/date/date.myr | 16 | ||||
-rw-r--r-- | lib/date/fmt.myr | 4 | ||||
-rw-r--r-- | lib/date/parse.myr | 161 | ||||
-rw-r--r-- | lib/date/test/parse.myr | 19 | ||||
-rw-r--r-- | lib/date/types.myr | 7 | ||||
-rw-r--r-- | lib/date/zoneinfo.myr | 12 |
6 files changed, 139 insertions, 80 deletions
diff --git a/lib/date/date.myr b/lib/date/date.myr index e5743c9..8ccb4d1 100644 --- a/lib/date/date.myr +++ b/lib/date/date.myr @@ -63,16 +63,17 @@ const mkinstant = {tm, tz var j, y, m, d var t, e var inst - var off inst.actual = tm /* time zones */ std.assert(tz.len <= inst._tzbuf.len, "time zone name too long\n") - off =_zoneinfo.findtzoff(tz, tm) - inst.tzoff = off + match _zoneinfo.findtzoff(tz, tm) + | `std.Some o: inst.tzoff = o + | `std.None: std.fatal("no zone named {}\n", tz) + ;; std.slcp(inst._tzbuf[:tz.len], tz) inst.tzname = inst._tzbuf[:tz.len] - tm += off castto(std.time) + tm += inst.tzoff castto(std.time) /* break up time */ t = tm % DayUsec /* time */ @@ -146,11 +147,14 @@ const mkinstant = {tm, tz } const localoff = {tm - -> _zoneinfo.findtzoff("local", tm) + -> tzoff("local", tm) } const tzoff = {tz, tm - -> _zoneinfo.findtzoff(tz, tm) + match _zoneinfo.findtzoff(tz, tm) + | `std.Some dt: -> dt + | `std.None: std.fatal("unable to load zoneinfo\n") + ;; } const isleap = {d diff --git a/lib/date/fmt.myr b/lib/date/fmt.myr index 66454d5..4fc22ee 100644 --- a/lib/date/fmt.myr +++ b/lib/date/fmt.myr @@ -18,10 +18,6 @@ const __init__ = { ][:]) } -const Datetimefmt = "%Y-%m-%d %H:%M:%S %z" -const Timefmt = "%h:%m:{} %z" -const Datefmt = "%Y-%m-%d %z" - /* Always formats in proleptic Gregorian format */ const sbfmt = {sb, ap, opts var d : instant diff --git a/lib/date/parse.myr b/lib/date/parse.myr index 0cd5d77..baa04b1 100644 --- a/lib/date/parse.myr +++ b/lib/date/parse.myr @@ -3,65 +3,51 @@ use std use "types.use" use "names.use" use "date.use" +use "zoneinfo.use" pkg date = /* date i/o */ const parsefmt : (f : byte[:], s: byte[:] -> std.option(instant)) + const parsefmtl : (f : byte[:], s: byte[:] -> std.option(instant)) const parsefmtz : (f : byte[:], s: byte[:], tz : byte[:] -> std.option(instant)) ;; const UnixJulianDiff = 719468 -const parsefmt = {f, s; -> parsefmtz(f, s, "")} -const parsefmtz = {f, s, tz - var d - var err +const parsefmt = {f, s; -> parse(f, s, "", false)} +const parsefmtl = {f, s; -> parse(f, s, "local", true)} +const parsefmtz = {f, s, tz; -> parse(f, s, tz, true)} + +const parse = {f, s, tz, replace + var d, err d = [.year = 0] err = false - s = filldate(&d, f, s, tz, &err) + s = filldate(&d, f, s, &err) + + d.actual -= d.tzoff castto(std.time) if err || s.len > 0 -> `std.None ;; - -> `std.Some d -} - -generic intval = {dst : @a::(numeric,integral)#, s : byte[:], min : @a::(numeric,integral), max : @a::(numeric,integral), err : bool# -> byte[:] - var i - var c - var num - num = s - for i = 0; i < min; i++ - (c, s) = std.striter(s) - if !std.isdigit(c) - err# = true - -> s - ;; - ;; - - for i = min ; i < max; i++ - (c, s) = std.striter(s) - if !std.isdigit(c) - break - ;; + if replace + d = mkinstant(d.actual, tz) ;; - num = num[:i] - match std.intparse(num) - | `std.Some v: - dst# = v - -> s - | `std.None: - err# = true - -> s - ;; + -> `std.Some d } +type parsedtz = union + `None + `Off duration + `Name byte[:] +;; + -const filldate = {d, f, s, tz, err -> byte[:] - var fc, sc +const filldate = {d, f, s, err -> byte[:] + var fc, sc, z + z = "" while f.len != 0 (fc, f) = std.striter(f) if fc == '%' @@ -72,49 +58,38 @@ const filldate = {d, f, s, tz, err -> byte[:] | 'A': s = indexof(&d.day, s, _names.fullday, err) | 'b': s = indexof(&d.mon, s, _names.abbrevmon, err) | 'B': s = indexof(&d.mon, s, _names.fullmon, err) - | 'c': s = filldate(d, "%Y-%m-%d", s, tz, err) + | 'c': s = filldate(d, "%Y-%m-%d", s, err) | 'C': s = intval(&d.year, s, 2, 2, err) d.year += 1900 | 'd': s = intval(&d.day, s, 2, 2, err) - | 'D': s = filldate(d, "%m/%d/%y", s, tz, err) + | 'D': s = filldate(d, "%m/%d/%y", s, err) | 'e': s = intval(&d.day, s, 1, 2, err) - | 'F': s = filldate(d, "%y-%m-%d", s, tz, err) - /* - | 'G': o += std.bfmt(buf[o:], ...? - | 'g': - */ + | 'F': s = filldate(d, "%y-%m-%d", s, err) | 'h': s = indexof(&d.day, s, _names.abbrevmon, err) - | 'H': s = intval(&d.h, s, 2, 2, err) - | 'I': s = intval(&d.h, s, 2, 2, err) - | 'j': std.fatal("unsupported '%j'\n") + | 'H': s = intval(&d.h, s, 1, 2, err) + | 'I': s = intval(&d.h, s, 1, 2, err) | 'k': s = intval(&d.h, s, 1, 2, err) | 'l': s = intval(&d.h, s, 1, 2, err) | 'm': s = intval(&d.mon, s, 1, 2, err) | 'M': s = intval(&d.m, s, 1, 2, err) | 'n': s = matchstr(s, "\n", err) - | 'O': std.fatal("unsupported %O\n") | 'p': s = matchampm(d, s, err) | 'P': s = matchampm(d, s, err) - | 'r': s = filldate(d, "%H:%M:%S %P", s, tz, err) - | 'R': s = filldate(d, "%H:%M %P", s, tz, err) + | 'r': s = filldate(d, "%H:%M:%S %P", s, err) + | 'R': s = filldate(d, "%H:%M %P", s, err) | 's': s = intval(&d.actual, s, 1, 64, err) | 'S': s = intval(&d.s, s, 1, 2, err) | 't': s = eatspace(s) | 'u': s = intval(&d.wday, s, 1, 1, err) - | 'U': std.fatal("unsupported '%U'\n") - /* - | 'x': o += bftime(buf[o:], Datefmt, d) - | 'X': o += bftime(buf[o:], Timefmt, d) - */ + | 'x': s = filldate(d, Datefmt, s, err) + | 'X': s = filldate(d, Timefmt, s, err) | 'y': s = intval(&d.year, s, 1, 2, err) d.year += 1900 | 'Y': s = intval(&d.year, s, 1, 4, err) - | 'z': s = timezone(&d.tzoff, s, err) - /* - | 'Z': o += std.bfmt(buf[o:], "%s", d.tzname) - */ + | 'z': s = tzoffset(&d.tzoff, s, err) + | 'Z': (s, z) = tzstring(d, s, err) | '%': s = matchstr(s, "%", err) | _: std.fatal("unknown format character %c\n", fc) ;; @@ -132,6 +107,12 @@ const filldate = {d, f, s, tz, err -> byte[:] ;; ;; d.actual = recalc(d) + if z.len > 0 + match _zoneinfo.findtzoff(z, d.actual) + | `std.Some o: d.tzoff = o + | `std.None: err# = true + ;; + ;; -> s } @@ -157,8 +138,8 @@ const indexof = {dst, s, set, err -> s } -const timezone = {dst, s, err - var isneg +const tzoffset = {dst, s, err + var sgn var tzoff if s.len < 1 @@ -166,18 +147,36 @@ const timezone = {dst, s, err -> "" ;; if std.sleq(s[:1], "-") - isneg = true + sgn = -1 elif std.sleq(s[:1], "+") - isneg = false + sgn = 1 else err# = true -> "" ;; s = intval(&tzoff, s[1:], 2, 4, err) - dst# = (tzoff / 100) * 3600 * 1_000_000 + (tzoff % 100) * 60 * 1_000_000 + dst# = sgn * (tzoff / 100) * 3600 * 1_000_000 + (tzoff % 100) * 60 * 1_000_000 -> s } +const tzstring = {d, s, err + var c, n + + while true + c = std.decode(s[n:]) + if c != '/' && !std.isalnum(c) + break + ;; + n += std.charlen(c) + ;; + if n < d._tzbuf.len + std.slcp(d._tzbuf[:n], s[:n]) + else + err# = true + ;; + -> (s[n:], s[:n]) +} + const matchstr = {s, str, err if s.len <= str.len || !std.sleq(s[:str.len], str) @@ -202,3 +201,37 @@ const matchampm = {d, s, err -> s ;; } +generic intval = {dst : @a::(numeric,integral)#, s : byte[:], \ + min : @a::(numeric,integral), max : @a::(numeric,integral), err : bool# -> byte[:] + var i + var c + var num + + num = s + for i = 0; i < min; i++ + (c, s) = std.striter(s) + if !std.isdigit(c) + err# = true + -> s + ;; + ;; + + for i = min ; i < max; i++ + c = std.decode(s) + if !std.isdigit(c) + break + ;; + s = s[std.charlen(c):] + ;; + + num = num[:i] + match std.intparse(num) + | `std.Some v: + dst# = v + -> s + | `std.None: + err# = true + -> s + ;; +} + diff --git a/lib/date/test/parse.myr b/lib/date/test/parse.myr index b560080..5ded726 100644 --- a/lib/date/test/parse.myr +++ b/lib/date/test/parse.myr @@ -4,9 +4,28 @@ use date const main = { var buf : byte[1024] + /* should be unix epoch */ + match date.parsefmt("%Y-%m-%d %H:%M:%S %z", "1969-12-31 16:00:00 -0800") + | `std.Some d: + std.assert(d.actual == 0, "got wrong date") + eq(std.bfmt(buf[:], "{D}", d), "1969-12-31 16:00:00 -0800") + | `std.None: + std.fatal("failed to parse date\n") + ;; + + /* parse into utc */ + match date.parsefmtz("%Y-%m-%d %H:%M:%S %z", "1969-12-31 16:00:00 -0800", "") + | `std.Some d: + std.assert(d.actual == 0, "got wrong date") + eq(std.bfmt(buf[:], "{D}", d), "1970-1-01 00:00:00 +0000") + | `std.None: + std.fatal("failed to parse date\n") + ;; + /*Fri 29 Aug 2014 07:47:43 PM UTC*/ match date.parsefmt("%Y-%m-%d %z", "1932-10-23 +0500") | `std.Some d: + std.assert(d.actual == -1173675600 * 1_000_000, "wrong timestamp") eq(std.bfmt(buf[:], "{D}", d), "1932-10-23 00:00:00 +0500") | `std.None: std.fatal("Failed to parse date") diff --git a/lib/date/types.myr b/lib/date/types.myr index e042e7e..3e3af76 100644 --- a/lib/date/types.myr +++ b/lib/date/types.myr @@ -26,4 +26,11 @@ pkg date = `Minute int `Second int ;; + const Datetimefmt + const Timefmt + const Datefmt + ;; +const Datetimefmt = "%Y-%m-%d %H:%M:%S %z" +const Timefmt = "%h:%m:{} %z" +const Datefmt = "%Y-%m-%d %z" diff --git a/lib/date/zoneinfo.myr b/lib/date/zoneinfo.myr index 88e6f06..7905f96 100644 --- a/lib/date/zoneinfo.myr +++ b/lib/date/zoneinfo.myr @@ -5,7 +5,7 @@ use "types.use" pkg _zoneinfo = type zifile - const findtzoff : (tz : byte[:], tm : std.time -> date.duration) + const findtzoff : (tz : byte[:], tm : std.time -> std.option(date.duration)) const load : (file : byte[:] -> zifile#) const free : (f : zifile# -> void) ;; @@ -32,7 +32,7 @@ const zonepath = [ "/etc/zoneinfo" ] -const findtzoff = {tz, tm +const findtzoff = {tz, tm -> std.option(date.duration) var path var zone var cur @@ -42,7 +42,7 @@ const findtzoff = {tz, tm /* load zone */ if std.sleq(tz, "") || std.sleq(tz, "UTC") - -> 0 + -> `std.Some 0 elif std.sleq(tz, "local") path = std.sldup("/etc/localtime") else @@ -54,7 +54,7 @@ const findtzoff = {tz, tm std.slfree(path) ;; std.slfree(path) - -> 0 + -> `std.None ;; :found zone = load(path) @@ -63,14 +63,14 @@ const findtzoff = {tz, tm /* find applicable gmt offset */ cur = (tm / 1_000_000) castto(int32) if zone.time.len == 0 - -> 0 + -> `std.None ;; for i = 0; i < zone.time.len && cur < zone.time[i]; i++ /* nothing */ ;; ds = zone.ttinfo[zone.timetype[i]].gmtoff free(zone) - -> (ds castto(date.duration)) * 1_000_000 + -> `std.Some (ds castto(date.duration)) * 1_000_000 } const load = {file |