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);