]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/seconds.c
merge
[plan9front.git] / sys / src / cmd / seconds.c
index 69e243301a0e8e70682e54ba83d0d9bb484724ae..c30330ede6da70867f5d0c953a3c9e10cace6f5d 100644 (file)
-/*
- * 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,
+char *knownfmt[] = {
+       /* asctime */
+       "WW 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 */
-
-/* keep this struct small since we have an array of them */
-typedef struct {
-       char    token[Maxtok];
-       char    type;
-       schar   value;
-} Datetok;
-
-int dtok_numparsed;
+char *datefmt[] = {
+       /* RFC5322 */
+       "?W ?DD ?MMM ?YYYY",
+       "?W, DD-?MM-YY",
+       /* RFC822/RFC2822 */
+       "DD MMM YYYY",
+       "DD MMM YY",
+       /* RFC850 */
+       "WW, DD-MMM-YY",
+       /* RFC1123 */
+       "WWW, 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
+};
 
-/* forwards */
-Datetok        *datetoktype(char *s, int *bigvalp);
+char *timefmt[] = {
+       " hh:mm:ss",
+       " hh:mm",
+       " hh",
+       " hh:mm:ss ?A",
+       " hh:mm ?A",
+       " hh ?A",
+       "",
+       nil,
+};
 
-static Datetok datetktbl[];
-static unsigned szdatetktbl;
+char *zonefmt[] = {
+       " ?Z",
+       "",
+       nil,
+};
 
-/* 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)
+nojunk(char *p)
 {
-       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);
+       while(isspace(*p))
+               p++;
+       if(*p == '\0')
                return 1;
-       }
-       print("%lud\n", tstime);
+       werrstr("trailing junk");
        return 0;
 }
 
 static void
 usage(void)
 {
-       fprint(2, "usage: %s date-time ...\n", argv0);
+       fprint(2, "usage: %s [-f fmt] date-time...\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);
-}
-
-/*
- * 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;
+       }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) && nojunk(ep))
+                               goto Found;
+               }else{
+                       for(f = knownfmt; *f != nil; f++)
+                               if(tmparse(&tm, *f, argv[i], tz, &ep) != nil && nojunk(ep))
+                                       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 && nojunk(ep))
+                                       goto Found;
+                       }
                }
+               sysfatal("tmparse: %r");
+Found:
+               print("%lld\n", tmnorm(&tm));
        }
-       return tp;
+       exits(nil);
 }
-
-
-/*
- * 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);