ref: 2cf555f110ec6d21a92ef3dfaeb5128c9fc3fd2d
parent: ba6015188abeb957de22a4412babc97d4587b4b9
author: Ori Bernstein <ori@eigenstate.org>
date: Wed Sep 23 17:18:54 EDT 2015
Return parse errors.
--- a/lib/date/parse.myr
+++ b/lib/date/parse.myr
@@ -6,28 +6,55 @@
use "zoneinfo.use"
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.option(instant))
- const parsefmtl : (f : byte[:], s: byte[:] -> std.option(instant))
- const parsefmtz : (f : byte[:], s: byte[:], tz : byte[:] -> std.option(instant))
+ 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; -> parse(f, s, "", false)}
-const parsefmtl = {f, s; -> parse(f, s, "local", true)}
-const parsefmtz = {f, s, tz; -> parse(f, s, tz, true)}
+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)}
-const parse = {f, s, tz, replace
+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 = false
- s = filldate(&d, f, s, &err)
+ err = `std.None
+ seen = std.mkbs()
+ s = filldate(&d, f, s, seen, &err)
+ std.bsfree(seen)
d.actual -= d.tzoff castto(std.time)
- if err || s.len > 0
- -> `std.None
+ match err
+ | `std.Some e: -> `std.Fail e
+ | `std.None: /* no error, we're ok */
;;
if replace
@@ -34,17 +61,10 @@
d = mkinstant(d.actual, tz)
;;
- -> `std.Some d
+ -> `std.Ok d
}
-type parsedtz = union
- `None
- `Off duration
- `Name byte[:]
-;;
-
-
-const filldate = {d, f, s, err -> byte[:]
+const filldate = {d, f, s, seen, err : std.option(parsefail)#
var fc, sc, z
z = ""
@@ -52,6 +72,11 @@
(fc, f) = std.striter(f)
if fc == '%'
(fc, f) = std.striter(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)
@@ -58,14 +83,14 @@
| '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, 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, 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, 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)
@@ -73,17 +98,16 @@
| '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, err)
| 'P': s = matchampm(d, s, err)
- | 'r': s = filldate(d, "%H:%M:%S %P", s, err)
- | 'R': s = filldate(d, "%H:%M %P", s, 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, err)
- | 'X': s = filldate(d, Timefmt, s, 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':
@@ -95,15 +119,16 @@
;;
else
(sc, s) = std.striter(s)
- if std.isspace(sc)
+ if std.isspace(fc) && std.isspace(sc)
s = eatspace(s)
- elif (sc != fc)
- err# = true
+ elif sc != fc
+ err# = `std.Some `Badsep (fc, sc)
-> s
;;
;;
- if err#
- -> s
+ match err#
+ | `std.Some _: -> s
+ | `std.None:
;;
;;
d.actual = recalc(d)
@@ -110,7 +135,7 @@
if z.len > 0
match _zoneinfo.findtzoff(z, d.actual)
| `std.Some o: d.tzoff = o
- | `std.None: err# = true
+ | `std.None: err# = `std.Some `Badzone z
;;
;;
-> s
@@ -133,7 +158,7 @@
-> s
;;
;;
- err# = true
+ err# = `std.Some `Badname s
dst# = 0
-> s
}
@@ -143,7 +168,7 @@
var tzoff
if s.len < 1
- err# = true
+ err# = `std.Some `Badzone s
-> ""
;;
if std.sleq(s[:1], "-")
@@ -151,8 +176,8 @@
elif std.sleq(s[:1], "+")
sgn = 1
else
- err# = true
- -> ""
+ 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
@@ -172,7 +197,7 @@
if n < d._tzbuf.len
std.slcp(d._tzbuf[:n], s[:n])
else
- err# = true
+ err# = `std.Some `Badzone s[:n]
;;
-> (s[n:], s[:n])
}
@@ -180,8 +205,8 @@
const matchstr = {s, str, err
if s.len <= str.len || !std.sleq(s[:str.len], str)
- err# = true
- -> ""
+ err# = `std.Some `Badchar
+ -> s
;;
-> s[str.len:]
}
@@ -188,7 +213,7 @@
const matchampm = {d, s, err
if s.len < 2
- err# = true
+ err# = `std.Some `Badampm
-> s
;;
if std.sleq(s[:2], "am") || std.sleq(s[:2], "AM")
@@ -197,12 +222,13 @@
d.h += 12
-> s[2:]
else
- err# = true
+ err# = `std.Some `Badampm
-> s
;;
}
generic intval = {dst : @a::(numeric,integral)#, s : byte[:], \
- min : @a::(numeric,integral), max : @a::(numeric,integral), err : bool# -> byte[:]
+ min : @a::(numeric,integral), max : @a::(numeric,integral), \
+ err : std.option(parsefail)# -> byte[:]
var i
var c
var num
@@ -211,7 +237,7 @@
for i = 0; i < min; i++
(c, s) = std.striter(s)
if !std.isdigit(c)
- err# = true
+ err# = `std.Some `Shortint
-> s
;;
;;
@@ -230,8 +256,21 @@
dst# = v
-> s
| `std.None:
- err# = true
+ 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")
+ ;;
+}
--- a/lib/date/test/parse.myr
+++ b/lib/date/test/parse.myr
@@ -6,29 +6,36 @@
/* 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.Ok 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")
+ | `std.Fail m:
+ std.fatal("failed to parse date: {}\n", m)
;;
+ match date.parsefmt("%Y-%m-%d", "1969 12 31")
+ | `std.Ok d:
+ std.fatal("should have errored")
+ | `std.Fail m:
+ eq(std.bfmt(buf[:], "{}", m), "expected separator '-', found ' '")
+ ;;
+
/* parse into utc */
match date.parsefmtz("%Y-%m-%d %H:%M:%S %z", "1969-12-31 16:00:00 -0800", "")
- | `std.Some d:
+ | `std.Ok 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")
+ | `std.Fail m:
+ std.fatal("failed to parse date: {}\n", m)
;;
/*Fri 29 Aug 2014 07:47:43 PM UTC*/
match date.parsefmt("%Y-%m-%d %z", "1932-10-23 +0500")
- | `std.Some d:
+ | `std.Ok 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")
+ | `std.Fail m:
+ std.fatal("Failed to parse date: {}", m)
;;
}