2 * seconds absolute_date ... - convert absolute_date to seconds since epoch
24 Maxtok = 6, /* only this many chars are stored in datetktbl */
29 * macros for squeezing values into low 7 bits of "value".
30 * all timezones we care about are divisible by 10, and the largest value
31 * (780) when divided is 78.
33 #define TOVAL(tp, v) ((tp)->value = (v) / 10)
34 #define FROMVAL(tp) ((tp)->value * 10) /* uncompress */
36 /* keep this struct small since we have an array of them */
46 Datetok *datetoktype(char *s, int *bigvalp);
48 static Datetok datetktbl[];
49 static unsigned szdatetktbl;
51 /* parse 1- or 2-digit number, advance *cpp past it */
60 if (!isascii(c) || !isdigit(c))
65 if (isascii(c) && isdigit(c)) {
73 /* return -1 on failure */
75 parsetime(char *time, Tm *tm)
77 tm->hour = eatnum(&time);
78 if (tm->hour == -1 || *time++ != ':')
79 return -1; /* only hour; too short */
81 tm->min = eatnum(&time);
86 return 0; /* no seconds; okay */
89 tm->sec = eatnum(&time);
93 /* this may be considered too strict. garbage at end of time? */
94 return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
98 * try to parse pre-split timestr in fields as an absolute date
101 tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
103 int i, mer = HR24, bigval = -1;
109 now = localtime(time(0)); /* default to local time (zone) */
110 tm->tzoff = now->tzoff;
111 strncpy(tm->zone, now->zone, sizeof tm->zone);
113 tm->mday = tm->mon = tm->year = -1; /* mandatory */
114 tm->hour = tm->min = tm->sec = 0;
117 for (i = 0; i < nf; i++) {
118 if (fields[i][0] == '\0')
120 tp = datetoktype(fields[i], &bigval);
121 ty = (1L << tp->type) & ~(1L << Ignore);
123 return -1; /* repeated type */
128 if (tm->year < 1970 || tm->year > 2106)
129 return -1; /* can't represent in ulong */
130 /* convert 4-digit year to 1900 origin */
131 if (tm->year >= 1900)
138 tm->mon = tp->value - 1; /* convert to zero-origin */
141 if (parsetime(fields[i], tm) < 0)
146 tm->tzoff = FROMVAL(tp);
147 /* tm2sec needs the name in upper case */
148 strcpy(upzone, fields[i]);
149 for (p = upzone; *p; p++)
150 if (isascii(*p) && islower(*p))
152 strncpy(tm->zone, upzone, sizeof tm->zone);
160 return -1; /* bad token type: CANTHAPPEN */
163 if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
164 return -1; /* missing component */
171 prsabsdate(char *timestr, Tm *now, Tm *tm)
174 char *fields[Maxdateflds];
175 static char delims[] = "- \t\n/,";
177 nf = gettokens(timestr, fields, nelem(fields), delims+1);
178 if (nf > nelem(fields))
180 if (tryabsdate(fields, nf, now, tm) < 0) {
184 * could be a DEC-date; glue it all back together, split it
185 * with dash as a delimiter and try again. Yes, this is a
186 * hack, but so are DEC-dates.
193 nf = gettokens(timestr, fields, nelem(fields), delims);
194 if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
203 if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
204 tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
205 tm->min < 0 || tm->min > 59 ||
206 tm->sec < 0 || tm->sec > 61) /* allow 2 leap seconds */
212 seconds(char *timestr)
216 memset(&date, 0, sizeof date);
217 if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
219 return validtm(&date)? tm2sec(&date): -1;
223 convert(char *timestr)
228 copy = strdup(timestr);
230 sysfatal("out of memory");
231 tstime = seconds(copy);
234 fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
237 print("%lud\n", tstime);
244 fprint(2, "usage: %s date-time ...\n", argv0);
249 main(int argc, char **argv)
260 for (i = 0; i < argc; i++)
261 sts |= convert(argv[i]);
262 exits(sts != 0? "bad": 0);
266 * Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this
267 * is WAY faster than the generic bsearch().
270 datebsearch(char *key, Datetok *base, unsigned nel)
273 Datetok *last = base + nel - 1, *pos;
275 while (last >= base) {
276 pos = base + ((last - base) >> 1);
277 cmp = key[0] - pos->token[0];
279 cmp = strncmp(key, pos->token, Maxtok);
292 datetoktype(char *s, int *bigvalp)
299 if (isascii(c) && isdigit(c)) {
300 int len = strlen(cp);
302 if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
306 *bigvalp = atoi(cp); /* won't fit in tp->value */
309 else if (++dtok_numparsed == 1)
314 } else if (c == '-' || c == '+') {
315 int val = atoi(cp + 1);
320 TOVAL(tp, c == '-'? -val: val);
323 char lowtoken[Maxtok+1];
324 char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
326 /* copy to lowtoken to avoid modifying s */
327 while ((c = *cp++) != '\0' && ltp < endltp)
328 *ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
330 tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
341 * to keep this table reasonably small, we divide the lexval for Tz and Dtz
342 * entries by 10 and truncate the text field at MAXTOKLEN characters.
343 * the text field is not guaranteed to be NUL-terminated.
345 static Datetok datetktbl[] = {
346 /* text token lexval */
347 "acsst", Dtz, 63, /* Cent. Australia */
348 "acst", Tz, 57, /* Cent. Australia */
349 "adt", Dtz, -18, /* Atlantic Daylight Time */
350 "aesst", Dtz, 66, /* E. Australia */
351 "aest", Tz, 60, /* Australia Eastern Std Time */
352 "ahst", Tz, 60, /* Alaska-Hawaii Std Time */
356 "ast", Tz, -24, /* Atlantic Std Time (Canada) */
357 "at", Ignore, 0, /* "at" (throwaway) */
360 "awsst", Dtz, 54, /* W. Australia */
361 "awst", Tz, 48, /* W. Australia */
362 "bst", Tz, 6, /* British Summer Time */
363 "bt", Tz, 18, /* Baghdad Time */
364 "cadt", Dtz, 63, /* Central Australian DST */
365 "cast", Tz, 57, /* Central Australian ST */
366 "cat", Tz, -60, /* Central Alaska Time */
367 "cct", Tz, 48, /* China Coast */
368 "cdt", Dtz, -30, /* Central Daylight Time */
369 "cet", Tz, 6, /* Central European Time */
370 "cetdst", Dtz, 12, /* Central European Dayl.Time */
371 "cst", Tz, -36, /* Central Standard Time */
374 "dnt", Tz, 6, /* Dansk Normal Tid */
376 "east", Tz, -60, /* East Australian Std Time */
377 "edt", Dtz, -24, /* Eastern Daylight Time */
378 "eet", Tz, 12, /* East. Europe, USSR Zone 1 */
379 "eetdst", Dtz, 18, /* Eastern Europe */
380 "est", Tz, -30, /* Eastern Standard Time */
385 "fst", Tz, 6, /* French Summer Time */
386 "fwt", Dtz, 12, /* French Winter Time */
387 "gmt", Tz, 0, /* Greenwish Mean Time */
388 "gst", Tz, 60, /* Guam Std Time, USSR Zone 9 */
389 "hdt", Dtz, -54, /* Hawaii/Alaska */
390 "hmt", Dtz, 18, /* Hellas ? ? */
391 "hst", Tz, -60, /* Hawaii Std Time */
392 "idle", Tz, 72, /* Intl. Date Line, East */
393 "idlw", Tz, -72, /* Intl. Date Line, West */
394 "ist", Tz, 12, /* Israel */
395 "it", Tz, 22, /* Iran Time */
398 "jst", Tz, 54, /* Japan Std Time,USSR Zone 8 */
399 "jt", Tz, 45, /* Java Time */
404 "kst", Tz, 54, /* Korea Standard Time */
405 "ligt", Tz, 60, /* From Melbourne, Australia */
409 "mdt", Dtz, -36, /* Mountain Daylight Time */
410 "mest", Dtz, 12, /* Middle Europe Summer Time */
411 "met", Tz, 6, /* Middle Europe Time */
412 "metdst", Dtz, 12, /* Middle Europe Daylight Time*/
413 "mewt", Tz, 6, /* Middle Europe Winter Time */
414 "mez", Tz, 6, /* Middle Europe Zone */
417 "mst", Tz, -42, /* Mountain Standard Time */
418 "mt", Tz, 51, /* Moluccas Time */
419 "ndt", Dtz, -15, /* Nfld. Daylight Time */
420 "nft", Tz, -21, /* Newfoundland Standard Time */
421 "nor", Tz, 6, /* Norway Standard Time */
424 "nst", Tz, -21, /* Nfld. Standard Time */
425 "nt", Tz, -66, /* Nome Time */
426 "nzdt", Dtz, 78, /* New Zealand Daylight Time */
427 "nzst", Tz, 72, /* New Zealand Standard Time */
428 "nzt", Tz, 72, /* New Zealand Time */
431 "on", Ignore, 0, /* "on" (throwaway) */
432 "pdt", Dtz, -42, /* Pacific Daylight Time */
434 "pst", Tz, -48, /* Pacific Standard Time */
435 "sadt", Dtz, 63, /* S. Australian Dayl. Time */
436 "sast", Tz, 57, /* South Australian Std Time */
442 "set", Tz, -6, /* Seychelles Time ?? */
443 "sst", Dtz, 12, /* Swedish Summer Time */
446 "swt", Tz, 6, /* Swedish Winter Time */
456 "wadt", Dtz, 48, /* West Australian DST */
457 "wast", Tz, 42, /* West Australian Std Time */
458 "wat", Tz, -6, /* West Africa Time */
459 "wdt", Dtz, 54, /* West Australian DST */
463 "wet", Tz, 0, /* Western Europe */
464 "wetdst", Dtz, 6, /* Western Europe */
465 "wst", Tz, 48, /* West Australian Std Time */
466 "ydt", Dtz, -48, /* Yukon Daylight Time */
467 "yst", Tz, -54, /* Yukon Standard Time */
468 "zp4", Tz, -24, /* GMT +4 hours. */
469 "zp5", Tz, -30, /* GMT +5 hours. */
470 "zp6", Tz, -36, /* GMT +6 hours. */
472 static unsigned szdatetktbl = nelem(datetktbl);