shithub: mc

Download patch

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