archive_parse_date.c 30 KB

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