9front - general discussion about 9front
 help / color / mirror / Atom feed
From: ori@eigenstate.org
To: ori@eigenstate.org, 9front@9front.org
Subject: Re: [9front] libc: date handling improvements
Date: Sun Jun 14 23:03:28 PDT 2020	[thread overview]
Message-ID: <BBADDAB4CED66A16CA14D7B468D4D9D0@eigenstate.org> (raw)

[-- 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;
 }

             reply	other threads:[~2020-07-20 15:04 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-06-15  6:03 ori [this message]
  -- 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=BBADDAB4CED66A16CA14D7B468D4D9D0@eigenstate.org \
    --to=ori@eigenstate.org \
    --cc=9front@9front.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).