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