seconds.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. * seconds absolute_date ... - convert absolute_date to seconds since epoch
  3. */
  4. #include <u.h>
  5. #include <libc.h>
  6. #include <ctype.h>
  7. typedef ulong Time;
  8. enum {
  9. AM, PM, HR24,
  10. /* token types */
  11. Month = 1,
  12. Year,
  13. Day,
  14. Timetok,
  15. Tz,
  16. Dtz,
  17. Ignore,
  18. Ampm,
  19. Maxtok = 6, /* only this many chars are stored in datetktbl */
  20. Maxdateflds = 25,
  21. };
  22. /*
  23. * macros for squeezing values into low 7 bits of "value".
  24. * all timezones we care about are divisible by 10, and the largest value
  25. * (780) when divided is 78.
  26. */
  27. #define TOVAL(tp, v) ((tp)->value = (v) / 10)
  28. #define FROMVAL(tp) ((tp)->value * 10) /* uncompress */
  29. /* keep this struct small since we have an array of them */
  30. typedef struct {
  31. char token[Maxtok];
  32. char type;
  33. schar value;
  34. } Datetok;
  35. int dtok_numparsed;
  36. /* forwards */
  37. Datetok *datetoktype(char *s, int *bigvalp);
  38. static Datetok datetktbl[];
  39. static unsigned szdatetktbl;
  40. /* parse 1- or 2-digit number, advance *cpp past it */
  41. static int
  42. eatnum(char **cpp)
  43. {
  44. int c, x;
  45. char *cp;
  46. cp = *cpp;
  47. c = *cp;
  48. if (!isascii(c) || !isdigit(c))
  49. return -1;
  50. x = c - '0';
  51. c = *++cp;
  52. if (isascii(c) && isdigit(c)) {
  53. x = 10*x + c - '0';
  54. cp++;
  55. }
  56. *cpp = cp;
  57. return x;
  58. }
  59. /* return -1 on failure */
  60. int
  61. parsetime(char *time, Tm *tm)
  62. {
  63. tm->hour = eatnum(&time);
  64. if (tm->hour == -1 || *time++ != ':')
  65. return -1; /* only hour; too short */
  66. tm->min = eatnum(&time);
  67. if (tm->min == -1)
  68. return -1;
  69. if (*time++ != ':') {
  70. tm->sec = 0;
  71. return 0; /* no seconds; okay */
  72. }
  73. tm->sec = eatnum(&time);
  74. if (tm->sec == -1)
  75. return -1;
  76. /* this may be considered too strict. garbage at end of time? */
  77. return *time == '\0' || isascii(*time) && isspace(*time)? 0: -1;
  78. }
  79. /*
  80. * try to parse pre-split timestr in fields as an absolute date
  81. */
  82. int
  83. tryabsdate(char **fields, int nf, Tm *now, Tm *tm)
  84. {
  85. int i, mer = HR24, bigval = -1;
  86. long flg = 0, ty;
  87. char *p;
  88. Datetok *tp;
  89. now = localtime(time(0)); /* default to local time (zone) */
  90. tm->tzoff = now->tzoff;
  91. strncpy(tm->zone, now->zone, sizeof tm->zone - 1);
  92. tm->zone[sizeof tm->zone - 1] = '\0';
  93. tm->mday = tm->mon = tm->year = -1; /* mandatory */
  94. tm->hour = tm->min = tm->sec = 0;
  95. dtok_numparsed = 0;
  96. for (i = 0; i < nf; i++) {
  97. if (fields[i][0] == '\0')
  98. continue;
  99. tp = datetoktype(fields[i], &bigval);
  100. ty = (1L << tp->type) & ~(1L << Ignore);
  101. if (flg & ty)
  102. return -1; /* repeated type */
  103. flg |= ty;
  104. switch (tp->type) {
  105. case Year:
  106. tm->year = bigval;
  107. if (tm->year < 1970 || tm->year > 2106)
  108. return -1; /* can't represent in ulong */
  109. /* convert 4-digit year to 1900 origin */
  110. if (tm->year >= 1900)
  111. tm->year -= 1900;
  112. break;
  113. case Day:
  114. tm->mday = bigval;
  115. break;
  116. case Month:
  117. tm->mon = tp->value - 1; /* convert to zero-origin */
  118. break;
  119. case Timetok:
  120. if (parsetime(fields[i], tm) < 0)
  121. return -1;
  122. break;
  123. case Dtz:
  124. case Tz:
  125. tm->tzoff = FROMVAL(tp);
  126. /* tm2sec needs the name in upper case */
  127. strncpy(tm->zone, fields[i], sizeof tm->zone - 1);
  128. tm->zone[sizeof tm->zone - 1] = '\0';
  129. for (p = tm->zone; *p; p++)
  130. if (isascii(*p) && islower(*p))
  131. *p = toupper(*p);
  132. break;
  133. case Ignore:
  134. break;
  135. case Ampm:
  136. mer = tp->value;
  137. break;
  138. default:
  139. return -1; /* bad token type: CANTHAPPEN */
  140. }
  141. }
  142. if (tm->year == -1 || tm->mon == -1 || tm->mday == -1)
  143. return -1; /* missing component */
  144. if (mer == PM)
  145. tm->hour += 12;
  146. return 0;
  147. }
  148. int
  149. prsabsdate(char *timestr, Tm *now, Tm *tm)
  150. {
  151. int nf;
  152. char *fields[Maxdateflds];
  153. static char delims[] = "- \t\n/,";
  154. nf = gettokens(timestr, fields, nelem(fields), delims+1);
  155. if (nf > nelem(fields))
  156. return -1;
  157. if (tryabsdate(fields, nf, now, tm) < 0) {
  158. char *p = timestr;
  159. /*
  160. * could be a DEC-date; glue it all back together, split it
  161. * with dash as a delimiter and try again. Yes, this is a
  162. * hack, but so are DEC-dates.
  163. */
  164. while (--nf > 0) {
  165. while (*p++ != '\0')
  166. ;
  167. p[-1] = ' ';
  168. }
  169. nf = gettokens(timestr, fields, nelem(fields), delims);
  170. if (nf > nelem(fields) || tryabsdate(fields, nf, now, tm) < 0)
  171. return -1;
  172. }
  173. return 0;
  174. }
  175. int
  176. validtm(Tm *tm)
  177. {
  178. if (tm->year < 0 || tm->mon < 0 || tm->mon > 11 ||
  179. tm->mday < 1 || tm->hour < 0 || tm->hour >= 24 ||
  180. tm->min < 0 || tm->min > 59 ||
  181. tm->sec < 0 || tm->sec > 61) /* allow 2 leap seconds */
  182. return 0;
  183. return 1;
  184. }
  185. Time
  186. seconds(char *timestr)
  187. {
  188. Tm date;
  189. memset(&date, 0, sizeof date);
  190. if (prsabsdate(timestr, localtime(time(0)), &date) < 0)
  191. return -1;
  192. return validtm(&date)? tm2sec(&date): -1;
  193. }
  194. int
  195. convert(char *timestr)
  196. {
  197. char *copy;
  198. Time tstime;
  199. copy = strdup(timestr);
  200. if (copy == nil)
  201. sysfatal("out of memory");
  202. tstime = seconds(copy);
  203. free(copy);
  204. if (tstime == -1) {
  205. fprint(2, "%s: `%s' not a valid date\n", argv0, timestr);
  206. return 1;
  207. }
  208. print("%lud\n", tstime);
  209. return 0;
  210. }
  211. static void
  212. usage(void)
  213. {
  214. fprint(2, "usage: %s date-time ...\n", argv0);
  215. exits("usage");
  216. }
  217. void
  218. main(int argc, char **argv)
  219. {
  220. int i, sts;
  221. sts = 0;
  222. ARGBEGIN{
  223. default:
  224. usage();
  225. }ARGEND
  226. if (argc == 0)
  227. usage();
  228. for (i = 0; i < argc; i++)
  229. sts |= convert(argv[i]);
  230. exits(sts != 0? "bad": 0);
  231. }
  232. /*
  233. * Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this
  234. * is WAY faster than the generic bsearch().
  235. */
  236. Datetok *
  237. datebsearch(char *key, Datetok *base, unsigned nel)
  238. {
  239. int cmp;
  240. Datetok *last = base + nel - 1, *pos;
  241. while (last >= base) {
  242. pos = base + ((last - base) >> 1);
  243. cmp = key[0] - pos->token[0];
  244. if (cmp == 0) {
  245. cmp = strncmp(key, pos->token, Maxtok);
  246. if (cmp == 0)
  247. return pos;
  248. }
  249. if (cmp < 0)
  250. last = pos - 1;
  251. else
  252. base = pos + 1;
  253. }
  254. return 0;
  255. }
  256. Datetok *
  257. datetoktype(char *s, int *bigvalp)
  258. {
  259. char *cp = s;
  260. char c = *cp;
  261. static Datetok t;
  262. Datetok *tp = &t;
  263. if (isascii(c) && isdigit(c)) {
  264. int len = strlen(cp);
  265. if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
  266. tp->type = Timetok;
  267. else {
  268. if (bigvalp != nil)
  269. *bigvalp = atoi(cp); /* won't fit in tp->value */
  270. if (len == 4)
  271. tp->type = Year;
  272. else if (++dtok_numparsed == 1)
  273. tp->type = Day;
  274. else
  275. tp->type = Year;
  276. }
  277. } else if (c == '-' || c == '+') {
  278. int val = atoi(cp + 1);
  279. int hr = val / 100;
  280. int min = val % 100;
  281. val = hr*60 + min;
  282. TOVAL(tp, c == '-'? -val: val);
  283. tp->type = Tz;
  284. } else {
  285. char lowtoken[Maxtok+1];
  286. char *ltp = lowtoken, *endltp = lowtoken+Maxtok;
  287. /* copy to lowtoken to avoid modifying s */
  288. while ((c = *cp++) != '\0' && ltp < endltp)
  289. *ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
  290. *ltp = '\0';
  291. tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
  292. if (tp == nil) {
  293. tp = &t;
  294. tp->type = Ignore;
  295. }
  296. }
  297. return tp;
  298. }
  299. /*
  300. * to keep this table reasonably small, we divide the lexval for Tz and Dtz
  301. * entries by 10 and truncate the text field at MAXTOKLEN characters.
  302. * the text field is not guaranteed to be NUL-terminated.
  303. */
  304. static Datetok datetktbl[] = {
  305. /* text token lexval */
  306. "acsst", Dtz, 63, /* Cent. Australia */
  307. "acst", Tz, 57, /* Cent. Australia */
  308. "adt", Dtz, -18, /* Atlantic Daylight Time */
  309. "aesst", Dtz, 66, /* E. Australia */
  310. "aest", Tz, 60, /* Australia Eastern Std Time */
  311. "ahst", Tz, 60, /* Alaska-Hawaii Std Time */
  312. "am", Ampm, AM,
  313. "apr", Month, 4,
  314. "april", Month, 4,
  315. "ast", Tz, -24, /* Atlantic Std Time (Canada) */
  316. "at", Ignore, 0, /* "at" (throwaway) */
  317. "aug", Month, 8,
  318. "august", Month, 8,
  319. "awsst", Dtz, 54, /* W. Australia */
  320. "awst", Tz, 48, /* W. Australia */
  321. "bst", Tz, 6, /* British Summer Time */
  322. "bt", Tz, 18, /* Baghdad Time */
  323. "cadt", Dtz, 63, /* Central Australian DST */
  324. "cast", Tz, 57, /* Central Australian ST */
  325. "cat", Tz, -60, /* Central Alaska Time */
  326. "cct", Tz, 48, /* China Coast */
  327. "cdt", Dtz, -30, /* Central Daylight Time */
  328. "cet", Tz, 6, /* Central European Time */
  329. "cetdst", Dtz, 12, /* Central European Dayl.Time */
  330. "cst", Tz, -36, /* Central Standard Time */
  331. "dec", Month, 12,
  332. "decemb", Month, 12,
  333. "dnt", Tz, 6, /* Dansk Normal Tid */
  334. "dst", Ignore, 0,
  335. "east", Tz, -60, /* East Australian Std Time */
  336. "edt", Dtz, -24, /* Eastern Daylight Time */
  337. "eet", Tz, 12, /* East. Europe, USSR Zone 1 */
  338. "eetdst", Dtz, 18, /* Eastern Europe */
  339. "est", Tz, -30, /* Eastern Standard Time */
  340. "feb", Month, 2,
  341. "februa", Month, 2,
  342. "fri", Ignore, 5,
  343. "friday", Ignore, 5,
  344. "fst", Tz, 6, /* French Summer Time */
  345. "fwt", Dtz, 12, /* French Winter Time */
  346. "gmt", Tz, 0, /* Greenwish Mean Time */
  347. "gst", Tz, 60, /* Guam Std Time, USSR Zone 9 */
  348. "hdt", Dtz, -54, /* Hawaii/Alaska */
  349. "hmt", Dtz, 18, /* Hellas ? ? */
  350. "hst", Tz, -60, /* Hawaii Std Time */
  351. "idle", Tz, 72, /* Intl. Date Line, East */
  352. "idlw", Tz, -72, /* Intl. Date Line, West */
  353. "ist", Tz, 12, /* Israel */
  354. "it", Tz, 22, /* Iran Time */
  355. "jan", Month, 1,
  356. "januar", Month, 1,
  357. "jst", Tz, 54, /* Japan Std Time,USSR Zone 8 */
  358. "jt", Tz, 45, /* Java Time */
  359. "jul", Month, 7,
  360. "july", Month, 7,
  361. "jun", Month, 6,
  362. "june", Month, 6,
  363. "kst", Tz, 54, /* Korea Standard Time */
  364. "ligt", Tz, 60, /* From Melbourne, Australia */
  365. "mar", Month, 3,
  366. "march", Month, 3,
  367. "may", Month, 5,
  368. "mdt", Dtz, -36, /* Mountain Daylight Time */
  369. "mest", Dtz, 12, /* Middle Europe Summer Time */
  370. "met", Tz, 6, /* Middle Europe Time */
  371. "metdst", Dtz, 12, /* Middle Europe Daylight Time*/
  372. "mewt", Tz, 6, /* Middle Europe Winter Time */
  373. "mez", Tz, 6, /* Middle Europe Zone */
  374. "mon", Ignore, 1,
  375. "monday", Ignore, 1,
  376. "mst", Tz, -42, /* Mountain Standard Time */
  377. "mt", Tz, 51, /* Moluccas Time */
  378. "ndt", Dtz, -15, /* Nfld. Daylight Time */
  379. "nft", Tz, -21, /* Newfoundland Standard Time */
  380. "nor", Tz, 6, /* Norway Standard Time */
  381. "nov", Month, 11,
  382. "novemb", Month, 11,
  383. "nst", Tz, -21, /* Nfld. Standard Time */
  384. "nt", Tz, -66, /* Nome Time */
  385. "nzdt", Dtz, 78, /* New Zealand Daylight Time */
  386. "nzst", Tz, 72, /* New Zealand Standard Time */
  387. "nzt", Tz, 72, /* New Zealand Time */
  388. "oct", Month, 10,
  389. "octobe", Month, 10,
  390. "on", Ignore, 0, /* "on" (throwaway) */
  391. "pdt", Dtz, -42, /* Pacific Daylight Time */
  392. "pm", Ampm, PM,
  393. "pst", Tz, -48, /* Pacific Standard Time */
  394. "sadt", Dtz, 63, /* S. Australian Dayl. Time */
  395. "sast", Tz, 57, /* South Australian Std Time */
  396. "sat", Ignore, 6,
  397. "saturd", Ignore, 6,
  398. "sep", Month, 9,
  399. "sept", Month, 9,
  400. "septem", Month, 9,
  401. "set", Tz, -6, /* Seychelles Time ?? */
  402. "sst", Dtz, 12, /* Swedish Summer Time */
  403. "sun", Ignore, 0,
  404. "sunday", Ignore, 0,
  405. "swt", Tz, 6, /* Swedish Winter Time */
  406. "thu", Ignore, 4,
  407. "thur", Ignore, 4,
  408. "thurs", Ignore, 4,
  409. "thursd", Ignore, 4,
  410. "tue", Ignore, 2,
  411. "tues", Ignore, 2,
  412. "tuesda", Ignore, 2,
  413. "ut", Tz, 0,
  414. "utc", Tz, 0,
  415. "wadt", Dtz, 48, /* West Australian DST */
  416. "wast", Tz, 42, /* West Australian Std Time */
  417. "wat", Tz, -6, /* West Africa Time */
  418. "wdt", Dtz, 54, /* West Australian DST */
  419. "wed", Ignore, 3,
  420. "wednes", Ignore, 3,
  421. "weds", Ignore, 3,
  422. "wet", Tz, 0, /* Western Europe */
  423. "wetdst", Dtz, 6, /* Western Europe */
  424. "wst", Tz, 48, /* West Australian Std Time */
  425. "ydt", Dtz, -48, /* Yukon Daylight Time */
  426. "yst", Tz, -54, /* Yukon Standard Time */
  427. "zp4", Tz, -24, /* GMT +4 hours. */
  428. "zp5", Tz, -30, /* GMT +5 hours. */
  429. "zp6", Tz, -36, /* GMT +6 hours. */
  430. };
  431. static unsigned szdatetktbl = nelem(datetktbl);