1238825Smm/*
2238825Smm * This code is in the public domain and has no copyright.
3238825Smm *
4238825Smm * This is a plain C recursive-descent translation of an old
5238825Smm * public-domain YACC grammar that has been used for parsing dates in
6238825Smm * very many open-source projects.
7238825Smm *
8238825Smm * Since the original authors were generous enough to donate their
9238825Smm * work to the public domain, I feel compelled to match their
10238825Smm * generosity.
11238825Smm *
12238825Smm * Tim Kientzle, February 2009.
13238825Smm */
14238825Smm
15238825Smm/*
16238825Smm * Header comment from original getdate.y:
17238825Smm */
18238825Smm
19238825Smm/*
20238825Smm**  Originally written by Steven M. Bellovin <smb@research.att.com> while
21238825Smm**  at the University of North Carolina at Chapel Hill.  Later tweaked by
22238825Smm**  a couple of people on Usenet.  Completely overhauled by Rich $alz
23238825Smm**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
24238825Smm**
25238825Smm**  This grammar has 10 shift/reduce conflicts.
26238825Smm**
27238825Smm**  This code is in the public domain and has no copyright.
28238825Smm*/
29238825Smm
30358088Smm#include "archive_platform.h"
31238825Smm#ifdef __FreeBSD__
32238825Smm#include <sys/cdefs.h>
33238825Smm__FBSDID("$FreeBSD$");
34238825Smm#endif
35238825Smm
36238825Smm#include <ctype.h>
37238825Smm#include <stdio.h>
38238825Smm#include <stdlib.h>
39238825Smm#include <string.h>
40238825Smm#include <time.h>
41238825Smm
42299529Smm#define __LIBARCHIVE_BUILD 1
43299529Smm#include "archive_getdate.h"
44238825Smm
45238825Smm/* Basic time units. */
46238825Smm#define	EPOCH		1970
47238825Smm#define	MINUTE		(60L)
48238825Smm#define	HOUR		(60L * MINUTE)
49238825Smm#define	DAY		(24L * HOUR)
50238825Smm
51238825Smm/* Daylight-savings mode:  on, off, or not yet known. */
52238825Smmenum DSTMODE { DSTon, DSToff, DSTmaybe };
53238825Smm/* Meridian:  am or pm. */
54238825Smmenum { tAM, tPM };
55238825Smm/* Token types returned by nexttoken() */
56238825Smmenum { tAGO = 260, tDAY, tDAYZONE, tAMPM, tMONTH, tMONTH_UNIT, tSEC_UNIT,
57238825Smm       tUNUMBER, tZONE, tDST };
58238825Smmstruct token { int token; time_t value; };
59238825Smm
60238825Smm/*
61238825Smm * Parser state.
62238825Smm */
63238825Smmstruct gdstate {
64238825Smm	struct token *tokenp; /* Pointer to next token. */
65238825Smm	/* HaveXxxx counts how many of this kind of phrase we've seen;
66238825Smm	 * it's a fatal error to have more than one time, zone, day,
67238825Smm	 * or date phrase. */
68238825Smm	int	HaveYear;
69238825Smm	int	HaveMonth;
70238825Smm	int	HaveDay;
71238825Smm	int	HaveWeekDay; /* Day of week */
72238825Smm	int	HaveTime; /* Hour/minute/second */
73238825Smm	int	HaveZone; /* timezone and/or DST info */
74238825Smm	int	HaveRel; /* time offset; we can have more than one */
75238825Smm	/* Absolute time values. */
76238825Smm	time_t	Timezone;  /* Seconds offset from GMT */
77238825Smm	time_t	Day;
78238825Smm	time_t	Hour;
79238825Smm	time_t	Minutes;
80238825Smm	time_t	Month;
81238825Smm	time_t	Seconds;
82238825Smm	time_t	Year;
83238825Smm	/* DST selection */
84238825Smm	enum DSTMODE	DSTmode;
85238825Smm	/* Day of week accounting, e.g., "3rd Tuesday" */
86238825Smm	time_t	DayOrdinal; /* "3" in "3rd Tuesday" */
87238825Smm	time_t	DayNumber; /* "Tuesday" in "3rd Tuesday" */
88238825Smm	/* Relative time values: hour/day/week offsets are measured in
89238825Smm	 * seconds, month/year are counted in months. */
90238825Smm	time_t	RelMonth;
91238825Smm	time_t	RelSeconds;
92238825Smm};
93238825Smm
94238825Smm/*
95238825Smm * A series of functions that recognize certain common time phrases.
96238825Smm * Each function returns 1 if it managed to make sense of some of the
97238825Smm * tokens, zero otherwise.
98238825Smm */
99238825Smm
100238825Smm/*
101238825Smm *  hour:minute or hour:minute:second with optional AM, PM, or numeric
102238825Smm *  timezone offset
103238825Smm */
104238825Smmstatic int
105238825Smmtimephrase(struct gdstate *gds)
106238825Smm{
107238825Smm	if (gds->tokenp[0].token == tUNUMBER
108238825Smm	    && gds->tokenp[1].token == ':'
109238825Smm	    && gds->tokenp[2].token == tUNUMBER
110238825Smm	    && gds->tokenp[3].token == ':'
111238825Smm	    && gds->tokenp[4].token == tUNUMBER) {
112238825Smm		/* "12:14:18" or "22:08:07" */
113238825Smm		++gds->HaveTime;
114238825Smm		gds->Hour = gds->tokenp[0].value;
115238825Smm		gds->Minutes = gds->tokenp[2].value;
116238825Smm		gds->Seconds = gds->tokenp[4].value;
117238825Smm		gds->tokenp += 5;
118238825Smm	}
119238825Smm	else if (gds->tokenp[0].token == tUNUMBER
120238825Smm	    && gds->tokenp[1].token == ':'
121238825Smm	    && gds->tokenp[2].token == tUNUMBER) {
122238825Smm		/* "12:14" or "22:08" */
123238825Smm		++gds->HaveTime;
124238825Smm		gds->Hour = gds->tokenp[0].value;
125238825Smm		gds->Minutes = gds->tokenp[2].value;
126238825Smm		gds->Seconds = 0;
127238825Smm		gds->tokenp += 3;
128238825Smm	}
129238825Smm	else if (gds->tokenp[0].token == tUNUMBER
130238825Smm	    && gds->tokenp[1].token == tAMPM) {
131238825Smm		/* "7" is a time if it's followed by "am" or "pm" */
132238825Smm		++gds->HaveTime;
133238825Smm		gds->Hour = gds->tokenp[0].value;
134238825Smm		gds->Minutes = gds->Seconds = 0;
135238825Smm		/* We'll handle the AM/PM below. */
136238825Smm		gds->tokenp += 1;
137238825Smm	} else {
138238825Smm		/* We can't handle this. */
139238825Smm		return 0;
140238825Smm	}
141238825Smm
142238825Smm	if (gds->tokenp[0].token == tAMPM) {
143238825Smm		/* "7:12pm", "12:20:13am" */
144238825Smm		if (gds->Hour == 12)
145238825Smm			gds->Hour = 0;
146238825Smm		if (gds->tokenp[0].value == tPM)
147238825Smm			gds->Hour += 12;
148238825Smm		gds->tokenp += 1;
149238825Smm	}
150238825Smm	if (gds->tokenp[0].token == '+'
151238825Smm	    && gds->tokenp[1].token == tUNUMBER) {
152238825Smm		/* "7:14+0700" */
153238825Smm		gds->HaveZone++;
154238825Smm		gds->DSTmode = DSToff;
155238825Smm		gds->Timezone = - ((gds->tokenp[1].value / 100) * HOUR
156238825Smm		    + (gds->tokenp[1].value % 100) * MINUTE);
157238825Smm		gds->tokenp += 2;
158238825Smm	}
159238825Smm	if (gds->tokenp[0].token == '-'
160238825Smm	    && gds->tokenp[1].token == tUNUMBER) {
161238825Smm		/* "19:14:12-0530" */
162238825Smm		gds->HaveZone++;
163238825Smm		gds->DSTmode = DSToff;
164238825Smm		gds->Timezone = + ((gds->tokenp[1].value / 100) * HOUR
165238825Smm		    + (gds->tokenp[1].value % 100) * MINUTE);
166238825Smm		gds->tokenp += 2;
167238825Smm	}
168238825Smm	return 1;
169238825Smm}
170238825Smm
171238825Smm/*
172238825Smm * Timezone name, possibly including DST.
173238825Smm */
174238825Smmstatic int
175238825Smmzonephrase(struct gdstate *gds)
176238825Smm{
177238825Smm	if (gds->tokenp[0].token == tZONE
178238825Smm	    && gds->tokenp[1].token == tDST) {
179238825Smm		gds->HaveZone++;
180238825Smm		gds->Timezone = gds->tokenp[0].value;
181238825Smm		gds->DSTmode = DSTon;
182238825Smm		gds->tokenp += 1;
183238825Smm		return 1;
184238825Smm	}
185238825Smm
186238825Smm	if (gds->tokenp[0].token == tZONE) {
187238825Smm		gds->HaveZone++;
188238825Smm		gds->Timezone = gds->tokenp[0].value;
189238825Smm		gds->DSTmode = DSToff;
190238825Smm		gds->tokenp += 1;
191238825Smm		return 1;
192238825Smm	}
193238825Smm
194238825Smm	if (gds->tokenp[0].token == tDAYZONE) {
195238825Smm		gds->HaveZone++;
196238825Smm		gds->Timezone = gds->tokenp[0].value;
197238825Smm		gds->DSTmode = DSTon;
198238825Smm		gds->tokenp += 1;
199238825Smm		return 1;
200238825Smm	}
201238825Smm	return 0;
202238825Smm}
203238825Smm
204238825Smm/*
205238825Smm * Year/month/day in various combinations.
206238825Smm */
207238825Smmstatic int
208238825Smmdatephrase(struct gdstate *gds)
209238825Smm{
210238825Smm	if (gds->tokenp[0].token == tUNUMBER
211238825Smm	    && gds->tokenp[1].token == '/'
212238825Smm	    && gds->tokenp[2].token == tUNUMBER
213238825Smm	    && gds->tokenp[3].token == '/'
214238825Smm	    && gds->tokenp[4].token == tUNUMBER) {
215238825Smm		gds->HaveYear++;
216238825Smm		gds->HaveMonth++;
217238825Smm		gds->HaveDay++;
218238825Smm		if (gds->tokenp[0].value >= 13) {
219238825Smm			/* First number is big:  2004/01/29, 99/02/17 */
220238825Smm			gds->Year = gds->tokenp[0].value;
221238825Smm			gds->Month = gds->tokenp[2].value;
222238825Smm			gds->Day = gds->tokenp[4].value;
223238825Smm		} else if ((gds->tokenp[4].value >= 13)
224238825Smm		    || (gds->tokenp[2].value >= 13)) {
225238825Smm			/* Last number is big:  01/07/98 */
226238825Smm			/* Middle number is big:  01/29/04 */
227238825Smm			gds->Month = gds->tokenp[0].value;
228238825Smm			gds->Day = gds->tokenp[2].value;
229238825Smm			gds->Year = gds->tokenp[4].value;
230238825Smm		} else {
231238825Smm			/* No significant clues: 02/03/04 */
232238825Smm			gds->Month = gds->tokenp[0].value;
233238825Smm			gds->Day = gds->tokenp[2].value;
234238825Smm			gds->Year = gds->tokenp[4].value;
235238825Smm		}
236238825Smm		gds->tokenp += 5;
237238825Smm		return 1;
238238825Smm	}
239238825Smm
240238825Smm	if (gds->tokenp[0].token == tUNUMBER
241238825Smm	    && gds->tokenp[1].token == '/'
242238825Smm	    && gds->tokenp[2].token == tUNUMBER) {
243238825Smm		/* "1/15" */
244238825Smm		gds->HaveMonth++;
245238825Smm		gds->HaveDay++;
246238825Smm		gds->Month = gds->tokenp[0].value;
247238825Smm		gds->Day = gds->tokenp[2].value;
248238825Smm		gds->tokenp += 3;
249238825Smm		return 1;
250238825Smm	}
251238825Smm
252238825Smm	if (gds->tokenp[0].token == tUNUMBER
253238825Smm	    && gds->tokenp[1].token == '-'
254238825Smm	    && gds->tokenp[2].token == tUNUMBER
255238825Smm	    && gds->tokenp[3].token == '-'
256238825Smm	    && gds->tokenp[4].token == tUNUMBER) {
257238825Smm		/* ISO 8601 format.  yyyy-mm-dd.  */
258238825Smm		gds->HaveYear++;
259238825Smm		gds->HaveMonth++;
260238825Smm		gds->HaveDay++;
261238825Smm		gds->Year = gds->tokenp[0].value;
262238825Smm		gds->Month = gds->tokenp[2].value;
263238825Smm		gds->Day = gds->tokenp[4].value;
264238825Smm		gds->tokenp += 5;
265238825Smm		return 1;
266238825Smm	}
267238825Smm
268238825Smm	if (gds->tokenp[0].token == tUNUMBER
269238825Smm	    && gds->tokenp[1].token == '-'
270238825Smm	    && gds->tokenp[2].token == tMONTH
271238825Smm	    && gds->tokenp[3].token == '-'
272238825Smm	    && gds->tokenp[4].token == tUNUMBER) {
273238825Smm		gds->HaveYear++;
274238825Smm		gds->HaveMonth++;
275238825Smm		gds->HaveDay++;
276238825Smm		if (gds->tokenp[0].value > 31) {
277238825Smm			/* e.g. 1992-Jun-17 */
278238825Smm			gds->Year = gds->tokenp[0].value;
279238825Smm			gds->Month = gds->tokenp[2].value;
280238825Smm			gds->Day = gds->tokenp[4].value;
281238825Smm		} else {
282238825Smm			/* e.g. 17-JUN-1992.  */
283238825Smm			gds->Day = gds->tokenp[0].value;
284238825Smm			gds->Month = gds->tokenp[2].value;
285238825Smm			gds->Year = gds->tokenp[4].value;
286238825Smm		}
287238825Smm		gds->tokenp += 5;
288238825Smm		return 1;
289238825Smm	}
290238825Smm
291238825Smm	if (gds->tokenp[0].token == tMONTH
292238825Smm	    && gds->tokenp[1].token == tUNUMBER
293238825Smm	    && gds->tokenp[2].token == ','
294238825Smm	    && gds->tokenp[3].token == tUNUMBER) {
295238825Smm		/* "June 17, 2001" */
296238825Smm		gds->HaveYear++;
297238825Smm		gds->HaveMonth++;
298238825Smm		gds->HaveDay++;
299238825Smm		gds->Month = gds->tokenp[0].value;
300238825Smm		gds->Day = gds->tokenp[1].value;
301238825Smm		gds->Year = gds->tokenp[3].value;
302238825Smm		gds->tokenp += 4;
303238825Smm		return 1;
304238825Smm	}
305238825Smm
306238825Smm	if (gds->tokenp[0].token == tMONTH
307238825Smm	    && gds->tokenp[1].token == tUNUMBER) {
308238825Smm		/* "May 3" */
309238825Smm		gds->HaveMonth++;
310238825Smm		gds->HaveDay++;
311238825Smm		gds->Month = gds->tokenp[0].value;
312238825Smm		gds->Day = gds->tokenp[1].value;
313238825Smm		gds->tokenp += 2;
314238825Smm		return 1;
315238825Smm	}
316238825Smm
317238825Smm	if (gds->tokenp[0].token == tUNUMBER
318238825Smm	    && gds->tokenp[1].token == tMONTH
319238825Smm	    && gds->tokenp[2].token == tUNUMBER) {
320238825Smm		/* "12 Sept 1997" */
321238825Smm		gds->HaveYear++;
322238825Smm		gds->HaveMonth++;
323238825Smm		gds->HaveDay++;
324238825Smm		gds->Day = gds->tokenp[0].value;
325238825Smm		gds->Month = gds->tokenp[1].value;
326238825Smm		gds->Year = gds->tokenp[2].value;
327238825Smm		gds->tokenp += 3;
328238825Smm		return 1;
329238825Smm	}
330238825Smm
331238825Smm	if (gds->tokenp[0].token == tUNUMBER
332238825Smm	    && gds->tokenp[1].token == tMONTH) {
333238825Smm		/* "12 Sept" */
334238825Smm		gds->HaveMonth++;
335238825Smm		gds->HaveDay++;
336238825Smm		gds->Day = gds->tokenp[0].value;
337238825Smm		gds->Month = gds->tokenp[1].value;
338238825Smm		gds->tokenp += 2;
339238825Smm		return 1;
340238825Smm	}
341238825Smm
342238825Smm	return 0;
343238825Smm}
344238825Smm
345238825Smm/*
346238825Smm * Relative time phrase: "tomorrow", "yesterday", "+1 hour", etc.
347238825Smm */
348238825Smmstatic int
349238825Smmrelunitphrase(struct gdstate *gds)
350238825Smm{
351238825Smm	if (gds->tokenp[0].token == '-'
352238825Smm	    && gds->tokenp[1].token == tUNUMBER
353238825Smm	    && gds->tokenp[2].token == tSEC_UNIT) {
354238825Smm		/* "-3 hours" */
355238825Smm		gds->HaveRel++;
356238825Smm		gds->RelSeconds -= gds->tokenp[1].value * gds->tokenp[2].value;
357238825Smm		gds->tokenp += 3;
358238825Smm		return 1;
359238825Smm	}
360238825Smm	if (gds->tokenp[0].token == '+'
361238825Smm	    && gds->tokenp[1].token == tUNUMBER
362238825Smm	    && gds->tokenp[2].token == tSEC_UNIT) {
363238825Smm		/* "+1 minute" */
364238825Smm		gds->HaveRel++;
365238825Smm		gds->RelSeconds += gds->tokenp[1].value * gds->tokenp[2].value;
366238825Smm		gds->tokenp += 3;
367238825Smm		return 1;
368238825Smm	}
369238825Smm	if (gds->tokenp[0].token == tUNUMBER
370238825Smm	    && gds->tokenp[1].token == tSEC_UNIT) {
371238825Smm		/* "1 day" */
372238825Smm		gds->HaveRel++;
373246229Skientzle		gds->RelSeconds += gds->tokenp[0].value * gds->tokenp[1].value;
374246229Skientzle		gds->tokenp += 2;
375238825Smm		return 1;
376238825Smm	}
377238825Smm	if (gds->tokenp[0].token == '-'
378238825Smm	    && gds->tokenp[1].token == tUNUMBER
379238825Smm	    && gds->tokenp[2].token == tMONTH_UNIT) {
380238825Smm		/* "-3 months" */
381238825Smm		gds->HaveRel++;
382238825Smm		gds->RelMonth -= gds->tokenp[1].value * gds->tokenp[2].value;
383238825Smm		gds->tokenp += 3;
384238825Smm		return 1;
385238825Smm	}
386238825Smm	if (gds->tokenp[0].token == '+'
387238825Smm	    && gds->tokenp[1].token == tUNUMBER
388238825Smm	    && gds->tokenp[2].token == tMONTH_UNIT) {
389238825Smm		/* "+5 years" */
390238825Smm		gds->HaveRel++;
391238825Smm		gds->RelMonth += gds->tokenp[1].value * gds->tokenp[2].value;
392238825Smm		gds->tokenp += 3;
393238825Smm		return 1;
394238825Smm	}
395238825Smm	if (gds->tokenp[0].token == tUNUMBER
396238825Smm	    && gds->tokenp[1].token == tMONTH_UNIT) {
397238825Smm		/* "2 years" */
398238825Smm		gds->HaveRel++;
399238825Smm		gds->RelMonth += gds->tokenp[0].value * gds->tokenp[1].value;
400238825Smm		gds->tokenp += 2;
401238825Smm		return 1;
402238825Smm	}
403238825Smm	if (gds->tokenp[0].token == tSEC_UNIT) {
404238825Smm		/* "now", "tomorrow" */
405238825Smm		gds->HaveRel++;
406238825Smm		gds->RelSeconds += gds->tokenp[0].value;
407246229Skientzle		gds->tokenp += 1;
408238825Smm		return 1;
409238825Smm	}
410238825Smm	if (gds->tokenp[0].token == tMONTH_UNIT) {
411238825Smm		/* "month" */
412238825Smm		gds->HaveRel++;
413238825Smm		gds->RelMonth += gds->tokenp[0].value;
414238825Smm		gds->tokenp += 1;
415238825Smm		return 1;
416238825Smm	}
417238825Smm	return 0;
418238825Smm}
419238825Smm
420238825Smm/*
421238825Smm * Day of the week specification.
422238825Smm */
423238825Smmstatic int
424238825Smmdayphrase(struct gdstate *gds)
425238825Smm{
426238825Smm	if (gds->tokenp[0].token == tDAY) {
427238825Smm		/* "tues", "wednesday," */
428238825Smm		gds->HaveWeekDay++;
429238825Smm		gds->DayOrdinal = 1;
430238825Smm		gds->DayNumber = gds->tokenp[0].value;
431238825Smm		gds->tokenp += 1;
432238825Smm		if (gds->tokenp[0].token == ',')
433238825Smm			gds->tokenp += 1;
434238825Smm		return 1;
435238825Smm	}
436238825Smm	if (gds->tokenp[0].token == tUNUMBER
437238825Smm		&& gds->tokenp[1].token == tDAY) {
438238825Smm		/* "second tues" "3 wed" */
439238825Smm		gds->HaveWeekDay++;
440238825Smm		gds->DayOrdinal = gds->tokenp[0].value;
441238825Smm		gds->DayNumber = gds->tokenp[1].value;
442238825Smm		gds->tokenp += 2;
443238825Smm		return 1;
444238825Smm	}
445238825Smm	return 0;
446238825Smm}
447238825Smm
448238825Smm/*
449238825Smm * Try to match a phrase using one of the above functions.
450238825Smm * This layer also deals with a couple of generic issues.
451238825Smm */
452238825Smmstatic int
453238825Smmphrase(struct gdstate *gds)
454238825Smm{
455238825Smm	if (timephrase(gds))
456238825Smm		return 1;
457238825Smm	if (zonephrase(gds))
458238825Smm		return 1;
459238825Smm	if (datephrase(gds))
460238825Smm		return 1;
461238825Smm	if (dayphrase(gds))
462238825Smm		return 1;
463238825Smm	if (relunitphrase(gds)) {
464238825Smm		if (gds->tokenp[0].token == tAGO) {
465238825Smm			gds->RelSeconds = -gds->RelSeconds;
466238825Smm			gds->RelMonth = -gds->RelMonth;
467238825Smm			gds->tokenp += 1;
468238825Smm		}
469238825Smm		return 1;
470238825Smm	}
471238825Smm
472238825Smm	/* Bare numbers sometimes have meaning. */
473238825Smm	if (gds->tokenp[0].token == tUNUMBER) {
474238825Smm		if (gds->HaveTime && !gds->HaveYear && !gds->HaveRel) {
475238825Smm			gds->HaveYear++;
476238825Smm			gds->Year = gds->tokenp[0].value;
477238825Smm			gds->tokenp += 1;
478238825Smm			return 1;
479238825Smm		}
480238825Smm
481238825Smm		if(gds->tokenp[0].value > 10000) {
482238825Smm			/* "20040301" */
483238825Smm			gds->HaveYear++;
484238825Smm			gds->HaveMonth++;
485238825Smm			gds->HaveDay++;
486238825Smm			gds->Day= (gds->tokenp[0].value)%100;
487238825Smm			gds->Month= (gds->tokenp[0].value/100)%100;
488238825Smm			gds->Year = gds->tokenp[0].value/10000;
489238825Smm			gds->tokenp += 1;
490238825Smm			return 1;
491238825Smm		}
492238825Smm
493238825Smm		if (gds->tokenp[0].value < 24) {
494238825Smm			gds->HaveTime++;
495238825Smm			gds->Hour = gds->tokenp[0].value;
496238825Smm			gds->Minutes = 0;
497238825Smm			gds->Seconds = 0;
498238825Smm			gds->tokenp += 1;
499238825Smm			return 1;
500238825Smm		}
501238825Smm
502238825Smm		if ((gds->tokenp[0].value / 100 < 24)
503238825Smm		    && (gds->tokenp[0].value % 100 < 60)) {
504238825Smm			/* "513" is same as "5:13" */
505238825Smm			gds->Hour = gds->tokenp[0].value / 100;
506238825Smm			gds->Minutes = gds->tokenp[0].value % 100;
507238825Smm			gds->Seconds = 0;
508238825Smm			gds->tokenp += 1;
509238825Smm			return 1;
510238825Smm		}
511238825Smm	}
512238825Smm
513238825Smm	return 0;
514238825Smm}
515238825Smm
516238825Smm/*
517238825Smm * A dictionary of time words.
518238825Smm */
519238825Smmstatic struct LEXICON {
520238825Smm	size_t		abbrev;
521238825Smm	const char	*name;
522238825Smm	int		type;
523238825Smm	time_t		value;
524238825Smm} const TimeWords[] = {
525238825Smm	/* am/pm */
526238825Smm	{ 0, "am",		tAMPM,	tAM },
527238825Smm	{ 0, "pm",		tAMPM,	tPM },
528238825Smm
529238825Smm	/* Month names. */
530238825Smm	{ 3, "january",		tMONTH,  1 },
531238825Smm	{ 3, "february",	tMONTH,  2 },
532238825Smm	{ 3, "march",		tMONTH,  3 },
533238825Smm	{ 3, "april",		tMONTH,  4 },
534238825Smm	{ 3, "may",		tMONTH,  5 },
535238825Smm	{ 3, "june",		tMONTH,  6 },
536238825Smm	{ 3, "july",		tMONTH,  7 },
537238825Smm	{ 3, "august",		tMONTH,  8 },
538238825Smm	{ 3, "september",	tMONTH,  9 },
539238825Smm	{ 3, "october",		tMONTH, 10 },
540238825Smm	{ 3, "november",	tMONTH, 11 },
541238825Smm	{ 3, "december",	tMONTH, 12 },
542238825Smm
543238825Smm	/* Days of the week. */
544238825Smm	{ 2, "sunday",		tDAY, 0 },
545238825Smm	{ 3, "monday",		tDAY, 1 },
546238825Smm	{ 2, "tuesday",		tDAY, 2 },
547238825Smm	{ 3, "wednesday",	tDAY, 3 },
548238825Smm	{ 2, "thursday",	tDAY, 4 },
549238825Smm	{ 2, "friday",		tDAY, 5 },
550238825Smm	{ 2, "saturday",	tDAY, 6 },
551238825Smm
552238825Smm	/* Timezones: Offsets are in seconds. */
553238825Smm	{ 0, "gmt",  tZONE,     0*HOUR }, /* Greenwich Mean */
554238825Smm	{ 0, "ut",   tZONE,     0*HOUR }, /* Universal (Coordinated) */
555238825Smm	{ 0, "utc",  tZONE,     0*HOUR },
556238825Smm	{ 0, "wet",  tZONE,     0*HOUR }, /* Western European */
557238825Smm	{ 0, "bst",  tDAYZONE,  0*HOUR }, /* British Summer */
558238825Smm	{ 0, "wat",  tZONE,     1*HOUR }, /* West Africa */
559238825Smm	{ 0, "at",   tZONE,     2*HOUR }, /* Azores */
560238825Smm	/* { 0, "bst", tZONE, 3*HOUR }, */ /* Brazil Standard: Conflict */
561238825Smm	/* { 0, "gst", tZONE, 3*HOUR }, */ /* Greenland Standard: Conflict*/
562238825Smm	{ 0, "nft",  tZONE,     3*HOUR+30*MINUTE }, /* Newfoundland */
563238825Smm	{ 0, "nst",  tZONE,     3*HOUR+30*MINUTE }, /* Newfoundland Standard */
564238825Smm	{ 0, "ndt",  tDAYZONE,  3*HOUR+30*MINUTE }, /* Newfoundland Daylight */
565238825Smm	{ 0, "ast",  tZONE,     4*HOUR }, /* Atlantic Standard */
566238825Smm	{ 0, "adt",  tDAYZONE,  4*HOUR }, /* Atlantic Daylight */
567238825Smm	{ 0, "est",  tZONE,     5*HOUR }, /* Eastern Standard */
568238825Smm	{ 0, "edt",  tDAYZONE,  5*HOUR }, /* Eastern Daylight */
569238825Smm	{ 0, "cst",  tZONE,     6*HOUR }, /* Central Standard */
570238825Smm	{ 0, "cdt",  tDAYZONE,  6*HOUR }, /* Central Daylight */
571238825Smm	{ 0, "mst",  tZONE,     7*HOUR }, /* Mountain Standard */
572238825Smm	{ 0, "mdt",  tDAYZONE,  7*HOUR }, /* Mountain Daylight */
573238825Smm	{ 0, "pst",  tZONE,     8*HOUR }, /* Pacific Standard */
574238825Smm	{ 0, "pdt",  tDAYZONE,  8*HOUR }, /* Pacific Daylight */
575238825Smm	{ 0, "yst",  tZONE,     9*HOUR }, /* Yukon Standard */
576238825Smm	{ 0, "ydt",  tDAYZONE,  9*HOUR }, /* Yukon Daylight */
577238825Smm	{ 0, "hst",  tZONE,     10*HOUR }, /* Hawaii Standard */
578238825Smm	{ 0, "hdt",  tDAYZONE,  10*HOUR }, /* Hawaii Daylight */
579238825Smm	{ 0, "cat",  tZONE,     10*HOUR }, /* Central Alaska */
580238825Smm	{ 0, "ahst", tZONE,     10*HOUR }, /* Alaska-Hawaii Standard */
581238825Smm	{ 0, "nt",   tZONE,     11*HOUR }, /* Nome */
582238825Smm	{ 0, "idlw", tZONE,     12*HOUR }, /* Intl Date Line West */
583238825Smm	{ 0, "cet",  tZONE,     -1*HOUR }, /* Central European */
584238825Smm	{ 0, "met",  tZONE,     -1*HOUR }, /* Middle European */
585238825Smm	{ 0, "mewt", tZONE,     -1*HOUR }, /* Middle European Winter */
586238825Smm	{ 0, "mest", tDAYZONE,  -1*HOUR }, /* Middle European Summer */
587238825Smm	{ 0, "swt",  tZONE,     -1*HOUR }, /* Swedish Winter */
588238825Smm	{ 0, "sst",  tDAYZONE,  -1*HOUR }, /* Swedish Summer */
589238825Smm	{ 0, "fwt",  tZONE,     -1*HOUR }, /* French Winter */
590238825Smm	{ 0, "fst",  tDAYZONE,  -1*HOUR }, /* French Summer */
591238825Smm	{ 0, "eet",  tZONE,     -2*HOUR }, /* Eastern Eur, USSR Zone 1 */
592238825Smm	{ 0, "bt",   tZONE,     -3*HOUR }, /* Baghdad, USSR Zone 2 */
593238825Smm	{ 0, "it",   tZONE,     -3*HOUR-30*MINUTE },/* Iran */
594238825Smm	{ 0, "zp4",  tZONE,     -4*HOUR }, /* USSR Zone 3 */
595238825Smm	{ 0, "zp5",  tZONE,     -5*HOUR }, /* USSR Zone 4 */
596238825Smm	{ 0, "ist",  tZONE,     -5*HOUR-30*MINUTE },/* Indian Standard */
597238825Smm	{ 0, "zp6",  tZONE,     -6*HOUR }, /* USSR Zone 5 */
598238825Smm	/* { 0, "nst",  tZONE, -6.5*HOUR }, */ /* North Sumatra: Conflict */
599238825Smm	/* { 0, "sst", tZONE, -7*HOUR }, */ /* So Sumatra, USSR 6: Conflict */
600238825Smm	{ 0, "wast", tZONE,     -7*HOUR }, /* West Australian Standard */
601238825Smm	{ 0, "wadt", tDAYZONE,  -7*HOUR }, /* West Australian Daylight */
602238825Smm	{ 0, "jt",   tZONE,     -7*HOUR-30*MINUTE },/* Java (3pm in Cronusland!)*/
603238825Smm	{ 0, "cct",  tZONE,     -8*HOUR }, /* China Coast, USSR Zone 7 */
604238825Smm	{ 0, "jst",  tZONE,     -9*HOUR }, /* Japan Std, USSR Zone 8 */
605238825Smm	{ 0, "cast", tZONE,     -9*HOUR-30*MINUTE },/* Ctrl Australian Std */
606238825Smm	{ 0, "cadt", tDAYZONE,  -9*HOUR-30*MINUTE },/* Ctrl Australian Daylt */
607238825Smm	{ 0, "east", tZONE,     -10*HOUR }, /* Eastern Australian Std */
608238825Smm	{ 0, "eadt", tDAYZONE,  -10*HOUR }, /* Eastern Australian Daylt */
609238825Smm	{ 0, "gst",  tZONE,     -10*HOUR }, /* Guam Std, USSR Zone 9 */
610238825Smm	{ 0, "nzt",  tZONE,     -12*HOUR }, /* New Zealand */
611238825Smm	{ 0, "nzst", tZONE,     -12*HOUR }, /* New Zealand Standard */
612238825Smm	{ 0, "nzdt", tDAYZONE,  -12*HOUR }, /* New Zealand Daylight */
613238825Smm	{ 0, "idle", tZONE,     -12*HOUR }, /* Intl Date Line East */
614238825Smm
615238825Smm	{ 0, "dst",  tDST,		0 },
616238825Smm
617238825Smm	/* Time units. */
618238825Smm	{ 4, "years",		tMONTH_UNIT,	12 },
619238825Smm	{ 5, "months",		tMONTH_UNIT,	1 },
620238825Smm	{ 9, "fortnights",	tSEC_UNIT,	14 * DAY },
621238825Smm	{ 4, "weeks",		tSEC_UNIT,	7 * DAY },
622238825Smm	{ 3, "days",		tSEC_UNIT,	DAY },
623238825Smm	{ 4, "hours",		tSEC_UNIT,	HOUR },
624238825Smm	{ 3, "minutes",		tSEC_UNIT,	MINUTE },
625238825Smm	{ 3, "seconds",		tSEC_UNIT,	1 },
626238825Smm
627238825Smm	/* Relative-time words. */
628238825Smm	{ 0, "tomorrow",	tSEC_UNIT,	DAY },
629238825Smm	{ 0, "yesterday",	tSEC_UNIT,	-DAY },
630238825Smm	{ 0, "today",		tSEC_UNIT,	0 },
631238825Smm	{ 0, "now",		tSEC_UNIT,	0 },
632238825Smm	{ 0, "last",		tUNUMBER,	-1 },
633238825Smm	{ 0, "this",		tSEC_UNIT,	0 },
634238825Smm	{ 0, "next",		tUNUMBER,	2 },
635238825Smm	{ 0, "first",		tUNUMBER,	1 },
636238825Smm	{ 0, "1st",		tUNUMBER,	1 },
637238825Smm/*	{ 0, "second",		tUNUMBER,	2 }, */
638238825Smm	{ 0, "2nd",		tUNUMBER,	2 },
639238825Smm	{ 0, "third",		tUNUMBER,	3 },
640238825Smm	{ 0, "3rd",		tUNUMBER,	3 },
641238825Smm	{ 0, "fourth",		tUNUMBER,	4 },
642238825Smm	{ 0, "4th",		tUNUMBER,	4 },
643238825Smm	{ 0, "fifth",		tUNUMBER,	5 },
644238825Smm	{ 0, "5th",		tUNUMBER,	5 },
645238825Smm	{ 0, "sixth",		tUNUMBER,	6 },
646238825Smm	{ 0, "seventh",		tUNUMBER,	7 },
647238825Smm	{ 0, "eighth",		tUNUMBER,	8 },
648238825Smm	{ 0, "ninth",		tUNUMBER,	9 },
649238825Smm	{ 0, "tenth",		tUNUMBER,	10 },
650238825Smm	{ 0, "eleventh",	tUNUMBER,	11 },
651238825Smm	{ 0, "twelfth",		tUNUMBER,	12 },
652238825Smm	{ 0, "ago",		tAGO,		1 },
653238825Smm
654238825Smm	/* Military timezones. */
655238825Smm	{ 0, "a",	tZONE,	1*HOUR },
656238825Smm	{ 0, "b",	tZONE,	2*HOUR },
657238825Smm	{ 0, "c",	tZONE,	3*HOUR },
658238825Smm	{ 0, "d",	tZONE,	4*HOUR },
659238825Smm	{ 0, "e",	tZONE,	5*HOUR },
660238825Smm	{ 0, "f",	tZONE,	6*HOUR },
661238825Smm	{ 0, "g",	tZONE,	7*HOUR },
662238825Smm	{ 0, "h",	tZONE,	8*HOUR },
663238825Smm	{ 0, "i",	tZONE,	9*HOUR },
664238825Smm	{ 0, "k",	tZONE,	10*HOUR },
665238825Smm	{ 0, "l",	tZONE,	11*HOUR },
666238825Smm	{ 0, "m",	tZONE,	12*HOUR },
667238825Smm	{ 0, "n",	tZONE,	-1*HOUR },
668238825Smm	{ 0, "o",	tZONE,	-2*HOUR },
669238825Smm	{ 0, "p",	tZONE,	-3*HOUR },
670238825Smm	{ 0, "q",	tZONE,	-4*HOUR },
671238825Smm	{ 0, "r",	tZONE,	-5*HOUR },
672238825Smm	{ 0, "s",	tZONE,	-6*HOUR },
673238825Smm	{ 0, "t",	tZONE,	-7*HOUR },
674238825Smm	{ 0, "u",	tZONE,	-8*HOUR },
675238825Smm	{ 0, "v",	tZONE,	-9*HOUR },
676238825Smm	{ 0, "w",	tZONE,	-10*HOUR },
677238825Smm	{ 0, "x",	tZONE,	-11*HOUR },
678238825Smm	{ 0, "y",	tZONE,	-12*HOUR },
679238825Smm	{ 0, "z",	tZONE,	0*HOUR },
680238825Smm
681238825Smm	/* End of table. */
682238825Smm	{ 0, NULL,	0,	0 }
683238825Smm};
684238825Smm
685238825Smm/*
686238825Smm * Year is either:
687238825Smm *  = A number from 0 to 99, which means a year from 1970 to 2069, or
688238825Smm *  = The actual year (>=100).
689238825Smm */
690238825Smmstatic time_t
691238825SmmConvert(time_t Month, time_t Day, time_t Year,
692238825Smm	time_t Hours, time_t Minutes, time_t Seconds,
693238825Smm	time_t Timezone, enum DSTMODE DSTmode)
694238825Smm{
695318482Smm	signed char DaysInMonth[12] = {
696238825Smm		31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
697238825Smm	};
698358088Smm	time_t		Julian;
699358088Smm	int		i;
700358088Smm	struct tm	*ltime;
701358088Smm#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S)
702358088Smm	struct tm	tmbuf;
703358088Smm#endif
704358088Smm#if defined(HAVE__LOCALTIME64_S)
705358088Smm	errno_t		terr;
706358088Smm	__time64_t	tmptime;
707358088Smm#endif
708238825Smm
709238825Smm	if (Year < 69)
710238825Smm		Year += 2000;
711238825Smm	else if (Year < 100)
712238825Smm		Year += 1900;
713238825Smm	DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
714238825Smm	    ? 29 : 28;
715238825Smm	/* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
716238825Smm	   I'm too lazy to try to check for time_t overflow in another way.  */
717238825Smm	if (Year < EPOCH || Year > 2038
718238825Smm	    || Month < 1 || Month > 12
719238825Smm	    /* Lint fluff:  "conversion from long may lose accuracy" */
720238825Smm	    || Day < 1 || Day > DaysInMonth[(int)--Month]
721238825Smm	    || Hours < 0 || Hours > 23
722238825Smm	    || Minutes < 0 || Minutes > 59
723238825Smm	    || Seconds < 0 || Seconds > 59)
724238825Smm		return -1;
725238825Smm
726238825Smm	Julian = Day - 1;
727238825Smm	for (i = 0; i < Month; i++)
728238825Smm		Julian += DaysInMonth[i];
729238825Smm	for (i = EPOCH; i < Year; i++)
730238825Smm		Julian += 365 + (i % 4 == 0);
731238825Smm	Julian *= DAY;
732238825Smm	Julian += Timezone;
733238825Smm	Julian += Hours * HOUR + Minutes * MINUTE + Seconds;
734358088Smm#if defined(HAVE_LOCALTIME_R)
735358088Smm	ltime = localtime_r(&Julian, &tmbuf);
736358088Smm#elif defined(HAVE__LOCALTIME64_S)
737358088Smm	tmptime = Julian;
738358088Smm	terr = _localtime64_s(&tmbuf, &tmptime);
739358088Smm	if (terr)
740358088Smm		ltime = NULL;
741358088Smm	else
742358088Smm		ltime = &tmbuf;
743358088Smm#else
744358088Smm	ltime = localtime(&Julian);
745358088Smm#endif
746238825Smm	if (DSTmode == DSTon
747358088Smm	    || (DSTmode == DSTmaybe && ltime->tm_isdst))
748238825Smm		Julian -= HOUR;
749238825Smm	return Julian;
750238825Smm}
751238825Smm
752238825Smmstatic time_t
753238825SmmDSTcorrect(time_t Start, time_t Future)
754238825Smm{
755358088Smm	time_t		StartDay;
756358088Smm	time_t		FutureDay;
757358088Smm	struct tm	*ltime;
758358088Smm#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S)
759358088Smm	struct tm	tmbuf;
760358088Smm#endif
761358088Smm#if defined(HAVE__LOCALTIME64_S)
762358088Smm	errno_t		terr;
763358088Smm	__time64_t	tmptime;
764358088Smm#endif
765238825Smm
766358088Smm#if defined(HAVE_LOCALTIME_R)
767358088Smm	ltime = localtime_r(&Start, &tmbuf);
768358088Smm#elif defined(HAVE__LOCALTIME64_S)
769358088Smm	tmptime = Start;
770358088Smm	terr = _localtime64_s(&tmbuf, &tmptime);
771358088Smm	if (terr)
772358088Smm		ltime = NULL;
773358088Smm	else
774358088Smm		ltime = &tmbuf;
775358088Smm#else
776358088Smm	ltime = localtime(&Start);
777358088Smm#endif
778358088Smm	StartDay = (ltime->tm_hour + 1) % 24;
779358088Smm#if defined(HAVE_LOCALTIME_R)
780358088Smm	ltime = localtime_r(&Future, &tmbuf);
781358088Smm#elif defined(HAVE__LOCALTIME64_S)
782358088Smm	tmptime = Future;
783358088Smm	terr = _localtime64_s(&tmbuf, &tmptime);
784358088Smm	if (terr)
785358088Smm		ltime = NULL;
786358088Smm	else
787358088Smm		ltime = &tmbuf;
788358088Smm#else
789358088Smm	ltime = localtime(&Future);
790358088Smm#endif
791358088Smm	FutureDay = (ltime->tm_hour + 1) % 24;
792238825Smm	return (Future - Start) + (StartDay - FutureDay) * HOUR;
793238825Smm}
794238825Smm
795238825Smm
796238825Smmstatic time_t
797238825SmmRelativeDate(time_t Start, time_t zone, int dstmode,
798238825Smm    time_t DayOrdinal, time_t DayNumber)
799238825Smm{
800238825Smm	struct tm	*tm;
801238825Smm	time_t	t, now;
802358088Smm#if defined(HAVE_GMTIME_R) || defined(HAVE__GMTIME64_S)
803358088Smm	struct tm	tmbuf;
804358088Smm#endif
805358088Smm#if defined(HAVE__GMTIME64_S)
806358088Smm	errno_t		terr;
807358088Smm	__time64_t	tmptime;
808358088Smm#endif
809238825Smm
810238825Smm	t = Start - zone;
811358088Smm#if defined(HAVE_GMTIME_R)
812358088Smm	tm = gmtime_r(&t, &tmbuf);
813358088Smm#elif defined(HAVE__GMTIME64_S)
814358088Smm	tmptime = t;
815358088Smm	terr = _gmtime64_s(&tmbuf, &tmptime);
816358088Smm	if (terr)
817358088Smm		tm = NULL;
818358088Smm	else
819358088Smm		tm = &tmbuf;
820358088Smm#else
821238825Smm	tm = gmtime(&t);
822358088Smm#endif
823238825Smm	now = Start;
824238825Smm	now += DAY * ((DayNumber - tm->tm_wday + 7) % 7);
825238825Smm	now += 7 * DAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
826238825Smm	if (dstmode == DSTmaybe)
827238825Smm		return DSTcorrect(Start, now);
828238825Smm	return now - Start;
829238825Smm}
830238825Smm
831238825Smm
832238825Smmstatic time_t
833238825SmmRelativeMonth(time_t Start, time_t Timezone, time_t RelMonth)
834238825Smm{
835238825Smm	struct tm	*tm;
836238825Smm	time_t	Month;
837238825Smm	time_t	Year;
838358088Smm#if defined(HAVE_LOCALTIME_R) || defined(HAVE__LOCALTIME64_S)
839358088Smm	struct tm	tmbuf;
840358088Smm#endif
841358088Smm#if defined(HAVE__LOCALTIME64_S)
842358088Smm	errno_t		terr;
843358088Smm	__time64_t	tmptime;
844358088Smm#endif
845238825Smm
846238825Smm	if (RelMonth == 0)
847238825Smm		return 0;
848358088Smm#if defined(HAVE_LOCALTIME_R)
849358088Smm	tm = localtime_r(&Start, &tmbuf);
850358088Smm#elif defined(HAVE__LOCALTIME64_S)
851358088Smm	tmptime = Start;
852358088Smm	terr = _localtime64_s(&tmbuf, &tmptime);
853358088Smm	if (terr)
854358088Smm		tm = NULL;
855358088Smm	else
856358088Smm		tm = &tmbuf;
857358088Smm#else
858238825Smm	tm = localtime(&Start);
859358088Smm#endif
860238825Smm	Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
861238825Smm	Year = Month / 12;
862238825Smm	Month = Month % 12 + 1;
863238825Smm	return DSTcorrect(Start,
864238825Smm	    Convert(Month, (time_t)tm->tm_mday, Year,
865238825Smm		(time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
866238825Smm		Timezone, DSTmaybe));
867238825Smm}
868238825Smm
869238825Smm/*
870238825Smm * Tokenizer.
871238825Smm */
872238825Smmstatic int
873299529Smmnexttoken(const char **in, time_t *value)
874238825Smm{
875238825Smm	char	c;
876238825Smm	char	buff[64];
877238825Smm
878238825Smm	for ( ; ; ) {
879238825Smm		while (isspace((unsigned char)**in))
880238825Smm			++*in;
881238825Smm
882238825Smm		/* Skip parenthesized comments. */
883238825Smm		if (**in == '(') {
884238825Smm			int Count = 0;
885238825Smm			do {
886238825Smm				c = *(*in)++;
887238825Smm				if (c == '\0')
888238825Smm					return c;
889238825Smm				if (c == '(')
890238825Smm					Count++;
891238825Smm				else if (c == ')')
892238825Smm					Count--;
893238825Smm			} while (Count > 0);
894238825Smm			continue;
895238825Smm		}
896238825Smm
897238825Smm		/* Try the next token in the word table first. */
898238825Smm		/* This allows us to match "2nd", for example. */
899238825Smm		{
900299529Smm			const char *src = *in;
901238825Smm			const struct LEXICON *tp;
902238825Smm			unsigned i = 0;
903238825Smm
904238825Smm			/* Force to lowercase and strip '.' characters. */
905238825Smm			while (*src != '\0'
906238825Smm			    && (isalnum((unsigned char)*src) || *src == '.')
907238825Smm			    && i < sizeof(buff)-1) {
908238825Smm				if (*src != '.') {
909238825Smm					if (isupper((unsigned char)*src))
910238825Smm						buff[i++] = tolower((unsigned char)*src);
911238825Smm					else
912238825Smm						buff[i++] = *src;
913238825Smm				}
914238825Smm				src++;
915238825Smm			}
916238825Smm			buff[i] = '\0';
917238825Smm
918238825Smm			/*
919238825Smm			 * Find the first match.  If the word can be
920238825Smm			 * abbreviated, make sure we match at least
921238825Smm			 * the minimum abbreviation.
922238825Smm			 */
923238825Smm			for (tp = TimeWords; tp->name; tp++) {
924238825Smm				size_t abbrev = tp->abbrev;
925238825Smm				if (abbrev == 0)
926238825Smm					abbrev = strlen(tp->name);
927238825Smm				if (strlen(buff) >= abbrev
928238825Smm				    && strncmp(tp->name, buff, strlen(buff))
929238825Smm				    	== 0) {
930238825Smm					/* Skip over token. */
931238825Smm					*in = src;
932238825Smm					/* Return the match. */
933238825Smm					*value = tp->value;
934238825Smm					return tp->type;
935238825Smm				}
936238825Smm			}
937238825Smm		}
938238825Smm
939238825Smm		/*
940238825Smm		 * Not in the word table, maybe it's a number.  Note:
941238825Smm		 * Because '-' and '+' have other special meanings, I
942238825Smm		 * don't deal with signed numbers here.
943238825Smm		 */
944238825Smm		if (isdigit((unsigned char)(c = **in))) {
945238825Smm			for (*value = 0; isdigit((unsigned char)(c = *(*in)++)); )
946238825Smm				*value = 10 * *value + c - '0';
947238825Smm			(*in)--;
948238825Smm			return (tUNUMBER);
949238825Smm		}
950238825Smm
951238825Smm		return *(*in)++;
952238825Smm	}
953238825Smm}
954238825Smm
955238825Smm#define	TM_YEAR_ORIGIN 1900
956238825Smm
957238825Smm/* Yield A - B, measured in seconds.  */
958238825Smmstatic long
959238825Smmdifftm (struct tm *a, struct tm *b)
960238825Smm{
961238825Smm	int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
962238825Smm	int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
963238825Smm	int days = (
964238825Smm		/* difference in day of year */
965238825Smm		a->tm_yday - b->tm_yday
966238825Smm		/* + intervening leap days */
967238825Smm		+  ((ay >> 2) - (by >> 2))
968238825Smm		-  (ay/100 - by/100)
969238825Smm		+  ((ay/100 >> 2) - (by/100 >> 2))
970238825Smm		/* + difference in years * 365 */
971238825Smm		+  (long)(ay-by) * 365
972238825Smm		);
973238825Smm	return (days * DAY + (a->tm_hour - b->tm_hour) * HOUR
974238825Smm	    + (a->tm_min - b->tm_min) * MINUTE
975238825Smm	    + (a->tm_sec - b->tm_sec));
976238825Smm}
977238825Smm
978238825Smm/*
979238825Smm *
980238825Smm * The public function.
981238825Smm *
982238825Smm * TODO: tokens[] array should be dynamically sized.
983238825Smm */
984238825Smmtime_t
985299529Smm__archive_get_date(time_t now, const char *p)
986238825Smm{
987238825Smm	struct token	tokens[256];
988238825Smm	struct gdstate	_gds;
989238825Smm	struct token	*lasttoken;
990238825Smm	struct gdstate	*gds;
991238825Smm	struct tm	local, *tm;
992238825Smm	struct tm	gmt, *gmt_ptr;
993238825Smm	time_t		Start;
994238825Smm	time_t		tod;
995238825Smm	long		tzone;
996358088Smm#if defined(HAVE__LOCALTIME64_S) || defined(HAVE__GMTIME64_S)
997358088Smm	errno_t		terr;
998358088Smm	__time64_t	tmptime;
999358088Smm#endif
1000238825Smm
1001238825Smm	/* Clear out the parsed token array. */
1002238825Smm	memset(tokens, 0, sizeof(tokens));
1003238825Smm	/* Initialize the parser state. */
1004238825Smm	memset(&_gds, 0, sizeof(_gds));
1005238825Smm	gds = &_gds;
1006238825Smm
1007238825Smm	/* Look up the current time. */
1008358088Smm#if defined(HAVE_LOCALTIME_R)
1009358088Smm	tm = localtime_r(&now, &local);
1010358088Smm#elif defined(HAVE__LOCALTIME64_S)
1011358088Smm	tmptime = now;
1012358088Smm	terr = _localtime64_s(&local, &tmptime);
1013358088Smm	if (terr)
1014358088Smm		tm = NULL;
1015358088Smm	else
1016358088Smm		tm = &local;
1017358088Smm#else
1018238825Smm	memset(&local, 0, sizeof(local));
1019358088Smm	tm = localtime(&now);
1020358088Smm#endif
1021238825Smm	if (tm == NULL)
1022238825Smm		return -1;
1023358088Smm#if !defined(HAVE_LOCALTIME_R) && !defined(HAVE__LOCALTIME64_S)
1024238825Smm	local = *tm;
1025358088Smm#endif
1026238825Smm
1027238825Smm	/* Look up UTC if we can and use that to determine the current
1028238825Smm	 * timezone offset. */
1029358088Smm#if defined(HAVE_GMTIME_R)
1030358088Smm	gmt_ptr = gmtime_r(&now, &gmt);
1031358088Smm#elif defined(HAVE__GMTIME64_S)
1032358088Smm	tmptime = now;
1033358088Smm	terr = _gmtime64_s(&gmt, &tmptime);
1034358088Smm	if (terr)
1035358088Smm		gmt_ptr = NULL;
1036358088Smm	else
1037358088Smm		gmt_ptr = &gmt;
1038358088Smm#else
1039238825Smm	memset(&gmt, 0, sizeof(gmt));
1040358088Smm	gmt_ptr = gmtime(&now);
1041238825Smm	if (gmt_ptr != NULL) {
1042238825Smm		/* Copy, in case localtime and gmtime use the same buffer. */
1043238825Smm		gmt = *gmt_ptr;
1044238825Smm	}
1045358088Smm#endif
1046238825Smm	if (gmt_ptr != NULL)
1047238825Smm		tzone = difftm (&gmt, &local);
1048238825Smm	else
1049238825Smm		/* This system doesn't understand timezones; fake it. */
1050238825Smm		tzone = 0;
1051238825Smm	if(local.tm_isdst)
1052238825Smm		tzone += HOUR;
1053238825Smm
1054238825Smm	/* Tokenize the input string. */
1055238825Smm	lasttoken = tokens;
1056238825Smm	while ((lasttoken->token = nexttoken(&p, &lasttoken->value)) != 0) {
1057238825Smm		++lasttoken;
1058238825Smm		if (lasttoken > tokens + 255)
1059238825Smm			return -1;
1060238825Smm	}
1061238825Smm	gds->tokenp = tokens;
1062238825Smm
1063238825Smm	/* Match phrases until we run out of input tokens. */
1064238825Smm	while (gds->tokenp < lasttoken) {
1065238825Smm		if (!phrase(gds))
1066238825Smm			return -1;
1067238825Smm	}
1068238825Smm
1069238825Smm	/* Use current local timezone if none was specified. */
1070238825Smm	if (!gds->HaveZone) {
1071238825Smm		gds->Timezone = tzone;
1072238825Smm		gds->DSTmode = DSTmaybe;
1073238825Smm	}
1074238825Smm
1075238825Smm	/* If a timezone was specified, use that for generating the default
1076238825Smm	 * time components instead of the local timezone. */
1077238825Smm	if (gds->HaveZone && gmt_ptr != NULL) {
1078238825Smm		now -= gds->Timezone;
1079358088Smm#if defined(HAVE_GMTIME_R)
1080358088Smm		gmt_ptr = gmtime_r(&now, &gmt);
1081358088Smm#elif defined(HAVE__GMTIME64_S)
1082358088Smm		tmptime = now;
1083358088Smm		terr = _gmtime64_s(&gmt, &tmptime);
1084358088Smm		if (terr)
1085358088Smm			gmt_ptr = NULL;
1086358088Smm		else
1087358088Smm			gmt_ptr = &gmt;
1088358088Smm#else
1089358088Smm		gmt_ptr = gmtime(&now);
1090358088Smm#endif
1091238825Smm		if (gmt_ptr != NULL)
1092238825Smm			local = *gmt_ptr;
1093238825Smm		now += gds->Timezone;
1094238825Smm	}
1095238825Smm
1096238825Smm	if (!gds->HaveYear)
1097238825Smm		gds->Year = local.tm_year + 1900;
1098238825Smm	if (!gds->HaveMonth)
1099238825Smm		gds->Month = local.tm_mon + 1;
1100238825Smm	if (!gds->HaveDay)
1101238825Smm		gds->Day = local.tm_mday;
1102238825Smm	/* Note: No default for hour/min/sec; a specifier that just
1103238825Smm	 * gives date always refers to 00:00 on that date. */
1104238825Smm
1105238825Smm	/* If we saw more than one time, timezone, weekday, year, month,
1106238825Smm	 * or day, then give up. */
1107238825Smm	if (gds->HaveTime > 1 || gds->HaveZone > 1 || gds->HaveWeekDay > 1
1108238825Smm	    || gds->HaveYear > 1 || gds->HaveMonth > 1 || gds->HaveDay > 1)
1109238825Smm		return -1;
1110238825Smm
1111238825Smm	/* Compute an absolute time based on whatever absolute information
1112238825Smm	 * we collected. */
1113238825Smm	if (gds->HaveYear || gds->HaveMonth || gds->HaveDay
1114238825Smm	    || gds->HaveTime || gds->HaveWeekDay) {
1115238825Smm		Start = Convert(gds->Month, gds->Day, gds->Year,
1116238825Smm		    gds->Hour, gds->Minutes, gds->Seconds,
1117238825Smm		    gds->Timezone, gds->DSTmode);
1118238825Smm		if (Start < 0)
1119238825Smm			return -1;
1120238825Smm	} else {
1121238825Smm		Start = now;
1122238825Smm		if (!gds->HaveRel)
1123238825Smm			Start -= local.tm_hour * HOUR + local.tm_min * MINUTE
1124238825Smm			    + local.tm_sec;
1125238825Smm	}
1126238825Smm
1127238825Smm	/* Add the relative offset. */
1128238825Smm	Start += gds->RelSeconds;
1129238825Smm	Start += RelativeMonth(Start, gds->Timezone, gds->RelMonth);
1130238825Smm
1131238825Smm	/* Adjust for day-of-week offsets. */
1132238825Smm	if (gds->HaveWeekDay
1133238825Smm	    && !(gds->HaveYear || gds->HaveMonth || gds->HaveDay)) {
1134238825Smm		tod = RelativeDate(Start, gds->Timezone,
1135238825Smm		    gds->DSTmode, gds->DayOrdinal, gds->DayNumber);
1136238825Smm		Start += tod;
1137238825Smm	}
1138238825Smm
1139238825Smm	/* -1 is an error indicator, so return 0 instead of -1 if
1140238825Smm	 * that's the actual time. */
1141238825Smm	return Start == -1 ? 0 : Start;
1142238825Smm}
1143238825Smm
1144238825Smm
1145238825Smm#if	defined(TEST)
1146238825Smm
1147238825Smm/* ARGSUSED */
1148238825Smmint
1149238825Smmmain(int argc, char **argv)
1150238825Smm{
1151238825Smm    time_t	d;
1152246229Skientzle    time_t	now = time(NULL);
1153238825Smm
1154238825Smm    while (*++argv != NULL) {
1155238825Smm	    (void)printf("Input: %s\n", *argv);
1156246229Skientzle	    d = get_date(now, *argv);
1157238825Smm	    if (d == -1)
1158238825Smm		    (void)printf("Bad format - couldn't convert.\n");
1159238825Smm	    else
1160238825Smm		    (void)printf("Output: %s\n", ctime(&d));
1161238825Smm    }
1162238825Smm    exit(0);
1163238825Smm    /* NOTREACHED */
1164238825Smm}
1165238825Smm#endif	/* defined(TEST) */
1166