summaryrefslogtreecommitdiff
path: root/sys/src/cmd/seconds.c
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@felloff.net>2020-06-15 00:12:57 +0200
committercinap_lenrek <cinap_lenrek@felloff.net>2020-06-15 00:12:57 +0200
commitfebe84af755d8b6941c6e59c1b350f889c4fccb0 (patch)
tree45cb13daa2c1ef116613a7e461e0c59fda1d5bd6 /sys/src/cmd/seconds.c
parent8b3efcfc4e3b38eab3f0ff503e573c072ff890f5 (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.c492
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);