9front - general discussion about 9front
 help / color / mirror / Atom feed
* Re: [9front] libc: date handling improvements
@ 2020-06-15  6:03 ori
  0 siblings, 0 replies; 5+ messages in thread
From: ori @ 2020-06-15  6:03 UTC (permalink / raw)
  To: ori, 9front

[-- Attachment #1: Type: text/plain, Size: 929 bytes --]

>> I implemented libdate -- but the right place to put
>> it is in libc, replacing our ctime and gmtime apis.
>> 
>> Follow ups will replace seconds(1), upas time parsing
>> code, and whatever other instances of hand rolled date
>> parsing and formatting can be found.
>> 
>> Here's a patch that does this. Testing appreciated,
>> and follow ups that I haven't done yet would also be
>> great :)
>> 
> 
> Committed -- please let me know if you notice any
> breakage.

Now, with a whole bunch of changes through the system.
I'm going to run these for a while and test a bit harder
to see if there's any fallout before committing, but
while I do that, I'll put them up for other people to.
test and comment.

I don't personally use httpd or imap4d, so while I'm
going to try to give them a good shake, if anyone does
use them, I'd appreciate reports on that.

All patches depend on libc-date, but should otherwise
be independent.


[-- Attachment #2: Type: text/plain, Size: 32082 bytes --]

diff -r e168590166a5 sys/include/libc.h
--- a/sys/include/libc.h	Sun Jul 19 14:14:14 2020 -0700
+++ b/sys/include/libc.h	Sun Jul 19 21:34:16 2020 -0700
@@ -314,22 +314,44 @@
 /*
  * Time-of-day
  */
+typedef struct Tzone Tzone;
+#pragma incomplete Tzone
+
 
 typedef
 struct Tm
 {
-	int	sec;
-	int	min;
-	int	hour;
-	int	mday;
-	int	mon;
-	int	year;
-	int	wday;
-	int	yday;
-	char	zone[4];
-	int	tzoff;
+	int	nsec;		/* nseconds (range 0...1e9) */
+	int	sec;		/* seconds (range 0..60) */
+	int	min;		/* minutes (0..59) */
+	int	hour;		/* hours (0..23) */
+	int	mday;		/* day of the month (1..31) */
+	int	mon;		/* month of the year (0..11) */
+	int	year;		/* year A.D. */
+	int	wday;		/* day of week (0..6, Sunday = 0) */
+	int	yday;		/* day of year (0..365) */
+	char	zone[16];	/* time zone name */
+	int	tzoff;		/* time zone delta from GMT */
+	Tzone	*tz;		/* time zone associated with this date */
 } Tm;
 
+typedef
+struct Tmfmt {
+	char	*fmt;
+	Tm	*tm;
+} Tmfmt;
+
+#pragma varargck	type	"τ"	Tmfmt
+
+extern	Tzone*	tzload(char *name);
+extern	Tm*	tmnow(Tm*, Tzone*);
+extern	Tm*	tmtime(Tm*, vlong, Tzone*);
+extern	Tm*	tmtimens(Tm*, vlong, int, Tzone*);
+extern	Tm*	tmparse(Tm*, char*, char*, Tzone*, char **ep);
+extern	vlong	tmnorm(Tm*);
+extern	Tmfmt	tmfmt(Tm*, char*);
+extern	void	tmfmtinstall(void);
+
 extern	Tm*	gmtime(long);
 extern	Tm*	localtime(long);
 extern	char*	asctime(Tm*);
diff -r e168590166a5 sys/src/libc/9sys/ctime.c
--- a/sys/src/libc/9sys/ctime.c	Sun Jul 19 14:14:14 2020 -0700
+++ b/sys/src/libc/9sys/ctime.c	Sun Jul 19 21:34:16 2020 -0700
@@ -1,301 +1,39 @@
-/*
- * This routine converts time as follows.
- * The epoch is 0000 Jan 1 1970 GMT.
- * The argument time is in seconds since then.
- * The localtime(t) entry returns a pointer to an array
- * containing
- *
- *	seconds (0-59)
- *	minutes (0-59)
- *	hours (0-23)
- *	day of month (1-31)
- *	month (0-11)
- *	year-1970
- *	weekday (0-6, Sun is 0)
- *	day of the year
- *	daylight savings flag
- *
- * The routine gets the daylight savings time from the environment.
- *
- * asctime(tvec))
- * where tvec is produced by localtime
- * returns a ptr to a character string
- * that has the ascii time in the form
- *
- *	                            \\
- *	Thu Jan 01 00:00:00 GMT 1970n0
- *	012345678901234567890123456789
- *	0	  1	    2
- *
- * ctime(t) just calls localtime, then asctime.
- */
-
 #include <u.h>
 #include <libc.h>
 
-static	char	dmsize[12] =
-{
-	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-};
-
-/*
- * The following table is used for 1974 and 1975 and
- * gives the day number of the first day after the Sunday of the
- * change.
- */
-
-static	int	dysize(int);
-static	void	ct_numb(char*, int);
-
-#define	TZSIZE	150
-static	void	readtimezone(void);
-static	int	rd_name(char**, char*);
-static	int	rd_long(char**, long*);
-static
-struct
-{
-	char	stname[4];
-	char	dlname[4];
-	long	stdiff;
-	long	dldiff;
-	long	dlpairs[TZSIZE];
-} timezone;
-
-char*
-ctime(long t)
-{
-	return asctime(localtime(t));
-}
-
 Tm*
 localtime(long tim)
 {
-	Tm *ct;
-	long t, *p;
-	int dlflag;
+	static Tm tm;
+	Tzone *tz;
 
-	if(timezone.stname[0] == 0)
-		readtimezone();
-	t = tim + timezone.stdiff;
-	dlflag = 0;
-	for(p = timezone.dlpairs; *p; p += 2)
-		if(t >= p[0])
-		if(t < p[1]) {
-			t = tim + timezone.dldiff;
-			dlflag++;
-			break;
-		}
-	ct = gmtime(t);
-	if(dlflag){
-		strcpy(ct->zone, timezone.dlname);
-		ct->tzoff = timezone.dldiff;
-	} else {
-		strcpy(ct->zone, timezone.stname);
-		ct->tzoff = timezone.stdiff;
-	}
-	return ct;
+	/*
+	 * We have no way to report errors,
+	 * so we just ignore them here.
+	 */
+	tz = tzload("local");
+	tmtime(&tm, tim, tz);
+	return &tm;
 }
 
 Tm*
-gmtime(long tim)
+gmtime(long abs)
 {
-	int d0, d1;
-	long hms, day;
-	static Tm xtime;
-
-	/*
-	 * break initial number into days
-	 */
-	hms = tim % 86400L;
-	day = tim / 86400L;
-	if(hms < 0) {
-		hms += 86400L;
-		day -= 1;
-	}
-
-	/*
-	 * generate hours:minutes:seconds
-	 */
-	xtime.sec = hms % 60;
-	d1 = hms / 60;
-	xtime.min = d1 % 60;
-	d1 /= 60;
-	xtime.hour = d1;
-
-	/*
-	 * day is the day number.
-	 * generate day of the week.
-	 * The addend is 4 mod 7 (1/1/1970 was Thursday)
-	 */
-
-	xtime.wday = (day + 7340036L) % 7;
-
-	/*
-	 * year number
-	 */
-	if(day >= 0)
-		for(d1 = 1970; day >= dysize(d1); d1++)
-			day -= dysize(d1);
-	else
-		for (d1 = 1970; day < 0; d1--)
-			day += dysize(d1-1);
-	xtime.year = d1-1900;
-	xtime.yday = d0 = day;
-
-	/*
-	 * generate month
-	 */
-
-	if(dysize(d1) == 366)
-		dmsize[1] = 29;
-	for(d1 = 0; d0 >= dmsize[d1]; d1++)
-		d0 -= dmsize[d1];
-	dmsize[1] = 28;
-	xtime.mday = d0 + 1;
-	xtime.mon = d1;
-	strcpy(xtime.zone, "GMT");
-	return &xtime;
+	static Tm tm;
+	return tmtime(&tm, abs, nil);
 }
 
 char*
-asctime(Tm *t)
+ctime(long abs)
 {
-	char *ncp;
-	static char cbuf[30];
+	Tzone *tz;
+	Tm tm;
 
-	strcpy(cbuf, "Thu Jan 01 00:00:00 GMT 1970\n");
-	ncp = &"SunMonTueWedThuFriSat"[t->wday*3];
-	cbuf[0] = *ncp++;
-	cbuf[1] = *ncp++;
-	cbuf[2] = *ncp;
-	ncp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[t->mon*3];
-	cbuf[4] = *ncp++;
-	cbuf[5] = *ncp++;
-	cbuf[6] = *ncp;
-	ct_numb(cbuf+8, t->mday);
-	ct_numb(cbuf+11, t->hour+100);
-	ct_numb(cbuf+14, t->min+100);
-	ct_numb(cbuf+17, t->sec+100);
-	ncp = t->zone;
-	cbuf[20] = *ncp++;
-	cbuf[21] = *ncp++;
-	cbuf[22] = *ncp;
-	ct_numb(cbuf+24, (t->year+1900) / 100 + 100);
-	ct_numb(cbuf+26, t->year+100);
-	return cbuf;
+	/*
+	 * We have no way to report errors,
+	 * so we just ignore them here.
+	 */
+	tz = tzload("local");
+	tmtime(&tm, abs, tz);
+	return asctime(&tm);
 }
-
-static
-dysize(int y)
-{
-
-	if(y%4 == 0 && (y%100 != 0 || y%400 == 0))
-		return 366;
-	return 365;
-}
-
-static
-void
-ct_numb(char *cp, int n)
-{
-
-	cp[0] = ' ';
-	if(n >= 10)
-		cp[0] = (n/10)%10 + '0';
-	cp[1] = n%10 + '0';
-}
-
-static
-void
-readtimezone(void)
-{
-	char buf[TZSIZE*11+30], *p;
-	int i;
-
-	memset(buf, 0, sizeof(buf));
-	i = open("/env/timezone", 0);
-	if(i < 0)
-		goto error;
-	if(read(i, buf, sizeof(buf)) >= sizeof(buf)){
-		close(i);
-		goto error;
-	}
-	close(i);
-	p = buf;
-	if(rd_name(&p, timezone.stname))
-		goto error;
-	if(rd_long(&p, &timezone.stdiff))
-		goto error;
-	if(rd_name(&p, timezone.dlname))
-		goto error;
-	if(rd_long(&p, &timezone.dldiff))
-		goto error;
-	for(i=0; i<TZSIZE; i++) {
-		if(rd_long(&p, &timezone.dlpairs[i]))
-			goto error;
-		if(timezone.dlpairs[i] == 0)
-			return;
-	}
-
-error:
-	timezone.stdiff = 0;
-	strcpy(timezone.stname, "GMT");
-	timezone.dlpairs[0] = 0;
-}
-
-static
-rd_name(char **f, char *p)
-{
-	int c, i;
-
-	for(;;) {
-		c = *(*f)++;
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	for(i=0; i<3; i++) {
-		if(c == ' ' || c == '\n')
-			return 1;
-		*p++ = c;
-		c = *(*f)++;
-	}
-	if(c != ' ' && c != '\n')
-		return 1;
-	*p = 0;
-	return 0;
-}
-
-static
-rd_long(char **f, long *p)
-{
-	int c, s;
-	long l;
-
-	s = 0;
-	for(;;) {
-		c = *(*f)++;
-		if(c == '-') {
-			s++;
-			continue;
-		}
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	if(c == 0) {
-		*p = 0;
-		return 0;
-	}
-	l = 0;
-	for(;;) {
-		if(c == ' ' || c == '\n')
-			break;
-		if(c < '0' || c > '9')
-			return 1;
-		l = l*10 + c-'0';
-		c = *(*f)++;
-	}
-	if(s)
-		l = -l;
-	*p = l;
-	return 0;
-}
diff -r e168590166a5 sys/src/libc/9sys/tm2sec.c
--- a/sys/src/libc/9sys/tm2sec.c	Sun Jul 19 14:14:14 2020 -0700
+++ b/sys/src/libc/9sys/tm2sec.c	Sun Jul 19 21:34:16 2020 -0700
@@ -1,202 +1,11 @@
 #include <u.h>
 #include <libc.h>
 
-#define	TZSIZE	150
-static	void	readtimezone(void);
-static	int	rd_name(char**, char*);
-static	int	rd_long(char**, long*);
-static
-struct
-{
-	char	stname[4];
-	char	dlname[4];
-	long	stdiff;
-	long	dldiff;
-	long	dlpairs[TZSIZE];
-} timezone;
-
-#define SEC2MIN 60L
-#define SEC2HOUR (60L*SEC2MIN)
-#define SEC2DAY (24L*SEC2HOUR)
-
-/*
- *  days per month plus days/year
- */
-static	int	dmsize[] =
-{
-	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-};
-static	int	ldmsize[] =
-{
-	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-};
-
-/*
- *  return the days/month for the given year
- */
-static int *
-yrsize(int y)
-{
-	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
-		return ldmsize;
-	else
-		return dmsize;
-}
-
-/*
- * compute seconds since Jan 1 1970 GMT
- * and convert to our timezone.
- */
 long
 tm2sec(Tm *tm)
 {
-	long secs, *p;
-	int i, yday, year, *d2m;
+	Tm tt;
 
-	if(strcmp(tm->zone, "GMT") != 0 && timezone.stname[0] == 0)
-		readtimezone();
-	secs = 0;
-
-	/*
-	 *  seconds per year
-	 */
-	year = tm->year + 1900;
-	for(i = 1970; i < year; i++){
-		d2m = yrsize(i);
-		secs += d2m[0] * SEC2DAY;
-	}
-
-	/*
-	 *  if mday is set, use mon and mday to compute yday
-	 */
-	if(tm->mday){
-		yday = 0;
-		d2m = yrsize(year);
-		for(i=0; i<tm->mon; i++)
-			yday += d2m[i+1];
-		yday += tm->mday-1;
-	}else{
-		yday = tm->yday;
-	}
-	secs += yday * SEC2DAY;
-
-	/*
-	 * hours, minutes, seconds
-	 */
-	secs += tm->hour * SEC2HOUR;
-	secs += tm->min * SEC2MIN;
-	secs += tm->sec;
-
-	/*
-	 * Only handles zones mentioned in /env/timezone,
-	 * but things get too ambiguous otherwise.
-	 */
-	if(strcmp(tm->zone, timezone.stname) == 0)
-		secs -= timezone.stdiff;
-	else if(strcmp(tm->zone, timezone.dlname) == 0)
-		secs -= timezone.dldiff;
-	else if(tm->zone[0] == 0){
-		secs -= timezone.dldiff;
-		for(p = timezone.dlpairs; *p; p += 2)
-			if(secs >= p[0] && secs < p[1])
-				break;
-		if(*p == 0){
-			secs += timezone.dldiff;
-			secs -= timezone.stdiff;
-		}
-	}
-	return secs;
+	tt = *tm;
+	return tmnorm(&tt);
 }
-
-static
-void
-readtimezone(void)
-{
-	char buf[TZSIZE*11+30], *p;
-	int i;
-
-	memset(buf, 0, sizeof(buf));
-	i = open("/env/timezone", 0);
-	if(i < 0)
-		goto error;
-	if(read(i, buf, sizeof(buf)) >= sizeof(buf))
-		goto error;
-	close(i);
-	p = buf;
-	if(rd_name(&p, timezone.stname))
-		goto error;
-	if(rd_long(&p, &timezone.stdiff))
-		goto error;
-	if(rd_name(&p, timezone.dlname))
-		goto error;
-	if(rd_long(&p, &timezone.dldiff))
-		goto error;
-	for(i=0; i<TZSIZE; i++) {
-		if(rd_long(&p, &timezone.dlpairs[i]))
-			goto error;
-		if(timezone.dlpairs[i] == 0)
-			return;
-	}
-
-error:
-	timezone.stdiff = 0;
-	strcpy(timezone.stname, "GMT");
-	timezone.dlpairs[0] = 0;
-}
-
-static int
-rd_name(char **f, char *p)
-{
-	int c, i;
-
-	for(;;) {
-		c = *(*f)++;
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	for(i=0; i<3; i++) {
-		if(c == ' ' || c == '\n')
-			return 1;
-		*p++ = c;
-		c = *(*f)++;
-	}
-	if(c != ' ' && c != '\n')
-		return 1;
-	*p = 0;
-	return 0;
-}
-
-static int
-rd_long(char **f, long *p)
-{
-	int c, s;
-	long l;
-
-	s = 0;
-	for(;;) {
-		c = *(*f)++;
-		if(c == '-') {
-			s++;
-			continue;
-		}
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	if(c == 0) {
-		*p = 0;
-		return 0;
-	}
-	l = 0;
-	for(;;) {
-		if(c == ' ' || c == '\n')
-			break;
-		if(c < '0' || c > '9')
-			return 1;
-		l = l*10 + c-'0';
-		c = *(*f)++;
-	}
-	if(s)
-		l = -l;
-	*p = l;
-	return 0;
-}
diff -r e168590166a5 sys/src/libc/port/date.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/libc/port/date.c	Sun Jul 19 21:34:16 2020 -0700
@@ -0,0 +1,992 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Tzabbrev Tzabbrev;
+typedef struct Tzoffpair Tzoffpair;
+
+#define Ctimefmt	"WW MMM _D hh:mm:ss ZZZ YYYY"
+#define P(pad, w)	((pad) < (w) ? 0 : pad - w)
+
+enum {
+	Tzsize		= 150,
+	Nsec		= 1000*1000*1000,
+	Usec		= 1000*1000,
+	Msec		= 1000,
+	Daysec		= (vlong)24*3600,
+	Days400y	= 365*400 + 4*25 - 3,
+	Days4y		= 365*4 + 1,
+};
+
+enum {
+	Cend,
+	Cspace,
+	Cnum,
+	Cletter,
+	Cpunct,
+};
+	
+struct Tzone {
+	char	tzname[32];
+	char	stname[16];
+	char	dlname[16];
+	long	stdiff;
+	long	dldiff;
+	long	dlpairs[150];
+};
+
+static QLock zlock;
+static int nzones;
+static Tzone **zones;
+static int mdays[] = {
+	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+static char *wday[] = {
+	"Sunday","Monday","Tuesday",
+	"Wednesday","Thursday","Friday",
+	"Saturday", nil,
+};
+static char *month[] = {
+	"January", "February", "March",
+	"April", "May", "June", "July",
+	"August", "September", "October",
+	"November", "December", nil
+};
+
+struct Tzabbrev {
+	char *abbr;
+	char *name;
+};
+
+struct Tzoffpair {
+	char *abbr;
+	int off;
+};
+
+#define isalpha(c)\
+	(((c)|0x60) >= 'a' && ((c)|0x60) <= 'z')
+
+/* Obsolete time zone names. Hardcoded to match RFC5322 */
+static Tzabbrev tzabbrev[] = {
+	{"UT", "GMT"}, {"GMT", "GMT"}, {"UTC", "GMT"},
+	{"EST",	"US_Eastern"}, {"EDT", "US_Eastern"},
+	{"CST", "US_Central"}, {"CDT", "US_Central"},
+	{"MST", "US_Mountain"}, {"MDT", "US_Mountain"},
+	{"PST", "US_Pacific"}, {"PDT", "US_Pacific"},
+	{nil},
+};
+
+/* Military timezone names */
+static Tzoffpair milabbrev[] = {
+	{"A", -1*3600},   {"B", -2*3600},   {"C", -3*3600},
+	{"D", -4*3600},   {"E", -5*3600},   {"F", -6*3600},
+	{"G", -7*3600},   {"H", -8*3600},   {"I", -9*3600},
+	{"K", -10*3600},  {"L", -11*3600},  {"M", -12*3600},
+	{"N", +1*3600},   {"O", +2*3600},   {"P", +3*3600},
+	{"Q", +4*3600},   {"R", +5*3600},   {"S", +6*3600},
+	{"T", +7*3600},   {"U", +8*3600},   {"V", +9*3600},
+	{"W", +10*3600},  {"X", +11*3600}, {"Y", +12*3600},
+	{"Z",	0}, {nil, 0}
+};
+
+static vlong
+mod(vlong a, vlong b)
+{
+	vlong r;
+
+	r = a % b;
+	if(r < 0)
+		r += b;
+	return r;
+}
+
+static int
+isleap(int y)
+{
+	return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
+}
+
+static int
+rdname(char **f, char *p, int n)
+{
+	char *s, *e;
+
+	for(s = *f; *s; s++)
+		if(*s != ' ' && *s != '\t'  && *s != '\n')
+			break;
+	e = s + n;
+	for(; *s && s != e; s++) {
+		if(*s == ' ' || *s == '\t' || *s == '\n')
+			break;
+		*p++ = *s;
+	}
+	*p = 0;
+	if(n - (e - s) < 3 || *s != ' ' && *s != '\t' && *s != '\n'){
+		werrstr("truncated name");
+		return -1;
+	}
+	*f = s;
+	return 0;
+}
+
+static int
+rdlong(char **f, long *p)
+{
+	int c, s;
+	long l;
+
+	s = 0;
+	while((c = *(*f)++) != 0){
+		if(c == '-')
+			s++;
+		else if(c != ' ' && c != '\n')
+			break;
+	}
+	if(c == 0) {
+		*p = 0;
+		return 0;
+	}
+	l = 0;
+	for(;;) {
+		if(c == ' ' || c == '\n')
+			break;
+		if(c < '0' || c > '9'){
+			werrstr("non-number %c in name", c);
+			return -1;
+		}
+		l = l*10 + c-'0';
+		c = *(*f)++;
+	}
+	if(s)
+		l = -l;
+	*p = l;
+	return 0;
+}
+
+static int
+loadzone(Tzone *tz, char *name)
+{
+	char buf[Tzsize*11+30], path[128], *p;
+	int i, f, r;
+
+	memset(tz, 0, sizeof(Tzone));
+	if(strcmp(name, "local") == 0)
+		snprint(path, sizeof(path), "/env/timezone");
+	else
+		snprint(path, sizeof(path), "/adm/timezone/%s", name);
+	memset(buf, 0, sizeof(buf));
+	if((f = open(path, 0)) == -1)
+		return -1;
+	r = read(f, buf, sizeof(buf));
+	close(f);
+	if(r == sizeof(buf) || r == -1)
+		return -1;
+	buf[r] = 0;
+	p = buf;
+	if(rdname(&p, tz->stname, sizeof(tz->stname)) == -1)
+		return -1;
+	if(rdlong(&p, &tz->stdiff) == -1)
+		return -1;
+	if(rdname(&p, tz->dlname, sizeof(tz->dlname)) == -1)
+		return -1;
+	if(rdlong(&p, &tz->dldiff) == -1)
+		return -1;
+	for(i=0; i < Tzsize; i++) {
+		if(rdlong(&p, &tz->dlpairs[i]) == -1){
+			werrstr("invalid transition time");
+			return -1;
+		}
+		if(tz->dlpairs[i] == 0)
+			return 0;
+	}
+	werrstr("invalid timezone %s", name);
+	return -1;
+}
+
+Tzone*
+tzload(char *tzname)
+{
+	Tzone *tz, **newzones;
+	int i;
+
+	if(tzname == nil)
+		tzname = "GMT";
+	qlock(&zlock);
+	for(i = 0; i < nzones; i++){
+		tz = zones[i];
+		if(strcmp(tz->stname, tzname) == 0)
+			goto found;
+		if(strcmp(tz->dlname, tzname) == 0)
+			goto found;
+		if(strcmp(tz->tzname, tzname) == 0)
+			goto found;
+	}
+
+	tz = malloc(sizeof(Tzone));
+	if(tz == nil)
+		goto error;
+	newzones = realloc(zones, (nzones + 1) * sizeof(Tzone*));
+	if(newzones == nil)
+		goto error;
+	if(loadzone(tz, tzname) != 0)
+		goto error;
+	if(snprint(tz->tzname, sizeof(tz->tzname), tzname) >= sizeof(tz->tzname)){
+		werrstr("timezone name too long");
+		return nil;
+	}
+	zones = newzones;
+	zones[nzones] = tz;
+	nzones++;
+found:
+	qunlock(&zlock);
+	return tz;
+error:
+	free(tz);
+	qunlock(&zlock);
+	return nil;
+}
+
+static void
+tzoffset(Tzone *tz, vlong abs, Tm *tm)
+{
+	long dl, *p;
+	dl = 0;
+	if(tz == nil){
+		snprint(tm->zone, sizeof(tm->zone), "GMT");
+		tm->tzoff = 0;
+		return;
+	}
+	for(p = tz->dlpairs; *p; p += 2)
+		if(abs > p[0] && abs <= p[1]){
+			dl = 1;
+			break;
+		}
+	if(dl){
+		snprint(tm->zone, sizeof(tm->zone), tz->dlname);
+		tm->tzoff = tz->dldiff;
+	}else{
+		snprint(tm->zone, sizeof(tm->zone), tz->stname);
+		tm->tzoff = tz->stdiff;
+	}
+}
+
+static Tm*
+tmfill(Tm *tm, vlong abs, vlong nsec)
+{
+	vlong zrel, j, y, m, d, t, e;
+	int i;
+
+	zrel = abs + tm->tzoff;
+	t = zrel % Daysec;
+	e = zrel / Daysec;
+	if(t < 0){
+		t += Daysec;
+		e -= 1;
+	}
+
+	t += nsec/Nsec;
+	tm->sec = mod(t, 60);
+	t /= 60;
+	tm->min = mod(t, 60);
+	t /= 60;
+	tm->hour = mod(t, 24);
+	tm->wday = mod((e + 4), 7);
+
+	/*
+	 * Split up year, month, day.
+	 * 
+	 * Implemented according to "Algorithm 199,
+	 * conversions between calendar  date and
+	 * Julian day number", Robert G. Tantzen,
+	 * Air Force Missile Development
+	 * Center, Holloman AFB, New Mex.
+	 * 
+	 * Lots of magic.
+	 */
+	j = (zrel + 2440588 * Daysec) / (Daysec) - 1721119;
+	y = (4 * j - 1) / Days400y;
+	j = 4 * j - 1 - Days400y * y;
+	d = j / 4;
+	j = (4 * d + 3) / Days4y;
+	d = 4 * d + 3 - Days4y * j;
+	d = (d + 4) / 4 ;
+	m = (5 * d - 3) / 153;
+	d = 5 * d - 3 - 153 * m;
+	d = (d + 5) / 5;
+	y = 100 * y + j;
+
+	if(m < 10)
+		m += 3;
+	else{
+		m -= 9;
+		y++;
+	}
+
+	/* there's no year 0 */
+	if(y <= 0)
+		y--;
+	/* and if j negative, the day and month are also negative */
+	if(m < 0)
+		m += 12;
+	if(d < 0)
+		d += mdays[m - 1];
+
+	tm->yday = d;
+	for(i = 0; i < m - 1; i++)
+		tm->yday += mdays[i];
+	if(m > 1 && isleap(y))
+		tm->yday++;
+	tm->year = y - 1900;
+	tm->mon = m - 1;
+	tm->mday = d;
+	tm->nsec = mod(nsec, Nsec);
+	return tm;
+}	
+
+
+Tm*
+tmtime(Tm *tm, vlong abs, Tzone *tz)
+{
+	return tmtimens(tm, abs, 0, tz);
+}
+
+Tm*
+tmtimens(Tm *tm, vlong abs, int ns, Tzone *tz)
+{
+	tm->tz = tz;
+	tzoffset(tz, abs, tm);
+	return tmfill(tm, abs, ns);
+}
+
+Tm*
+tmnow(Tm *tm, Tzone *tz)
+{
+	vlong ns;
+
+	ns = nsec();
+	return tmtimens(tm, nsec()/Nsec, mod(ns, Nsec), tz);
+}
+
+vlong
+tmnorm(Tm *tm)
+{
+	vlong c, yadj, j, abs, y, m, d;
+
+	if(tm->mon > 1){
+		m = tm->mon - 2;
+		y = tm->year + 1900;
+	}else{
+		m = tm->mon + 10;
+		y = tm->year + 1899;
+	}
+	d = tm->mday;
+	c = y / 100;
+	yadj = y - 100 * c;
+	j = (c * Days400y / 4 + 
+		Days4y * yadj / 4 +
+		(153 * m + 2)/5 + d -
+		719469);
+	abs = j * Daysec;
+	abs += tm->hour * 3600;
+	abs += tm->min * 60;
+	abs += tm->sec;
+	if(tm->tz){
+		tzoffset(tm->tz, abs - tm->tzoff, tm);
+		tzoffset(tm->tz, abs - tm->tzoff, tm);
+	}
+	abs -= tm->tzoff;
+	tmfill(tm, abs, tm->nsec);
+	return abs;
+}
+
+static int
+τconv(Fmt *f)
+{
+	int depth, n, v, w, h, m, c0, sgn, pad, off;
+	char *p, *am;
+	Tmfmt tf;
+	Tm *tm;
+
+	n = 0;
+	tf = va_arg(f->args, Tmfmt);
+	tm = tf.tm;
+	p = tf.fmt;
+	if(p == nil)
+		p = Ctimefmt;
+	while(*p){
+		w = 1;
+		pad = 0;
+		while(*p == '_'){
+			pad++;
+			p++;
+		}
+		c0 = *p++;
+		while(c0 && *p == c0){
+			w++;
+			p++;
+		}
+		pad += w;
+		switch(c0){
+		case 0:
+			break;
+		case 'Y':
+			switch(w){
+			case 1:	n += fmtprint(f, "%*d", pad, tm->year + 1900);		break;
+			case 2: n += fmtprint(f, "%*d", pad, tm->year % 100);		break;
+			case 4:	n += fmtprint(f, "%*d", pad, tm->year + 1900);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'M':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->mon + 1);		break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->mon + 1);	break;
+			case 3:	n += fmtprint(f, "%*.3s", pad, month[tm->mon]);		break;
+			case 4:	n += fmtprint(f, "%*s", pad, month[tm->mon]);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'D':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->mday);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->mday);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'W':
+			switch(w){
+			case 1:	n += fmtprint(f, "%*d", pad, tm->wday + 1);		break;
+			case 2:	n += fmtprint(f, "%*.3s", pad, wday[tm->wday]);		break;
+			case 3:	n += fmtprint(f, "%*s", pad, wday[tm->wday]);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'H':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->hour % 12);		break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->hour % 12);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'h':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->hour);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->hour);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'm':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->min);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->min);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 's':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->sec);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->sec);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 't':
+			v = tm->nsec / (1000*1000);
+			switch(w){
+			case 1:	n += fmtprint(f, "%*d", pad, v % 1000);			break;
+			case 2:
+			case 3:	n += fmtprint(f, "%*s%03d", P(pad, 3), "", v % 1000);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'u':
+			v = tm->nsec / 1000;
+			switch(w){
+			case 1:	n += fmtprint(f, "%*d", pad, v % 1000);			break;
+			case 2:	n += fmtprint(f, "%*s%03d", P(pad, 3), "", v % 1000);	break;
+			case 3:	n += fmtprint(f, "%*d", P(pad, 6), v);			break;
+			case 4:	n += fmtprint(f, "%*s%06d", P(pad, 6), "", v);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'n':
+			v = tm->nsec;
+			switch(w){
+			case 1:	n += fmtprint(f, "%*d", pad, v%1000);			break;
+			case 2:	n += fmtprint(f, "%*s%03d", P(pad, 3), "", v % 1000);	break;
+			case 3:	n += fmtprint(f, "%*d", pad , v%(1000*1000));		break;
+			case 4: n += fmtprint(f, "%*s%06d", P(pad, 6), "", v%(1000000)); break;
+			case 5:	n += fmtprint(f, "%*d", pad, v);			break;
+			case 6:	n += fmtprint(f, "%*s%09d", P(pad, 9), "", v);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'z':
+			if(w != 1)
+				goto badfmt;
+		case 'Z':
+			sgn = (tm->tzoff < 0) ? '-' : '+';
+			off = (tm->tzoff < 0) ? -tm->tzoff : tm->tzoff;
+			h = off/3600;
+			m = (off/60)%60;
+			if(w < 3 && pad < 5)
+				pad = 5;
+			switch(w){
+			case 1:	n += fmtprint(f, "%*s%c%02d%02d", pad-5, "", sgn, h, m); break;
+			case 2:	n += fmtprint(f, "%*s%c%02d:%02d", pad-5, "", sgn, h, m); break;
+			case 3:	n += fmtprint(f, "%*s", pad, tm->zone);			 break;
+			}
+			break;
+		case 'A':
+		case 'a':
+			if(w != 1)
+				goto badfmt;
+			if(c0 == 'a')
+				am = (tm->hour < 12) ? "am" : "pm";
+			else
+				am = (tm->hour < 12) ? "AM" : "PM";
+			n += fmtprint(f, "%*s", pad, am);
+			break;
+		case '[':
+			depth = 1;
+			while(*p){
+				if(*p == '[')
+					depth++;
+				if(*p == ']')
+					depth--;
+				if(*p == '\\')
+					p++;
+				if(depth == 0)
+					break;
+				fmtrune(f, *p++);
+			}
+			if(*p++ != ']')
+				goto badfmt;
+			break;
+		default:
+			while(w-- > 0)
+				n += fmtrune(f, c0);
+			break;
+		}
+	}
+	return n;
+badfmt:
+	werrstr("garbled format %s", tf.fmt);
+	return -1;			
+}
+
+static int
+getnum(char **ps, int maxw, int *ok)
+{
+	char *s, *e;
+	int n;
+
+	n = 0;
+	e = *ps + maxw;
+	for(s = *ps; s != e && *s >= '0' && *s <= '9'; s++){
+		n *= 10;
+		n += *s - '0';
+	}
+	*ok = s != *ps;
+	*ps = s;
+	return n;
+}
+
+static int
+lookup(char **s, char **tab, int len, int *ok)
+{
+	int nc, i;
+
+	*ok = 0;
+	for(i = 0; *tab; tab++){
+		nc = (len != -1) ? len : strlen(*tab);
+		if(cistrncmp(*s, *tab, nc) == 0){
+			*s += nc;
+			*ok = 1;
+			return i;
+		}
+		i++;
+	}
+	*ok = 0;
+	return -1;
+}
+
+Tm*
+tmparse(Tm *tm, char *fmt, char *str, Tzone *tz, char **ep)
+{
+	int depth, n, w, c0, zs, z0, z1, md, ampm, zoned, sloppy, tzo, ok;
+	vlong abs;
+	char *s, *p, *q;
+	Tzone *zparsed;
+	Tzabbrev *a;
+	Tzoffpair *m;
+
+	p = fmt;
+	s = str;
+	tzo = 0;
+	ampm = -1;
+	zoned = 0;
+	zparsed = nil;
+	sloppy = 0;
+	/* Default all fields */
+	tmtime(tm, 0, nil);
+	if(*p == '~'){
+		sloppy = 1;
+		p++;
+	}
+	while(*p){
+		w = 1;
+		c0 = *p++;
+		if(c0 == '?'){
+			w = -1;
+			c0 = *p++;
+		}
+		while(*p == c0){
+			if(w != -1) w++;
+			p++;
+		}
+		ok = 1;
+		switch(c0){
+		case 'Y':
+			switch(w){
+			case -1:
+				tm->year = getnum(&s, 4, &ok);
+				if(tm->year > 100) tm->year -= 1900;
+				break;
+			case 1:	tm->year = getnum(&s, 4, &ok) - 1900;	break;
+			case 2: tm->year = getnum(&s, 2, &ok);		break;
+			case 3:
+			case 4:	tm->year = getnum(&s, 4, &ok) - 1900;	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'M':
+			switch(w){
+			case -1:
+				tm->mon = getnum(&s, 2, &ok) - 1;
+				if(!ok) tm->mon = lookup(&s, month, -1, &ok);
+				if(!ok) tm->mon = lookup(&s, month, 3, &ok);
+				break;
+			case 1:
+			case 2: tm->mon = getnum(&s, 2, &ok) - 1;	break;
+			case 3:	tm->mon = lookup(&s, month, 3, &ok);	break;
+			case 4:	tm->mon = lookup(&s, month, -1, &ok);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'D':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->mday = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'W':
+			switch(w){
+			case -1:
+				tm->wday = lookup(&s, wday, -1, &ok);
+				if(!ok) tm->wday = lookup(&s, wday, 3, &ok);
+				break;
+			case 1:	tm->wday = lookup(&s, wday, 3, &ok);	break;
+			case 2:	tm->wday = lookup(&s, wday, -1, &ok);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'h':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->hour = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'm':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->min = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 's':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->sec = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 't':
+			switch(w){
+			case -1:
+			case 1:
+			case 2:
+			case 3:	tm->nsec += getnum(&s, 3, &ok)*1000000;	break;
+			}
+			break;
+		case 'u':
+			switch(w){
+			case -1:
+			case 1:
+			case 2:	tm->nsec += getnum(&s, 3, &ok)*1000;	break;
+			case 3:
+			case 4: tm->nsec += getnum(&s, 6, &ok)*1000;	break;
+			}
+			break;
+		case 'n':
+			switch(w){
+			case 1:
+			case 2:	tm->nsec += getnum(&s, 3, &ok);		break;
+			case 3:
+			case 4: tm->nsec += getnum(&s, 6, &ok);		break;
+			case -1:
+			case 5:
+			case 6: tm->nsec += getnum(&s, 9, &ok);		break;
+			}
+			break;
+		case 'z':
+			if(w != 1)
+				goto badfmt;
+		case 'Z':
+			zs = 0;
+			zoned = 1;
+			switch(*s++){
+			case '+': zs = 1; break;
+			case '-': zs = -1; break;
+			default: s--; break;
+			}
+			q = s;
+			switch(w){
+			case -1:
+			case 3:
+				for(a = tzabbrev; a->abbr; a++){
+					n = strlen(a->abbr);
+					if(cistrncmp(s, a->abbr, n) == 0 && !isalpha(s[n]))
+						break;
+				}
+				if(a->abbr != nil){
+					s += strlen(a->abbr);
+					zparsed = tzload(a->name);
+					if(zparsed == nil){
+						werrstr("unloadable zone %s (%s)", a->abbr, a->name);
+						if(w != -1)
+							return nil;
+					}
+					goto Zoneparsed;
+				}
+				for(m = milabbrev; m->abbr != nil; m++){
+					n = strlen(m->abbr);
+					if(cistrncmp(s, m->abbr, n) == 0 && !isalpha(s[n]))
+						break;
+				}
+				if(m->abbr != nil){
+					snprint(tm->zone, sizeof(tm->zone), "%s", m->abbr);
+					tzo = m->off;
+					goto Zoneparsed;
+				}
+				if(w != -1)
+					break;
+				/* fall through */
+			case 1:
+				/* offset: [+-]hhmm */
+				z0 = getnum(&s, 4, &ok);
+				if(s - q == 4){
+					z1 = z0 % 100;
+					if(z0/100 > 13 || z1 >= 60)
+						goto baddate;
+					tzo = zs*(3600*(z0/100) + 60*z1);
+					snprint(tm->zone, sizeof(tm->zone), "%c%02d%02d", zs<0?'-':'+', z0/100, z1);
+					goto Zoneparsed;
+				}
+				if(w != -1)
+					goto baddate;
+				/* fall through */
+			case 2:
+				s = q;
+				/* offset: [+-]hh:mm */
+				z0 = getnum(&s, 2, &ok);
+				if(*s++ != ':')
+					break;
+				z1 = getnum(&s, 2, &ok);
+				if(z1 > 60)
+					break;
+				tzo = zs*(3600*z0 + 60*z1);
+				snprint(tm->zone, sizeof(tm->zone), "%c%d02:%02d", zs<0?'-':'+', z0, z1);
+				goto Zoneparsed;
+			}
+			if(w != -1)
+				goto baddate;
+			/*
+			 * Final fuzzy fallback: If we have what looks like an
+			 * unknown timezone abbreviation, keep the zone name,
+			 * but give it a timezone offset of 0. This allows us
+			 * to avoid rejecting zones outside of RFC5322.
+			 */
+			for(s = q; *s; s++)
+				if(!isalpha(*s))
+					break;
+			if(s - q >= 3 && !isalpha(*s)){
+				strncpy(tm->zone, q, s - q);
+				tzo = 0;
+				ok = 1;
+				goto Zoneparsed;
+			}
+			goto baddate;
+Zoneparsed:
+			break;
+		case 'A':
+		case 'a':
+			if(cistrncmp(s, "am", 2) == 0)
+				ampm = 0;
+			else if(cistrncmp(s, "pm", 2) == 0)
+				ampm = 1;
+			else
+				goto baddate;
+			s += 2;
+			break;
+		case '[':
+			depth = 1;
+			while(*p){
+				if(*p == '[')
+					depth++;
+				if(*p == ']')
+					depth--;
+				if(*p == '\\')
+					p++;
+				if(depth == 0)
+					break;
+				if(*s == 0)
+					goto baddate;
+				if(*s++ != *p++)
+					goto baddate;
+			}
+			if(*p != ']')
+				goto badfmt;
+			p++;
+			break;
+		case '_':
+		case ',':
+		case ' ':
+
+			if(*s != ' ' && *s != '\t' && *s != ',' && *s != '\n' && *s != '\0')
+				goto baddate;
+			p += strspn(p, " ,_\t\n");
+			s += strspn(s, " ,\t\n");
+			break;
+		default:
+			if(*s == 0)
+				goto baddate;
+			if(*s++ != c0)
+				goto baddate;
+			break;
+		}
+		if(!ok)
+			goto baddate;
+	}
+	if(*p != '\0')
+		goto baddate;
+	if(ep != nil)
+		*ep = s;
+	if(!sloppy && ampm != -1 && (tm->hour < 1 || tm->hour > 12))
+		goto baddate;
+	if(ampm == 0 && tm->hour == 12)
+		tm->hour = 0;
+	else if(ampm == 1 && tm->hour < 12)
+		tm->hour += 12;
+	/*
+	 * If we're allowing sloppy date ranges,
+	 * we'll normalize out of range values.
+	 */
+	if(!sloppy){
+		if(tm->yday < 0 || tm->yday > 365 + isleap(tm->year + 1900))
+			goto baddate;
+		if(tm->wday < 0 || tm->wday > 6)
+			goto baddate;
+		if(tm->mon < 0 || tm->mon > 11)
+			goto baddate;
+		md = mdays[tm->mon];
+		if(tm->mon == 1 && isleap(tm->year + 1900))
+			md++;
+		if(tm->mday < 0 || tm->mday > md)
+			goto baddate;
+		if(tm->hour < 0 || tm->hour > 24)
+			goto baddate;
+		if(tm->min < 0 || tm->min > 59)
+			goto baddate;
+		if(tm->sec < 0 || tm->sec > 60)
+			goto baddate;
+		if(tm->nsec < 0 || tm->nsec > Nsec)
+			goto baddate;
+	}
+
+	/*
+	 * Normalizing gives us the local time,
+	 * but because we havnen't applied the
+	 * timezone, we think we're GMT. So, we
+	 * need to shift backwards. Then, we move
+	 * the "GMT that was local" back to local
+	 * time.
+	 */
+	abs = tmnorm(tm);
+	tm->tzoff = tzo;
+	if(!zoned)
+		tzoffset(tz, abs, tm);
+	else if(zparsed != nil){
+		tzoffset(zparsed, abs, tm);
+		tzoffset(zparsed, abs + tm->tzoff, tm);
+	}
+	abs -= tm->tzoff;
+	if(tz != nil || !zoned)
+		tmtimens(tm, abs, tm->nsec, tz);
+	return tm;
+baddate:
+	werrstr("invalid date %s", str);
+	return nil;
+badfmt:
+	werrstr("garbled format %s near '%s'", fmt, p);
+	return nil;			
+}
+
+Tmfmt
+tmfmt(Tm *d, char *fmt)
+{
+	return (Tmfmt){fmt, d};
+}
+
+void
+tmfmtinstall(void)
+{
+	fmtinstall(L'τ', τconv);
+}
+
+/* These legacy functions need access to τconv */
+static char*
+dotmfmt(Fmt *f, ...)
+{
+	static char buf[30];
+	va_list ap;
+
+	va_start(ap, f);
+	f->runes = 0;
+	f->start = buf;
+	f->to = buf;
+	f->stop = buf + sizeof(buf) - 1;
+	f->flush = nil;
+	f->farg = nil;
+	f->nfmt = 0;
+	f->args = ap;
+	τconv(f);
+	va_end(ap);
+	buf[sizeof(buf) - 1] = 0;
+	return buf;
+}
+
+char*
+asctime(Tm* tm)
+{
+	Tmfmt tf;
+	Fmt f;
+
+	tf = tmfmt(tm, "WW MMM _D hh:mm:ss ZZZ YYYY\n");
+	return dotmfmt(&f, tf);
+}
+
diff -r e168590166a5 sys/src/libc/port/mkfile
--- a/sys/src/libc/port/mkfile	Sun Jul 19 14:14:14 2020 -0700
+++ b/sys/src/libc/port/mkfile	Sun Jul 19 21:34:16 2020 -0700
@@ -20,6 +20,7 @@
 	cleanname.c\
 	crypt.c\
 	ctype.c\
+	date.c\
 	encodefmt.c\
 	execl.c\
 	exits.c\

[-- Attachment #3: Type: text/plain, Size: 3094 bytes --]

diff -r f8f63e944375 sys/src/cmd/date.c
--- a/sys/src/cmd/date.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/date.c	Sat Jul 18 22:37:01 2020 -0700
@@ -1,101 +1,68 @@
 #include <u.h>
 #include <libc.h>
 
-static char *day[] = {
-	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+enum {
+	Nsec = 1000*1000*1000,
 };
 
-static char *mon[] = {
-	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
-	"Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-int uflg, nflg, iflg, tflg, mflg;
-
-char*
-isodate(Tm *t)
+void
+usage(void)
 {
-	static char c[26]; /* leave room to append isotime */
-	snprint(c, 11, "%04d-%02d-%02d", 
-		t->year + 1900, t->mon + 1, t->mday);
-	return c;
-}
-
-char*
-isotime(Tm *t)
-{
-	int tz;
-	char *c, *d;
-	d = isodate(t);
-	c = d+10;
-	snprint(c, 10, "T%02d:%02d:%02d",
-		t->hour, t->min, t->sec); /* append to isodate */
-	tz = t->tzoff / 60;
-	if(t->tzoff) {
-		/* localtime */
-		if (t->tzoff > 0) {
-			c[9] = '+';
-		} else {
-			c[9] = '-';
-			tz = -tz;
-		}
-		snprint(c+10, 6, "%02d:%02d", tz / 60, tz % 60);
-	} else {
-		c[9] = 'Z';
-		c[10] = 0;
-	}
-	return d;
-}
-
-char *
-mailtime(Tm *t)
-{
-	static char c[64];
-	char *sgn;
-	int off;
-
-	sgn = "+";
-	if(t->tzoff < 0)
-		sgn = "";
-	off = (t->tzoff/3600)*100 + (t->tzoff/60)%60;
-	snprint(c, sizeof(c), "%s, %.2d %s %.4d %.2d:%.2d:%.2d %s%.4d",
-		day[t->wday], t->mday, mon[t->mon], t->year + 1900,
-		t->hour, t->min, t->sec, sgn, off);
-	return c;
+	fprint(2, "usage: date [-itunm] [-f fmt] [seconds]\n");
+	exits("usage");
 }
 
 void
 main(int argc, char *argv[])
 {
-	ulong now;
-	Tm *tm;
+	int nflg, uflg;
+	char *fmt;
+	vlong s, ns;
+	Tzone *tz;
+	Tm tm;
+
+	nflg = 0;
+	uflg = 0;
+	tz = nil;
+	fmt =  "W MMM _D hh:mm:ss ZZZ YYYY";
+	tmfmtinstall();
+
 	ARGBEGIN{
-	case 'n':	nflg = 1; break;
-	case 'u':	uflg = 1; break;
-	case 't':	tflg = 1; /* implies -i */
-	case 'i':	iflg = 1; break;
-	case 'm':	mflg = 1; break;
-	default:	fprint(2, "usage: date [-itunm] [seconds]\n"); exits("usage");
+	case 'n':	nflg = 1;				break;
+	case 'u':	uflg = 1;				break;
+	case 't':	fmt = "YYYY-MM-DDThh:mm:ssZZ";		break;
+	case 'i':	fmt = "YYYY-MM-DD";			break;
+	case 'm':	fmt = "W, DD MMM YYYY hh:mm:ss Z";	break;
+	case 'f':	fmt = EARGF(usage());			break;
+	default:	usage();
 	}ARGEND
 
-	if(argc == 1)
-		now = strtoul(*argv, 0, 0);
-	else
-		now = time(0);
+	s = 0;
+	ns = 0;
+	switch(argc) {
+	case 0:
+		ns = nsec();
+		s = ns/Nsec;
+		ns = ns%Nsec;
+		break;
+	case 1:
+		s = strtoll(argv[0], nil, 0);
+		ns = 0;
+		break;
+	default:
+		usage();
+		break;
+	}
+
+	if(!uflg && (tz = tzload("local")) == nil)
+		sysfatal("timezone: %r");
+	if(tmtimens(&tm, s, ns, tz) == nil)
+		sysfatal("now: %r");
 
 	if(nflg)
-		print("%ld\n", now);
-	else {
-		tm = uflg ? gmtime(now) : localtime(now);
-		if(iflg) {
-			if(tflg)
-				print("%s\n", isotime(tm));
-			else
-				print("%s\n", isodate(tm));
-		} else if(mflg)
-			print("%s\n", mailtime(tm));
-		else
-			print("%s", asctime(tm));
-	}
+		print("%lld\n", tmnorm(&tm));
+	else
+		if(print("%τ\n", tmfmt(&tm, fmt)) == -1)
+			sysfatal("%r");
 	exits(0);
 }

[-- Attachment #4: Type: text/plain, Size: 13722 bytes --]

diff -r f8f63e944375 sys/src/cmd/seconds.c
--- a/sys/src/cmd/seconds.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/seconds.c	Sat Jul 18 22:37:07 2020 -0700
@@ -1,466 +1,114 @@
-/*
- * seconds absolute_date ... - convert absolute_date to seconds since epoch
- */
-
 #include <u.h>
 #include <libc.h>
-#include <ctype.h>
 
-typedef ulong Time;
-
-enum {
-	AM, PM, HR24,
-
-	/* token types */
-	Month = 1,
-	Year,
-	Day,
-	Timetok,
-	Tz,
-	Dtz,
-	Ignore,
-	Ampm,
-
-	Maxtok		= 6, /* only this many chars are stored in datetktbl */
-	Maxdateflds	= 25,
+char *knownfmt[] = {
+	/* asctime */
+	"W MMM DD hh:mm:ss ?Z YYYY",
+	/* RFC3339 */
+	"YYYY-MM-DD[T]hh:mm:ss[Z]?Z",
+	"YYYY-MM-DD[T]hh:mm:ss[Z]?Z",
+	"YYYY-MM-DD[T]hh:mm:ss ?Z",
+	"YYYY-MM-DD[T]hh:mm:ss?Z",
+	nil,
 };
 
-/*
- * macros for squeezing values into low 7 bits of "value".
- * all timezones we care about are divisible by 10, and the largest value
- * (780) when divided is 78.
- */
-#define TOVAL(tp, v)	((tp)->value = (v) / 10)
-#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
+char *datefmt[] = {
+	/* RFC5322 */
+	"?W ?DD ?MMM ?YYYY",
+	"?W, DD-?MM-YY",
+	/* RFC822/RFC2822 */
+	"DD MMM YYYY",
+	"DD MMM YY",
+	/* RFC850 */
+	"W, DD-MMM-YY",
+	/* RFC1123 */
+	"WW, DD MMM YYYY",
+	/* RFC 3339 and human-readable variants */
+	"YYYY-MM-DD",
+	"YYYY-MM-DD [@] ",
+	/* random formats */
+	"?W ?MMM ?DD ?YYYY",
+	"?MMM ?DD ?YYYY",
+	"?DD ?MM ?YYYY",
+	"MMM ?DD ?YYYY",
+	"YYYY ?MM ?DD",
+	"YYYY ?DD ?MM",
+	"YYYY/MM?/DD?",
+	"MMM YYYY ?DD",
+	"?DD YYYY MMM",
+	"MM/DD/YYYY",
+	nil
+};
 
-/* keep this struct small since we have an array of them */
-typedef struct {
-	char	token[Maxtok];
-	char	type;
-	schar	value;
-} Datetok;
+char *timefmt[] = {
+	" hh:mm:ss",
+	" hh:mm",
+	" hh",
+	" hh:mm:ss ?A",
+	" hh:mm ?A",
+	" hh ?A",
+	"",
+	nil,
+};
 
-int dtok_numparsed;
+char *zonefmt[] = {
+	" ?Z",
+	"",
+	nil,
+};
 
-/* forwards */
-Datetok	*datetoktype(char *s, int *bigvalp);
-
-static Datetok datetktbl[];
-static unsigned szdatetktbl;
-
-/* parse 1- or 2-digit number, advance *cpp past it */
-static int
-eatnum(char **cpp)
-{
-	int c, x;
-	char *cp;
-
-	cp = *cpp;
-	c = *cp;
-	if (!isascii(c) || !isdigit(c))
-		return -1;
-	x = c - '0';
-
-	c = *++cp;
-	if (isascii(c) && isdigit(c)) {
-		x = 10*x + c - '0';
-		cp++;
-	}
-	*cpp = cp;
-	return x;
-}
-
-/* return -1 on failure */
-int
-parsetime(char *time, Tm *tm)
-{
-	tm->hour = eatnum(&time);
-	if (tm->hour == -1 || *time++ != ':')
-		return -1;			/* only hour; too short */
-
-	tm->min = eatnum(&time);
-	if (tm->min == -1)
-		return -1;
-	if (*time++ != ':') {
-		tm->sec = 0;
-		return 0;			/* no seconds; okay */
-	}
-
-	tm->sec = eatnum(&time);
-	if (tm->sec == -1)
-		return -1;
-
-	/* this may be considered too strict.  garbage at end of time? */
-	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
-}
-
-/*
- * try to parse pre-split timestr in fields as an absolute date
- */
-int
-tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
-{
-	int i, mer = HR24, bigval = -1;
-	long flg = 0, ty;
-	Datetok *tp;
-
-	now = localtime(time(0));	/* default to local time (zone) */
-	tm->tzoff = now->tzoff;
-	strncpy(tm->zone, now->zone, sizeof tm->zone);
-
-	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
-	tm->hour = tm->min = tm->sec = 0;
-	dtok_numparsed = 0;
-
-	for (i = 0; i < nf; i++) {
-		if (fields[i][0] == '\0')
-			continue;
-		tp = datetoktype(fields[i], &bigval);
-		ty = (1L << tp->type) & ~(1L << Ignore);
-		if (flg & ty)
-			return -1;		/* repeated type */
-		flg |= ty;
-		switch (tp->type) {
-		case Year:
-			tm->year = bigval;
-			if (tm->year < 1970 || tm->year > 2106)
-				return -1;	/* can't represent in ulong */
-			/* convert 4-digit year to 1900 origin */
-			if (tm->year >= 1900)
-				tm->year -= 1900;
-			break;
-		case Day:
-			tm->mday = bigval;
-			break;
-		case Month:
-			tm->mon = tp->value - 1; /* convert to zero-origin */
-			break;
-		case Timetok:
-			if (parsetime(fields[i], tm) < 0)
-				return -1;
-			break;
-		case Dtz:
-		case Tz:
-			/* tm2sec mangles timezones, so we do our own handling */
-			tm->tzoff = FROMVAL(tp);
-			snprint(tm->zone, sizeof(tm->zone), "GMT");
-			break;
-		case Ignore:
-			break;
-		case Ampm:
-			mer = tp->value;
-			break;
-		default:
-			return -1;	/* bad token type: CANTHAPPEN */
-		}
-	}
-	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
-		return -1;		/* missing component */
-	if (mer == PM)
-		tm->hour += 12;
-	return 0;
-}
-
-int
-prsabsdate(char *timestr, Tm *now, Tm *tm)
-{
-	int nf;
-	char *fields[Maxdateflds];
-	static char delims[] = "- \t\n/,";
-
-	nf = gettokens(timestr, fields, nelem(fields), delims+1);
-	if (nf > nelem(fields))
-		return -1;
-	if (tryabsdate(fields, nf, now, tm) < 0) {
-		char *p = timestr;
-
-		/*
-		 * could be a DEC-date; glue it all back together, split it
-		 * with dash as a delimiter and try again.  Yes, this is a
-		 * hack, but so are DEC-dates.
-		 */
-		while (--nf > 0) {
-			while (*p++ != '\0')
-				;
-			p[-1] = ' ';
-		}
-		nf = gettokens(timestr, fields, nelem(fields), delims);
-		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
-			return -1;
-	}
-	return 0;
-}
-
-int
-validtm(Tm *tm)
-{
-	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
-	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
-	    tm->min < 0 || tm->min > 59 ||
-	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
-		return 0;
-	return 1;
-}
-
-Time
-seconds(char *timestr)
-{
-	Tm date;
-
-	memset(&date, 0, sizeof date);
-	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
-		return -1;
-	return validtm(&date)? tm2sec(&date) - 60*date.tzoff: -1;
-}
-
-int
-convert(char *timestr)
-{
-	char *copy;
-	Time tstime;
-
-	copy = strdup(timestr);
-	if (copy == nil)
-		sysfatal("out of memory");
-	tstime = seconds(copy);
-	free(copy);
-	if (tstime == -1) {
-		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
-		return 1;
-	}
-	print("%lud\n", tstime);
-	return 0;
-}
 
 static void
 usage(void)
 {
-	fprint(2, "usage: %s date-time ...\n", argv0);
+	fprint(2, "usage: %s [-f fmt] date-time/win m...\n", argv0);
 	exits("usage");
 }
 
+/*
+ * seconds absolute_date ... - convert absolute_date to seconds since epoch
+ */
 void
 main(int argc, char **argv)
 {
-	int i, sts;
+	char **f, **df, **tf, **zf, *fmt, *ep, buf[256];
+	Tzone *tz;
+	Tm tm;
+	int i;
 
-	sts = 0;
+	fmt = nil;
 	ARGBEGIN{
+	case 'f':
+		fmt = EARGF(usage());
+		break;
 	default:
 		usage();
-	}ARGEND
-	if (argc == 0)
-		usage();
-	for (i = 0; i < argc; i++)
-		sts |= convert(argv[i]);
-	exits(sts != 0? "bad": 0);
+	}ARGEND;
+
+	if((tz = tzload("local")) == nil)
+		sysfatal("bad local time: %r");
+	for(i = 0; i < argc; i++){
+		if(fmt != nil){
+			if(tmparse(&tm, fmt, argv[i], tz, &ep) != nil && *ep == 0)
+				goto Found;
+		}else{
+			for(f = knownfmt; *f != nil; f++)
+				if(tmparse(&tm, *f, argv[i], tz, &ep) != nil && *ep == 0)
+					goto Found;
+			for(df = datefmt; *df; df++)
+			for(tf = timefmt; *tf; tf++)
+			for(zf = zonefmt; *zf; zf++){
+				snprint(buf, sizeof(buf), "%s%s%s", *df, *tf, *zf);
+				if(tmparse(&tm, buf, argv[i], tz, &ep) != nil && *ep == 0)
+					goto Found;
+			}
+		}
+		if(*ep == 0)
+			sysfatal("tmparse: %r");
+		else
+			sysfatal("tmparse: trailing junk");
+Found:
+		print("%lld\n", tmnorm(&tm));
+	}
+	exits(nil);
 }
-
-/*
- * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
- * is WAY faster than the generic bsearch().
- */
-Datetok *
-datebsearch(char *key, Datetok *base, unsigned nel)
-{
-	int cmp;
-	Datetok *last = base + nel - 1, *pos;
-
-	while (last >= base) {
-		pos = base + ((last - base) >> 1);
-		cmp = key[0] - pos->token[0];
-		if (cmp == 0) {
-			cmp = strncmp(key, pos->token, Maxtok);
-			if (cmp == 0)
-				return pos;
-		}
-		if (cmp < 0)
-			last = pos - 1;
-		else
-			base = pos + 1;
-	}
-	return 0;
-}
-
-Datetok *
-datetoktype(char *s, int *bigvalp)
-{
-	char *cp = s;
-	char c = *cp;
-	static Datetok t;
-	Datetok *tp = &t;
-
-	if (isascii(c) && isdigit(c)) {
-		int len = strlen(cp);
-
-		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
-			tp->type = Timetok;
-		else {
-			if (bigvalp != nil)
-				*bigvalp = atoi(cp); /* won't fit in tp->value */
-			if (len == 4)
-				tp->type = Year;
-			else if (++dtok_numparsed == 1)
-				tp->type = Day;
-			else
-				tp->type = Year;
-		}
-	} else if (c == '-' || c == '+') {
-		int val = atoi(cp + 1);
-		int hr =  val / 100;
-		int min = val % 100;
-
-		val = hr*60 + min;
-		TOVAL(tp, c == '-'? -val: val);
-		tp->type = Tz;
-	} else {
-		char lowtoken[Maxtok+1];
-		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
-
-		/* copy to lowtoken to avoid modifying s */
-		while ((c = *cp++) != '\0' && ltp < endltp)
-			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
-		*ltp = '\0';
-		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
-		if (tp == nil) {
-			tp = &t;
-			tp->type = Ignore;
-		}
-	}
-	return tp;
-}
-
-
-/*
- * to keep this table reasonably small, we divide the lexval for Tz and Dtz
- * entries by 10 and truncate the text field at MAXTOKLEN characters.
- * the text field is not guaranteed to be NUL-terminated.
- */
-static Datetok datetktbl[] = {
-/*	text		token	lexval */
-	"acsst",	Dtz,	63,	/* Cent. Australia */
-	"acst",		Tz,	57,	/* Cent. Australia */
-	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
-	"aesst",	Dtz,	66,	/* E. Australia */
-	"aest",		Tz,	60,	/* Australia Eastern Std Time */
-	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
-	"am",		Ampm,	AM,
-	"apr",		Month,	4,
-	"april",	Month,	4,
-	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
-	"at",		Ignore,	0,	/* "at" (throwaway) */
-	"aug",		Month,	8,
-	"august",	Month,	8,
-	"awsst",	Dtz,	54,	/* W. Australia */
-	"awst",		Tz,	48,	/* W. Australia */
-	"bst",		Tz,	6,	/* British Summer Time */
-	"bt",		Tz,	18,	/* Baghdad Time */
-	"cadt",		Dtz,	63,	/* Central Australian DST */
-	"cast",		Tz,	57,	/* Central Australian ST */
-	"cat",		Tz,	-60,	/* Central Alaska Time */
-	"cct",		Tz,	48,	/* China Coast */
-	"cdt",		Dtz,	-30,	/* Central Daylight Time */
-	"cet",		Tz,	6,	/* Central European Time */
-	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
-	"cst",		Tz,	-36,	/* Central Standard Time */
-	"dec",		Month,	12,
-	"decemb",	Month,	12,
-	"dnt",		Tz,	6,	/* Dansk Normal Tid */
-	"dst",		Ignore,	0,
-	"east",		Tz,	-60,	/* East Australian Std Time */
-	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
-	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
-	"eetdst",	Dtz,	18,	/* Eastern Europe */
-	"est",		Tz,	-30,	/* Eastern Standard Time */
-	"feb",		Month,	2,
-	"februa",	Month,	2,
-	"fri",		Ignore,	5,
-	"friday",	Ignore,	5,
-	"fst",		Tz,	6,	/* French Summer Time */
-	"fwt",		Dtz,	12,	/* French Winter Time  */
-	"gmt",		Tz,	0,	/* Greenwish Mean Time */
-	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
-	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
-	"hmt",		Dtz,	18,	/* Hellas ? ? */
-	"hst",		Tz,	-60,	/* Hawaii Std Time */
-	"idle",		Tz,	72,	/* Intl. Date Line, East */
-	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
-	"ist",		Tz,	12,	/* Israel */
-	"it",		Tz,	22,	/* Iran Time */
-	"jan",		Month,	1,
-	"januar",	Month,	1,
-	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
-	"jt",		Tz,	45,	/* Java Time */
-	"jul",		Month,	7,
-	"july",		Month,	7,
-	"jun",		Month,	6,
-	"june",		Month,	6,
-	"kst",		Tz,	54,	/* Korea Standard Time */
-	"ligt",		Tz,	60,	/* From Melbourne, Australia */
-	"mar",		Month,	3,
-	"march",	Month,	3,
-	"may",		Month,	5,
-	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
-	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
-	"met",		Tz,	6,	/* Middle Europe Time */
-	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
-	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
-	"mez",		Tz,	6,	/* Middle Europe Zone */
-	"mon",		Ignore,	1,
-	"monday",	Ignore,	1,
-	"mst",		Tz,	-42,	/* Mountain Standard Time */
-	"mt",		Tz,	51,	/* Moluccas Time */
-	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
-	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
-	"nor",		Tz,	6,	/* Norway Standard Time */
-	"nov",		Month,	11,
-	"novemb",	Month,	11,
-	"nst",		Tz,	-21,	/* Nfld. Standard Time */
-	"nt",		Tz,	-66,	/* Nome Time */
-	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
-	"nzst",		Tz,	72,	/* New Zealand Standard Time */
-	"nzt",		Tz,	72,	/* New Zealand Time */
-	"oct",		Month,	10,
-	"octobe",	Month,	10,
-	"on",		Ignore,	0,	/* "on" (throwaway) */
-	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
-	"pm",		Ampm,	PM,
-	"pst",		Tz,	-48,	/* Pacific Standard Time */
-	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
-	"sast",		Tz,	57,	/* South Australian Std Time */
-	"sat",		Ignore,	6,
-	"saturd",	Ignore,	6,
-	"sep",		Month,	9,
-	"sept",		Month,	9,
-	"septem",	Month,	9,
-	"set",		Tz,	-6,	/* Seychelles Time ?? */
-	"sst",		Dtz,	12,	/* Swedish Summer Time */
-	"sun",		Ignore,	0,
-	"sunday",	Ignore,	0,
-	"swt",		Tz,	6,	/* Swedish Winter Time  */
-	"thu",		Ignore,	4,
-	"thur",		Ignore,	4,
-	"thurs",	Ignore,	4,
-	"thursd",	Ignore,	4,
-	"tue",		Ignore,	2,
-	"tues",		Ignore,	2,
-	"tuesda",	Ignore,	2,
-	"ut",		Tz,	0,
-	"utc",		Tz,	0,
-	"wadt",		Dtz,	48,	/* West Australian DST */
-	"wast",		Tz,	42,	/* West Australian Std Time */
-	"wat",		Tz,	-6,	/* West Africa Time */
-	"wdt",		Dtz,	54,	/* West Australian DST */
-	"wed",		Ignore,	3,
-	"wednes",	Ignore,	3,
-	"weds",		Ignore,	3,
-	"wet",		Tz,	0,	/* Western Europe */
-	"wetdst",	Dtz,	6,	/* Western Europe */
-	"wst",		Tz,	48,	/* West Australian Std Time */
-	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
-	"yst",		Tz,	-54,	/* Yukon Standard Time */
-	"zp4",		Tz,	-24,	/* GMT +4  hours. */
-	"zp5",		Tz,	-30,	/* GMT +5  hours. */
-	"zp6",		Tz,	-36,	/* GMT +6  hours. */
-};
-static unsigned szdatetktbl = nelem(datetktbl);

[-- Attachment #5: Type: text/plain, Size: 2450 bytes --]

diff -r f8f63e944375 sys/src/cmd/upas/common/common.h
--- a/sys/src/cmd/upas/common/common.h	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/common/common.h	Sat Jul 18 23:30:20 2020 -0700
@@ -52,9 +52,6 @@
 void	mailfmtinstall(void);	/* 'U' = 2047fmt */
 #pragma varargck	type	"U"	char*
 
-/* totm.c */
-int	fromtotm(char*, Tm*);
-
 /* a pipe between parent and child*/
 typedef struct{
 	Biobuf	bb;
diff -r f8f63e944375 sys/src/cmd/upas/common/folder.c
--- a/sys/src/cmd/upas/common/folder.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/common/folder.c	Sat Jul 18 23:30:20 2020 -0700
@@ -1,5 +1,7 @@
 #include "common.h"
 
+#define Ctimefmt "W MMM _D hh:mm:ss ZZZ YYYY"
+
 enum{
 	Mbox	= 1,
 	Mdir,
@@ -185,7 +187,7 @@
 appendfolder(Biobuf *b, char *addr, int fd)
 {
 	char *s;
-	int r;
+	int r, n;
 	Biobuf bin;
 	Folder *f;
 	Tm tm;
@@ -194,9 +196,10 @@
 	Bseek(f->out, 0, 2);
 	Binit(&bin, fd, OREAD);
 	s = Brdstr(&bin, '\n', 0);
-	if(!s || strncmp(s, "From ", 5))
+	n = strlen(s);
+	if(!s || strncmp(s, "From ", 5) != 0)
 		Bprint(f->out, "From %s %.28s\n", addr, ctime(f->t));
-	else if(fromtotm(s, &tm) >= 0)
+	else if(n > 5 && tmparse(&tm, Ctimefmt, s + 5, nil, nil) != nil)
 		f->t = tm2sec(&tm);
 	if(s)
 		Bwrite(f->out, s, strlen(s));
diff -r f8f63e944375 sys/src/cmd/upas/common/mkfile
--- a/sys/src/cmd/upas/common/mkfile	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/common/mkfile	Sat Jul 18 23:30:20 2020 -0700
@@ -10,7 +10,6 @@
 	fmt.$O\
 	libsys.$O\
 	process.$O\
-	totm.$O\
 
 HFILES=common.h\
 	sys.h\
diff -r f8f63e944375 sys/src/cmd/upas/common/totm.c
--- a/sys/src/cmd/upas/common/totm.c	Fri Jul 17 16:53:20 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#include <common.h>
-
-static char mtab[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
-
-int
-ctimetotm(char *s, Tm *tm)
-{
-	char buf[32];
-
-	if(strlen(s) < 28)
-		return -1;
-	snprint(buf, sizeof buf, "%s", s);
-	memset(tm, 0, sizeof *tm);
-	buf[7] = 0;
-	tm->mon = (strstr(mtab, buf+4) - mtab)/3;
-	tm->mday = atoi(buf+8);
-	tm->hour = atoi(buf+11);
-	tm->min = atoi(buf+14);
-	tm->sec = atoi(buf+17);
-	tm->zone[0] = buf[20];
-	tm->zone[1] = buf[21];
-	tm->zone[2] = buf[22];
-	tm->year = atoi(buf+24) - 1900;
-	return 0;
-}
-
-int
-fromtotm(char *s, Tm *tm)
-{
-	char buf[256], *f[3];
-
-	snprint(buf, sizeof buf, "%s", s);
-	if(getfields(buf, f, nelem(f), 0, " ") != 3)
-		return -1;
-	return ctimetotm(f[2], tm);
-}

[-- Attachment #6: Type: text/plain, Size: 14221 bytes --]

diff -r f8f63e944375 sys/src/cmd/upas/common/common.h
--- a/sys/src/cmd/upas/common/common.h	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/common/common.h	Sat Jul 18 23:30:43 2020 -0700
@@ -52,9 +52,6 @@
 void	mailfmtinstall(void);	/* 'U' = 2047fmt */
 #pragma varargck	type	"U"	char*
 
-/* totm.c */
-int	fromtotm(char*, Tm*);
-
 /* a pipe between parent and child*/
 typedef struct{
 	Biobuf	bb;
diff -r f8f63e944375 sys/src/cmd/upas/common/folder.c
--- a/sys/src/cmd/upas/common/folder.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/common/folder.c	Sat Jul 18 23:30:43 2020 -0700
@@ -1,5 +1,7 @@
 #include "common.h"
 
+#define Ctimefmt "W MMM _D hh:mm:ss ZZZ YYYY"
+
 enum{
 	Mbox	= 1,
 	Mdir,
@@ -185,7 +187,7 @@
 appendfolder(Biobuf *b, char *addr, int fd)
 {
 	char *s;
-	int r;
+	int r, n;
 	Biobuf bin;
 	Folder *f;
 	Tm tm;
@@ -194,9 +196,10 @@
 	Bseek(f->out, 0, 2);
 	Binit(&bin, fd, OREAD);
 	s = Brdstr(&bin, '\n', 0);
-	if(!s || strncmp(s, "From ", 5))
+	n = strlen(s);
+	if(!s || strncmp(s, "From ", 5) != 0)
 		Bprint(f->out, "From %s %.28s\n", addr, ctime(f->t));
-	else if(fromtotm(s, &tm) >= 0)
+	else if(n > 5 && tmparse(&tm, Ctimefmt, s + 5, nil, nil) != nil)
 		f->t = tm2sec(&tm);
 	if(s)
 		Bwrite(f->out, s, strlen(s));
diff -r f8f63e944375 sys/src/cmd/upas/common/mkfile
--- a/sys/src/cmd/upas/common/mkfile	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/common/mkfile	Sat Jul 18 23:30:43 2020 -0700
@@ -10,7 +10,6 @@
 	fmt.$O\
 	libsys.$O\
 	process.$O\
-	totm.$O\
 
 HFILES=common.h\
 	sys.h\
diff -r f8f63e944375 sys/src/cmd/upas/common/totm.c
--- a/sys/src/cmd/upas/common/totm.c	Fri Jul 17 16:53:20 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-#include <common.h>
-
-static char mtab[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
-
-int
-ctimetotm(char *s, Tm *tm)
-{
-	char buf[32];
-
-	if(strlen(s) < 28)
-		return -1;
-	snprint(buf, sizeof buf, "%s", s);
-	memset(tm, 0, sizeof *tm);
-	buf[7] = 0;
-	tm->mon = (strstr(mtab, buf+4) - mtab)/3;
-	tm->mday = atoi(buf+8);
-	tm->hour = atoi(buf+11);
-	tm->min = atoi(buf+14);
-	tm->sec = atoi(buf+17);
-	tm->zone[0] = buf[20];
-	tm->zone[1] = buf[21];
-	tm->zone[2] = buf[22];
-	tm->year = atoi(buf+24) - 1900;
-	return 0;
-}
-
-int
-fromtotm(char *s, Tm *tm)
-{
-	char buf[256], *f[3];
-
-	snprint(buf, sizeof buf, "%s", s);
-	if(getfields(buf, f, nelem(f), 0, " ") != 3)
-		return -1;
-	return ctimetotm(f[2], tm);
-}
diff -r f8f63e944375 sys/src/cmd/upas/fs/strtotm.c
--- a/sys/src/cmd/upas/fs/strtotm.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/fs/strtotm.c	Sat Jul 18 23:30:43 2020 -0700
@@ -1,98 +1,25 @@
 #include <u.h>
 #include <libc.h>
 
-static char*
-skiptext(char *q)
+int
+strtotm(char *s, Tm *t)
 {
-	while(*q != '\0' && *q != ' ' && *q != '\t' && *q != '\r' && *q != '\n')
-		q++;
-	return q;
+	char **f, *fmt[] = {
+		"W MMM DD hh:mm:ss ?Z YYYY",
+		"?W ?DD ?MMM ?YYYY hh:mm:ss ?Z",
+		"?W ?DD ?MMM ?YYYY hh:mm:ss",
+		"?W, DD-?MM-YY",
+		"?DD ?MMM ?YYYY hh:mm:ss ?Z",
+		"?DD ?MMM ?YYYY hh:mm:ss",
+		"?DD-?MM-YY",
+		"?MMM/?DD/?YYYY hh:mm:ss ?Z",
+		"?MMM/?DD/?YYYY hh:mm:ss",
+		"?MMM/?DD/?YYYY",
+		nil,
+	};
+
+	for(f = fmt; *f; f++)
+		if(tmparse(t, *f, s, nil, nil) != nil)
+			return 0;
+	return -1;
 }
-
-static char*
-skipwhite(char *q)
-{
-	while(*q == ' ' || *q == '\t' || *q == '\r' || *q == '\n')
-		q++;
-	return q;
-}
-
-static char* months[] = {
-	"jan", "feb", "mar", "apr",
-	"may", "jun", "jul", "aug",
-	"sep", "oct", "nov", "dec"
-};
-
-int
-strtotm(char *p, Tm *t)
-{
-	char *q, *r;
-	int j;
-	Tm tm;
-	int delta;
-
-	delta = 0;
-	memset(&tm, 0, sizeof(tm));
-	tm.mon = -1;
-	tm.hour = -1;
-	tm.min = -1;
-	tm.year = -1;
-	tm.mday = -1;
-	memcpy(tm.zone, "GMT", 3);
-	for(p = skipwhite(p); *p; p = skipwhite(q)){
-		q = skiptext(p);
-
-		/* look for time in hh:mm[:ss] */
-		if(r = memchr(p, ':', q - p)){
-			tm.hour = strtol(p, 0, 10);
-			tm.min = strtol(r + 1, 0, 10);
-			if(r = memchr(r + 1, ':', q - (r + 1)))
-				tm.sec = strtol(r + 1, 0, 10);
-			else
-				tm.sec = 0;
-			continue;
-		}
-
-		/* look for month */
-		for(j = 0; j < 12; j++)
-			if(cistrncmp(p, months[j], 3) == 0){
-				tm.mon = j;
-				break;
-			}
-		if(j != 12)
-			continue;
-
-		/* look for time zone [A-Z][A-Z]T */
-		if(q - p == 3)
-		if(p[0] >= 'A' && p[0] <= 'Z')
-		if(p[1] >= 'A' && p[1] <= 'Z')
-		if(p[2] == 'T'){
-			strecpy(tm.zone, tm.zone + 4, p);
-			continue;
-		}
-
-		if(p[0] == '+'||p[0] == '-')
-		if(q - p == 5 && strspn(p + 1, "0123456789") == 4){
-			delta = (((p[1] - '0')*10 + p[2] - '0')*60 + (p[3] - '0')*10 + p[4] - '0')*60;
-			if(p[0] == '-')
-				delta = -delta;
-			continue;
-		}
-		if(strspn(p, "0123456789") == q - p){
-			j = strtol(p, nil, 10);
-			if(j >= 1 && j <= 31)
-				tm.mday = j;
-			if(j >= 1900)
-				tm.year = j - 1900;
-			continue;
-		}
-		//eprint("strtotm: garbage %.*s\n", utfnlen(p, q - p), p);
-	}
-	if(tm.mon < 0 || tm.year < 0
-	|| tm.hour < 0 || tm.min < 0
-	|| tm.mday < 0)
-		return -1;
-
-	*t = *localtime(tm2sec(&tm) - delta);
-	return 0;
-}
diff -r f8f63e944375 sys/src/cmd/upas/imap4d/date.c
--- a/sys/src/cmd/upas/imap4d/date.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/imap4d/date.c	Sat Jul 18 23:30:43 2020 -0700
@@ -1,142 +1,10 @@
 #include "imap4d.h"
 
-static char *wdayname[] = {
-	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-
-static char *monname[] = {
-	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-/*
- * zone	: [A-Za-z][A-Za-z][A-Za-z]	some time zone names
- *	| [A-IK-Z]			military time; rfc1123 says the rfc822 spec is wrong.
- *	| "UT"				universal time
- *	| [+-][0-9][0-9][0-9][0-9]
- * zones is the rfc-822 list of time zone names
- */
-static Namedint zones[] =
-{
-	{"A",	-1 * 3600},
-	{"B",	-2 * 3600},
-	{"C",	-3 * 3600},
-	{"CDT", -5 * 3600},
-	{"CST", -6 * 3600},
-	{"D",	-4 * 3600},
-	{"E",	-5 * 3600},
-	{"EDT", -4 * 3600},
-	{"EST", -5 * 3600},
-	{"F",	-6 * 3600},
-	{"G",	-7 * 3600},
-	{"GMT", 0},
-	{"H",	-8 * 3600},
-	{"I",	-9 * 3600},
-	{"K",	-10 * 3600},
-	{"L",	-11 * 3600},
-	{"M",	-12 * 3600},
-	{"MDT", -6 * 3600},
-	{"MST", -7 * 3600},
-	{"N",	+1 * 3600},
-	{"O",	+2 * 3600},
-	{"P",	+3 * 3600},
-	{"PDT", -7 * 3600},
-	{"PST", -8 * 3600},
-	{"Q",	+4 * 3600},
-	{"R",	+5 * 3600},
-	{"S",	+6 * 3600},
-	{"T",	+7 * 3600},
-	{"U",	+8 * 3600},
-	{"UT",	0},
-	{"V",	+9 * 3600},
-	{"W",	+10 * 3600},
-	{"X",	+11 * 3600},
-	{"Y",	+12 * 3600},
-	{"Z",	0},
-};
-
-static void
-zone2tm(Tm *tm, char *s)
-{
-	int i;
-	Tm aux, *atm;
-
-	if(*s == '+' || *s == '-'){
-		i = strtol(s, &s, 10);
-		tm->tzoff = (i/100)*3600 + i%100;
-		strncpy(tm->zone, "", 4);
-		return;
-	}
-
-	/*
-	 * look it up in the standard rfc822 table
-	 */
-	strncpy(tm->zone, s, 3);
-	tm->zone[3] = 0;
-	tm->tzoff = 0;
-	for(i = 0; i < nelem(zones); i++){
-		if(cistrcmp(zones[i].name, s) == 0){
-			tm->tzoff = zones[i].v;
-			return;
-		}
-	}
-
-	/*
-	 * one last try: look it up in the current local timezone
-	 * probe a couple of times to get daylight/standard time change.
-	 */
-	aux = *tm;
-	memset(aux.zone, 0, 4);
-	aux.hour--;
-	for(i = 0; i < 2; i++){
-		atm = localtime(tm2sec(&aux));
-		if(cistrcmp(tm->zone, atm->zone) == 0){
-			tm->tzoff = atm->tzoff;
-			return;
-		}
-		aux.hour++;
-	}
-
-	strncpy(tm->zone, "GMT", 4);
-	tm->tzoff = 0;
-}
-
-/*
- * hh[:mm[:ss]]
- */
-static void
-time2tm(Tm *tm, char *s)
-{
-	tm->hour = strtoul(s, &s, 10);
-	if(*s++ != ':')
-		return;
-	tm->min = strtoul(s, &s, 10);
-	if(*s++ != ':')
-		return;
-	tm->sec = strtoul(s, &s, 10);
-}
-
-static int
-dateindex(char *d, char **tab, int n)
-{
-	int i;
-
-	for(i = 0; i < n; i++)
-		if(cistrcmp(d, tab[i]) == 0)
-			return i;
-	return -1;
-}
-
 int
 imap4date(Tm *tm, char *date)
 {
-	char *flds[4];
-
-	if(getfields(date, flds, 3, 0, "-") != 3)
+	if(tmparse(tm, "DD-?MM-YYYY hh:mm:ss ?Z", date, nil, nil) == nil)
 		return 0;
-
-	tm->mday = strtol(flds[0], nil, 10);
-	tm->mon = dateindex(flds[1], monname, 12);
-	tm->year = strtol(flds[2], nil, 10) - 1900;
 	return 1;
 }
 
@@ -146,29 +14,17 @@
 ulong
 imap4datetime(char *date)
 {
-	char *flds[4], *sflds[4];
-	ulong t;
 	Tm tm;
+	vlong s;
 
-	if(getfields(date, flds, 4, 0, " ") != 3)
-		return ~0;
-
-	if(!imap4date(&tm, flds[0]))
-		return ~0;
-
-	if(getfields(flds[1], sflds, 3, 0, ":") != 3)
-		return ~0;
-
-	tm.hour = strtol(sflds[0], nil, 10);
-	tm.min = strtol(sflds[1], nil, 10);
-	tm.sec = strtol(sflds[2], nil, 10);
-
-	strcpy(tm.zone, "GMT");
-	tm.yday = 0;
-	t = tm2sec(&tm);
-	zone2tm(&tm, flds[2]);
-	t -= tm.tzoff;
-	return t;
+	s = -1;
+	if(tmparse(&tm, "?DD-?MM-YYYY hh:mm:ss ?Z", date, nil, nil) != nil)
+		s = tmnorm(&tm);
+	else if(tmparse(&tm, "?W, ?DD-?MM-YYYY hh:mm:ss ?Z", date, nil, nil) != nil)
+		s = tmnorm(&tm);
+	if(s > 0 && s < (1ULL<<31))
+		return s;
+	return ~0;
 }
 
 /*
@@ -181,85 +37,18 @@
 Tm*
 date2tm(Tm *tm, char *date)
 {
-	char *flds[7], *s, dstr[64];
-	int n;
-	Tm gmt, *atm;
+	char **f, *fmts[] = {
+		"?W, ?DD ?MMM YYYY hh:mm:ss ?Z",
+		"?W ?M ?DD hh:mm:ss ?Z YYYY",
+		"?W, DD-?MM-YY hh:mm:ss ?Z",
+		"?DD ?MMM YYYY hh:mm:ss ?Z",
+		"?M ?DD hh:mm:ss ?Z YYYY",
+		"DD-?MM-YYYY hh:mm:ss ?Z",
+		nil,
+	};
 
-	/*
-	 * default date is Thu Jan  1 00:00:00 GMT 1970
-	 */
-	tm->wday = 4;
-	tm->mday = 1;
-	tm->mon = 1;
-	tm->hour = 0;
-	tm->min = 0;
-	tm->sec = 0;
-	tm->year = 70;
-	strcpy(tm->zone, "GMT");
-	tm->tzoff = 0;
-
-	strncpy(dstr, date, sizeof dstr);
-	dstr[sizeof dstr - 1] = 0;
-	n = tokenize(dstr, flds, 7);
-	if(n != 6 && n != 5)
-		return nil;
-
-	if(n == 5){
-		for(n = 5; n >= 1; n--)
-			flds[n] = flds[n - 1];
-		n = 5;
-	}else{
-		/*
-		 * Wday[,]
-		 */
-		s = strchr(flds[0], ',');
-		if(s != nil)
-			*s = 0;
-		tm->wday = dateindex(flds[0], wdayname, 7);
-		if(tm->wday < 0)
-			return nil;
-	}
-
-	/*
-	 * check for the two major formats:
-	 * Month first or day first
-	 */
-	tm->mon = dateindex(flds[1], monname, 12);
-	if(tm->mon >= 0){
-		tm->mday = strtoul(flds[2], nil, 10);
-		time2tm(tm, flds[3]);
-		zone2tm(tm, flds[4]);
-		tm->year = strtoul(flds[5], nil, 10);
-		if(strlen(flds[5]) > 2)
-			tm->year -= 1900;
-	}else{
-		tm->mday = strtoul(flds[1], nil, 10);
-		tm->mon = dateindex(flds[2], monname, 12);
-		if(tm->mon < 0)
-			return nil;
-		tm->year = strtoul(flds[3], nil, 10);
-		if(strlen(flds[3]) > 2)
-			tm->year -= 1900;
-		time2tm(tm, flds[4]);
-		zone2tm(tm, flds[5]);
-	}
-
-	if(n == 5){
-		gmt = *tm;
-		strncpy(gmt.zone, "", 4);
-		gmt.tzoff = 0;
-		atm = gmtime(tm2sec(&gmt));
-		tm->wday = atm->wday;
-	}else{
-		/*
-		 * Wday[,]
-		 */
-		s = strchr(flds[0], ',');
-		if(s != nil)
-			*s = 0;
-		tm->wday = dateindex(flds[0], wdayname, 7);
-		if(tm->wday < 0)
-			return nil;
-	}
-	return tm;
+	for(f = fmts; *f; f++)
+		if(tmparse(tm, *f, date, nil, nil) != nil)
+			return tm;
+	return nil;
 }
diff -r f8f63e944375 sys/src/cmd/upas/imap4d/imap4d.c
--- a/sys/src/cmd/upas/imap4d/imap4d.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/imap4d/imap4d.c	Sat Jul 18 23:30:43 2020 -0700
@@ -215,6 +215,7 @@
 	Binit(&bin, dup(0, -1), OREAD);
 	close(0);
 	Binit(&bout, 1, OWRITE);
+	tmfmtinstall();
 	quotefmtinstall();
 	fmtinstall('F', Ffmt);
 	fmtinstall('D', Dfmt);	/* rfc822; # imap date %Z */
diff -r f8f63e944375 sys/src/cmd/upas/imap4d/print.c
--- a/sys/src/cmd/upas/imap4d/print.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/imap4d/print.c	Sat Jul 18 23:30:43 2020 -0700
@@ -90,40 +90,24 @@
 	return fmtstrcpy(f, encfs(buf, sizeof buf, s));
 }
 
-static char *day[] = {
-	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
-};
-
-static char *mon[] = {
-	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
 int
 Dfmt(Fmt *f)
 {
-	char buf[128], *p, *e, *sgn, *fmt;
-	int off;
-	Tm *tm;
+	char buf[128], *fmt;
+	Tm *tm, t;
 
 	tm = va_arg(f->args, Tm*);
-	if(tm == nil)
-		tm = localtime(time(0));
-	sgn = "+";
-	if(tm->tzoff < 0)
-		sgn = "";
-	e = buf + sizeof buf;
-	p = buf;
-	off = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
+	if(tm == nil){
+		tz = tzload("local");
+		tm = tmtime(&t, time(0), tz);
+	}
 	if((f->flags & FmtSharp) == 0){
 		/* rfc822 style */
-		fmt = "%.2d %s %.4d %.2d:%.2d:%.2d %s%.4d";
-		p = seprint(p, e, "%s, ", day[tm->wday]);
+		fmt = "WW, DD MMM YYYY hh:mm:ss Z";
 	}else
-		fmt = "%2d-%s-%.4d %2.2d:%2.2d:%2.2d %s%4.4d";
-	seprint(p, e, fmt,
-		tm->mday, mon[tm->mon], tm->year + 1900, tm->hour, tm->min, tm->sec,
-		sgn, off);
+		fmt = "DD-MMM-YYYY hh:mm:ss Z";
 	if(f->r == L'δ')
-		return fmtstrcpy(f, buf);
+		return fmtprint(f, "%τ", tmfmt(tm, fmt));
+	snprint(buf, sizeof(buf), "%τ", tmfmt(tm, fmt));
 	return fmtprint(f, "%Z", buf);
 }
diff -r f8f63e944375 sys/src/cmd/upas/marshal/marshal.c
--- a/sys/src/cmd/upas/marshal/marshal.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/upas/marshal/marshal.c	Sat Jul 18 23:30:43 2020 -0700
@@ -140,6 +140,7 @@
 char lastchar;
 char *replymsg;
 
+#define Rfc822fmt	"WW, DD MMM YYYY hh:mm:ss Z"
 enum
 {
 	Ok = 0,
@@ -208,6 +209,7 @@
 	hdrstring = nil;
 	ccargc = bccargc = 0;
 
+	tmfmtinstall();
 	quotefmtinstall();
 	fmtinstall('Z', doublequote);
 	fmtinstall('U', rfc2047fmt);
@@ -792,29 +794,13 @@
 	Bterm(f);
 }
 
-char *ascwday[] =
-{
-	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-
-char *ascmon[] =
-{
-	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
-	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
 int
 printdate(Biobuf *b)
 {
-	int tz;
 	Tm *tm;
 
 	tm = localtime(time(0));
-	tz = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
-
-	return Bprint(b, "Date: %s, %d %s %d %2.2d:%2.2d:%2.2d %s%.4d\n",
-		ascwday[tm->wday], tm->mday, ascmon[tm->mon], 1900 + tm->year,
-		tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz);
+	return Bprint(b, "Date: %τ\n", tmfmt(tm, Rfc822fmt));
 }
 
 int
@@ -1003,16 +989,10 @@
 int
 printunixfrom(int fd)
 {
-	int tz;
 	Tm *tm;
 
 	tm = localtime(time(0));
-	tz = (tm->tzoff/3600)*100 + (tm->tzoff/60)%60;
-
-	return fprint(fd, "From %s %s %s %d %2.2d:%2.2d:%2.2d %s%.4d %d\n",
-		user,
-		ascwday[tm->wday], ascmon[tm->mon], tm->mday,
-		tm->hour, tm->min, tm->sec, tz>=0?"+":"", tz, 1900 + tm->year);
+	return fprint(fd, "From %s %τ\n", user, tmfmt(tm, Rfc822fmt));
 }
 
 char *specialfile[] =

[-- Attachment #7: Type: text/plain, Size: 3283 bytes --]

diff -r f8f63e944375 sys/src/cmd/webcookies.c
--- a/sys/src/cmd/webcookies.c	Fri Jul 17 16:53:20 2020 +0200
+++ b/sys/src/cmd/webcookies.c	Sun Jul 19 11:23:07 2020 -0700
@@ -606,143 +606,25 @@
 }
 
 /*
+ * Parse a date in one of these formats:
  * Sunday, 25-Jan-2002 12:24:36 GMT
  * Sunday, 25 Jan 2002 12:24:36 GMT
  * Sun, 25 Jan 02 12:24:36 GMT
  */
-int
-isleap(int year)
-{
-	return year%4==0 && (year%100!=0 || year%400==0);
-}
-
 uint
 strtotime(char *s)
 {
-	char *os;
-	int i;
+	char **f, *fmts[] = {
+		"?WW, ?DD-?MM-?YYYY hh:mm:ss ?Z",
+		"?WW, ?DD ?MM ?YYYY hh:mm:ss ?Z",
+		nil,
+	};
 	Tm tm;
 
-	static int mday[2][12] = {
-		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
-		31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
-	};
-	static char *wday[] = {
-		"Sunday", "Monday", "Tuesday", "Wednesday",
-		"Thursday", "Friday", "Saturday",
-	};
-	static char *mon[] = {
-		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
-		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
-	};
-
-	memset(&tm, 0, sizeof(tm));
-
-	os = s;
-	/* Sunday, */
-	for(i=0; i<nelem(wday); i++){
-		if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){
-			s += strlen(wday[i]);
-			break;
-		}
-		if(cistrncmp(s, wday[i], 3) == 0){
-			s += 3;
-			break;
-		}
-	}
-	if(i==nelem(wday)){
-		if(debug)
-			fprint(2, "bad wday (%s)\n", os);
-		return -1;
-	}
-	if(*s++ != ',' || *s++ != ' '){
-		if(debug)
-			fprint(2, "bad wday separator (%s)\n", os);
-		return -1;
-	}
-
-	/* 25- */
-	if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){
-		if(debug)
-			fprint(2, "bad day of month (%s)\n", os);
-		return -1;
-	}
-	tm.mday = strtol(s, 0, 10);
-	s += 3;
-
-	/* Jan- */
-	for(i=0; i<nelem(mon); i++)
-		if(cistrncmp(s, mon[i], 3) == 0){
-			tm.mon = i;
-			s += 3;
-			break;
-		}
-	if(i==nelem(mon)){
-		if(debug)
-			fprint(2, "bad month (%s)\n", os);
-		return -1;
-	}
-	if(s[0] != '-' && s[0] != ' '){
-		if(debug)
-			fprint(2, "bad month separator (%s)\n", os);
-		return -1;
-	}
-	s++;
-
-	/* 2002 */
-	if(!isdigit(s[0]) || !isdigit(s[1])){
-		if(debug)
-			fprint(2, "bad year (%s)\n", os);
-		return -1;
-	}
-	tm.year = strtol(s, 0, 10);
-	s += 2;
-	if(isdigit(s[0]) && isdigit(s[1]))
-		s += 2;
-	else{
-		if(tm.year <= 68)
-			tm.year += 2000;
-		else
-			tm.year += 1900;
-	}
-	if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){
-		if(debug)
-			fprint(2, "invalid day of month (%s)\n", os);
-		return -1;
-	}
-	tm.year -= 1900;
-	if(*s++ != ' '){
-		if(debug)
-			fprint(2, "bad year separator (%s)\n", os);
-		return -1;
-	}
-
-	if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':'
-	|| !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':'
-	|| !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){
-		if(debug)
-			fprint(2, "bad time (%s)\n", os);
-		return -1;
-	}
-
-	tm.hour = strtol(s, 0, 10);
-	tm.min = strtol(s+3, 0, 10);
-	tm.sec = strtol(s+6, 0, 10);
-	if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){
-		if(debug)
-			fprint(2, "invalid time (%s)\n", os);
-		return -1;
-	}
-	s += 9;
-
-	if(cistrcmp(s, "GMT") != 0){
-		if(debug)
-			fprint(2, "time zone not GMT (%s)\n", os);
-		return -1;
-	}
-	strcpy(tm.zone, "GMT");
-	tm.yday = 0;
-	return tm2sec(&tm);
+	for(f = fmts; *f != nil; f++)
+		if(tmparse(&tm, *f, s, nil, nil) != nil)
+			return tmnorm(&tm);
+	return -1;
 }
 
 /*

[-- Attachment #8: Type: text/plain, Size: 4793 bytes --]

diff -r e168590166a5 sys/src/cmd/ip/httpd/httpd.c
--- a/sys/src/cmd/ip/httpd/httpd.c	Sun Jul 19 14:14:14 2020 -0700
+++ b/sys/src/cmd/ip/httpd/httpd.c	Sun Jul 19 14:18:57 2020 -0700
@@ -51,6 +51,7 @@
 	address = nil;
 	hmydomain = nil;
 	netdir = "/net";
+	tmfmtinstall();
 	fmtinstall('D', hdatefmt);
 	fmtinstall('H', httpfmt);
 	fmtinstall('U', hurlfmt);
diff -r e168590166a5 sys/src/libhttpd/date.c
--- a/sys/src/libhttpd/date.c	Sun Jul 19 14:14:14 2020 -0700
+++ b/sys/src/libhttpd/date.c	Sun Jul 19 14:18:57 2020 -0700
@@ -4,212 +4,41 @@
 
 /*
  * print dates in the format
- * Wkd, DD Mon YYYY HH:MM:SS GMT
+ * Wkd, DD Mon YYYY HH:MM:SS +0000
+ */
+int
+hdatefmt(Fmt *f)
+{
+	Tm *tm, ts;
+	ulong t;
+
+	t = va_arg(f->args, ulong);
+	tm = tmtime(&ts, t, nil);
+	return fmtprint(f, "%τ", tmfmt(tm, "WW DD MMM YYYY hh:mm:ss Z"));
+}
+
+/*
  * parse dates of formats
  * Wkd, DD Mon YYYY HH:MM:SS GMT
  * Weekday, DD-Mon-YY HH:MM:SS GMT
  * Wkd Mon ( D|DD) HH:MM:SS YYYY
  * plus anything similar
- */
-static char *
-weekdayname[7] =
-{
-	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
-};
-static char *
-wdayname[7] =
-{
-	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
-};
-
-static char *
-monname[12] =
-{
-	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
-};
-
-static	int	dateindex(char*, char**, int);
-
-static int
-dtolower(int c)
-{
-	if(c >= 'A' && c <= 'Z')
-		return c - 'A' + 'a';
-	return c;
-}
-
-static int
-disalpha(int c)
-{
-	return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
-}
-
-static int
-disdig(int c)
-{
-	return c >= '0' && c <= '9';
-}
-
-int
-hdatefmt(Fmt *f)
-{
-	Tm *tm;
-	ulong t;
-
-	t = va_arg(f->args, ulong);
-	tm = gmtime(t);
-	return fmtprint(f, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
-		wdayname[tm->wday], tm->mday, monname[tm->mon], tm->year+1900,
-		tm->hour, tm->min, tm->sec);
-}
-
-static char*
-dateword(char *date, char *buf)
-{
-	char *p;
-	int c;
-
-	p = buf;
-	while(!disalpha(c = *date) && !disdig(c) && c)
-		date++;
-	while(disalpha(c = *date)){
-		if(p - buf < 30)
-			*p++ = dtolower(c);
-		date++;
-	}
-	*p = 0;
-	return date;
-}
-
-static int
-datenum(char **d)
-{
-	char *date;
-	int c, n;
-
-	date = *d;
-	while(!disdig(c = *date) && c)
-		date++;
-	if(c == 0){
-		*d = date;
-		return -1;
-	}
-	n = 0;
-	while(disdig(c = *date)){
-		n = n * 10 + c - '0';
-		date++;
-	}
-	*d = date;
-	return n;
-}
-
-/*
- * parse a date and return the seconds since the epoch
- * return 0 for a failure
+ * return seconds from epoch, 0 for a failure
  */
 ulong
 hdate2sec(char *date)
 {
+	char **f, *fmt[] = {
+		"?W, ?DD ?MM ?YYYY hh:mm:ss ?Z",
+		"?W, ?DD-?MM-?YYYY-hh:mm:ss ?Z",
+		"?W, ?DD ?MM ?YYYY hh:mm:ss",
+		"?W, ?DD-?MM-?YYYY hh:mm:ss",
+		nil,
+	};
 	Tm tm;
-	char buf[32];
 
-	memset(&tm, 0, sizeof(tm));
-
-	/*
-	 * Weekday|Wday
-	 */
-	date = dateword(date, buf);
-	tm.wday = dateindex(buf, wdayname, 7);
-	if(tm.wday < 0)
-		tm.wday = dateindex(buf, weekdayname, 7);
-	if(tm.wday < 0)
-		return 0;
-
-	/*
-	 * check for the two major formats
-	 */
-	date = dateword(date, buf);
-	tm.mon = dateindex(buf, monname, 12);
-	if(tm.mon >= 0){
-		/*
-		 * MM
-		 */
-		tm.mday = datenum(&date);
-		if(tm.mday < 1 || tm.mday > 31)
-			return 0;
-
-		/*
-		 * HH:MM:SS
-		 */
-		tm.hour = datenum(&date);
-		if(tm.hour < 0 || tm.hour >= 24)
-			return 0;
-		tm.min = datenum(&date);
-		if(tm.min < 0 || tm.min >= 60)
-			return 0;
-		tm.sec = datenum(&date);
-		if(tm.sec < 0 || tm.sec >= 60)
-			return 0;
-
-		/*
-		 * YYYY
-		 */
-		tm.year = datenum(&date);
-		if(tm.year < 70 || tm.year > 99 && tm.year < 1970)
-			return 0;
-		if(tm.year >= 1970)
-			tm.year -= 1900;
-	}else{
-		/*
-		 * MM-Mon-(YY|YYYY)
-		 */
-		tm.mday = datenum(&date);
-		if(tm.mday < 1 || tm.mday > 31)
-			return 0;
-		date = dateword(date, buf);
-		tm.mon = dateindex(buf, monname, 12);
-		if(tm.mon < 0 || tm.mon >= 12)
-			return 0;
-		tm.year = datenum(&date);
-		if(tm.year < 70 || tm.year > 99 && tm.year < 1970)
-			return 0;
-		if(tm.year >= 1970)
-			tm.year -= 1900;
-
-		/*
-		 * HH:MM:SS
-		 */
-		tm.hour = datenum(&date);
-		if(tm.hour < 0 || tm.hour >= 24)
-			return 0;
-		tm.min = datenum(&date);
-		if(tm.min < 0 || tm.min >= 60)
-			return 0;
-		tm.sec = datenum(&date);
-		if(tm.sec < 0 || tm.sec >= 60)
-			return 0;
-
-		/*
-		 * timezone
-		 */
-		dateword(date, buf);
-		if(strncmp(buf, "gmt", 3) != 0)
-			return 0;
-	}
-
-	strcpy(tm.zone, "GMT");
-	tm.tzoff = 0;
-	tm.yday = 0;
-	return tm2sec(&tm);
-}
-
-static int
-dateindex(char *d, char **tab, int n)
-{
-	int i;
-
-	for(i = 0; i < n; i++)
-		if(cistrcmp(d, tab[i]) == 0)
-			return i;
+	for(f = fmt; *f != nil; f++)
+		if(tmparse(&tm, *f, date, nil, nil) != nil)
+			return tmnorm(&tm);
 	return -1;
 }

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] libc: date handling improvements
  2020-06-13 19:40 ` ori
@ 2020-06-15  6:02   ` ori
  0 siblings, 0 replies; 5+ messages in thread
From: ori @ 2020-06-15  6:02 UTC (permalink / raw)
  To: ori, 9front

>> I implemented libdate -- but the right place to put
>> it is in libc, replacing our ctime and gmtime apis.
>> 
>> Follow ups will replace seconds(1), upas time parsing
>> code, and whatever other instances of hand rolled date
>> parsing and formatting can be found.
>> 
>> Here's a patch that does this. Testing appreciated,
>> and follow ups that I haven't done yet would also be
>> great :)
>> 
> 
> Committed -- please let me know if you notice any
> breakage.

One more attempt -- and now, because this shit is annoying
enough to test, I started writing some regression tests.

Hoping to add other shit to the tests, too, as I start
touching other parts of the system.

	https://git.sr.ht/~ori/regress



Attached, with a few bugfixes:

	- Fix a missed case in the change from 0-based years
	  to 1900-based years.

	- Fix a bug with negative modulo for years before 1970.

	- Add a few more common formats to seconds(1), to match
	  the documentation.

	- Make seconds(1) more flexible in the formats accepted:
	  when possible, build up dates from parts. (Perf is fine,
      in spite of a lot more dates tried.)

diff -r 6055167dc76a sys/include/libc.h
--- a/sys/include/libc.h	Mon Jun 15 00:12:57 2020 +0200
+++ b/sys/include/libc.h	Sun Jun 14 23:01:58 2020 -0700
@@ -314,22 +314,45 @@
 /*
  * Time-of-day
  */
+typedef struct Tzone Tzone;
+#pragma incomplete Tzone
+
 
 typedef
 struct Tm
 {
-	int	sec;
-	int	min;
-	int	hour;
-	int	mday;
-	int	mon;
-	int	year;
-	int	wday;
-	int	yday;
-	char	zone[4];
-	int	tzoff;
+	vlong	abs;		/* seconds since Jan 1 1970, GMT */
+	int	nsec;		/* nseconds (range 0...1e9) */
+	int	sec;		/* seconds (range 0..60) */
+	int	min;		/* minutes (0..59) */
+	int	hour;		/* hours (0..23) */
+	int	mday;		/* day of the month (1..31) */
+	int	mon;		/* month of the year (0..11) */
+	int	year;		/* year A.D. */
+	int	wday;		/* day of week (0..6, Sunday = 0) */
+	int	yday;		/* day of year (0..365) */
+	char	zone[16];	/* time zone name */
+	int	tzoff;		/* time zone delta from GMT */
+	Tzone	*tz;		/* time zone associated with this date */
 } Tm;
 
+typedef
+struct Tmfmt {
+	char	*fmt;
+	Tm	*tm;
+} Tmfmt;
+
+#pragma varargck	type	"τ"	Tmfmt
+
+extern	Tzone*	tmgetzone(char *name);
+extern	Tm*	tmnow(Tm*, Tzone*);
+extern	Tm*	tmtime(Tm*, vlong, Tzone*);
+extern	Tm*	tmtimens(Tm*, vlong, int, Tzone*);
+extern	Tm*	tmparse(Tm*, char*, char*, Tzone*);
+extern	Tm*	tmnorm(Tm*);
+extern	Tmfmt	tmfmt(Tm*, char*);
+extern	void	tmfmtinstall(void);
+
 extern	Tm*	gmtime(long);
 extern	Tm*	localtime(long);
 extern	char*	asctime(Tm*);
diff -r 6055167dc76a sys/man/1/seconds
--- a/sys/man/1/seconds	Mon Jun 15 00:12:57 2020 +0200
+++ b/sys/man/1/seconds	Sun Jun 14 23:01:58 2020 -0700
@@ -3,6 +3,9 @@
 seconds \- convert human-readable date (and time) to seconds since epoch
 .SH SYNOPSIS
 .B seconds
+[
+.I -f
+]
 .I date
 \&...
 .SH DESCRIPTION
@@ -18,16 +21,15 @@
 it will usually be necessary to enclose it in quotes.
 .PP
 .I Seconds
-accepts a somewhat wider range of input than just output from
+accepts a superset of a number of well known formats.
 .IR date (1).
-The main requirement is that the date must be fully specified,
-with a day of month, month and year
-in any order.
-The month must be an English name (or abbreviation),
-not a number, and the year must contain 4 digits.
-Unambiguous time-zone names are understood (i.e., not
-.LR IST )
-or time zones may be written as
+Among these are RFC5322, RFC822, RFC2822, RFC850,
+RFC1123, and several other commmonly used formats.
+
+.PP
+The -f flag allows using a specific format, specified
+in the syntax of
+.IR tmdate (2).
 .IR ±hhmm .
 Case is ignored.
 .SH EXAMPLES
diff -r 6055167dc76a sys/src/cmd/seconds.c
--- a/sys/src/cmd/seconds.c	Mon Jun 15 00:12:57 2020 +0200
+++ b/sys/src/cmd/seconds.c	Sun Jun 14 23:01:58 2020 -0700
@@ -1,236 +1,56 @@
-/*
- * seconds absolute_date ... - convert absolute_date to seconds since epoch
- */
-
 #include <u.h>
 #include <libc.h>
-#include <ctype.h>
 
-typedef ulong Time;
-
-enum {
-	AM, PM, HR24,
-
-	/* token types */
-	Month = 1,
-	Year,
-	Day,
-	Timetok,
-	Tz,
-	Dtz,
-	Ignore,
-	Ampm,
-
-	Maxtok		= 6, /* only this many chars are stored in datetktbl */
-	Maxdateflds	= 25,
+char *knownfmt[] = {
+	/* asctime */
+	"W MMM DD hh:mm:ss ?Z YYYY",
+	/* RFC3339 */
+	"YYYY-MM-DD[T]hh:mm:ss[Z]?Z",
+	"YYYY-MM-DD[T]hh:mm:ss[Z]?Z",
+	"YYYY-MM-DD[T]hh:mm:ss ?Z",
+	"YYYY-MM-DD[T]hh:mm:ss?Z",
 };
 
-/*
- * macros for squeezing values into low 7 bits of "value".
- * all timezones we care about are divisible by 10, and the largest value
- * (780) when divided is 78.
- */
-#define TOVAL(tp, v)	((tp)->value = (v) / 10)
-#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
+char *datefmt[] = {
+	/* RFC5322 */
+	"?W ?DD ?MMM ?YYYY",
+	"?W, DD-?MM-YY",
+	/* RFC822/RFC2822 */
+	"DD MMM YYYY",
+	"DD MMM YY",
+	/* RFC850 */
+	"W, DD-MMM-YY",
+	/* RFC1123 */
+	"WW, DD MMM YYYY",
+	/* RFC 3339 and human-readable variants */
+	"YYYY-MM-DD",
+	"YYYY-MM-DD [@] ",
+	/* random formats */
+	"?DD ?MM ?YYYY",
+	"MMM ?DD ?YYYY",
+	"YYYY ?MM ?DD",
+	"YYYY ?DD ?MM",
+	"YYYY/MM?/DD?",
+	"MMM YYYY ?DD",
+	"?DD YYYY MMM",
+	"MM/DD/YYYY",
+	nil
+};
 
-/* keep this struct small since we have an array of them */
-typedef struct {
-	char	token[Maxtok];
-	char	type;
-	schar	value;
-} Datetok;
+char *timefmt[] = {
+	" hh:mm:ss",
+	" hh:mm",
+	" hh",
+	"",
+	nil,
+};
 
-int dtok_numparsed;
+char *zonefmt[] = {
+	" ?Z",
+	"",
+	nil,
+};
 
-/* forwards */
-Datetok	*datetoktype(char *s, int *bigvalp);
-
-static Datetok datetktbl[];
-static unsigned szdatetktbl;
-
-/* parse 1- or 2-digit number, advance *cpp past it */
-static int
-eatnum(char **cpp)
-{
-	int c, x;
-	char *cp;
-
-	cp = *cpp;
-	c = *cp;
-	if (!isascii(c) || !isdigit(c))
-		return -1;
-	x = c - '0';
-
-	c = *++cp;
-	if (isascii(c) && isdigit(c)) {
-		x = 10*x + c - '0';
-		cp++;
-	}
-	*cpp = cp;
-	return x;
-}
-
-/* return -1 on failure */
-int
-parsetime(char *time, Tm *tm)
-{
-	tm->hour = eatnum(&time);
-	if (tm->hour == -1 || *time++ != ':')
-		return -1;			/* only hour; too short */
-
-	tm->min = eatnum(&time);
-	if (tm->min == -1)
-		return -1;
-	if (*time++ != ':') {
-		tm->sec = 0;
-		return 0;			/* no seconds; okay */
-	}
-
-	tm->sec = eatnum(&time);
-	if (tm->sec == -1)
-		return -1;
-
-	/* this may be considered too strict.  garbage at end of time? */
-	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
-}
-
-/*
- * try to parse pre-split timestr in fields as an absolute date
- */
-int
-tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
-{
-	int i, mer = HR24, bigval = -1;
-	long flg = 0, ty;
-	Datetok *tp;
-
-	now = localtime(time(0));	/* default to local time (zone) */
-	tm->tzoff = now->tzoff;
-	strncpy(tm->zone, now->zone, sizeof tm->zone);
-
-	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
-	tm->hour = tm->min = tm->sec = 0;
-	dtok_numparsed = 0;
-
-	for (i = 0; i < nf; i++) {
-		if (fields[i][0] == '\0')
-			continue;
-		tp = datetoktype(fields[i], &bigval);
-		ty = (1L << tp->type) & ~(1L << Ignore);
-		if (flg & ty)
-			return -1;		/* repeated type */
-		flg |= ty;
-		switch (tp->type) {
-		case Year:
-			tm->year = bigval;
-			if (tm->year < 1970 || tm->year > 2106)
-				return -1;	/* can't represent in ulong */
-			/* convert 4-digit year to 1900 origin */
-			if (tm->year >= 1900)
-				tm->year -= 1900;
-			break;
-		case Day:
-			tm->mday = bigval;
-			break;
-		case Month:
-			tm->mon = tp->value - 1; /* convert to zero-origin */
-			break;
-		case Timetok:
-			if (parsetime(fields[i], tm) < 0)
-				return -1;
-			break;
-		case Dtz:
-		case Tz:
-			/* tm2sec mangles timezones, so we do our own handling */
-			tm->tzoff = FROMVAL(tp);
-			snprint(tm->zone, sizeof(tm->zone), "GMT");
-			break;
-		case Ignore:
-			break;
-		case Ampm:
-			mer = tp->value;
-			break;
-		default:
-			return -1;	/* bad token type: CANTHAPPEN */
-		}
-	}
-	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
-		return -1;		/* missing component */
-	if (mer == PM)
-		tm->hour += 12;
-	return 0;
-}
-
-int
-prsabsdate(char *timestr, Tm *now, Tm *tm)
-{
-	int nf;
-	char *fields[Maxdateflds];
-	static char delims[] = "- \t\n/,";
-
-	nf = gettokens(timestr, fields, nelem(fields), delims+1);
-	if (nf > nelem(fields))
-		return -1;
-	if (tryabsdate(fields, nf, now, tm) < 0) {
-		char *p = timestr;
-
-		/*
-		 * could be a DEC-date; glue it all back together, split it
-		 * with dash as a delimiter and try again.  Yes, this is a
-		 * hack, but so are DEC-dates.
-		 */
-		while (--nf > 0) {
-			while (*p++ != '\0')
-				;
-			p[-1] = ' ';
-		}
-		nf = gettokens(timestr, fields, nelem(fields), delims);
-		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
-			return -1;
-	}
-	return 0;
-}
-
-int
-validtm(Tm *tm)
-{
-	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
-	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
-	    tm->min < 0 || tm->min > 59 ||
-	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
-		return 0;
-	return 1;
-}
-
-Time
-seconds(char *timestr)
-{
-	Tm date;
-
-	memset(&date, 0, sizeof date);
-	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
-		return -1;
-	return validtm(&date)? tm2sec(&date) - 60*date.tzoff: -1;
-}
-
-int
-convert(char *timestr)
-{
-	char *copy;
-	Time tstime;
-
-	copy = strdup(timestr);
-	if (copy == nil)
-		sysfatal("out of memory");
-	tstime = seconds(copy);
-	free(copy);
-	if (tstime == -1) {
-		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
-		return 1;
-	}
-	print("%lud\n", tstime);
-	return 0;
-}
 
 static void
 usage(void)
@@ -239,228 +59,44 @@
 	exits("usage");
 }
 
+/*
+ * seconds absolute_date ... - convert absolute_date to seconds since epoch
+ */
 void
 main(int argc, char **argv)
 {
-	int i, sts;
+	char **f, **df, **tf, **zf, *fmt, buf[256];
+	Tm tm;
+	int i;
 
-	sts = 0;
+	fmt = nil;
 	ARGBEGIN{
+	case 'f':
+		fmt = EARGF(usage());
+		break;
 	default:
 		usage();
-	}ARGEND
-	if (argc == 0)
-		usage();
-	for (i = 0; i < argc; i++)
-		sts |= convert(argv[i]);
-	exits(sts != 0? "bad": 0);
+	}ARGEND;
+
+	for(i = 0; i < argc; i++){
+		if(fmt != nil){
+			if(tmparse(&tm, fmt, argv[i], nil) != nil)
+				goto Found;
+		}else{
+			for(f = knownfmt; *f != nil; f++)
+				if(tmparse(&tm, *f, argv[i], nil) != nil)
+					goto Found;
+			for(df = datefmt; *df; df++)
+			for(tf = timefmt; *tf; tf++)
+			for(zf = zonefmt; *zf; zf++){
+				snprint(buf, sizeof(buf), "%s%s%s", *df, *tf, *zf);
+				if(tmparse(&tm, buf, argv[i], nil) != nil)
+					goto Found;
+			}
+		}
+		sysfatal("tmparse: %r");
+Found:
+		print("%lld\n", tm.abs);
+	}
+	exits(nil);
 }
-
-/*
- * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
- * is WAY faster than the generic bsearch().
- */
-Datetok *
-datebsearch(char *key, Datetok *base, unsigned nel)
-{
-	int cmp;
-	Datetok *last = base + nel - 1, *pos;
-
-	while (last >= base) {
-		pos = base + ((last - base) >> 1);
-		cmp = key[0] - pos->token[0];
-		if (cmp == 0) {
-			cmp = strncmp(key, pos->token, Maxtok);
-			if (cmp == 0)
-				return pos;
-		}
-		if (cmp < 0)
-			last = pos - 1;
-		else
-			base = pos + 1;
-	}
-	return 0;
-}
-
-Datetok *
-datetoktype(char *s, int *bigvalp)
-{
-	char *cp = s;
-	char c = *cp;
-	static Datetok t;
-	Datetok *tp = &t;
-
-	if (isascii(c) && isdigit(c)) {
-		int len = strlen(cp);
-
-		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
-			tp->type = Timetok;
-		else {
-			if (bigvalp != nil)
-				*bigvalp = atoi(cp); /* won't fit in tp->value */
-			if (len == 4)
-				tp->type = Year;
-			else if (++dtok_numparsed == 1)
-				tp->type = Day;
-			else
-				tp->type = Year;
-		}
-	} else if (c == '-' || c == '+') {
-		int val = atoi(cp + 1);
-		int hr =  val / 100;
-		int min = val % 100;
-
-		val = hr*60 + min;
-		TOVAL(tp, c == '-'? -val: val);
-		tp->type = Tz;
-	} else {
-		char lowtoken[Maxtok+1];
-		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
-
-		/* copy to lowtoken to avoid modifying s */
-		while ((c = *cp++) != '\0' && ltp < endltp)
-			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
-		*ltp = '\0';
-		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
-		if (tp == nil) {
-			tp = &t;
-			tp->type = Ignore;
-		}
-	}
-	return tp;
-}
-
-
-/*
- * to keep this table reasonably small, we divide the lexval for Tz and Dtz
- * entries by 10 and truncate the text field at MAXTOKLEN characters.
- * the text field is not guaranteed to be NUL-terminated.
- */
-static Datetok datetktbl[] = {
-/*	text		token	lexval */
-	"acsst",	Dtz,	63,	/* Cent. Australia */
-	"acst",		Tz,	57,	/* Cent. Australia */
-	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
-	"aesst",	Dtz,	66,	/* E. Australia */
-	"aest",		Tz,	60,	/* Australia Eastern Std Time */
-	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
-	"am",		Ampm,	AM,
-	"apr",		Month,	4,
-	"april",	Month,	4,
-	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
-	"at",		Ignore,	0,	/* "at" (throwaway) */
-	"aug",		Month,	8,
-	"august",	Month,	8,
-	"awsst",	Dtz,	54,	/* W. Australia */
-	"awst",		Tz,	48,	/* W. Australia */
-	"bst",		Tz,	6,	/* British Summer Time */
-	"bt",		Tz,	18,	/* Baghdad Time */
-	"cadt",		Dtz,	63,	/* Central Australian DST */
-	"cast",		Tz,	57,	/* Central Australian ST */
-	"cat",		Tz,	-60,	/* Central Alaska Time */
-	"cct",		Tz,	48,	/* China Coast */
-	"cdt",		Dtz,	-30,	/* Central Daylight Time */
-	"cet",		Tz,	6,	/* Central European Time */
-	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
-	"cst",		Tz,	-36,	/* Central Standard Time */
-	"dec",		Month,	12,
-	"decemb",	Month,	12,
-	"dnt",		Tz,	6,	/* Dansk Normal Tid */
-	"dst",		Ignore,	0,
-	"east",		Tz,	-60,	/* East Australian Std Time */
-	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
-	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
-	"eetdst",	Dtz,	18,	/* Eastern Europe */
-	"est",		Tz,	-30,	/* Eastern Standard Time */
-	"feb",		Month,	2,
-	"februa",	Month,	2,
-	"fri",		Ignore,	5,
-	"friday",	Ignore,	5,
-	"fst",		Tz,	6,	/* French Summer Time */
-	"fwt",		Dtz,	12,	/* French Winter Time  */
-	"gmt",		Tz,	0,	/* Greenwish Mean Time */
-	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
-	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
-	"hmt",		Dtz,	18,	/* Hellas ? ? */
-	"hst",		Tz,	-60,	/* Hawaii Std Time */
-	"idle",		Tz,	72,	/* Intl. Date Line, East */
-	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
-	"ist",		Tz,	12,	/* Israel */
-	"it",		Tz,	22,	/* Iran Time */
-	"jan",		Month,	1,
-	"januar",	Month,	1,
-	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
-	"jt",		Tz,	45,	/* Java Time */
-	"jul",		Month,	7,
-	"july",		Month,	7,
-	"jun",		Month,	6,
-	"june",		Month,	6,
-	"kst",		Tz,	54,	/* Korea Standard Time */
-	"ligt",		Tz,	60,	/* From Melbourne, Australia */
-	"mar",		Month,	3,
-	"march",	Month,	3,
-	"may",		Month,	5,
-	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
-	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
-	"met",		Tz,	6,	/* Middle Europe Time */
-	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
-	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
-	"mez",		Tz,	6,	/* Middle Europe Zone */
-	"mon",		Ignore,	1,
-	"monday",	Ignore,	1,
-	"mst",		Tz,	-42,	/* Mountain Standard Time */
-	"mt",		Tz,	51,	/* Moluccas Time */
-	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
-	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
-	"nor",		Tz,	6,	/* Norway Standard Time */
-	"nov",		Month,	11,
-	"novemb",	Month,	11,
-	"nst",		Tz,	-21,	/* Nfld. Standard Time */
-	"nt",		Tz,	-66,	/* Nome Time */
-	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
-	"nzst",		Tz,	72,	/* New Zealand Standard Time */
-	"nzt",		Tz,	72,	/* New Zealand Time */
-	"oct",		Month,	10,
-	"octobe",	Month,	10,
-	"on",		Ignore,	0,	/* "on" (throwaway) */
-	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
-	"pm",		Ampm,	PM,
-	"pst",		Tz,	-48,	/* Pacific Standard Time */
-	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
-	"sast",		Tz,	57,	/* South Australian Std Time */
-	"sat",		Ignore,	6,
-	"saturd",	Ignore,	6,
-	"sep",		Month,	9,
-	"sept",		Month,	9,
-	"septem",	Month,	9,
-	"set",		Tz,	-6,	/* Seychelles Time ?? */
-	"sst",		Dtz,	12,	/* Swedish Summer Time */
-	"sun",		Ignore,	0,
-	"sunday",	Ignore,	0,
-	"swt",		Tz,	6,	/* Swedish Winter Time  */
-	"thu",		Ignore,	4,
-	"thur",		Ignore,	4,
-	"thurs",	Ignore,	4,
-	"thursd",	Ignore,	4,
-	"tue",		Ignore,	2,
-	"tues",		Ignore,	2,
-	"tuesda",	Ignore,	2,
-	"ut",		Tz,	0,
-	"utc",		Tz,	0,
-	"wadt",		Dtz,	48,	/* West Australian DST */
-	"wast",		Tz,	42,	/* West Australian Std Time */
-	"wat",		Tz,	-6,	/* West Africa Time */
-	"wdt",		Dtz,	54,	/* West Australian DST */
-	"wed",		Ignore,	3,
-	"wednes",	Ignore,	3,
-	"weds",		Ignore,	3,
-	"wet",		Tz,	0,	/* Western Europe */
-	"wetdst",	Dtz,	6,	/* Western Europe */
-	"wst",		Tz,	48,	/* West Australian Std Time */
-	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
-	"yst",		Tz,	-54,	/* Yukon Standard Time */
-	"zp4",		Tz,	-24,	/* GMT +4  hours. */
-	"zp5",		Tz,	-30,	/* GMT +5  hours. */
-	"zp6",		Tz,	-36,	/* GMT +6  hours. */
-};
-static unsigned szdatetktbl = nelem(datetktbl);
diff -r 6055167dc76a sys/src/libc/9sys/ctime.c
--- a/sys/src/libc/9sys/ctime.c	Mon Jun 15 00:12:57 2020 +0200
+++ b/sys/src/libc/9sys/ctime.c	Sun Jun 14 23:01:58 2020 -0700
@@ -33,269 +33,34 @@
 #include <u.h>
 #include <libc.h>
 
-static	char	dmsize[12] =
+Tm*
+localtime(long tim)
 {
-	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-};
+	static Tm tm;
+	Tzone *tz;
 
-/*
- * The following table is used for 1974 and 1975 and
- * gives the day number of the first day after the Sunday of the
- * change.
- */
+	/* No error checking: the API doesn't allow it. */
+	tz = tmgetzone("local");
+	tmtime(&tm, tim, tz);
+	return &tm;
 
-static	int	dysize(int);
-static	void	ct_numb(char*, int);
-
-#define	TZSIZE	150
-static	void	readtimezone(void);
-static	int	rd_name(char**, char*);
-static	int	rd_long(char**, long*);
-static
-struct
-{
-	char	stname[4];
-	char	dlname[4];
-	long	stdiff;
-	long	dldiff;
-	long	dlpairs[TZSIZE];
-} timezone;
-
-char*
-ctime(long t)
-{
-	return asctime(localtime(t));
 }
 
 Tm*
-localtime(long tim)
+gmtime(long abs)
 {
-	Tm *ct;
-	long t, *p;
-	int dlflag;
-
-	if(timezone.stname[0] == 0)
-		readtimezone();
-	t = tim + timezone.stdiff;
-	dlflag = 0;
-	for(p = timezone.dlpairs; *p; p += 2)
-		if(t >= p[0])
-		if(t < p[1]) {
-			t = tim + timezone.dldiff;
-			dlflag++;
-			break;
-		}
-	ct = gmtime(t);
-	if(dlflag){
-		strcpy(ct->zone, timezone.dlname);
-		ct->tzoff = timezone.dldiff;
-	} else {
-		strcpy(ct->zone, timezone.stname);
-		ct->tzoff = timezone.stdiff;
-	}
-	return ct;
-}
-
-Tm*
-gmtime(long tim)
-{
-	int d0, d1;
-	long hms, day;
-	static Tm xtime;
-
-	/*
-	 * break initial number into days
-	 */
-	hms = tim % 86400L;
-	day = tim / 86400L;
-	if(hms < 0) {
-		hms += 86400L;
-		day -= 1;
-	}
-
-	/*
-	 * generate hours:minutes:seconds
-	 */
-	xtime.sec = hms % 60;
-	d1 = hms / 60;
-	xtime.min = d1 % 60;
-	d1 /= 60;
-	xtime.hour = d1;
-
-	/*
-	 * day is the day number.
-	 * generate day of the week.
-	 * The addend is 4 mod 7 (1/1/1970 was Thursday)
-	 */
-
-	xtime.wday = (day + 7340036L) % 7;
-
-	/*
-	 * year number
-	 */
-	if(day >= 0)
-		for(d1 = 1970; day >= dysize(d1); d1++)
-			day -= dysize(d1);
-	else
-		for (d1 = 1970; day < 0; d1--)
-			day += dysize(d1-1);
-	xtime.year = d1-1900;
-	xtime.yday = d0 = day;
-
-	/*
-	 * generate month
-	 */
-
-	if(dysize(d1) == 366)
-		dmsize[1] = 29;
-	for(d1 = 0; d0 >= dmsize[d1]; d1++)
-		d0 -= dmsize[d1];
-	dmsize[1] = 28;
-	xtime.mday = d0 + 1;
-	xtime.mon = d1;
-	strcpy(xtime.zone, "GMT");
-	return &xtime;
+	static Tm tm;
+	return tmtime(&tm, abs, nil);
 }
 
 char*
-asctime(Tm *t)
+ctime(long abs)
 {
-	char *ncp;
-	static char cbuf[30];
+	Tzone *tz;
+	Tm tm;
 
-	strcpy(cbuf, "Thu Jan 01 00:00:00 GMT 1970\n");
-	ncp = &"SunMonTueWedThuFriSat"[t->wday*3];
-	cbuf[0] = *ncp++;
-	cbuf[1] = *ncp++;
-	cbuf[2] = *ncp;
-	ncp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[t->mon*3];
-	cbuf[4] = *ncp++;
-	cbuf[5] = *ncp++;
-	cbuf[6] = *ncp;
-	ct_numb(cbuf+8, t->mday);
-	ct_numb(cbuf+11, t->hour+100);
-	ct_numb(cbuf+14, t->min+100);
-	ct_numb(cbuf+17, t->sec+100);
-	ncp = t->zone;
-	cbuf[20] = *ncp++;
-	cbuf[21] = *ncp++;
-	cbuf[22] = *ncp;
-	ct_numb(cbuf+24, (t->year+1900) / 100 + 100);
-	ct_numb(cbuf+26, t->year+100);
-	return cbuf;
+	/* No error checking: the API doesn't allow it. */
+	tz = tmgetzone("local");
+	tmtime(&tm, abs, tz);
+	return asctime(&tm);
 }
-
-static
-dysize(int y)
-{
-
-	if(y%4 == 0 && (y%100 != 0 || y%400 == 0))
-		return 366;
-	return 365;
-}
-
-static
-void
-ct_numb(char *cp, int n)
-{
-
-	cp[0] = ' ';
-	if(n >= 10)
-		cp[0] = (n/10)%10 + '0';
-	cp[1] = n%10 + '0';
-}
-
-static
-void
-readtimezone(void)
-{
-	char buf[TZSIZE*11+30], *p;
-	int i;
-
-	memset(buf, 0, sizeof(buf));
-	i = open("/env/timezone", 0);
-	if(i < 0)
-		goto error;
-	if(read(i, buf, sizeof(buf)) >= sizeof(buf)){
-		close(i);
-		goto error;
-	}
-	close(i);
-	p = buf;
-	if(rd_name(&p, timezone.stname))
-		goto error;
-	if(rd_long(&p, &timezone.stdiff))
-		goto error;
-	if(rd_name(&p, timezone.dlname))
-		goto error;
-	if(rd_long(&p, &timezone.dldiff))
-		goto error;
-	for(i=0; i<TZSIZE; i++) {
-		if(rd_long(&p, &timezone.dlpairs[i]))
-			goto error;
-		if(timezone.dlpairs[i] == 0)
-			return;
-	}
-
-error:
-	timezone.stdiff = 0;
-	strcpy(timezone.stname, "GMT");
-	timezone.dlpairs[0] = 0;
-}
-
-static
-rd_name(char **f, char *p)
-{
-	int c, i;
-
-	for(;;) {
-		c = *(*f)++;
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	for(i=0; i<3; i++) {
-		if(c == ' ' || c == '\n')
-			return 1;
-		*p++ = c;
-		c = *(*f)++;
-	}
-	if(c != ' ' && c != '\n')
-		return 1;
-	*p = 0;
-	return 0;
-}
-
-static
-rd_long(char **f, long *p)
-{
-	int c, s;
-	long l;
-
-	s = 0;
-	for(;;) {
-		c = *(*f)++;
-		if(c == '-') {
-			s++;
-			continue;
-		}
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	if(c == 0) {
-		*p = 0;
-		return 0;
-	}
-	l = 0;
-	for(;;) {
-		if(c == ' ' || c == '\n')
-			break;
-		if(c < '0' || c > '9')
-			return 1;
-		l = l*10 + c-'0';
-		c = *(*f)++;
-	}
-	if(s)
-		l = -l;
-	*p = l;
-	return 0;
-}
diff -r 6055167dc76a sys/src/libc/9sys/tm2sec.c
--- a/sys/src/libc/9sys/tm2sec.c	Mon Jun 15 00:12:57 2020 +0200
+++ b/sys/src/libc/9sys/tm2sec.c	Sun Jun 14 23:01:58 2020 -0700
@@ -1,202 +1,9 @@
 #include <u.h>
 #include <libc.h>
 
-#define	TZSIZE	150
-static	void	readtimezone(void);
-static	int	rd_name(char**, char*);
-static	int	rd_long(char**, long*);
-static
-struct
-{
-	char	stname[4];
-	char	dlname[4];
-	long	stdiff;
-	long	dldiff;
-	long	dlpairs[TZSIZE];
-} timezone;
-
-#define SEC2MIN 60L
-#define SEC2HOUR (60L*SEC2MIN)
-#define SEC2DAY (24L*SEC2HOUR)
-
-/*
- *  days per month plus days/year
- */
-static	int	dmsize[] =
-{
-	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-};
-static	int	ldmsize[] =
-{
-	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
-};
-
-/*
- *  return the days/month for the given year
- */
-static int *
-yrsize(int y)
-{
-	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
-		return ldmsize;
-	else
-		return dmsize;
-}
-
-/*
- * compute seconds since Jan 1 1970 GMT
- * and convert to our timezone.
- */
 long
 tm2sec(Tm *tm)
 {
-	long secs, *p;
-	int i, yday, year, *d2m;
-
-	if(strcmp(tm->zone, "GMT") != 0 && timezone.stname[0] == 0)
-		readtimezone();
-	secs = 0;
-
-	/*
-	 *  seconds per year
-	 */
-	year = tm->year + 1900;
-	for(i = 1970; i < year; i++){
-		d2m = yrsize(i);
-		secs += d2m[0] * SEC2DAY;
-	}
-
-	/*
-	 *  if mday is set, use mon and mday to compute yday
-	 */
-	if(tm->mday){
-		yday = 0;
-		d2m = yrsize(year);
-		for(i=0; i<tm->mon; i++)
-			yday += d2m[i+1];
-		yday += tm->mday-1;
-	}else{
-		yday = tm->yday;
-	}
-	secs += yday * SEC2DAY;
-
-	/*
-	 * hours, minutes, seconds
-	 */
-	secs += tm->hour * SEC2HOUR;
-	secs += tm->min * SEC2MIN;
-	secs += tm->sec;
-
-	/*
-	 * Only handles zones mentioned in /env/timezone,
-	 * but things get too ambiguous otherwise.
-	 */
-	if(strcmp(tm->zone, timezone.stname) == 0)
-		secs -= timezone.stdiff;
-	else if(strcmp(tm->zone, timezone.dlname) == 0)
-		secs -= timezone.dldiff;
-	else if(tm->zone[0] == 0){
-		secs -= timezone.dldiff;
-		for(p = timezone.dlpairs; *p; p += 2)
-			if(secs >= p[0] && secs < p[1])
-				break;
-		if(*p == 0){
-			secs += timezone.dldiff;
-			secs -= timezone.stdiff;
-		}
-	}
-	return secs;
+	tmnorm(tm);
+	return tm->abs;
 }
-
-static
-void
-readtimezone(void)
-{
-	char buf[TZSIZE*11+30], *p;
-	int i;
-
-	memset(buf, 0, sizeof(buf));
-	i = open("/env/timezone", 0);
-	if(i < 0)
-		goto error;
-	if(read(i, buf, sizeof(buf)) >= sizeof(buf))
-		goto error;
-	close(i);
-	p = buf;
-	if(rd_name(&p, timezone.stname))
-		goto error;
-	if(rd_long(&p, &timezone.stdiff))
-		goto error;
-	if(rd_name(&p, timezone.dlname))
-		goto error;
-	if(rd_long(&p, &timezone.dldiff))
-		goto error;
-	for(i=0; i<TZSIZE; i++) {
-		if(rd_long(&p, &timezone.dlpairs[i]))
-			goto error;
-		if(timezone.dlpairs[i] == 0)
-			return;
-	}
-
-error:
-	timezone.stdiff = 0;
-	strcpy(timezone.stname, "GMT");
-	timezone.dlpairs[0] = 0;
-}
-
-static int
-rd_name(char **f, char *p)
-{
-	int c, i;
-
-	for(;;) {
-		c = *(*f)++;
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	for(i=0; i<3; i++) {
-		if(c == ' ' || c == '\n')
-			return 1;
-		*p++ = c;
-		c = *(*f)++;
-	}
-	if(c != ' ' && c != '\n')
-		return 1;
-	*p = 0;
-	return 0;
-}
-
-static int
-rd_long(char **f, long *p)
-{
-	int c, s;
-	long l;
-
-	s = 0;
-	for(;;) {
-		c = *(*f)++;
-		if(c == '-') {
-			s++;
-			continue;
-		}
-		if(c != ' ' && c != '\n')
-			break;
-	}
-	if(c == 0) {
-		*p = 0;
-		return 0;
-	}
-	l = 0;
-	for(;;) {
-		if(c == ' ' || c == '\n')
-			break;
-		if(c < '0' || c > '9')
-			return 1;
-		l = l*10 + c-'0';
-		c = *(*f)++;
-	}
-	if(s)
-		l = -l;
-	*p = l;
-	return 0;
-}
diff -r 6055167dc76a sys/src/libc/port/mkfile
--- a/sys/src/libc/port/mkfile	Mon Jun 15 00:12:57 2020 +0200
+++ b/sys/src/libc/port/mkfile	Sun Jun 14 23:01:58 2020 -0700
@@ -20,6 +20,7 @@
 	cleanname.c\
 	crypt.c\
 	ctype.c\
+	date.c\
 	encodefmt.c\
 	execl.c\
 	exits.c\



^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] libc: date handling improvements
  2020-05-31  3:39 ori
  2020-05-31  4:57 ` [9front] " ori
  2020-06-06 20:56 ` ori
@ 2020-06-13 19:40 ` ori
  2020-06-15  6:02   ` ori
  2 siblings, 1 reply; 5+ messages in thread
From: ori @ 2020-06-13 19:40 UTC (permalink / raw)
  To: ori, 9front

> I implemented libdate -- but the right place to put
> it is in libc, replacing our ctime and gmtime apis.
> 
> Follow ups will replace seconds(1), upas time parsing
> code, and whatever other instances of hand rolled date
> parsing and formatting can be found.
> 
> Here's a patch that does this. Testing appreciated,
> and follow ups that I haven't done yet would also be
> great :)
> 

Committed -- please let me know if you notice any
breakage.



^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] libc: date handling improvements
  2020-05-31  3:39 ori
  2020-05-31  4:57 ` [9front] " ori
@ 2020-06-06 20:56 ` ori
  2020-06-13 19:40 ` ori
  2 siblings, 0 replies; 5+ messages in thread
From: ori @ 2020-06-06 20:56 UTC (permalink / raw)
  To: ori, 9front

> I implemented libdate -- but the right place to put
> it is in libc, replacing our ctime and gmtime apis.
> 
> Follow ups will replace seconds(1), upas time parsing
> code, and whatever other instances of hand rolled date
> parsing and formatting can be found.
> 
> Here's a patch that does this. Testing appreciated,
> and follow ups that I haven't done yet would also be
> great :)
> 

Updated version. Changes:

- I included the manpage.
- I included the seconds(1) conversion, as an example.
- We can pad formats using '_', which allows us to do
  the asctime format.
- Shuffled around arguments for consistency.
- Added a nanoseconds field. Abs is still in seconds,
  so for complete equality, you need to do:

	a->abs == b->abs && a->nsec == b->nsec.

  It's unfortunate, but I'd rather keep the range that
  seconds have for our abs time.
- Fixed the year range offset, so now it's based from
  1900. Keeps compatibility with the original struct tm,
  which made this unfortunate choice.
- Enforces range checks when parsing dates, so that
  Jan 32 is rejected, instead of normalized to Feb 1.
  The ~ modifier brings back the old behavior.


diff -r adb80c57a05c sys/include/libc.h
--- a/sys/include/libc.h	Fri Jun 05 18:52:22 2020 -0700
+++ b/sys/include/libc.h	Sat Jun 06 13:56:26 2020 -0700
@@ -314,22 +314,45 @@
 /*
  * Time-of-day
  */
+typedef struct Tzone Tzone;
+#pragma incomplete Tzone
+
 
 typedef
 struct Tm
 {
-	int	sec;
-	int	min;
-	int	hour;
-	int	mday;
-	int	mon;
-	int	year;
-	int	wday;
-	int	yday;
-	char	zone[4];
-	int	tzoff;
+	vlong	abs;		/* seconds since Jan 1 1970, GMT */
+	int	nsec;		/* nseconds (range 0...1e9) */
+	int	sec;		/* seconds (range 0..60) */
+	int	min;		/* minutes (0..59) */
+	int	hour;		/* hours (0..23) */
+	int	mday;		/* day of the month (1..31) */
+	int	mon;		/* month of the year (0..11) */
+	int	year;		/* year A.D. */
+	int	wday;		/* day of week (0..6, Sunday = 0) */
+	int	yday;		/* day of year (0..365) */
+	char	zone[16];	/* time zone name */
+	int	tzoff;		/* time zone delta from GMT */
+	Tzone	*tz;		/* time zone associated with this date */
 } Tm;
 
+typedef
+struct Tmfmt {
+	char	*fmt;
+	Tm	*tm;
+} Tmfmt;
+
+#pragma varargck	type	"τ"	Tmfmt
+
+extern	Tzone*	tmgetzone(char *name);
+extern	Tm*	tmnow(Tm*, Tzone*);
+extern	Tm*	tmtime(Tm*, vlong, Tzone*);
+extern	Tm*	tmtimens(Tm*, vlong, int, Tzone*);
+extern	Tm*	tmparse(Tm*, char*, char*, Tzone*);
+extern	Tm*	tmnorm(Tm*);
+extern	Tmfmt	tmfmt(Tm*, char*);
+extern	void	tmfmtinstall(void);
+
 extern	Tm*	gmtime(long);
 extern	Tm*	localtime(long);
 extern	char*	asctime(Tm*);
diff -r adb80c57a05c sys/man/2/tmdate
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/man/2/tmdate	Sat Jun 06 13:56:26 2020 -0700
@@ -0,0 +1,235 @@
+.TH TMDATE 2
+.SH NAME
+tmnow, tmgetzone, tmtime, tmparse, tmfmt, tmnorm, - convert date and time
+.SH SYNOPSIS
+.B #include <u.h>
+.br
+.B #include <libc.h>
+.PP
+.ft L
+.nf
+.EX
+typedef struct Tmd Tmd;
+typedef struct Tmfmt Tmfmt;
+
+struct {
+	vlong	abs;	/* seconds since Jan 1 1970, UTC */
+	int	nsec;	/* nanoseconds (range 0..1e9) */
+	int	sec;	/* seconds (range 0..59) */
+	int	min;	/* minutes (0..59) */
+	int	hour;	/* hours (0..23) */
+	int	mday;	/* day of the month (1..31) */
+	int	mon;	/* month of the year (0..11) */
+	int	year;	/* C.E year - 1900 */
+	int	wday;	/* day of week (0..6, Sunday = 0) */
+	int	yday;	/* day of year (0..365) */
+	char	zone[];	/* time zone name */
+	int	tzoff;	/* time	zone delta from GMT, seconds */
+};
+
+Tzone *tmgetzone(char *name);
+Tm    *tmnow(Tm *tm, char *tz);
+Tm    *tmtime(Tm *tm, vlong abs, Tzone *tz);
+Tm    *tmtimens(Tm *tm, vlong abs, int ns, Tzone *tz);
+Tm    *tmparse(Tm *dst, char *fmt, char *tm, Tzone *zone);
+void   tmnorm(Tm *tm);
+Tmfmt  tmfmt(Tm *tm, char *fmt);
+void   tmfmtinstall(void);
+.EE
+.SH DESCRIPTION
+.PP
+This family of functions handles simple date and time manipulation.
+Times are represented as an absolute instant in time, combined with a time zone.
+.PP
+Time zones are loaded by as name.
+They can be specified as the abbreviated timezone name,
+the full timezone name, the path to a timezone file,
+or an absolute offset in the HHMM form.
+.PP
+When given as a timezone, any instant-dependent adjustments such as leap
+seconds and daylight savings time will be applied to the derived fields of
+struct tm, but will not affect the absolute time.
+The time zone name local always refers to the time in /env/timezone.
+The nil timezone always refers to GMT.
+.PP
+Tmgetzone loads a timezone by name. The returned timezone is
+cached for the lifetime of the program, and should not be freed.
+Loading a timezone repeatedly by name loads from the cache, and
+does not leak.
+.PP
+Tmnow gets the current time of day in the requested time zone.
+.PP
+Tmtime converts the millisecond-resolution timestamp 'abs'
+into a Tm struct in the requested timezone.
+Tmtimens does the same, but with a nanosecond accuracy.
+.PP
+Tmstime is identical to tmtime, but accepts the time in sec-
+onds.
+.PP
+Tmparse parses a time from a string according to the format
+argument. The result is returned in the timezone requested.
+If there is a timezone in the date, then we tzshift to the
+local timezone.
+.PP
+The format argument contains zero or more of the following components:
+.TP
+.B Y, YY, YYYY
+Represents the year.
+.I YY
+prints the year in 2 digit form.
+.TP
+.B M, MM, MMM, MMMM
+The month of the year, in unpadded numeric, padded numeric, short name, or long name,
+respectively.
+.TP
+.B D, DD
+The day of month in unpadded or padded numeric form, respectively.
+.TP
+.B W, WW
+The day of week in short or long name form, respectively.
+.TP
+.B h, hh
+The hour in unpadded or padded form, respectively
+.TP
+.B m, mm
+The minute in unpadded or padded form, respectively
+.TP
+.B s, ss
+The second in unpadded or padded form, respectively
+.TP
+.B z, Z, ZZ
+The timezone in named, [+-]HHMM and [+-]HH:MM form, respectively
+.TP
+.B a, A
+Lower and uppercase 'am' and 'pm' specifiers, respectively.
+.TP
+.B [...]
+Quoted text, copied directly to the output.
+.TP
+.B _
+When formatting, this inserts padding into the date format.
+The padded width of a field is the sum of format and specifier
+characters combined.
+For example,
+.I __h
+will format to a width of 3.
+.TP
+.B ?
+When parsing, this makes the following argument match fuzzily.
+Fuzzy matching means that all formats are tried, from most to least specific.
+For example, 
+.I ?M
+will match 
+.IR January ,
+.IR Jan ,
+.IR 01 ,
+and 
+.IR 1 ,
+in that order of preference.
+.TP
+.B ~
+When parsing a date, this slackens range enforcement, accepting
+out of range values such as January
+.IR 32 ,
+which would get normalized to February 1st.
+.PP
+Any characters not specified above are copied directly to output,
+without modification.
+
+
+
+.PP
+If the format argument is nil, it makes an
+attempt to parse common human readable date formats.  These
+formats include ISO-8601,RFC-3339 and RFC-2822 dates.
+.
+.PP
+Tmfmt produces a format description structure suitable for passing
+to
+.IR fmtprint (2) .
+If  fmt is nil, we default to the format used in
+.IR ctime (2).
+The format of the format string is identical to
+.IR tmparse.
+
+.PP
+When parsing, any amount of whitespace is treated as a single token.
+All string matches are case insensitive, and zero padding is optional.
+
+.PP
+Tmnorm takes a manually adjusted Tm structure, and recal-
+culates the absolute time from the
+.I year, mon, mday, hr, min
+and
+.I sec
+fields. Other fields are ignored.
+This recalculation respects the time zone stored in struct tm.
+Out of range values are wrapped. For example, December 32nd
+becomes January 1st.
+
+.PP
+Tmfmtinstall installs a time format specifier %τ. The time
+format behaves as in tmfmt
+
+.SH Examples
+.PP
+All examples assume tmfmtinstall has been called.
+.PP
+Get the current date in the local timezone, UTC, and
+US_Pacific time. Print it using the default format.
+
+.IP
+.EX
+Tm t;
+Tzone *zl, *zp;
+if((zl = tmgetzone("local") == nil)
+	sysfatal("load zone: %r");
+if((zp = tmgetzone("US_Pacific") == nil)
+	sysfatal("load zone: %r");
+print("local: %τ\\n", tmfmt(tmnow(&t, zl), nil));
+print("gmt: %τ\\n", tmfmt(tmnow(&t, nil), nil));
+print("eastern: %τ\\n", tmfmt(tmnow(&t, zp), nil));
+.EE
+.PP
+Compare if two times are the same, regardless of timezone.
+
+.IP
+.EX
+Tm a, b;
+
+tmparse(&a, nil, "Tue Dec 10 12:36:00 PST 2019");
+tmparse(&b, nil, "Tue Dec 10 15:36:00 EST 2019");
+if(a.abs == b.abs)
+	print("same\\n");
+else
+	print("different\\n");
+.EE
+
+.PP
+Convert from one timezone to another.
+
+.IP
+.EX
+Tm here, there;
+Tzone *zl, *zp;
+if((zl = tmgetzone("local")) == nil)
+	sysfatal("load zone: %r");
+if((zp = tmgetzone("US_Pacific")) == nil)
+	sysfatal("load zone: %r");
+if(tmnow(&here, zl) == nil)
+	sysfatal("get time: %r");
+if(tmtime(&there, here.abs, zp) == nil)
+	sysfatal("shift time: %r");
+.EE
+
+.PP
+Add a day to two times. Because we picked daylight savings
+time to adjust over, only 23 hours are added.
+
+.EX
+Tm t;
+tmparse(&t, "W MMM D hh:mm:ss z YYYY, "Sun Nov 2 13:11:11 PST 2019");
+tm.day++;
+tmrecalc(&t);
+print("%τ", &t); /*  Mon Nov 3 13:11:11 PST 2019 */
+.EE
diff -r adb80c57a05c sys/src/cmd/seconds.c
--- a/sys/src/cmd/seconds.c	Fri Jun 05 18:52:22 2020 -0700
+++ b/sys/src/cmd/seconds.c	Sat Jun 06 13:56:26 2020 -0700
@@ -1,237 +1,37 @@
+#include <u.h>
+#include <libc.h>
+
 /*
  * seconds absolute_date ... - convert absolute_date to seconds since epoch
  */
-
-#include <u.h>
-#include <libc.h>
-#include <ctype.h>
-
-typedef ulong Time;
-
-enum {
-	AM, PM, HR24,
-
-	/* token types */
-	Month = 1,
-	Year,
-	Day,
-	Timetok,
-	Tz,
-	Dtz,
-	Ignore,
-	Ampm,
-
-	Maxtok		= 6, /* only this many chars are stored in datetktbl */
-	Maxdateflds	= 25,
+char *formats[] = {
+	/* asctime */
+	"W MMM DD hh:mm:ss ZZZ YYYY",
+	/* RFC5322 */
+	"?W ?DD ?MMM ?YYYY hh:mm:ss ?Z",
+	"?W, DD-?MM-YY hh:mm:ss ?Z",
+	/* RFC822/RFC8222 */
+	"DD MMM YY hh:mm ZZZ",
+	"DD MMM YY hh:mm Z",
+	/* RFC850 */
+	"W, DD-MMM-YY hh:mm:ss MST",
+	/* RFC1123 */
+	"WW, DD MMM YYYY hh:mm:ss ZZZ",
+	/* RFC1123Z */
+	"WW, DD MMM YYYY hh:mm:ss ZZ",
+	/* RFC3339 */
+	"YYYY-MM-DD[T]hh:mm:ss[Z]ZZ",
+	"YYYY-MM-DD[T]hh:mm:ss[Z]Z",
+	"YYYY-MM-DD[T]hh:mm:ss ZZ",
+	"YYYY-MM-DD[T]hh:mm:ss Z",
+	/* RFC 3339 and human-readable variants */
+	"YYYY-MM-DD hh:mm:ss",
+	"YYYY-MM-DD hh:mm:ss ?Z",
+	"YYYY-MM-DD [@] hh:mm:ss",
+	"YYYY-MM-DD [@] hh:mm:ss ?Z",
+	nil
 };
 
-/*
- * macros for squeezing values into low 7 bits of "value".
- * all timezones we care about are divisible by 10, and the largest value
- * (780) when divided is 78.
- */
-#define TOVAL(tp, v)	((tp)->value = (v) / 10)
-#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
-
-/* keep this struct small since we have an array of them */
-typedef struct {
-	char	token[Maxtok];
-	char	type;
-	schar	value;
-} Datetok;
-
-int dtok_numparsed;
-
-/* forwards */
-Datetok	*datetoktype(char *s, int *bigvalp);
-
-static Datetok datetktbl[];
-static unsigned szdatetktbl;
-
-/* parse 1- or 2-digit number, advance *cpp past it */
-static int
-eatnum(char **cpp)
-{
-	int c, x;
-	char *cp;
-
-	cp = *cpp;
-	c = *cp;
-	if (!isascii(c) || !isdigit(c))
-		return -1;
-	x = c - '0';
-
-	c = *++cp;
-	if (isascii(c) && isdigit(c)) {
-		x = 10*x + c - '0';
-		cp++;
-	}
-	*cpp = cp;
-	return x;
-}
-
-/* return -1 on failure */
-int
-parsetime(char *time, Tm *tm)
-{
-	tm->hour = eatnum(&time);
-	if (tm->hour == -1 || *time++ != ':')
-		return -1;			/* only hour; too short */
-
-	tm->min = eatnum(&time);
-	if (tm->min == -1)
-		return -1;
-	if (*time++ != ':') {
-		tm->sec = 0;
-		return 0;			/* no seconds; okay */
-	}
-
-	tm->sec = eatnum(&time);
-	if (tm->sec == -1)
-		return -1;
-
-	/* this may be considered too strict.  garbage at end of time? */
-	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
-}
-
-/*
- * try to parse pre-split timestr in fields as an absolute date
- */
-int
-tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
-{
-	int i, mer = HR24, bigval = -1;
-	long flg = 0, ty;
-	Datetok *tp;
-
-	now = localtime(time(0));	/* default to local time (zone) */
-	tm->tzoff = now->tzoff;
-	strncpy(tm->zone, now->zone, sizeof tm->zone);
-
-	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
-	tm->hour = tm->min = tm->sec = 0;
-	dtok_numparsed = 0;
-
-	for (i = 0; i < nf; i++) {
-		if (fields[i][0] == '\0')
-			continue;
-		tp = datetoktype(fields[i], &bigval);
-		ty = (1L << tp->type) & ~(1L << Ignore);
-		if (flg & ty)
-			return -1;		/* repeated type */
-		flg |= ty;
-		switch (tp->type) {
-		case Year:
-			tm->year = bigval;
-			if (tm->year < 1970 || tm->year > 2106)
-				return -1;	/* can't represent in ulong */
-			/* convert 4-digit year to 1900 origin */
-			if (tm->year >= 1900)
-				tm->year -= 1900;
-			break;
-		case Day:
-			tm->mday = bigval;
-			break;
-		case Month:
-			tm->mon = tp->value - 1; /* convert to zero-origin */
-			break;
-		case Timetok:
-			if (parsetime(fields[i], tm) < 0)
-				return -1;
-			break;
-		case Dtz:
-		case Tz:
-			/* tm2sec mangles timezones, so we do our own handling */
-			tm->tzoff = FROMVAL(tp);
-			snprint(tm->zone, sizeof(tm->zone), "GMT");
-			break;
-		case Ignore:
-			break;
-		case Ampm:
-			mer = tp->value;
-			break;
-		default:
-			return -1;	/* bad token type: CANTHAPPEN */
-		}
-	}
-	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
-		return -1;		/* missing component */
-	if (mer == PM)
-		tm->hour += 12;
-	return 0;
-}
-
-int
-prsabsdate(char *timestr, Tm *now, Tm *tm)
-{
-	int nf;
-	char *fields[Maxdateflds];
-	static char delims[] = "- \t\n/,";
-
-	nf = gettokens(timestr, fields, nelem(fields), delims+1);
-	if (nf > nelem(fields))
-		return -1;
-	if (tryabsdate(fields, nf, now, tm) < 0) {
-		char *p = timestr;
-
-		/*
-		 * could be a DEC-date; glue it all back together, split it
-		 * with dash as a delimiter and try again.  Yes, this is a
-		 * hack, but so are DEC-dates.
-		 */
-		while (--nf > 0) {
-			while (*p++ != '\0')
-				;
-			p[-1] = ' ';
-		}
-		nf = gettokens(timestr, fields, nelem(fields), delims);
-		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
-			return -1;
-	}
-	return 0;
-}
-
-int
-validtm(Tm *tm)
-{
-	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
-	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
-	    tm->min < 0 || tm->min > 59 ||
-	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
-		return 0;
-	return 1;
-}
-
-Time
-seconds(char *timestr)
-{
-	Tm date;
-
-	memset(&date, 0, sizeof date);
-	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
-		return -1;
-	return validtm(&date)? tm2sec(&date) - 60*date.tzoff: -1;
-}
-
-int
-convert(char *timestr)
-{
-	char *copy;
-	Time tstime;
-
-	copy = strdup(timestr);
-	if (copy == nil)
-		sysfatal("out of memory");
-	tstime = seconds(copy);
-	free(copy);
-	if (tstime == -1) {
-		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
-		return 1;
-	}
-	print("%lud\n", tstime);
-	return 0;
-}
-
 static void
 usage(void)
 {
@@ -242,225 +42,31 @@
 void
 main(int argc, char **argv)
 {
-	int i, sts;
+	Tm tm;
+	char **f, *fmt;
+	int i;
 
-	sts = 0;
+	fmt = nil;
 	ARGBEGIN{
+	case 'f':
+		fmt = EARGF(usage());
+		break;
 	default:
 		usage();
-	}ARGEND
-	if (argc == 0)
-		usage();
-	for (i = 0; i < argc; i++)
-		sts |= convert(argv[i]);
-	exits(sts != 0? "bad": 0);
+	}ARGEND;
+
+	for(i = 0; i < argc; i++){
+		if(fmt != nil){
+			if(tmparse(&tm, fmt, argv[i], nil) != nil)
+				goto Found;
+		}else{
+			for(f = formats; *f != nil; f++)
+				if(tmparse(&tm, *f, argv[i], nil) != nil)
+					goto Found;
+		}
+		sysfatal("tmparse: %r");
+Found:
+		print("%lld\n", tm.abs);
+	}
+	exits(nil);
 }
-
-/*
- * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
- * is WAY faster than the generic bsearch().
- */
-Datetok *
-datebsearch(char *key, Datetok *base, unsigned nel)
-{
-	int cmp;
-	Datetok *last = base + nel - 1, *pos;
-
-	while (last >= base) {
-		pos = base + ((last - base) >> 1);
-		cmp = key[0] - pos->token[0];
-		if (cmp == 0) {
-			cmp = strncmp(key, pos->token, Maxtok);
-			if (cmp == 0)
-				return pos;
-		}
-		if (cmp < 0)
-			last = pos - 1;
-		else
-			base = pos + 1;
-	}
-	return 0;
-}
-
-Datetok *
-datetoktype(char *s, int *bigvalp)
-{
-	char *cp = s;
-	char c = *cp;
-	static Datetok t;
-	Datetok *tp = &t;
-
-	if (isascii(c) && isdigit(c)) {
-		int len = strlen(cp);
-
-		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
-			tp->type = Timetok;
-		else {
-			if (bigvalp != nil)
-				*bigvalp = atoi(cp); /* won't fit in tp->value */
-			if (len == 4)
-				tp->type = Year;
-			else if (++dtok_numparsed == 1)
-				tp->type = Day;
-			else
-				tp->type = Year;
-		}
-	} else if (c == '-' || c == '+') {
-		int val = atoi(cp + 1);
-		int hr =  val / 100;
-		int min = val % 100;
-
-		val = hr*60 + min;
-		TOVAL(tp, c == '-'? -val: val);
-		tp->type = Tz;
-	} else {
-		char lowtoken[Maxtok+1];
-		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
-
-		/* copy to lowtoken to avoid modifying s */
-		while ((c = *cp++) != '\0' && ltp < endltp)
-			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
-		*ltp = '\0';
-		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
-		if (tp == nil) {
-			tp = &t;
-			tp->type = Ignore;
-		}
-	}
-	return tp;
-}
-
-
-/*
- * to keep this table reasonably small, we divide the lexval for Tz and Dtz
- * entries by 10 and truncate the text field at MAXTOKLEN characters.
- * the text field is not guaranteed to be NUL-terminated.
- */
-static Datetok datetktbl[] = {
-/*	text		token	lexval */
-	"acsst",	Dtz,	63,	/* Cent. Australia */
-	"acst",		Tz,	57,	/* Cent. Australia */
-	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
-	"aesst",	Dtz,	66,	/* E. Australia */
-	"aest",		Tz,	60,	/* Australia Eastern Std Time */
-	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
-	"am",		Ampm,	AM,
-	"apr",		Month,	4,
-	"april",	Month,	4,
-	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
-	"at",		Ignore,	0,	/* "at" (throwaway) */
-	"aug",		Month,	8,
-	"august",	Month,	8,
-	"awsst",	Dtz,	54,	/* W. Australia */
-	"awst",		Tz,	48,	/* W. Australia */
-	"bst",		Tz,	6,	/* British Summer Time */
-	"bt",		Tz,	18,	/* Baghdad Time */
-	"cadt",		Dtz,	63,	/* Central Australian DST */
-	"cast",		Tz,	57,	/* Central Australian ST */
-	"cat",		Tz,	-60,	/* Central Alaska Time */
-	"cct",		Tz,	48,	/* China Coast */
-	"cdt",		Dtz,	-30,	/* Central Daylight Time */
-	"cet",		Tz,	6,	/* Central European Time */
-	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
-	"cst",		Tz,	-36,	/* Central Standard Time */
-	"dec",		Month,	12,
-	"decemb",	Month,	12,
-	"dnt",		Tz,	6,	/* Dansk Normal Tid */
-	"dst",		Ignore,	0,
-	"east",		Tz,	-60,	/* East Australian Std Time */
-	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
-	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
-	"eetdst",	Dtz,	18,	/* Eastern Europe */
-	"est",		Tz,	-30,	/* Eastern Standard Time */
-	"feb",		Month,	2,
-	"februa",	Month,	2,
-	"fri",		Ignore,	5,
-	"friday",	Ignore,	5,
-	"fst",		Tz,	6,	/* French Summer Time */
-	"fwt",		Dtz,	12,	/* French Winter Time  */
-	"gmt",		Tz,	0,	/* Greenwish Mean Time */
-	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
-	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
-	"hmt",		Dtz,	18,	/* Hellas ? ? */
-	"hst",		Tz,	-60,	/* Hawaii Std Time */
-	"idle",		Tz,	72,	/* Intl. Date Line, East */
-	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
-	"ist",		Tz,	12,	/* Israel */
-	"it",		Tz,	22,	/* Iran Time */
-	"jan",		Month,	1,
-	"januar",	Month,	1,
-	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
-	"jt",		Tz,	45,	/* Java Time */
-	"jul",		Month,	7,
-	"july",		Month,	7,
-	"jun",		Month,	6,
-	"june",		Month,	6,
-	"kst",		Tz,	54,	/* Korea Standard Time */
-	"ligt",		Tz,	60,	/* From Melbourne, Australia */
-	"mar",		Month,	3,
-	"march",	Month,	3,
-	"may",		Month,	5,
-	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
-	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
-	"met",		Tz,	6,	/* Middle Europe Time */
-	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
-	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
-	"mez",		Tz,	6,	/* Middle Europe Zone */
-	"mon",		Ignore,	1,
-	"monday",	Ignore,	1,
-	"mst",		Tz,	-42,	/* Mountain Standard Time */
-	"mt",		Tz,	51,	/* Moluccas Time */
-	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
-	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
-	"nor",		Tz,	6,	/* Norway Standard Time */
-	"nov",		Month,	11,
-	"novemb",	Month,	11,
-	"nst",		Tz,	-21,	/* Nfld. Standard Time */
-	"nt",		Tz,	-66,	/* Nome Time */
-	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
-	"nzst",		Tz,	72,	/* New Zealand Standard Time */
-	"nzt",		Tz,	72,	/* New Zealand Time */
-	"oct",		Month,	10,
-	"octobe",	Month,	10,
-	"on",		Ignore,	0,	/* "on" (throwaway) */
-	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
-	"pm",		Ampm,	PM,
-	"pst",		Tz,	-48,	/* Pacific Standard Time */
-	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
-	"sast",		Tz,	57,	/* South Australian Std Time */
-	"sat",		Ignore,	6,
-	"saturd",	Ignore,	6,
-	"sep",		Month,	9,
-	"sept",		Month,	9,
-	"septem",	Month,	9,
-	"set",		Tz,	-6,	/* Seychelles Time ?? */
-	"sst",		Dtz,	12,	/* Swedish Summer Time */
-	"sun",		Ignore,	0,
-	"sunday",	Ignore,	0,
-	"swt",		Tz,	6,	/* Swedish Winter Time  */
-	"thu",		Ignore,	4,
-	"thur",		Ignore,	4,
-	"thurs",	Ignore,	4,
-	"thursd",	Ignore,	4,
-	"tue",		Ignore,	2,
-	"tues",		Ignore,	2,
-	"tuesda",	Ignore,	2,
-	"ut",		Tz,	0,
-	"utc",		Tz,	0,
-	"wadt",		Dtz,	48,	/* West Australian DST */
-	"wast",		Tz,	42,	/* West Australian Std Time */
-	"wat",		Tz,	-6,	/* West Africa Time */
-	"wdt",		Dtz,	54,	/* West Australian DST */
-	"wed",		Ignore,	3,
-	"wednes",	Ignore,	3,
-	"weds",		Ignore,	3,
-	"wet",		Tz,	0,	/* Western Europe */
-	"wetdst",	Dtz,	6,	/* Western Europe */
-	"wst",		Tz,	48,	/* West Australian Std Time */
-	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
-	"yst",		Tz,	-54,	/* Yukon Standard Time */
-	"zp4",		Tz,	-24,	/* GMT +4  hours. */
-	"zp5",		Tz,	-30,	/* GMT +5  hours. */
-	"zp6",		Tz,	-36,	/* GMT +6  hours. */
-};
-static unsigned szdatetktbl = nelem(datetktbl);
diff -r adb80c57a05c sys/src/libc/9sys/mkfile
--- a/sys/src/libc/9sys/mkfile	Fri Jun 05 18:52:22 2020 -0700
+++ b/sys/src/libc/9sys/mkfile	Sat Jun 06 13:56:26 2020 -0700
@@ -10,7 +10,6 @@
 	convM2S.$O\
 	convS2M.$O\
 	cputime.$O\
-	ctime.$O\
 	dial.$O\
 	dirfstat.$O\
 	dirfwstat.$O\
@@ -47,7 +46,6 @@
 	sysname.$O\
 	time.$O\
 	times.$O\
-	tm2sec.$O\
 	truerand.$O\
 	wait.$O\
 	waitpid.$O\
diff -r adb80c57a05c sys/src/libc/port/date.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/libc/port/date.c	Sat Jun 06 13:56:26 2020 -0700
@@ -0,0 +1,872 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Tzabbrev Tzabbrev;
+typedef struct Tzoffpair Tzoffpair;
+
+#define Ctimefmt "W MMM _D hh:mm:ss ZZZ YYYY\n"
+enum {
+	Tzsize		= 150,
+	Nsec		= 1000*1000*1000,
+	Daysec		= (vlong)24*3600,
+	Days400y	= 365*400 + 4*25 - 3,
+	Days4y		= 365*4 + 1,
+};
+
+enum {
+	Cend,
+	Cspace,
+	Cnum,
+	Cletter,
+	Cpunct,
+};
+	
+struct Tzone {
+	char	tzname[16];
+	char	stname[4];
+	char	dlname[4];
+	long	stdiff;
+	long	dldiff;
+	long	dlpairs[Tzsize];
+};
+
+static char tfmt[128];
+static QLock zlock;
+static int nzones;
+static Tzone **zones;
+static int mdays[] = {
+	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+static char *wday[] = {
+	"Sunday","Monday","Tuesday",
+	"Wednesday","Thursday","Friday",
+	"Saturday", nil,
+};
+static char *month[] = {
+	"January", "February", "March",
+	"April", "May", "June", "July",
+	"August", "September", "October",
+	"November", "December", nil
+};
+
+struct Tzabbrev {
+	char *abbr;
+	char *name;
+};
+
+struct Tzoffpair {
+	char *abbr;
+	int off;
+};
+
+/* Obsolete time zone names. Hardcoded to match RFC5322 */
+static Tzabbrev tzabbrev[] = {
+	{"UT", "GMT"}, {"GMT", "GMT"}, {"UTC", "GMT"},
+	{"EST",	"US_Eastern"}, {"EDT", "US_Eastern"},
+	{"CST", "US_Central"}, {"CDT", "US_Central"},
+	{"MST", "US_Mountain"}, {"MDT", "US_Mountain"},
+	{"PST", "US_Pacific"}, {"PDT", "US_Pacific"},
+	{nil},
+};
+
+/* Military timezone names */
+static Tzoffpair milabbrev[] = {
+	{"A", -1*3600},   {"B", -2*3600},   {"C", -3*3600},
+	{"D", -4*3600},   {"E", -5*3600},   {"F", -6*3600},
+	{"G", -7*3600},   {"H", -8*3600},   {"I", -9*3600},
+	{"K", -10*3600},  {"L", -11*3600},  {"M", -12*3600},
+	{"N", +1*3600},   {"O", +2*3600},   {"P", +3*3600},
+	{"Q", +4*3600},   {"R", +5*3600},   {"S", +6*3600},
+	{"T", +7*3600},   {"U", +8*3600},   {"V", +9*3600},
+	{"W", +10*3600},  {"X", +11*3600}, {"Y", +12*3600},
+	{"Z",	0}, {nil, 0}
+};
+
+static int
+isleap(int y)
+{
+	return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
+}
+
+static int
+rdname(char **f, char *p)
+{
+	int c, i;
+
+	while((c = *(*f)++) != 0)
+		if(c != ' ' && c != '\n')
+			break;
+	for(i=0; i<3; i++) {
+		if(c == ' ' || c == '\n')
+			return 1;
+		*p++ = c;
+		c = *(*f)++;
+	}
+	if(c != ' ' && c != '\n')
+		return 1;
+	*p = 0;
+	return 0;
+}
+
+static int
+rdlong(char **f, long *p)
+{
+	int c, s;
+	long l;
+
+	s = 0;
+	while((c = *(*f)++) != 0){
+		if(c == '-')
+			s++;
+		else if(c != ' ' && c != '\n')
+			break;
+	}
+	if(c == 0) {
+		*p = 0;
+		return 0;
+	}
+	l = 0;
+	for(;;) {
+		if(c == ' ' || c == '\n')
+			break;
+		if(c < '0' || c > '9')
+			return 1;
+		l = l*10 + c-'0';
+		c = *(*f)++;
+	}
+	if(s)
+		l = -l;
+	*p = l;
+	return 0;
+}
+
+static int
+loadzone(Tzone *tz, char *name)
+{
+	char buf[Tzsize*11+30], path[128], *p;
+	int i, f, r;
+
+	if(strcmp(name, "local") == 0)
+		snprint(path, sizeof(path), "/env/timezone");
+	else
+		snprint(path, sizeof(path), "/adm/timezone/%s", name);
+	memset(buf, 0, sizeof(buf));
+	if((f = open(path, 0)) == -1)
+		return -1;
+	r = read(f, buf, sizeof(buf));
+	close(f);
+	if(r == sizeof(buf) || r == -1)
+		return -1;
+	p = buf;
+	if(rdname(&p, tz->stname))
+		return -1;
+	if(rdlong(&p, &tz->stdiff))
+		return -1;
+	if(rdname(&p, tz->dlname))
+		return -1;
+	if(rdlong(&p, &tz->dldiff))
+		return -1;
+	for(i=0; i < Tzsize; i++) {
+		if(rdlong(&p, &tz->dlpairs[i]))
+			return -1;
+		if(tz->dlpairs[i] == 0)
+			return 0;
+	}
+	return -1;
+}
+
+Tzone*
+tmgetzone(char *tzname)
+{
+	Tzone *tz, **newzones;
+	int i;
+
+	if(tzname == nil)
+		tzname = "GMT";
+	qlock(&zlock);
+	for(i = 0; i < nzones; i++){
+		tz = zones[i];
+		if(strcmp(tz->stname, tzname) == 0)
+			goto found;
+		if(strcmp(tz->dlname, tzname) == 0)
+			goto found;
+		if(strcmp(tz->tzname, tzname) == 0)
+			goto found;
+	}
+
+	tz = malloc(sizeof(Tzone));
+	if(tz == nil)
+		goto error;
+	newzones = realloc(zones, (nzones + 1) * sizeof(Tzone*));
+	if(newzones == nil)	
+		goto error;
+	if(loadzone(tz, tzname) != 0)
+		goto error;
+	if(snprint(tz->tzname, sizeof(tz->tzname), tzname) >= sizeof(tz->tzname)){
+		werrstr("timezone name too long");
+		return nil;
+	}
+	zones = newzones;
+	zones[nzones] = tz;
+	nzones++;
+found:
+	qunlock(&zlock);
+	return tz;
+error:
+	free(tz);
+	qunlock(&zlock);
+	return nil;
+}
+
+static void
+getzoneoff(Tzone *tz, vlong abs, Tm *tm)
+{
+	long dl, *p;
+	dl = 0;
+	if(tz == nil){
+		snprint(tm->zone, sizeof(tm->zone), "GMT");
+		tm->tzoff = 0;
+		return;
+	}
+	for(p = tz->dlpairs; *p; p += 2)
+		if(abs >= p[0] && abs < p[1])
+			dl = 1;
+	if(dl){
+		snprint(tm->zone, sizeof(tm->zone), tz->dlname);
+		tm->tzoff = tz->dldiff;
+	}else{
+		snprint(tm->zone, sizeof(tm->zone), tz->stname);
+		tm->tzoff = tz->stdiff;
+	}
+}
+
+static Tm*
+tmfill(Tm *tm, vlong abs, vlong nsec)
+{
+	vlong zrel, j, y, m, d, t, e;
+	int i;
+
+	tm->abs = abs;
+	zrel = abs + tm->tzoff;
+	t = zrel % Daysec;
+	e = zrel / Daysec;
+	if(t < 0){
+		t += Daysec;
+		e -= 1;
+	}
+
+	t += nsec/Nsec;
+	tm->sec = t % 60;
+	t /= 60;
+	tm->min = t % 60;
+	t /= 60;
+	tm->hour = t;
+	tm->wday = (e + 4) % 7;
+
+	/*
+	 * Split up year, month, day.
+	 * 
+	 * Implemented according to "Algorithm 199,
+	 * conversions between calendar  date and
+	 * Julian day number", Robert G. Tantzen,
+	 * Air Force Missile Development
+	 * Center, Holloman AFB, New Mex.
+	 * 
+	 * Lots of magic.
+	 */
+	j = (zrel + 2440588 * Daysec) / (Daysec) - 1721119;
+	y = (4 * j - 1) / Days400y;
+	j = 4 * j - 1 - Days400y * y;
+	d = j / 4;
+	j = (4 * d + 3) / Days4y;
+	d = 4 * d + 3 - Days4y * j;
+	d = (d + 4) / 4 ;
+	m = (5 * d - 3) / 153;
+	d = 5 * d - 3 - 153 * m;
+	d = (d + 5) / 5;
+	y = 100 * y + j;
+
+	if(m < 10)
+		m += 3;
+	else{
+		m -= 9;
+		y++;
+	}
+
+	/* there's no year 0 */
+	if(y <= 0)
+		y--;
+	/* and if j negative, the day and month are also negative */
+	if(m < 0)
+		m += 12;
+	if(d < 0)
+		d += mdays[m - 1];
+
+	tm->yday = d;
+	for(i = 0; i < m - 1; i++)
+		tm->yday += mdays[i];
+	if(m > 1 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
+		tm->yday++;
+	tm->year = y - 1900;
+	tm->mon = m - 1;
+	tm->mday = d;
+	tm->nsec = nsec%Nsec;
+	return tm;
+}	
+
+
+Tm*
+tmtime(Tm *tm, vlong abs, Tzone *tz)
+{
+	return tmtimens(tm, abs, 0, tz);
+}
+
+Tm*
+tmtimens(Tm *tm, vlong abs, int ns, Tzone *tz)
+{
+	tm->tz = tz;
+	getzoneoff(tz, abs, tm);
+	return tmfill(tm, abs, ns);
+}
+
+Tm*
+tmnow(Tm *tm, Tzone *tz)
+{
+	vlong ns;
+
+	ns = nsec();
+	return tmtimens(tm, nsec()/Nsec, ns%Nsec, tz);
+}
+
+Tm*
+tmnorm(Tm *tm)
+{
+	vlong c, yadj, j, abs, y, m, d;
+
+	if(tm->mon > 1){
+		m = tm->mon - 2;
+		y = tm->year + 1900;
+	}else{
+		m = tm->mon + 10;
+		y = tm->year - 1901;
+	}
+	d = tm->mday;
+	c = y / 100;
+	yadj = y - 100 * c;
+	j = (c * Days400y / 4 + 
+		Days4y * yadj / 4 +
+		(153 * m + 2)/5 + d -
+		719469);
+	abs = j * Daysec;
+	abs += tm->hour * 3600;
+	abs += tm->min * 60;
+	abs += tm->sec;
+	abs -= tm->tzoff;
+	return tmfill(tm, abs, tm->nsec);
+}
+
+static int
+τconv(Fmt *f)
+{
+	int depth, n, w, h, m, c0, sgn, pad, off;
+	char *p, *am;
+	Tmfmt tf;
+	Tm *tm;
+
+	n = 0;
+	tf = va_arg(f->args, Tmfmt);
+	tm = tf.tm;
+	p = tf.fmt;
+	if(p == nil)
+		p = Ctimefmt;
+	while(*p){
+		w = 1;
+		pad = 0;
+		while(*p == '_'){
+			pad++;
+			p++;
+		}
+		c0 = *p++;
+		while(c0 && *p == c0){
+			w++;
+			p++;
+		}
+		pad += w;
+		switch(c0){
+		case 0:
+			break;
+		case 'Y':
+			switch(w){
+			case 1:	n += fmtprint(f, "%*d", pad, tm->year + 1900);		break;
+			case 2: n += fmtprint(f, "%*d", pad, tm->year % 100);		break;
+			case 4:	n += fmtprint(f, "%*d", pad, tm->year + 1900);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'M':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->mon + 1);		break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->mon + 1);	break;
+			case 3:	n += fmtprint(f, "%*.3s", pad, month[tm->mon]);		break;
+			case 4:	n += fmtprint(f, "%*s", pad, month[tm->mon]);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'D':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->mday);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->mday);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'W':
+			switch(w){
+			case 1:	n += fmtprint(f, "%*.3s", pad, wday[tm->wday]);	break;
+			case 2:	n += fmtprint(f, "%*s", pad, wday[tm->wday]);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'H':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->hour % 12);		break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->hour % 12);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'h':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->hour);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->hour);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'm':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->min);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->min);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 's':
+			switch(w){
+			case 1: n += fmtprint(f, "%*d", pad, tm->sec);			break;
+			case 2:	n += fmtprint(f, "%*s%02d", pad-2, "", tm->sec);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'z':
+			if(w != 1)
+				goto badfmt;
+		case 'Z':
+			sgn = (tm->tzoff < 0) ? '-' : '+';
+			off = (tm->tzoff < 0) ? -tm->tzoff : tm->tzoff;
+			h = off/3600;
+			m = (off/60)%60;
+			if(w < 3 && pad < 5)
+				pad = 5;
+			switch(w){
+			case 1:	n += fmtprint(f, "%*s%c%02d%02d", pad-5, "", sgn, h, m); break;
+			case 2:	n += fmtprint(f, "%*s%c%02d:%02d", pad-5, "", sgn, h, m); break;
+			case 3:	n += fmtprint(f, "%*s", pad, tm->zone);			 break;
+			}
+			break;
+		case 'A':
+		case 'a':
+			if(w != 1)
+				goto badfmt;
+			if(c0 == 'a')
+				am = (tm->hour < 12) ? "am" : "pm";
+			else
+				am = (tm->hour < 12) ? "AM" : "PM";
+			n += fmtprint(f, "%*s", pad, am);
+			break;
+		case '[':
+			depth = 1;
+			while(*p){
+				if(*p == '[')
+					depth++;
+				if(*p == ']')
+					depth--;
+				if(*p == '\\')
+					p++;
+				if(depth == 0)
+					break;
+				fmtrune(f, *p++);
+			}
+			if(*p++ != ']')
+				goto badfmt;
+			break;
+		default:
+			n += fmtrune(f, c0);
+			break;
+		}
+	}
+	return n;
+badfmt:
+	werrstr("garbled format %s", tf.fmt);
+	return -1;			
+}
+
+static int
+getnum(char **ps, int maxw, int *ok)
+{
+	char *s, *e;
+	int n;
+
+	n = 0;
+	e = *ps + maxw;
+	for(s = *ps; s != e && *s >= '0' && *s <= '9'; s++){
+		n *= 10;
+		n += *s - '0';
+	}
+	*ok = s != *ps;
+	*ps = s;
+	return n;
+}
+
+static int
+lookup(char **s, char **tab, int len, int *ok)
+{
+	int nc, i;
+
+	*ok = 0;
+	for(i = 0; *tab; tab++){
+		nc = (len != -1) ? len : strlen(*tab);
+		if(cistrncmp(*s, *tab, nc) == 0){
+			*s += nc;
+			*ok = 1;
+			return i;
+		}
+		i++;
+	}
+	*ok = 0;
+	return -1;
+}
+
+Tm*
+tmparse(Tm *tm, char *fmt, char *str, Tzone *tz)
+{
+	int depth, w, c0, zs, z0, z1, ampm, zoned, sloppy, tzo, ok;
+	char *s, *p, *q;
+	Tzone *zparsed;
+	Tzabbrev *a;
+	Tzoffpair *m;
+
+	p = fmt;
+	s = str;
+	tzo = 0;
+	ampm = -1;
+	zoned = 0;
+	zparsed = nil;
+	sloppy = 0;
+	/* Default all fields */
+	tmtime(tm, 0, nil);
+	if(*p == '~'){
+		sloppy = 1;
+		p++;
+	}
+	while(*p){
+		w = 1;
+		c0 = *p++;
+		if(c0 == '?'){
+			w = -1;
+			c0 = *p++;
+		}
+		while(*p == c0){
+			if(w != -1) w++;
+			p++;
+		}
+		ok = 1;
+		switch(c0){
+		case 'Y':
+			switch(w){
+			case -1:
+				tm->year = getnum(&s, 4, &ok);
+				if(tm->year > 100) tm->year -= 1900;
+				break;
+			case 1:	tm->year = getnum(&s, 4, &ok) - 1900;	break;
+			case 2: tm->year = getnum(&s, 2, &ok);		break;
+			case 3:
+			case 4:	tm->year = getnum(&s, 4, &ok) - 1900;	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'M':
+			switch(w){
+			case -1:
+				tm->mon = getnum(&s, 2, &ok) - 1;
+				if(!ok) tm->mon = lookup(&s, month, -1, &ok);
+				if(!ok) tm->mon = lookup(&s, month, 3, &ok);
+				break;
+			case 1:
+			case 2: tm->mon = getnum(&s, 2, &ok) - 1;	break;
+			case 3:	tm->mon = lookup(&s, month, 3, &ok);	break;
+			case 4:	tm->mon = lookup(&s, month, -1, &ok);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'D':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->mday = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'W':
+			switch(w){
+			case -1:
+				tm->wday = lookup(&s, wday, -1, &ok);
+				if(!ok) tm->wday = lookup(&s, wday, 3, &ok);
+				break;
+			case 1:	tm->wday = lookup(&s, wday, 3, &ok);	break;
+			case 2:	tm->wday = lookup(&s, wday, -1, &ok);	break;
+			default: goto badfmt;
+			}
+			break;
+		case 'h':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->hour = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'm':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->min = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 's':
+			switch(w){
+			case -1:
+			case 1:
+			case 2: tm->sec = getnum(&s, 2, &ok);		break;
+			default: goto badfmt;
+			}
+			break;
+		case 'z':
+			if(w != 1)
+				goto badfmt;
+		case 'Z':
+			zs = 0;
+			zoned = 1;
+			switch(*s++){
+			case '+': zs = 1; break;
+			case '-': zs = -1; break;
+			default: s--; break;
+			}
+			switch(w){
+			case -1:
+			case 3:
+				for(a = tzabbrev; a->abbr; a++)
+					if(strncmp(s, a->abbr, strlen(a->abbr)) == 0)
+						break;
+				if(a->abbr != nil){
+					s += strlen(a->abbr);
+					zparsed = tmgetzone(a->name);
+					if(zparsed == nil){
+						werrstr("unloadable zone %s (%s)", a->abbr, a->name);
+						return nil;
+					}
+					break;
+				}
+				for(m = milabbrev; m->abbr != nil; m++)
+					if(strncmp(s, m->abbr, strlen(m->abbr)) == 0)
+						break;
+				if(m->abbr != nil){
+					snprint(tm->zone, sizeof(tm->zone), "%s", m->abbr);
+					tzo = m->off;
+					break;
+				}
+				/* fall through */
+			case 1:
+				/* offset: [+-]hhmm */
+				q = s;
+				z0 = getnum(&s, 4, &ok);
+				if(s - q == 4){
+					z1 = z0 % 100;
+					if(z0/100 > 13 || z1 >= 60)
+						goto baddate;
+					tzo = zs*(3600*z0/100 + 60*z1);
+					snprint(tm->zone, sizeof(tm->zone), "%c%02d%02d", zs<0?'-':'+', z0/100, z1);
+					break;
+				}
+				if(w != -1)
+					goto baddate;
+				s = q;
+				/* fall through */
+			case 2:
+				/* offset: [+-]hh:mm */
+				z0 = getnum(&s, 2, &ok);
+				if(*s++ != ':')
+					goto baddate;
+				z1 = getnum(&s, 2, &ok);
+				if(z1 > 60)
+					goto baddate;
+				tzo = zs*(3600*z0 + 60*z1);
+				snprint(tm->zone, sizeof(tm->zone), "%c%d02:%02d", zs<0?'-':'+', z0, z1);
+				break;
+			}
+			break;
+		case 'A':
+		case 'a':
+			if(cistrncmp(s, "am", 2) == 0)
+				ampm = 0;
+			else if(cistrncmp(s, "pm", 2) == 0)
+				ampm = 1;
+			else
+				goto baddate;
+			break;
+		case '[':
+			depth = 1;
+			while(*p){
+				if(*p == '[')
+					depth++;
+				if(*p == ']')
+					depth--;
+				if(*p == '\\')
+					p++;
+				if(depth == 0)
+					break;
+				if(*s == 0)
+					goto baddate;
+				if(*s++ != *p++)
+					goto baddate;
+			}
+			if(*p != ']')
+				goto badfmt;
+			p++;
+			break;
+		case ',':
+		case ' ':
+		case '\t':
+			if(*s != ' ' && *s != '\t' && *s != ',')
+				goto baddate;
+			while(*p == ' ' || *p == '\t' || *p == ',')
+				p++;
+			while(*s == ' ' || *s == '\t' || *p == ',')
+				s++;
+			break;
+		default:
+			if(*s == 0)
+				goto baddate;
+			if(*s++ != c0)
+				goto baddate;
+			break;
+		}
+		if(!ok)
+			goto baddate;
+	}
+	
+	if(!sloppy && ampm != -1 && tm->hour > 12)
+		goto baddate;
+	if(ampm == 1)
+		tm->hour += 12;
+	/*
+	 * If we're allowing sloppy date ranges,
+	 * we'll normalize out of range values.
+	 */
+	if(!sloppy){
+		if(tm->yday < 0 && tm->yday > 365 + isleap(tm->year + 1900))
+			goto baddate;
+		if(tm->wday < 0 && tm->wday > 6)
+			goto baddate;
+		if(tm->mon < 0 || tm->mon > 11)
+			goto baddate;
+		if(tm->mday < 0 || tm->mday > mdays[tm->mon])
+			goto baddate;
+		if(tm->hour < 0 || tm->hour > 24)
+			goto baddate;
+		if(tm->min < 0 || tm->min > 59)
+			goto baddate;
+		if(tm->sec < 0 || tm->sec > 60)
+			goto baddate;
+		if(tm->nsec < 0 || tm->nsec > Nsec)
+			goto baddate;
+	}
+
+	/*
+	 * Normalizing gives us the local time,
+	 * but because we havnen't applied the
+	 * timezone, we think we're GMT. So, we
+	 * need to shift backwards. Then, we move
+	 * the "GMT that was local" back to local
+	 * time.
+	 */
+ 	tmnorm(tm);
+	tm->tzoff = tzo;
+	if(!zoned)
+		getzoneoff(tz, tm->abs, tm);
+	else if(zparsed != nil)
+		getzoneoff(zparsed, tm->abs, tm);
+	tm->abs -= tm->tzoff;
+	if(tz != nil || !zoned)
+		tmtime(tm, tm->abs, tz);
+	return tm;
+baddate:
+	werrstr("invalid date %s near '%c'", str, *p);
+	return nil;
+badfmt:
+	werrstr("garbled format %s near '%s'", fmt, p);
+	return nil;			
+}
+
+Tmfmt
+tmfmt(Tm *d, char *fmt)
+{
+	return (Tmfmt){fmt, d};
+}
+
+void
+tmfmtinstall(void)
+{
+	fmtinstall(L'τ', τconv);
+}
+
+/* These legacy functions need access to τconv */
+static char*
+dotmfmt(Fmt *f, ...)
+{
+	static char buf[30];
+	va_list ap;
+
+	va_start(ap, f);
+	f->runes = 0;
+	f->start = buf;
+	f->to = buf;
+	f->stop = buf + sizeof(buf) - 1;
+	f->flush = nil;
+	f->farg = nil;
+	f->nfmt = 0;
+	f->args = ap;
+	τconv(f);
+	va_end(ap);
+	buf[sizeof(buf) - 1] = 0;
+	return buf;
+}
+
+char*
+asctime(Tm* tm)
+{
+	Tmfmt tf;
+	Fmt f;
+
+	tf = tmfmt(tm, nil);
+	return dotmfmt(&f, tf);
+}
+
+char*
+ctime(long abs)
+{
+	Tzone *tz;
+	Tm tm;
+
+	/* No error checking: the API doesn't allow it. */
+	tz = tmgetzone("local");
+	tmtime(&tm, abs, tz);
+	return asctime(&tm);
+}
diff -r adb80c57a05c sys/src/libc/port/gmtime.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sys/src/libc/port/gmtime.c	Sat Jun 06 13:56:26 2020 -0700
@@ -0,0 +1,27 @@
+#include<u.h>
+#include <libc.h>
+
+Tm*
+gmtime(long abs)
+{
+	static Tm tm;
+	return tmtime(&tm, abs, nil);
+}
+
+Tm*
+localtime(long abs)
+{
+	Tzone *tz;
+	static Tm tm;
+
+	/* No error checking: the API doesn't allow it. */
+	tz = tmgetzone("local");
+	return tmtime(&tm, abs, tz);
+}
+
+long
+tm2sec(Tm *tm)
+{
+	tmnorm(tm);
+	return tm->abs;
+}
diff -r adb80c57a05c sys/src/libc/port/mkfile
--- a/sys/src/libc/port/mkfile	Fri Jun 05 18:52:22 2020 -0700
+++ b/sys/src/libc/port/mkfile	Sat Jun 06 13:56:26 2020 -0700
@@ -20,6 +20,7 @@
 	cleanname.c\
 	crypt.c\
 	ctype.c\
+	date.c\
 	encodefmt.c\
 	execl.c\
 	exits.c\
@@ -32,6 +33,7 @@
 	getcallerpc.c\
 	getfields.c\
 	getuser.c\
+	gmtime.c\
 	hangup.c\
 	hypot.c\
 	lnrand.c\



^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [9front] libc: date handling improvements
  2020-05-31  3:39 ori
@ 2020-05-31  4:57 ` ori
  2020-06-06 20:56 ` ori
  2020-06-13 19:40 ` ori
  2 siblings, 0 replies; 5+ messages in thread
From: ori @ 2020-05-31  4:57 UTC (permalink / raw)
  To: ori, 9front

> I implemented libdate -- but the right place to put
> it is in libc, replacing our ctime and gmtime apis.
> 
> Follow ups will replace seconds(1), upas time parsing
> code, and whatever other instances of hand rolled date
> parsing and formatting can be found.
> 
> Here's a patch that does this. Testing appreciated,
> and follow ups that I haven't done yet would also be
> great :)

And here's a rough replacement of seconds(1). Testing
needed, especially since it's likely we no longer parse
some date formats that we used to. The old code was
hacky and heuristic filled, and the grammar wasn't very
clear.

If there's a date format this no longer parses for you,
let me know and I'll add it.

diff -r d45cfe9072cd sys/src/cmd/seconds.c
--- a/sys/src/cmd/seconds.c	Sat May 30 13:03:05 2020 +0200
+++ b/sys/src/cmd/seconds.c	Sat May 30 21:56:13 2020 -0700
@@ -1,237 +1,38 @@
+#include <u.h>
+#include <libc.h>
+#include <date.h>
+
 /*
  * seconds absolute_date ... - convert absolute_date to seconds since epoch
  */
-
-#include <u.h>
-#include <libc.h>
-#include <ctype.h>
-
-typedef ulong Time;
-
-enum {
-	AM, PM, HR24,
-
-	/* token types */
-	Month = 1,
-	Year,
-	Day,
-	Timetok,
-	Tz,
-	Dtz,
-	Ignore,
-	Ampm,
-
-	Maxtok		= 6, /* only this many chars are stored in datetktbl */
-	Maxdateflds	= 25,
+char *formats[] = {
+	/* asctime */
+	"W MMM DD hh:mm:ss ZZZ YYYY",
+	/* RFC5322 */
+	"?W ?DD ?MMM ?YYYY hh:mm:ss ?Z",
+	"?W, DD-?MM-YY hh:mm:ss ?Z",
+	/* RFC822/RFC8222 */
+	"DD MMM YY hh:mm ZZZ",
+	"DD MMM YY hh:mm Z",
+	/* RFC850 */
+	"W, DD-MMM-YY hh:mm:ss MST",
+	/* RFC1123 */
+	"WW, DD MMM YYYY hh:mm:ss ZZZ",
+	/* RFC1123Z */
+	"WW, DD MMM YYYY hh:mm:ss ZZ",
+	/* RFC3339 */
+	"YYYY-MM-DD[T]hh:mm:ss[Z]ZZ",
+	"YYYY-MM-DD[T]hh:mm:ss[Z]Z",
+	"YYYY-MM-DD[T]hh:mm:ss ZZ",
+	"YYYY-MM-DD[T]hh:mm:ss Z",
+	/* RFC 3339 and human-readable variants */
+	"YYYY-MM-DD hh:mm:ss",
+	"YYYY-MM-DD hh:mm:ss ?Z",
+	"YYYY-MM-DD [@] hh:mm:ss",
+	"YYYY-MM-DD [@] hh:mm:ss ?Z",
+	nil
 };
 
-/*
- * macros for squeezing values into low 7 bits of "value".
- * all timezones we care about are divisible by 10, and the largest value
- * (780) when divided is 78.
- */
-#define TOVAL(tp, v)	((tp)->value = (v) / 10)
-#define FROMVAL(tp)	((tp)->value * 10)	/* uncompress */
-
-/* keep this struct small since we have an array of them */
-typedef struct {
-	char	token[Maxtok];
-	char	type;
-	schar	value;
-} Datetok;
-
-int dtok_numparsed;
-
-/* forwards */
-Datetok	*datetoktype(char *s, int *bigvalp);
-
-static Datetok datetktbl[];
-static unsigned szdatetktbl;
-
-/* parse 1- or 2-digit number, advance *cpp past it */
-static int
-eatnum(char **cpp)
-{
-	int c, x;
-	char *cp;
-
-	cp = *cpp;
-	c = *cp;
-	if (!isascii(c) || !isdigit(c))
-		return -1;
-	x = c - '0';
-
-	c = *++cp;
-	if (isascii(c) && isdigit(c)) {
-		x = 10*x + c - '0';
-		cp++;
-	}
-	*cpp = cp;
-	return x;
-}
-
-/* return -1 on failure */
-int
-parsetime(char *time, Tm *tm)
-{
-	tm->hour = eatnum(&time);
-	if (tm->hour == -1 || *time++ != ':')
-		return -1;			/* only hour; too short */
-
-	tm->min = eatnum(&time);
-	if (tm->min == -1)
-		return -1;
-	if (*time++ != ':') {
-		tm->sec = 0;
-		return 0;			/* no seconds; okay */
-	}
-
-	tm->sec = eatnum(&time);
-	if (tm->sec == -1)
-		return -1;
-
-	/* this may be considered too strict.  garbage at end of time? */
-	return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
-}
-
-/*
- * try to parse pre-split timestr in fields as an absolute date
- */
-int
-tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
-{
-	int i, mer = HR24, bigval = -1;
-	long flg = 0, ty;
-	Datetok *tp;
-
-	now = localtime(time(0));	/* default to local time (zone) */
-	tm->tzoff = now->tzoff;
-	strncpy(tm->zone, now->zone, sizeof tm->zone);
-
-	tm->mday = tm->mon = tm->year = -1;	/* mandatory */
-	tm->hour = tm->min = tm->sec = 0;
-	dtok_numparsed = 0;
-
-	for (i = 0; i < nf; i++) {
-		if (fields[i][0] == '\0')
-			continue;
-		tp = datetoktype(fields[i], &bigval);
-		ty = (1L << tp->type) & ~(1L << Ignore);
-		if (flg & ty)
-			return -1;		/* repeated type */
-		flg |= ty;
-		switch (tp->type) {
-		case Year:
-			tm->year = bigval;
-			if (tm->year < 1970 || tm->year > 2106)
-				return -1;	/* can't represent in ulong */
-			/* convert 4-digit year to 1900 origin */
-			if (tm->year >= 1900)
-				tm->year -= 1900;
-			break;
-		case Day:
-			tm->mday = bigval;
-			break;
-		case Month:
-			tm->mon = tp->value - 1; /* convert to zero-origin */
-			break;
-		case Timetok:
-			if (parsetime(fields[i], tm) < 0)
-				return -1;
-			break;
-		case Dtz:
-		case Tz:
-			/* tm2sec mangles timezones, so we do our own handling */
-			tm->tzoff = FROMVAL(tp);
-			snprint(tm->zone, sizeof(tm->zone), "GMT");
-			break;
-		case Ignore:
-			break;
-		case Ampm:
-			mer = tp->value;
-			break;
-		default:
-			return -1;	/* bad token type: CANTHAPPEN */
-		}
-	}
-	if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
-		return -1;		/* missing component */
-	if (mer == PM)
-		tm->hour += 12;
-	return 0;
-}
-
-int
-prsabsdate(char *timestr, Tm *now, Tm *tm)
-{
-	int nf;
-	char *fields[Maxdateflds];
-	static char delims[] = "- \t\n/,";
-
-	nf = gettokens(timestr, fields, nelem(fields), delims+1);
-	if (nf > nelem(fields))
-		return -1;
-	if (tryabsdate(fields, nf, now, tm) < 0) {
-		char *p = timestr;
-
-		/*
-		 * could be a DEC-date; glue it all back together, split it
-		 * with dash as a delimiter and try again.  Yes, this is a
-		 * hack, but so are DEC-dates.
-		 */
-		while (--nf > 0) {
-			while (*p++ != '\0')
-				;
-			p[-1] = ' ';
-		}
-		nf = gettokens(timestr, fields, nelem(fields), delims);
-		if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
-			return -1;
-	}
-	return 0;
-}
-
-int
-validtm(Tm *tm)
-{
-	if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
-	    tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
-	    tm->min < 0 || tm->min > 59 ||
-	    tm->sec < 0 || tm->sec > 61)	/* allow 2 leap seconds */
-		return 0;
-	return 1;
-}
-
-Time
-seconds(char *timestr)
-{
-	Tm date;
-
-	memset(&date, 0, sizeof date);
-	if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
-		return -1;
-	return validtm(&date)? tm2sec(&date) - 60*date.tzoff: -1;
-}
-
-int
-convert(char *timestr)
-{
-	char *copy;
-	Time tstime;
-
-	copy = strdup(timestr);
-	if (copy == nil)
-		sysfatal("out of memory");
-	tstime = seconds(copy);
-	free(copy);
-	if (tstime == -1) {
-		fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
-		return 1;
-	}
-	print("%lud\n", tstime);
-	return 0;
-}
-
 static void
 usage(void)
 {
@@ -242,225 +43,31 @@
 void
 main(int argc, char **argv)
 {
-	int i, sts;
+	Tmd tm;
+	char **f, *fmt;
+	int i;
 
-	sts = 0;
+	fmt = nil;
 	ARGBEGIN{
+	case 'f':
+		fmt = EARGF(usage());
+		break;
 	default:
 		usage();
-	}ARGEND
-	if (argc == 0)
-		usage();
-	for (i = 0; i < argc; i++)
-		sts |= convert(argv[i]);
-	exits(sts != 0? "bad": 0);
+	}ARGEND;
+
+	for(i = 0; i < argc; i++){
+		if(fmt != nil){
+			if(tmparse(fmt, argv[i], nil, &tm) != nil)
+				goto Found;
+		}else{
+			for(f = formats; *f != nil; f++)
+				if(tmparse(&tm, *f, argv[i], nil) != nil)
+					goto Found;
+		}
+		sysfatal("tmparse: %r");
+Found:
+		print("%lld\n", tm.abs);
+	}
+	exits(nil);
 }
-
-/*
- * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
- * is WAY faster than the generic bsearch().
- */
-Datetok *
-datebsearch(char *key, Datetok *base, unsigned nel)
-{
-	int cmp;
-	Datetok *last = base + nel - 1, *pos;
-
-	while (last >= base) {
-		pos = base + ((last - base) >> 1);
-		cmp = key[0] - pos->token[0];
-		if (cmp == 0) {
-			cmp = strncmp(key, pos->token, Maxtok);
-			if (cmp == 0)
-				return pos;
-		}
-		if (cmp < 0)
-			last = pos - 1;
-		else
-			base = pos + 1;
-	}
-	return 0;
-}
-
-Datetok *
-datetoktype(char *s, int *bigvalp)
-{
-	char *cp = s;
-	char c = *cp;
-	static Datetok t;
-	Datetok *tp = &t;
-
-	if (isascii(c) && isdigit(c)) {
-		int len = strlen(cp);
-
-		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
-			tp->type = Timetok;
-		else {
-			if (bigvalp != nil)
-				*bigvalp = atoi(cp); /* won't fit in tp->value */
-			if (len == 4)
-				tp->type = Year;
-			else if (++dtok_numparsed == 1)
-				tp->type = Day;
-			else
-				tp->type = Year;
-		}
-	} else if (c == '-' || c == '+') {
-		int val = atoi(cp + 1);
-		int hr =  val / 100;
-		int min = val % 100;
-
-		val = hr*60 + min;
-		TOVAL(tp, c == '-'? -val: val);
-		tp->type = Tz;
-	} else {
-		char lowtoken[Maxtok+1];
-		char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
-
-		/* copy to lowtoken to avoid modifying s */
-		while ((c = *cp++) != '\0' && ltp < endltp)
-			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
-		*ltp = '\0';
-		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
-		if (tp == nil) {
-			tp = &t;
-			tp->type = Ignore;
-		}
-	}
-	return tp;
-}
-
-
-/*
- * to keep this table reasonably small, we divide the lexval for Tz and Dtz
- * entries by 10 and truncate the text field at MAXTOKLEN characters.
- * the text field is not guaranteed to be NUL-terminated.
- */
-static Datetok datetktbl[] = {
-/*	text		token	lexval */
-	"acsst",	Dtz,	63,	/* Cent. Australia */
-	"acst",		Tz,	57,	/* Cent. Australia */
-	"adt",		Dtz,	-18,	/* Atlantic Daylight Time */
-	"aesst",	Dtz,	66,	/* E. Australia */
-	"aest",		Tz,	60,	/* Australia Eastern Std Time */
-	"ahst",		Tz,	60,	/* Alaska-Hawaii Std Time */
-	"am",		Ampm,	AM,
-	"apr",		Month,	4,
-	"april",	Month,	4,
-	"ast",		Tz,	-24,	/* Atlantic Std Time (Canada) */
-	"at",		Ignore,	0,	/* "at" (throwaway) */
-	"aug",		Month,	8,
-	"august",	Month,	8,
-	"awsst",	Dtz,	54,	/* W. Australia */
-	"awst",		Tz,	48,	/* W. Australia */
-	"bst",		Tz,	6,	/* British Summer Time */
-	"bt",		Tz,	18,	/* Baghdad Time */
-	"cadt",		Dtz,	63,	/* Central Australian DST */
-	"cast",		Tz,	57,	/* Central Australian ST */
-	"cat",		Tz,	-60,	/* Central Alaska Time */
-	"cct",		Tz,	48,	/* China Coast */
-	"cdt",		Dtz,	-30,	/* Central Daylight Time */
-	"cet",		Tz,	6,	/* Central European Time */
-	"cetdst",	Dtz,	12,	/* Central European Dayl.Time */
-	"cst",		Tz,	-36,	/* Central Standard Time */
-	"dec",		Month,	12,
-	"decemb",	Month,	12,
-	"dnt",		Tz,	6,	/* Dansk Normal Tid */
-	"dst",		Ignore,	0,
-	"east",		Tz,	-60,	/* East Australian Std Time */
-	"edt",		Dtz,	-24,	/* Eastern Daylight Time */
-	"eet",		Tz,	12,	/* East. Europe, USSR Zone 1 */
-	"eetdst",	Dtz,	18,	/* Eastern Europe */
-	"est",		Tz,	-30,	/* Eastern Standard Time */
-	"feb",		Month,	2,
-	"februa",	Month,	2,
-	"fri",		Ignore,	5,
-	"friday",	Ignore,	5,
-	"fst",		Tz,	6,	/* French Summer Time */
-	"fwt",		Dtz,	12,	/* French Winter Time  */
-	"gmt",		Tz,	0,	/* Greenwish Mean Time */
-	"gst",		Tz,	60,	/* Guam Std Time, USSR Zone 9 */
-	"hdt",		Dtz,	-54,	/* Hawaii/Alaska */
-	"hmt",		Dtz,	18,	/* Hellas ? ? */
-	"hst",		Tz,	-60,	/* Hawaii Std Time */
-	"idle",		Tz,	72,	/* Intl. Date Line, East */
-	"idlw",		Tz,	-72,	/* Intl. Date Line, West */
-	"ist",		Tz,	12,	/* Israel */
-	"it",		Tz,	22,	/* Iran Time */
-	"jan",		Month,	1,
-	"januar",	Month,	1,
-	"jst",		Tz,	54,	/* Japan Std Time,USSR Zone 8 */
-	"jt",		Tz,	45,	/* Java Time */
-	"jul",		Month,	7,
-	"july",		Month,	7,
-	"jun",		Month,	6,
-	"june",		Month,	6,
-	"kst",		Tz,	54,	/* Korea Standard Time */
-	"ligt",		Tz,	60,	/* From Melbourne, Australia */
-	"mar",		Month,	3,
-	"march",	Month,	3,
-	"may",		Month,	5,
-	"mdt",		Dtz,	-36,	/* Mountain Daylight Time */
-	"mest",		Dtz,	12,	/* Middle Europe Summer Time */
-	"met",		Tz,	6,	/* Middle Europe Time */
-	"metdst",	Dtz,	12,	/* Middle Europe Daylight Time*/
-	"mewt",		Tz,	6,	/* Middle Europe Winter Time */
-	"mez",		Tz,	6,	/* Middle Europe Zone */
-	"mon",		Ignore,	1,
-	"monday",	Ignore,	1,
-	"mst",		Tz,	-42,	/* Mountain Standard Time */
-	"mt",		Tz,	51,	/* Moluccas Time */
-	"ndt",		Dtz,	-15,	/* Nfld. Daylight Time */
-	"nft",		Tz,	-21,	/* Newfoundland Standard Time */
-	"nor",		Tz,	6,	/* Norway Standard Time */
-	"nov",		Month,	11,
-	"novemb",	Month,	11,
-	"nst",		Tz,	-21,	/* Nfld. Standard Time */
-	"nt",		Tz,	-66,	/* Nome Time */
-	"nzdt",		Dtz,	78,	/* New Zealand Daylight Time */
-	"nzst",		Tz,	72,	/* New Zealand Standard Time */
-	"nzt",		Tz,	72,	/* New Zealand Time */
-	"oct",		Month,	10,
-	"octobe",	Month,	10,
-	"on",		Ignore,	0,	/* "on" (throwaway) */
-	"pdt",		Dtz,	-42,	/* Pacific Daylight Time */
-	"pm",		Ampm,	PM,
-	"pst",		Tz,	-48,	/* Pacific Standard Time */
-	"sadt",		Dtz,	63,	/* S. Australian Dayl. Time */
-	"sast",		Tz,	57,	/* South Australian Std Time */
-	"sat",		Ignore,	6,
-	"saturd",	Ignore,	6,
-	"sep",		Month,	9,
-	"sept",		Month,	9,
-	"septem",	Month,	9,
-	"set",		Tz,	-6,	/* Seychelles Time ?? */
-	"sst",		Dtz,	12,	/* Swedish Summer Time */
-	"sun",		Ignore,	0,
-	"sunday",	Ignore,	0,
-	"swt",		Tz,	6,	/* Swedish Winter Time  */
-	"thu",		Ignore,	4,
-	"thur",		Ignore,	4,
-	"thurs",	Ignore,	4,
-	"thursd",	Ignore,	4,
-	"tue",		Ignore,	2,
-	"tues",		Ignore,	2,
-	"tuesda",	Ignore,	2,
-	"ut",		Tz,	0,
-	"utc",		Tz,	0,
-	"wadt",		Dtz,	48,	/* West Australian DST */
-	"wast",		Tz,	42,	/* West Australian Std Time */
-	"wat",		Tz,	-6,	/* West Africa Time */
-	"wdt",		Dtz,	54,	/* West Australian DST */
-	"wed",		Ignore,	3,
-	"wednes",	Ignore,	3,
-	"weds",		Ignore,	3,
-	"wet",		Tz,	0,	/* Western Europe */
-	"wetdst",	Dtz,	6,	/* Western Europe */
-	"wst",		Tz,	48,	/* West Australian Std Time */
-	"ydt",		Dtz,	-48,	/* Yukon Daylight Time */
-	"yst",		Tz,	-54,	/* Yukon Standard Time */
-	"zp4",		Tz,	-24,	/* GMT +4  hours. */
-	"zp5",		Tz,	-30,	/* GMT +5  hours. */
-	"zp6",		Tz,	-36,	/* GMT +6  hours. */
-};
-static unsigned szdatetktbl = nelem(datetktbl);



^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-07-20 15:04 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-06-15  6:03 [9front] libc: date handling improvements ori
  -- strict thread matches above, loose matches on Subject: below --
2020-05-31  3:39 ori
2020-05-31  4:57 ` [9front] " ori
2020-06-06 20:56 ` ori
2020-06-13 19:40 ` ori
2020-06-15  6:02   ` ori

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).