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