getdate.c 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. /*
  2. * This code is in the public domain and has no copyright.
  3. *
  4. * This is a plain C recursive-descent translation of an old
  5. * public-domain YACC grammar that has been used for parsing dates in
  6. * very many open-source projects.
  7. *
  8. * Since the original authors were generous enough to donate their
  9. * work to the public domain, I feel compelled to match their
  10. * generosity.
  11. *
  12. * Tim Kientzle, February 2009.
  13. */
  14. /*
  15. * Header comment from original getdate.y:
  16. */
  17. /*
  18. ** Originally written by Steven M. Bellovin <smb@research.att.com> while
  19. ** at the University of North Carolina at Chapel Hill. Later tweaked by
  20. ** a couple of people on Usenet. Completely overhauled by Rich $alz
  21. ** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
  22. **
  23. ** This grammar has 10 shift/reduce conflicts.
  24. **
  25. ** This code is in the public domain and has no copyright.
  26. */
  27. #ifdef __FreeBSD__
  28. #include <sys/cdefs.h>
  29. __FBSDID("$FreeBSD$");
  30. #endif
  31. #include <ctype.h>
  32. #include <stdio.h>
  33. #include <stdlib.h>
  34. #include <string.h>
  35. #include <time.h>
  36. /* This file defines a single public function. */
  37. time_t get_date(time_t now, char *);
  38. /* Basic time units. */
  39. #define EPOCH 1970
  40. #define MINUTE (60L)
  41. #define HOUR (60L * MINUTE)
  42. #define DAY (24L * HOUR)
  43. /* Daylight-savings mode: on, off, or not yet known. */
  44. enum DSTMODE { DSTon, DSToff, DSTmaybe };
  45. /* Meridian: am or pm. */
  46. enum { tAM, tPM };
  47. /* Token types returned by nexttoken() */
  48. enum { tAGO = 260, tDAY, tDAYZONE, tAMPM, tMONTH, tMONTH_UNIT, tSEC_UNIT,
  49. tUNUMBER, tZONE, tDST };
  50. struct token { int token; time_t value; };
  51. /*
  52. * Parser state.
  53. */
  54. struct gdstate {
  55. struct token *tokenp; /* Pointer to next token. */
  56. /* HaveXxxx counts how many of this kind of phrase we've seen;
  57. * it's a fatal error to have more than one time, zone, day,
  58. * or date phrase. */
  59. int HaveYear;
  60. int HaveMonth;
  61. int HaveDay;
  62. int HaveWeekDay; /* Day of week */
  63. int HaveTime; /* Hour/minute/second */
  64. int HaveZone; /* timezone and/or DST info */
  65. int HaveRel; /* time offset; we can have more than one */
  66. /* Absolute time values. */
  67. time_t Timezone; /* Seconds offset from GMT */
  68. time_t Day;
  69. time_t Hour;
  70. time_t Minutes;
  71. time_t Month;
  72. time_t Seconds;
  73. time_t Year;
  74. /* DST selection */
  75. enum DSTMODE DSTmode;
  76. /* Day of week accounting, e.g., "3rd Tuesday" */
  77. time_t DayOrdinal; /* "3" in "3rd Tuesday" */
  78. time_t DayNumber; /* "Tuesday" in "3rd Tuesday" */
  79. /* Relative time values: hour/day/week offsets are measured in
  80. * seconds, month/year are counted in months. */
  81. time_t RelMonth;
  82. time_t RelSeconds;
  83. };
  84. /*
  85. * A series of functions that recognize certain common time phrases.
  86. * Each function returns 1 if it managed to make sense of some of the
  87. * tokens, zero otherwise.
  88. */
  89. /*
  90. * hour:minute or hour:minute:second with optional AM, PM, or numeric
  91. * timezone offset
  92. */
  93. static int
  94. timephrase(struct gdstate *gds)
  95. {
  96. if (gds->tokenp[0].token == tUNUMBER
  97. && gds->tokenp[1].token == ':'
  98. && gds->tokenp[2].token == tUNUMBER
  99. && gds->tokenp[3].token == ':'
  100. && gds->tokenp[4].token == tUNUMBER) {
  101. /* "12:14:18" or "22:08:07" */
  102. ++gds->HaveTime;
  103. gds->Hour = gds->tokenp[0].value;
  104. gds->Minutes = gds->tokenp[2].value;
  105. gds->Seconds = gds->tokenp[4].value;
  106. gds->tokenp += 5;
  107. }
  108. else if (gds->tokenp[0].token == tUNUMBER
  109. && gds->tokenp[1].token == ':'
  110. && gds->tokenp[2].token == tUNUMBER) {
  111. /* "12:14" or "22:08" */
  112. ++gds->HaveTime;
  113. gds->Hour = gds->tokenp[0].value;
  114. gds->Minutes = gds->tokenp[2].value;
  115. gds->Seconds = 0;
  116. gds->tokenp += 3;
  117. }
  118. else if (gds->tokenp[0].token == tUNUMBER
  119. && gds->tokenp[1].token == tAMPM) {
  120. /* "7" is a time if it's followed by "am" or "pm" */
  121. ++gds->HaveTime;
  122. gds->Hour = gds->tokenp[0].value;
  123. gds->Minutes = gds->Seconds = 0;
  124. /* We'll handle the AM/PM below. */
  125. gds->tokenp += 1;
  126. } else {
  127. /* We can't handle this. */
  128. return 0;
  129. }
  130. if (gds->tokenp[0].token == tAMPM) {
  131. /* "7:12pm", "12:20:13am" */
  132. if (gds->Hour == 12)
  133. gds->Hour = 0;
  134. if (gds->tokenp[0].value == tPM)
  135. gds->Hour += 12;
  136. gds->tokenp += 1;
  137. }
  138. if (gds->tokenp[0].token == '+'
  139. && gds->tokenp[1].token == tUNUMBER) {
  140. /* "7:14+0700" */
  141. gds->HaveZone++;
  142. gds->DSTmode = DSToff;
  143. gds->Timezone = - ((gds->tokenp[1].value / 100) * HOUR
  144. + (gds->tokenp[1].value % 100) * MINUTE);
  145. gds->tokenp += 2;
  146. }
  147. if (gds->tokenp[0].token == '-'
  148. && gds->tokenp[1].token == tUNUMBER) {
  149. /* "19:14:12-0530" */
  150. gds->HaveZone++;
  151. gds->DSTmode = DSToff;
  152. gds->Timezone = + ((gds->tokenp[1].value / 100) * HOUR
  153. + (gds->tokenp[1].value % 100) * MINUTE);
  154. gds->tokenp += 2;
  155. }
  156. return 1;
  157. }
  158. /*
  159. * Timezone name, possibly including DST.
  160. */
  161. static int
  162. zonephrase(struct gdstate *gds)
  163. {
  164. if (gds->tokenp[0].token == tZONE
  165. && gds->tokenp[1].token == tDST) {
  166. gds->HaveZone++;
  167. gds->Timezone = gds->tokenp[0].value;
  168. gds->DSTmode = DSTon;
  169. gds->tokenp += 1;
  170. return 1;
  171. }
  172. if (gds->tokenp[0].token == tZONE) {
  173. gds->HaveZone++;
  174. gds->Timezone = gds->tokenp[0].value;
  175. gds->DSTmode = DSToff;
  176. gds->tokenp += 1;
  177. return 1;
  178. }
  179. if (gds->tokenp[0].token == tDAYZONE) {
  180. gds->HaveZone++;
  181. gds->Timezone = gds->tokenp[0].value;
  182. gds->DSTmode = DSTon;
  183. gds->tokenp += 1;
  184. return 1;
  185. }
  186. return 0;
  187. }
  188. /*
  189. * Year/month/day in various combinations.
  190. */
  191. static int
  192. datephrase(struct gdstate *gds)
  193. {
  194. if (gds->tokenp[0].token == tUNUMBER
  195. && gds->tokenp[1].token == '/'
  196. && gds->tokenp[2].token == tUNUMBER
  197. && gds->tokenp[3].token == '/'
  198. && gds->tokenp[4].token == tUNUMBER) {
  199. gds->HaveYear++;
  200. gds->HaveMonth++;
  201. gds->HaveDay++;
  202. if (gds->tokenp[0].value >= 13) {
  203. /* First number is big: 2004/01/29, 99/02/17 */
  204. gds->Year = gds->tokenp[0].value;
  205. gds->Month = gds->tokenp[2].value;
  206. gds->Day = gds->tokenp[4].value;
  207. } else if ((gds->tokenp[4].value >= 13) || (gds->tokenp[2].value >= 13)) {
  208. /* Last number is big: 01/07/98 */
  209. /* Middle number is big: 01/29/04 */
  210. gds->Month = gds->tokenp[0].value;
  211. gds->Day = gds->tokenp[2].value;
  212. gds->Year = gds->tokenp[4].value;
  213. } else {
  214. /* No significant clues: 02/03/04 */
  215. gds->Month = gds->tokenp[0].value;
  216. gds->Day = gds->tokenp[2].value;
  217. gds->Year = gds->tokenp[4].value;
  218. }
  219. gds->tokenp += 5;
  220. return 1;
  221. }
  222. if (gds->tokenp[0].token == tUNUMBER
  223. && gds->tokenp[1].token == '/'
  224. && gds->tokenp[2].token == tUNUMBER) {
  225. /* "1/15" */
  226. gds->HaveMonth++;
  227. gds->HaveDay++;
  228. gds->Month = gds->tokenp[0].value;
  229. gds->Day = gds->tokenp[2].value;
  230. gds->tokenp += 3;
  231. return 1;
  232. }
  233. if (gds->tokenp[0].token == tUNUMBER
  234. && gds->tokenp[1].token == '-'
  235. && gds->tokenp[2].token == tUNUMBER
  236. && gds->tokenp[3].token == '-'
  237. && gds->tokenp[4].token == tUNUMBER) {
  238. /* ISO 8601 format. yyyy-mm-dd. */
  239. gds->HaveYear++;
  240. gds->HaveMonth++;
  241. gds->HaveDay++;
  242. gds->Year = gds->tokenp[0].value;
  243. gds->Month = gds->tokenp[2].value;
  244. gds->Day = gds->tokenp[4].value;
  245. gds->tokenp += 5;
  246. return 1;
  247. }
  248. if (gds->tokenp[0].token == tUNUMBER
  249. && gds->tokenp[1].token == '-'
  250. && gds->tokenp[2].token == tMONTH
  251. && gds->tokenp[3].token == '-'
  252. && gds->tokenp[4].token == tUNUMBER) {
  253. gds->HaveYear++;
  254. gds->HaveMonth++;
  255. gds->HaveDay++;
  256. if (gds->tokenp[0].value > 31) {
  257. /* e.g. 1992-Jun-17 */
  258. gds->Year = gds->tokenp[0].value;
  259. gds->Month = gds->tokenp[2].value;
  260. gds->Day = gds->tokenp[4].value;
  261. } else {
  262. /* e.g. 17-JUN-1992. */
  263. gds->Day = gds->tokenp[0].value;
  264. gds->Month = gds->tokenp[2].value;
  265. gds->Year = gds->tokenp[4].value;
  266. }
  267. gds->tokenp += 5;
  268. return 1;
  269. }
  270. if (gds->tokenp[0].token == tMONTH
  271. && gds->tokenp[1].token == tUNUMBER
  272. && gds->tokenp[2].token == ','
  273. && gds->tokenp[3].token == tUNUMBER) {
  274. /* "June 17, 2001" */
  275. gds->HaveYear++;
  276. gds->HaveMonth++;
  277. gds->HaveDay++;
  278. gds->Month = gds->tokenp[0].value;
  279. gds->Day = gds->tokenp[1].value;
  280. gds->Year = gds->tokenp[3].value;
  281. gds->tokenp += 4;
  282. return 1;
  283. }
  284. if (gds->tokenp[0].token == tMONTH
  285. && gds->tokenp[1].token == tUNUMBER) {
  286. /* "May 3" */
  287. gds->HaveMonth++;
  288. gds->HaveDay++;
  289. gds->Month = gds->tokenp[0].value;
  290. gds->Day = gds->tokenp[1].value;
  291. gds->tokenp += 2;
  292. return 1;
  293. }
  294. if (gds->tokenp[0].token == tUNUMBER
  295. && gds->tokenp[1].token == tMONTH
  296. && gds->tokenp[2].token == tUNUMBER) {
  297. /* "12 Sept 1997" */
  298. gds->HaveYear++;
  299. gds->HaveMonth++;
  300. gds->HaveDay++;
  301. gds->Day = gds->tokenp[0].value;
  302. gds->Month = gds->tokenp[1].value;
  303. gds->Year = gds->tokenp[2].value;
  304. gds->tokenp += 3;
  305. return 1;
  306. }
  307. if (gds->tokenp[0].token == tUNUMBER
  308. && gds->tokenp[1].token == tMONTH) {
  309. /* "12 Sept" */
  310. gds->HaveMonth++;
  311. gds->HaveDay++;
  312. gds->Day = gds->tokenp[0].value;
  313. gds->Month = gds->tokenp[1].value;
  314. gds->tokenp += 2;
  315. return 1;
  316. }
  317. return 0;
  318. }
  319. /*
  320. * Relative time phrase: "tomorrow", "yesterday", "+1 hour", etc.
  321. */
  322. static int
  323. relunitphrase(struct gdstate *gds)
  324. {
  325. if (gds->tokenp[0].token == '-'
  326. && gds->tokenp[1].token == tUNUMBER
  327. && gds->tokenp[2].token == tSEC_UNIT) {
  328. /* "-3 hours" */
  329. gds->HaveRel++;
  330. gds->RelSeconds -= gds->tokenp[1].value * gds->tokenp[2].value;
  331. gds->tokenp += 3;
  332. return 1;
  333. }
  334. if (gds->tokenp[0].token == '+'
  335. && gds->tokenp[1].token == tUNUMBER
  336. && gds->tokenp[2].token == tSEC_UNIT) {
  337. /* "+1 minute" */
  338. gds->HaveRel++;
  339. gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value;
  340. gds->tokenp += 3;
  341. return 1;
  342. }
  343. if (gds->tokenp[0].token == tUNUMBER
  344. && gds->tokenp[1].token == tSEC_UNIT) {
  345. /* "1 day" */
  346. gds->HaveRel++;
  347. gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value;
  348. gds->tokenp += 3;
  349. return 1;
  350. }
  351. if (gds->tokenp[0].token == '-'
  352. && gds->tokenp[1].token == tUNUMBER
  353. && gds->tokenp[2].token == tMONTH_UNIT) {
  354. /* "-3 months" */
  355. gds->HaveRel++;
  356. gds->RelMonth -= gds->tokenp[1].value * gds->tokenp[2].value;
  357. gds->tokenp += 3;
  358. return 1;
  359. }
  360. if (gds->tokenp[0].token == '+'
  361. && gds->tokenp[1].token == tUNUMBER
  362. && gds->tokenp[2].token == tMONTH_UNIT) {
  363. /* "+5 years" */
  364. gds->HaveRel++;
  365. gds->RelMonth += gds->tokenp[1].value * gds->tokenp[2].value;
  366. gds->tokenp += 3;
  367. return 1;
  368. }
  369. if (gds->tokenp[0].token == tUNUMBER
  370. && gds->tokenp[1].token == tMONTH_UNIT) {
  371. /* "2 years" */
  372. gds->HaveRel++;
  373. gds->RelMonth += gds->tokenp[0].value * gds->tokenp[1].value;
  374. gds->tokenp += 2;
  375. return 1;
  376. }
  377. if (gds->tokenp[0].token == tSEC_UNIT) {
  378. /* "now", "tomorrow" */
  379. gds->HaveRel++;
  380. gds->RelSeconds += gds->tokenp[0].value;
  381. ++gds->tokenp;
  382. return 1;
  383. }
  384. if (gds->tokenp[0].token == tMONTH_UNIT) {
  385. /* "month" */
  386. gds->HaveRel++;
  387. gds->RelMonth += gds->tokenp[0].value;
  388. gds->tokenp += 1;
  389. return 1;
  390. }
  391. return 0;
  392. }
  393. /*
  394. * Day of the week specification.
  395. */
  396. static int
  397. dayphrase(struct gdstate *gds)
  398. {
  399. if (gds->tokenp[0].token == tDAY) {
  400. /* "tues", "wednesday," */
  401. gds->HaveWeekDay++;
  402. gds->DayOrdinal = 1;
  403. gds->DayNumber = gds->tokenp[0].value;
  404. gds->tokenp += 1;
  405. if (gds->tokenp[0].token == ',')
  406. gds->tokenp += 1;
  407. return 1;
  408. }
  409. if (gds->tokenp[0].token == tUNUMBER
  410. && gds->tokenp[1].token == tDAY) {
  411. /* "second tues" "3 wed" */
  412. gds->HaveWeekDay++;
  413. gds->DayOrdinal = gds->tokenp[0].value;
  414. gds->DayNumber = gds->tokenp[1].value;
  415. gds->tokenp += 2;
  416. return 1;
  417. }
  418. return 0;
  419. }
  420. /*
  421. * Try to match a phrase using one of the above functions.
  422. * This layer also deals with a couple of generic issues.
  423. */
  424. static int
  425. phrase(struct gdstate *gds)
  426. {
  427. if (timephrase(gds))
  428. return 1;
  429. if (zonephrase(gds))
  430. return 1;
  431. if (datephrase(gds))
  432. return 1;
  433. if (dayphrase(gds))
  434. return 1;
  435. if (relunitphrase(gds)) {
  436. if (gds->tokenp[0].token == tAGO) {
  437. gds->RelSeconds = -gds->RelSeconds;
  438. gds->RelMonth = -gds->RelMonth;
  439. gds->tokenp += 1;
  440. }
  441. return 1;
  442. }
  443. /* Bare numbers sometimes have meaning. */
  444. if (gds->tokenp[0].token == tUNUMBER) {
  445. if (gds->HaveTime && !gds->HaveYear && !gds->HaveRel) {
  446. gds->HaveYear++;
  447. gds->Year = gds->tokenp[0].value;
  448. gds->tokenp += 1;
  449. return 1;
  450. }
  451. if(gds->tokenp[0].value > 10000) {
  452. /* "20040301" */
  453. gds->HaveYear++;
  454. gds->HaveMonth++;
  455. gds->HaveDay++;
  456. gds->Day= (gds->tokenp[0].value)%100;
  457. gds->Month= (gds->tokenp[0].value/100)%100;
  458. gds->Year = gds->tokenp[0].value/10000;
  459. gds->tokenp += 1;
  460. return 1;
  461. }
  462. if (gds->tokenp[0].value < 24) {
  463. gds->HaveTime++;
  464. gds->Hour = gds->tokenp[0].value;
  465. gds->Minutes = 0;
  466. gds->Seconds = 0;
  467. gds->tokenp += 1;
  468. return 1;
  469. }
  470. if ((gds->tokenp[0].value / 100 < 24)
  471. && (gds->tokenp[0].value % 100 < 60)) {
  472. /* "513" is same as "5:13" */
  473. gds->Hour = gds->tokenp[0].value / 100;
  474. gds->Minutes = gds->tokenp[0].value % 100;
  475. gds->Seconds = 0;
  476. gds->tokenp += 1;
  477. return 1;
  478. }
  479. }
  480. return 0;
  481. }
  482. /*
  483. * A dictionary of time words.
  484. */
  485. static struct LEXICON {
  486. size_t abbrev;
  487. const char *name;
  488. int type;
  489. time_t value;
  490. } const TimeWords[] = {
  491. /* am/pm */
  492. { 0, "am", tAMPM, tAM },
  493. { 0, "pm", tAMPM, tPM },
  494. /* Month names. */
  495. { 3, "january", tMONTH, 1 },
  496. { 3, "february", tMONTH, 2 },
  497. { 3, "march", tMONTH, 3 },
  498. { 3, "april", tMONTH, 4 },
  499. { 3, "may", tMONTH, 5 },
  500. { 3, "june", tMONTH, 6 },
  501. { 3, "july", tMONTH, 7 },
  502. { 3, "august", tMONTH, 8 },
  503. { 3, "september", tMONTH, 9 },
  504. { 3, "october", tMONTH, 10 },
  505. { 3, "november", tMONTH, 11 },
  506. { 3, "december", tMONTH, 12 },
  507. /* Days of the week. */
  508. { 2, "sunday", tDAY, 0 },
  509. { 3, "monday", tDAY, 1 },
  510. { 2, "tuesday", tDAY, 2 },
  511. { 3, "wednesday", tDAY, 3 },
  512. { 2, "thursday", tDAY, 4 },
  513. { 2, "friday", tDAY, 5 },
  514. { 2, "saturday", tDAY, 6 },
  515. /* Timezones: Offsets are in seconds. */
  516. { 0, "gmt", tZONE, 0*HOUR }, /* Greenwich Mean */
  517. { 0, "ut", tZONE, 0*HOUR }, /* Universal (Coordinated) */
  518. { 0, "utc", tZONE, 0*HOUR },
  519. { 0, "wet", tZONE, 0*HOUR }, /* Western European */
  520. { 0, "bst", tDAYZONE, 0*HOUR }, /* British Summer */
  521. { 0, "wat", tZONE, 1*HOUR }, /* West Africa */
  522. { 0, "at", tZONE, 2*HOUR }, /* Azores */
  523. /* { 0, "bst", tZONE, 3*HOUR }, */ /* Brazil Standard: Conflict */
  524. /* { 0, "gst", tZONE, 3*HOUR }, */ /* Greenland Standard: Conflict*/
  525. { 0, "nft", tZONE, 3*HOUR+30*MINUTE }, /* Newfoundland */
  526. { 0, "nst", tZONE, 3*HOUR+30*MINUTE }, /* Newfoundland Standard */
  527. { 0, "ndt", tDAYZONE, 3*HOUR+30*MINUTE }, /* Newfoundland Daylight */
  528. { 0, "ast", tZONE, 4*HOUR }, /* Atlantic Standard */
  529. { 0, "adt", tDAYZONE, 4*HOUR }, /* Atlantic Daylight */
  530. { 0, "est", tZONE, 5*HOUR }, /* Eastern Standard */
  531. { 0, "edt", tDAYZONE, 5*HOUR }, /* Eastern Daylight */
  532. { 0, "cst", tZONE, 6*HOUR }, /* Central Standard */
  533. { 0, "cdt", tDAYZONE, 6*HOUR }, /* Central Daylight */
  534. { 0, "mst", tZONE, 7*HOUR }, /* Mountain Standard */
  535. { 0, "mdt", tDAYZONE, 7*HOUR }, /* Mountain Daylight */
  536. { 0, "pst", tZONE, 8*HOUR }, /* Pacific Standard */
  537. { 0, "pdt", tDAYZONE, 8*HOUR }, /* Pacific Daylight */
  538. { 0, "yst", tZONE, 9*HOUR }, /* Yukon Standard */
  539. { 0, "ydt", tDAYZONE, 9*HOUR }, /* Yukon Daylight */
  540. { 0, "hst", tZONE, 10*HOUR }, /* Hawaii Standard */
  541. { 0, "hdt", tDAYZONE, 10*HOUR }, /* Hawaii Daylight */
  542. { 0, "cat", tZONE, 10*HOUR }, /* Central Alaska */
  543. { 0, "ahst", tZONE, 10*HOUR }, /* Alaska-Hawaii Standard */
  544. { 0, "nt", tZONE, 11*HOUR }, /* Nome */
  545. { 0, "idlw", tZONE, 12*HOUR }, /* Intl Date Line West */
  546. { 0, "cet", tZONE, -1*HOUR }, /* Central European */
  547. { 0, "met", tZONE, -1*HOUR }, /* Middle European */
  548. { 0, "mewt", tZONE, -1*HOUR }, /* Middle European Winter */
  549. { 0, "mest", tDAYZONE, -1*HOUR }, /* Middle European Summer */
  550. { 0, "swt", tZONE, -1*HOUR }, /* Swedish Winter */
  551. { 0, "sst", tDAYZONE, -1*HOUR }, /* Swedish Summer */
  552. { 0, "fwt", tZONE, -1*HOUR }, /* French Winter */
  553. { 0, "fst", tDAYZONE, -1*HOUR }, /* French Summer */
  554. { 0, "eet", tZONE, -2*HOUR }, /* Eastern Eur, USSR Zone 1 */
  555. { 0, "bt", tZONE, -3*HOUR }, /* Baghdad, USSR Zone 2 */
  556. { 0, "it", tZONE, -3*HOUR-30*MINUTE },/* Iran */
  557. { 0, "zp4", tZONE, -4*HOUR }, /* USSR Zone 3 */
  558. { 0, "zp5", tZONE, -5*HOUR }, /* USSR Zone 4 */
  559. { 0, "ist", tZONE, -5*HOUR-30*MINUTE },/* Indian Standard */
  560. { 0, "zp6", tZONE, -6*HOUR }, /* USSR Zone 5 */
  561. /* { 0, "nst", tZONE, -6.5*HOUR }, */ /* North Sumatra: Conflict */
  562. /* { 0, "sst", tZONE, -7*HOUR }, */ /* So Sumatra, USSR 6: Conflict */
  563. { 0, "wast", tZONE, -7*HOUR }, /* West Australian Standard */
  564. { 0, "wadt", tDAYZONE, -7*HOUR }, /* West Australian Daylight */
  565. { 0, "jt", tZONE, -7*HOUR-30*MINUTE },/* Java (3pm in Cronusland!)*/
  566. { 0, "cct", tZONE, -8*HOUR }, /* China Coast, USSR Zone 7 */
  567. { 0, "jst", tZONE, -9*HOUR }, /* Japan Std, USSR Zone 8 */
  568. { 0, "cast", tZONE, -9*HOUR-30*MINUTE },/* Ctrl Australian Std */
  569. { 0, "cadt", tDAYZONE, -9*HOUR-30*MINUTE },/* Ctrl Australian Daylt */
  570. { 0, "east", tZONE, -10*HOUR }, /* Eastern Australian Std */
  571. { 0, "eadt", tDAYZONE, -10*HOUR }, /* Eastern Australian Daylt */
  572. { 0, "gst", tZONE, -10*HOUR }, /* Guam Std, USSR Zone 9 */
  573. { 0, "nzt", tZONE, -12*HOUR }, /* New Zealand */
  574. { 0, "nzst", tZONE, -12*HOUR }, /* New Zealand Standard */
  575. { 0, "nzdt", tDAYZONE, -12*HOUR }, /* New Zealand Daylight */
  576. { 0, "idle", tZONE, -12*HOUR }, /* Intl Date Line East */
  577. { 0, "dst", tDST, 0 },
  578. /* Time units. */
  579. { 4, "years", tMONTH_UNIT, 12 },
  580. { 5, "months", tMONTH_UNIT, 1 },
  581. { 9, "fortnights", tSEC_UNIT, 14 * DAY },
  582. { 4, "weeks", tSEC_UNIT, 7 * DAY },
  583. { 3, "days", tSEC_UNIT, DAY },
  584. { 4, "hours", tSEC_UNIT, HOUR },
  585. { 3, "minutes", tSEC_UNIT, MINUTE },
  586. { 3, "seconds", tSEC_UNIT, 1 },
  587. /* Relative-time words. */
  588. { 0, "tomorrow", tSEC_UNIT, DAY },
  589. { 0, "yesterday", tSEC_UNIT, -DAY },
  590. { 0, "today", tSEC_UNIT, 0 },
  591. { 0, "now", tSEC_UNIT, 0 },
  592. { 0, "last", tUNUMBER, -1 },
  593. { 0, "this", tSEC_UNIT, 0 },
  594. { 0, "next", tUNUMBER, 2 },
  595. { 0, "first", tUNUMBER, 1 },
  596. { 0, "1st", tUNUMBER, 1 },
  597. /* { 0, "second", tUNUMBER, 2 }, */
  598. { 0, "2nd", tUNUMBER, 2 },
  599. { 0, "third", tUNUMBER, 3 },
  600. { 0, "3rd", tUNUMBER, 3 },
  601. { 0, "fourth", tUNUMBER, 4 },
  602. { 0, "4th", tUNUMBER, 4 },
  603. { 0, "fifth", tUNUMBER, 5 },
  604. { 0, "5th", tUNUMBER, 5 },
  605. { 0, "sixth", tUNUMBER, 6 },
  606. { 0, "seventh", tUNUMBER, 7 },
  607. { 0, "eighth", tUNUMBER, 8 },
  608. { 0, "ninth", tUNUMBER, 9 },
  609. { 0, "tenth", tUNUMBER, 10 },
  610. { 0, "eleventh", tUNUMBER, 11 },
  611. { 0, "twelfth", tUNUMBER, 12 },
  612. { 0, "ago", tAGO, 1 },
  613. /* Military timezones. */
  614. { 0, "a", tZONE, 1*HOUR },
  615. { 0, "b", tZONE, 2*HOUR },
  616. { 0, "c", tZONE, 3*HOUR },
  617. { 0, "d", tZONE, 4*HOUR },
  618. { 0, "e", tZONE, 5*HOUR },
  619. { 0, "f", tZONE, 6*HOUR },
  620. { 0, "g", tZONE, 7*HOUR },
  621. { 0, "h", tZONE, 8*HOUR },
  622. { 0, "i", tZONE, 9*HOUR },
  623. { 0, "k", tZONE, 10*HOUR },
  624. { 0, "l", tZONE, 11*HOUR },
  625. { 0, "m", tZONE, 12*HOUR },
  626. { 0, "n", tZONE, -1*HOUR },
  627. { 0, "o", tZONE, -2*HOUR },
  628. { 0, "p", tZONE, -3*HOUR },
  629. { 0, "q", tZONE, -4*HOUR },
  630. { 0, "r", tZONE, -5*HOUR },
  631. { 0, "s", tZONE, -6*HOUR },
  632. { 0, "t", tZONE, -7*HOUR },
  633. { 0, "u", tZONE, -8*HOUR },
  634. { 0, "v", tZONE, -9*HOUR },
  635. { 0, "w", tZONE, -10*HOUR },
  636. { 0, "x", tZONE, -11*HOUR },
  637. { 0, "y", tZONE, -12*HOUR },
  638. { 0, "z", tZONE, 0*HOUR },
  639. /* End of table. */
  640. { 0, NULL, 0, 0 }
  641. };
  642. /*
  643. * Convert hour/minute/second to count of seconds.
  644. */
  645. static time_t
  646. ToSeconds(time_t Hours, time_t Minutes, time_t Seconds)
  647. {
  648. if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
  649. return -1;
  650. if (Hours < 0 || Hours > 23)
  651. return -1;
  652. return Hours * HOUR + Minutes * MINUTE + Seconds;
  653. }
  654. /*
  655. * Year is either:
  656. * = A number from 0 to 99, which means a year from 1970 to 2069, or
  657. * = The actual year (>=100).
  658. */
  659. static time_t
  660. Convert(time_t Month, time_t Day, time_t Year,
  661. time_t Hours, time_t Minutes, time_t Seconds,
  662. time_t Timezone, enum DSTMODE DSTmode)
  663. {
  664. static int DaysInMonth[12] = {
  665. 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
  666. };
  667. time_t tod;
  668. time_t Julian;
  669. int i;
  670. if (Year < 69)
  671. Year += 2000;
  672. else if (Year < 100)
  673. Year += 1900;
  674. DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
  675. ? 29 : 28;
  676. /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
  677. I'm too lazy to try to check for time_t overflow in another way. */
  678. if (Year < EPOCH || Year >= 2038
  679. || Month < 1 || Month > 12
  680. /* Lint fluff: "conversion from long may lose accuracy" */
  681. || Day < 1 || Day > DaysInMonth[(int)--Month])
  682. return -1;
  683. Julian = Day - 1;
  684. for (i = 0; i < Month; i++)
  685. Julian += DaysInMonth[i];
  686. for (i = EPOCH; i < Year; i++)
  687. Julian += 365 + (i % 4 == 0);
  688. Julian *= DAY;
  689. Julian += Timezone;
  690. if ((tod = ToSeconds(Hours, Minutes, Seconds)) < 0)
  691. return -1;
  692. Julian += tod;
  693. if (DSTmode == DSTon
  694. || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
  695. Julian -= HOUR;
  696. return Julian;
  697. }
  698. static time_t
  699. DSTcorrect(time_t Start, time_t Future)
  700. {
  701. time_t StartDay;
  702. time_t FutureDay;
  703. StartDay = (localtime(&Start)->tm_hour + 1) % 24;
  704. FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
  705. return (Future - Start) + (StartDay - FutureDay) * HOUR;
  706. }
  707. static time_t
  708. RelativeDate(time_t Start, time_t zone, int dstmode,
  709. time_t DayOrdinal, time_t DayNumber)
  710. {
  711. struct tm *tm;
  712. time_t t, now;
  713. t = Start - zone;
  714. tm = gmtime(&t);
  715. now = Start;
  716. now += DAY * ((DayNumber - tm->tm_wday + 7) % 7);
  717. now += 7 * DAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
  718. if (dstmode == DSTmaybe)
  719. return DSTcorrect(Start, now);
  720. return now - Start;
  721. }
  722. static time_t
  723. RelativeMonth(time_t Start, time_t Timezone, time_t RelMonth)
  724. {
  725. struct tm *tm;
  726. time_t Month;
  727. time_t Year;
  728. if (RelMonth == 0)
  729. return 0;
  730. tm = localtime(&Start);
  731. Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
  732. Year = Month / 12;
  733. Month = Month % 12 + 1;
  734. return DSTcorrect(Start,
  735. Convert(Month, (time_t)tm->tm_mday, Year,
  736. (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
  737. Timezone, DSTmaybe));
  738. }
  739. /*
  740. * Tokenizer.
  741. */
  742. static int
  743. nexttoken(char **in, time_t *value)
  744. {
  745. char c;
  746. char buff[64];
  747. for ( ; ; ) {
  748. while (isspace((unsigned char)**in))
  749. ++*in;
  750. /* Skip parenthesized comments. */
  751. if (**in == '(') {
  752. int Count = 0;
  753. do {
  754. c = *(*in)++;
  755. if (c == '\0')
  756. return c;
  757. if (c == '(')
  758. Count++;
  759. else if (c == ')')
  760. Count--;
  761. } while (Count > 0);
  762. continue;
  763. }
  764. /* Try the next token in the word table first. */
  765. /* This allows us to match "2nd", for example. */
  766. {
  767. char *src = *in;
  768. const struct LEXICON *tp;
  769. unsigned i = 0;
  770. /* Force to lowercase and strip '.' characters. */
  771. while (*src != '\0'
  772. && (isalnum((unsigned char)*src) || *src == '.')
  773. && i < sizeof(buff)-1) {
  774. if (*src != '.') {
  775. if (isupper((unsigned char)*src))
  776. buff[i++] = tolower((unsigned char)*src);
  777. else
  778. buff[i++] = *src;
  779. }
  780. src++;
  781. }
  782. buff[i++] = '\0';
  783. /*
  784. * Find the first match. If the word can be
  785. * abbreviated, make sure we match at least
  786. * the minimum abbreviation.
  787. */
  788. for (tp = TimeWords; tp->name; tp++) {
  789. size_t abbrev = tp->abbrev;
  790. if (abbrev == 0)
  791. abbrev = strlen(tp->name);
  792. if (strlen(buff) >= abbrev
  793. && strncmp(tp->name, buff, strlen(buff))
  794. == 0) {
  795. /* Skip over token. */
  796. *in = src;
  797. /* Return the match. */
  798. *value = tp->value;
  799. return tp->type;
  800. }
  801. }
  802. }
  803. /*
  804. * Not in the word table, maybe it's a number. Note:
  805. * Because '-' and '+' have other special meanings, I
  806. * don't deal with signed numbers here.
  807. */
  808. if (isdigit((unsigned char)(c = **in))) {
  809. for (*value = 0; isdigit((unsigned char)(c = *(*in)++)); )
  810. *value = 10 * *value + c - '0';
  811. (*in)--;
  812. return (tUNUMBER);
  813. }
  814. return *(*in)++;
  815. }
  816. }
  817. #define TM_YEAR_ORIGIN 1900
  818. /* Yield A - B, measured in seconds. */
  819. static long
  820. difftm (struct tm *a, struct tm *b)
  821. {
  822. int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
  823. int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
  824. int days = (
  825. /* difference in day of year */
  826. a->tm_yday - b->tm_yday
  827. /* + intervening leap days */
  828. + ((ay >> 2) - (by >> 2))
  829. - (ay/100 - by/100)
  830. + ((ay/100 >> 2) - (by/100 >> 2))
  831. /* + difference in years * 365 */
  832. + (long)(ay-by) * 365
  833. );
  834. return (days * DAY + (a->tm_hour - b->tm_hour) * HOUR
  835. + (a->tm_min - b->tm_min) * MINUTE
  836. + (a->tm_sec - b->tm_sec));
  837. }
  838. /*
  839. *
  840. * The public function.
  841. *
  842. * TODO: tokens[] array should be dynamically sized.
  843. */
  844. time_t
  845. get_date(time_t now, char *p)
  846. {
  847. struct token tokens[256];
  848. struct gdstate _gds;
  849. struct token *lasttoken;
  850. struct gdstate *gds;
  851. struct tm local, *tm;
  852. struct tm gmt, *gmt_ptr;
  853. time_t Start;
  854. time_t tod;
  855. long tzone;
  856. /* Clear out the parsed token array. */
  857. memset(tokens, 0, sizeof(tokens));
  858. /* Initialize the parser state. */
  859. memset(&_gds, 0, sizeof(_gds));
  860. gds = &_gds;
  861. /* Look up the current time. */
  862. memset(&local, 0, sizeof(local));
  863. tm = localtime (&now);
  864. if (tm == NULL)
  865. return -1;
  866. local = *tm;
  867. /* Look up UTC if we can and use that to determine the current
  868. * timezone offset. */
  869. memset(&gmt, 0, sizeof(gmt));
  870. gmt_ptr = gmtime (&now);
  871. if (gmt_ptr != NULL) {
  872. /* Copy, in case localtime and gmtime use the same buffer. */
  873. gmt = *gmt_ptr;
  874. }
  875. if (gmt_ptr != NULL)
  876. tzone = difftm (&gmt, &local);
  877. else
  878. /* This system doesn't understand timezones; fake it. */
  879. tzone = 0;
  880. if(local.tm_isdst)
  881. tzone += HOUR;
  882. /* Tokenize the input string. */
  883. lasttoken = tokens;
  884. while ((lasttoken->token = nexttoken(&p, &lasttoken->value)) != 0) {
  885. ++lasttoken;
  886. if (lasttoken > tokens + 255)
  887. return -1;
  888. }
  889. gds->tokenp = tokens;
  890. /* Match phrases until we run out of input tokens. */
  891. while (gds->tokenp < lasttoken) {
  892. if (!phrase(gds))
  893. return -1;
  894. }
  895. /* Use current local timezone if none was specified. */
  896. if (!gds->HaveZone) {
  897. gds->Timezone = tzone;
  898. gds->DSTmode = DSTmaybe;
  899. }
  900. /* If a timezone was specified, use that for generating the default
  901. * time components instead of the local timezone. */
  902. if (gds->HaveZone && gmt_ptr != NULL) {
  903. now -= gds->Timezone;
  904. gmt_ptr = gmtime (&now);
  905. if (gmt_ptr != NULL)
  906. local = *gmt_ptr;
  907. now += gds->Timezone;
  908. }
  909. if (!gds->HaveYear)
  910. gds->Year = local.tm_year + 1900;
  911. if (!gds->HaveMonth)
  912. gds->Month = local.tm_mon + 1;
  913. if (!gds->HaveDay)
  914. gds->Day = local.tm_mday;
  915. /* Note: No default for hour/min/sec; a specifier that just
  916. * gives date always refers to 00:00 on that date. */
  917. /* If we saw more than one time, timezone, weekday, year, month,
  918. * or day, then give up. */
  919. if (gds->HaveTime > 1 || gds->HaveZone > 1 || gds->HaveWeekDay > 1
  920. || gds->HaveYear > 1 || gds->HaveMonth > 1 || gds->HaveDay > 1)
  921. return -1;
  922. /* Compute an absolute time based on whatever absolute information
  923. * we collected. */
  924. if (gds->HaveYear || gds->HaveMonth || gds->HaveDay
  925. || gds->HaveTime || gds->HaveWeekDay) {
  926. Start = Convert(gds->Month, gds->Day, gds->Year,
  927. gds->Hour, gds->Minutes, gds->Seconds,
  928. gds->Timezone, gds->DSTmode);
  929. if (Start < 0)
  930. return -1;
  931. } else {
  932. Start = now;
  933. if (!gds->HaveRel)
  934. Start -= local.tm_hour * HOUR + local.tm_min * MINUTE
  935. + local.tm_sec;
  936. }
  937. /* Add the relative offset. */
  938. Start += gds->RelSeconds;
  939. Start += RelativeMonth(Start, gds->Timezone, gds->RelMonth);
  940. /* Adjust for day-of-week offsets. */
  941. if (gds->HaveWeekDay
  942. && !(gds->HaveYear || gds->HaveMonth || gds->HaveDay)) {
  943. tod = RelativeDate(Start, gds->Timezone,
  944. gds->DSTmode, gds->DayOrdinal, gds->DayNumber);
  945. Start += tod;
  946. }
  947. /* -1 is an error indicator, so return 0 instead of -1 if
  948. * that's the actual time. */
  949. return Start == -1 ? 0 : Start;
  950. }
  951. #if defined(TEST)
  952. /* ARGSUSED */
  953. int
  954. main(int argc, char **argv)
  955. {
  956. time_t d;
  957. while (*++argv != NULL) {
  958. (void)printf("Input: %s\n", *argv);
  959. d = get_date(*argv);
  960. if (d == -1)
  961. (void)printf("Bad format - couldn't convert.\n");
  962. else
  963. (void)printf("Output: %s\n", ctime(&d));
  964. }
  965. exit(0);
  966. /* NOTREACHED */
  967. }
  968. #endif /* defined(TEST) */