diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2020-06-15 00:12:57 +0200 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2020-06-15 00:12:57 +0200 |
commit | febe84af755d8b6941c6e59c1b350f889c4fccb0 (patch) | |
tree | 45cb13daa2c1ef116613a7e461e0c59fda1d5bd6 /sys/src/cmd/seconds.c | |
parent | 8b3efcfc4e3b38eab3f0ff503e573c072ff890f5 (diff) |
libc: revert date change again. this is getting ridicuoulus.
this breaks the sample from the seconds manpage, and overall
produces funky results. this needs alot more testing.
term% seconds '23 may 2011'
seconds: tmparse: invalid date 23 may 2011 near 'may 2011'
term% seconds '2019-01-01 00:00:00'
-118370073600
Diffstat (limited to 'sys/src/cmd/seconds.c')
-rw-r--r-- | sys/src/cmd/seconds.c | 492 |
1 files changed, 443 insertions, 49 deletions
diff --git a/sys/src/cmd/seconds.c b/sys/src/cmd/seconds.c index 01d481ee4..69e243301 100644 --- a/sys/src/cmd/seconds.c +++ b/sys/src/cmd/seconds.c @@ -1,36 +1,236 @@ +/* + * 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, +}; /* - * seconds absolute_date ... - convert absolute_date to seconds since epoch + * 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. */ -char *formats[] = { - /* asctime */ - "W MMM DD hh:mm:ss ?Z YYYY", - /* RFC5322 */ - "?W ?DD ?MMM ?YYYY hh:mm:ss ?Z", - "?W, DD-?MM-YY hh:mm:ss ?Z", - /* RFC822/RFC2822 */ - "DD MMM YY hh:mm ZZZ", - "DD MMM YY hh:mm Z", - /* RFC850 */ - "W, DD-MMM-YY hh:mm:ss MST", - /* RFC1123 */ - "WW, DD MMM YYYY hh:mm:ss ZZZ", - /* RFC1123Z */ - "WW, DD MMM YYYY hh:mm:ss ZZ", - /* RFC3339 */ - "YYYY-MM-DD[T]hh:mm:ss[Z]ZZ", - "YYYY-MM-DD[T]hh:mm:ss[Z]Z", - "YYYY-MM-DD[T]hh:mm:ss ZZ", - "YYYY-MM-DD[T]hh:mm:ss Z", - /* RFC 3339 and human-readable variants */ - "YYYY-MM-DD hh:mm:ss", - "YYYY-MM-DD hh:mm:ss ?Z", - "YYYY-MM-DD [@] hh:mm:ss", - "YYYY-MM-DD [@] hh:mm:ss ?Z", - nil -}; +#define TOVAL(tp, v) ((tp)->value = (v) / 10) +#define FROMVAL(tp) ((tp)->value * 10) /* uncompress */ + +/* keep this struct small since we have an array of them */ +typedef struct { + char token[Maxtok]; + char type; + schar value; +} Datetok; + +int dtok_numparsed; + +/* forwards */ +Datetok *datetoktype(char *s, int *bigvalp); + +static Datetok datetktbl[]; +static unsigned szdatetktbl; + +/* parse 1- or 2-digit number, advance *cpp past it */ +static int +eatnum(char **cpp) +{ + int c, x; + char *cp; + + cp = *cpp; + c = *cp; + if (!isascii(c) || !isdigit(c)) + return -1; + x = c - '0'; + + c = *++cp; + if (isascii(c) && isdigit(c)) { + x = 10*x + c - '0'; + cp++; + } + *cpp = cp; + return x; +} + +/* return -1 on failure */ +int +parsetime(char *time, Tm *tm) +{ + tm->hour = eatnum(&time); + if (tm->hour == -1 || *time++ != ':') + return -1; /* only hour; too short */ + + tm->min = eatnum(&time); + if (tm->min == -1) + return -1; + if (*time++ != ':') { + tm->sec = 0; + return 0; /* no seconds; okay */ + } + + tm->sec = eatnum(&time); + if (tm->sec == -1) + return -1; + + /* this may be considered too strict. garbage at end of time? */ + return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1; +} + +/* + * try to parse pre-split timestr in fields as an absolute date + */ +int +tryabsdate(char **fields, int nf, Tm *now, Tm *tm) +{ + int i, mer = HR24, bigval = -1; + long flg = 0, ty; + Datetok *tp; + + now = localtime(time(0)); /* default to local time (zone) */ + tm->tzoff = now->tzoff; + strncpy(tm->zone, now->zone, sizeof tm->zone); + + tm->mday = tm->mon = tm->year = -1; /* mandatory */ + tm->hour = tm->min = tm->sec = 0; + dtok_numparsed = 0; + + for (i = 0; i < nf; i++) { + if (fields[i][0] == '\0') + continue; + tp = datetoktype(fields[i], &bigval); + ty = (1L << tp->type) & ~(1L << Ignore); + if (flg & ty) + return -1; /* repeated type */ + flg |= ty; + switch (tp->type) { + case Year: + tm->year = bigval; + if (tm->year < 1970 || tm->year > 2106) + return -1; /* can't represent in ulong */ + /* convert 4-digit year to 1900 origin */ + if (tm->year >= 1900) + tm->year -= 1900; + break; + case Day: + tm->mday = bigval; + break; + case Month: + tm->mon = tp->value - 1; /* convert to zero-origin */ + break; + case Timetok: + if (parsetime(fields[i], tm) < 0) + return -1; + break; + case Dtz: + case Tz: + /* tm2sec mangles timezones, so we do our own handling */ + tm->tzoff = FROMVAL(tp); + snprint(tm->zone, sizeof(tm->zone), "GMT"); + break; + case Ignore: + break; + case Ampm: + mer = tp->value; + break; + default: + return -1; /* bad token type: CANTHAPPEN */ + } + } + if (tm->year == -1 || tm->mon == -1 || tm->mday == -1) + return -1; /* missing component */ + if (mer == PM) + tm->hour += 12; + return 0; +} + +int +prsabsdate(char *timestr, Tm *now, Tm *tm) +{ + int nf; + char *fields[Maxdateflds]; + static char delims[] = "- \t\n/,"; + + nf = gettokens(timestr, fields, nelem(fields), delims+1); + if (nf > nelem(fields)) + return -1; + if (tryabsdate(fields, nf, now, tm) < 0) { + char *p = timestr; + + /* + * could be a DEC-date; glue it all back together, split it + * with dash as a delimiter and try again. Yes, this is a + * hack, but so are DEC-dates. + */ + while (--nf > 0) { + while (*p++ != '\0') + ; + p[-1] = ' '; + } + nf = gettokens(timestr, fields, nelem(fields), delims); + if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0) + return -1; + } + return 0; +} + +int +validtm(Tm *tm) +{ + if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 || + tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 || + tm->min < 0 || tm->min > 59 || + tm->sec < 0 || tm->sec > 61) /* allow 2 leap seconds */ + return 0; + return 1; +} + +Time +seconds(char *timestr) +{ + Tm date; + + memset(&date, 0, sizeof date); + if (prsabsdate(timestr, localtime(time(0)), &date) < 0) + return -1; + return validtm(&date)? tm2sec(&date) - 60*date.tzoff: -1; +} + +int +convert(char *timestr) +{ + char *copy; + Time tstime; + + copy = strdup(timestr); + if (copy == nil) + sysfatal("out of memory"); + tstime = seconds(copy); + free(copy); + if (tstime == -1) { + fprint(2, "%s: `%s' not a valid date\n", argv0, timestr); + return 1; + } + print("%lud\n", tstime); + return 0; +} static void usage(void) @@ -42,31 +242,225 @@ usage(void) void main(int argc, char **argv) { - Tm tm; - char **f, *fmt; - int i; + int i, sts; - fmt = nil; + sts = 0; ARGBEGIN{ - case 'f': - fmt = EARGF(usage()); - break; default: usage(); - }ARGEND; - - for(i = 0; i < argc; i++){ - if(fmt != nil){ - if(tmparse(&tm, fmt, argv[i], nil) != nil) - goto Found; - }else{ - for(f = formats; *f != nil; f++) - if(tmparse(&tm, *f, argv[i], nil) != nil) - goto Found; + }ARGEND + if (argc == 0) + usage(); + for (i = 0; i < argc; i++) + sts |= convert(argv[i]); + exits(sts != 0? "bad": 0); +} + +/* + * 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; } - sysfatal("tmparse: %r"); -Found: - print("%lld\n", tm.abs); + if (cmp < 0) + last = pos - 1; + else + base = pos + 1; } - exits(nil); + 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); |