From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mimir.eigenstate.org ([206.124.132.107]) by ewsd; Mon Jul 20 11:04:47 EDT 2020 Received: from abbatoir.fios-router.home (pool-74-101-2-6.nycmny.fios.verizon.net [74.101.2.6]) by mimir.eigenstate.org (OpenSMTPD) with ESMTPSA id 03b77a8a (TLSv1.2:ECDHE-RSA-AES256-SHA:256:NO); Mon, 20 Jul 2020 08:04:31 -0700 (PDT) Message-ID: To: ori@eigenstate.org, 9front@9front.org Date: Sun Jun 14 23:03:28 PDT 2020 Subject: Re: [9front] libc: date handling improvements From: ori@eigenstate.org MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="upas-vifcjtzphvlspuipkzlambctcz" List-ID: <9front.9front.org> List-Help: X-Glyph: ➈ X-Bullshit: encrypted stateless CMS-scale scripting-based realtime-oriented cloud framework This is a multi-part message in MIME format. --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit >> 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. --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit 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 #include -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 '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 #include -#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; imon; 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 '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 +#include + +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\ --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit 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 #include -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); } --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit 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 #include -#include -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); --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit 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 - -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); -} --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit 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 - -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 #include -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[] = --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit 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 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; } /* --upas-vifcjtzphvlspuipkzlambctcz Content-Disposition: inline Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: 8bit 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; } --upas-vifcjtzphvlspuipkzlambctcz--