shithub: mc

ref: 37e813c6855bb09b73f4a7679ca2ce146152ee47
dir: /lib/date/parse.myr/

View raw version
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")
	;;
}