ref: 74d91a0021de012908cfdb35fb61a1473a376130
dir: /lib/date/parse.myr/
use std use "types" use "names" use "date" use "zoneinfo" pkg date = type parsefail = union `Doublefmt char `Badsep (char, char) `Badfmt char `Badzone byte[:] `Badname byte[:] `Badchar `Badampm `Shortint `Badint ;; /* date i/o */ const parsefmt : (f : byte[:], s: byte[:] -> std.result(instant, parsefail)) const parsefmtl : (f : byte[:], s: byte[:] -> std.result(instant, parsefail)) const parsefmtz : (f : byte[:], s: byte[:], tz : byte[:] -> std.result(instant, parsefail)) ;; const UnixJulianDiff = 719468 const parsefmt = {f, s; -> strparse(f, s, "", false)} const parsefmtl = {f, s; -> strparse(f, s, "local", true)} const parsefmtz = {f, s, tz; -> strparse(f, s, tz, true)} type parsedtz = union `None `Off duration `Name byte[:] ;; const __init__ = { var fail : parsefail std.fmtinstall(std.typeof(fail), failfmt) } const strparse = {f, s, tz, replace var d, err var seen d = [.year = 0] err = `std.None seen = std.mkbs() s = filldate(&d, f, s, seen, &err) std.bsfree(seen) d.actual -= (d.tzoff : std.time) match err | `std.Some e: -> `std.Err e | `std.None: /* no error, we're ok */ ;; if replace d = mkinstant(d.actual, tz) ;; -> `std.Ok d } const filldate = {d, f, s, seen, err var fc, sc, z, am z = "" am = `std.None while f.len != 0 (fc, f) = std.charstep(f) if fc == '%' (fc, f) = std.charstep(f) if std.bshas(seen, fc) err# = `std.Some `Doublefmt fc -> s ;; std.bsput(seen, fc) match fc /* named things */ | 'a': s = indexof(&d.day, s, _names.abbrevday[:], err) | '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, seen, 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, seen, err) | 'e': s = intval(&d.day, s, 1, 2, err) | 'F': s = filldate(d, "%y-%m-%d", s, seen, err) | 'h': s = indexof(&d.day, s, _names.abbrevmon[:], err) | '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) | 'p': s = matchampm(d, s, &am, err) | 'P': s = matchampm(d, s, &am, err) | 'r': s = filldate(d, "%H:%M:%S %P", s, seen, err) | 'R': s = filldate(d, "%H:%M %P", s, seen, 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) | 'x': s = filldate(d, Datefmt, s, seen, err) | 'X': s = filldate(d, Timefmt, s, seen, err) | 'y': s = intval(&d.year, s, 1, 2, err) d.year += 1900 | 'Y': s = intval(&d.year, s, 1, 4, err) | '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) ;; else (sc, s) = std.charstep(s) if std.isspace(fc) && std.isspace(sc) s = eatspace(s) elif sc != fc err# = `std.Some `Badsep (fc, sc) -> s ;; ;; match err# | `std.Some _: -> s | `std.None: ;; ;; d.actual = recalc(d) if z.len > 0 match _zoneinfo.findtzoff(z, d.actual) | `std.Some o: d.tzoff = o | `std.None: err# = `std.Some `Badzone z ;; ;; match am | `std.None: | `std.Some true: d.h %= 12 | `std.Some false: d.h %= 12 d.h += 12 ;; -> s } const eatspace = {s var c while std.isspace(std.decode(s)) (c, s) = std.charstep(s) ;; -> s } const indexof = {dst, s, set, err for var i = 0; i < set.len; i++ if s.len >= set[i].len && std.eq(s[:set[i].len], set[i]) dst# = i -> s[set[i].len:] ;; ;; err# = `std.Some `Badname s dst# = 0 -> s } const tzoffset = {dst, s, err var sgn var tzoff if s.len < 1 err# = `std.Some `Badzone s -> "" ;; if std.eq(s[:1], "-") sgn = -1 elif std.eq(s[:1], "+") sgn = 1 else err# = `std.Some `Badzone s -> s ;; s = intval(&tzoff, s[1:], 2, 4, err) dst# = sgn * (tzoff / 100) * 3600 * 1_000_000 + (tzoff % 100) * 60 * 1_000_000 -> s } const tzstring = {d, s, err var c, n n = 0 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# = `std.Some `Badzone s[:n] ;; -> (s[n:], s[:n]) } const matchstr = {s, str, err if s.len <= str.len || !std.eq(s[:str.len], str) err# = `std.Some `Badchar -> s ;; -> s[str.len:] } const matchampm = {d, s, am, err if s.len < 2 err# = `std.Some `Badampm -> s ;; if std.eq(s[:2], "am") || std.eq(s[:2], "AM") am# = `std.Some true -> s[2:] elif std.eq(s[:2], "pm") || std.eq(s[:2], "PM") am# = `std.Some false -> s[2:] else err# = `std.Some `Badampm -> s ;; } generic intval = {dst : @a#, s : byte[:], min : @a, max : @a, err : std.option(parsefail)# -> byte[:] :: numeric,integral @a var i, c, num num = s for i = 0; i < min; i++ (c, s) = std.charstep(s) if !std.isdigit(c) err# = `std.Some `Shortint -> 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# = `std.Some `Badint -> s ;; } const failfmt = {sb, ap, opt match std.vanext(ap) | `Doublefmt chr: std.sbfmt(sb, "saw duplicate format char '{}'", chr) | `Badsep (e, f): std.sbfmt(sb, "expected separator '{}', found '{}'", e, f) | `Badfmt chr: std.sbfmt(sb, "invalid format character '{}'", chr) | `Badzone zone: std.sbfmt(sb, "unknown time zone '{}'", zone) | `Badname name: std.sbfmt(sb, "could not find name '{}'", name) | `Badchar: std.sbfmt(sb, "unexpected character in parsed string") | `Badampm: std.sbfmt(sb, "invalid am/pm specifier") | `Shortint: std.sbfmt(sb, "integer had too few characters") | `Badint: std.sbfmt(sb, "integer could not be parsed") ;; }