123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- /*
- * This file is part of the UCB release of Plan 9. It is subject to the license
- * terms in the LICENSE file found in the top-level directory of this
- * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
- * part of the UCB release of Plan 9, including this file, may be copied,
- * modified, propagated, or distributed except according to the terms contained
- * in the LICENSE file.
- */
- /*
- * seconds absolute_date ... - convert absolute_date to seconds since epoch
- */
- #include <u.h>
- #include <libc.h>
- #include <ctype.h>
- typedef uint32_t 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,
- };
- /*
- * 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 */
- /* keep this struct small since we have an array of them */
- typedef struct {
- char token[Maxtok];
- char type;
- uint8_t 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;
- int32_t flg = 0, ty;
- char *p;
- Datetok *tp;
- now = localtime(time(0)); /* default to local time (zone) */
- tm->tzoff = now->tzoff;
- strncpy(tm->zone, now->zone, sizeof tm->zone - 1);
- tm->zone[sizeof tm->zone - 1] = '\0';
- 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 uint32_t */
- /* 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:
- tm->tzoff = FROMVAL(tp);
- /* tm2sec needs the name in upper case */
- strncpy(tm->zone, fields[i], sizeof tm->zone - 1);
- tm->zone[sizeof tm->zone - 1] = '\0';
- for (p = tm->zone; *p; p++)
- if (isascii(*p) && islower(*p))
- *p = toupper(*p);
- 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): -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("%lu\n", tstime);
- return 0;
- }
- static void
- usage(void)
- {
- fprint(2, "usage: %s date-time ...\n", argv0);
- exits("usage");
- }
- void
- main(int argc, char **argv)
- {
- int i, sts;
- sts = 0;
- ARGBEGIN{
- default:
- usage();
- }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;
- }
- 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);
|