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