ref: a51d1fcf9346ae3d9e6418f568fa6a06d4111ea8
parent: c376b622ebbff34ddb414b50a383339a9ef79b32
author: Ori Bernstein <[email protected]>
date: Tue Sep 22 20:13:15 EDT 2015
Fix and test parsing with timezones.
--- a/lib/date/date.myr
+++ b/lib/date/date.myr
@@ -63,16 +63,17 @@
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 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
--- a/lib/date/fmt.myr
+++ b/lib/date/fmt.myr
@@ -18,10 +18,6 @@
][:])
}
-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
--- a/lib/date/parse.myr
+++ b/lib/date/parse.myr
@@ -3,65 +3,51 @@
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
- ;;
+ if replace
+ d = mkinstant(d.actual, tz)
;;
- for i = min ; i < max; i++
- (c, s) = std.striter(s)
- if !std.isdigit(c)
- break
- ;;
- ;;
-
- 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 @@
| '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 @@
;;
;;
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 @@
-> s
}
-const timezone = {dst, s, err
- var isneg
+const tzoffset = {dst, s, err
+ var sgn
var tzoff
if s.len < 1
@@ -166,19 +147,37 @@
-> ""
;;
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)
err# = true
@@ -202,3 +201,37 @@
-> 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
+ ;;
+}
+
--- a/lib/date/test/parse.myr
+++ b/lib/date/test/parse.myr
@@ -4,9 +4,28 @@
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")
--- a/lib/date/types.myr
+++ b/lib/date/types.myr
@@ -26,4 +26,11 @@
`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"
--- a/lib/date/zoneinfo.myr
+++ b/lib/date/zoneinfo.myr
@@ -5,7 +5,7 @@
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 @@
"/etc/zoneinfo"
]
-const findtzoff = {tz, tm
+const findtzoff = {tz, tm -> std.option(date.duration)
var path
var zone
var cur
@@ -42,7 +42,7 @@
/* 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 @@
std.slfree(path)
;;
std.slfree(path)
- -> 0
+ -> `std.None
;;
:found
zone = load(path)
@@ -63,7 +63,7 @@
/* 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 */
@@ -70,7 +70,7 @@
;;
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