archive_getdate.c revision 358090
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. */
52enum DSTMODE { DSTon, DSToff, DSTmaybe };
53/* Meridian:  am or pm. */
54enum { tAM, tPM };
55/* Token types returned by nexttoken() */
56enum { tAGO = 260, tDAY, tDAYZONE, tAMPM, tMONTH, tMONTH_UNIT, tSEC_UNIT,
57       tUNUMBER, tZONE, tDST };
58struct token { int token; time_t value; };
59
60/*
61 * Parser state.
62 */
63struct 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 */
104static int
105timephrase(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 */
174static int
175zonephrase(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 */
207static int
208datephrase(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 */
348static int
349relunitphrase(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 */
423static int
424dayphrase(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 */
452static int
453phrase(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 */
519static 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 */
690static time_t
691Convert(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
752static time_t
753DSTcorrect(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
796static time_t
797RelativeDate(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
832static time_t
833RelativeMonth(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 */
872static int
873nexttoken(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.  */
958static long
959difftm (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 */
984time_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 */
1148int
1149main(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