xref: /netbsd-src/lib/libutil/parsedate.y (revision fdd524d4ccd2bb0c6f67401e938dabf773eb0372)
1 %{
2 /*
3 **  Originally written by Steven M. Bellovin <smb@research.att.com> while
4 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
5 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
6 **  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
7 **
8 **  This grammar has 10 shift/reduce conflicts.
9 **
10 **  This code is in the public domain and has no copyright.
11 */
12 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
13 /* SUPPRESS 288 on yyerrlab *//* Label unused */
14 
15 #include <sys/cdefs.h>
16 #ifdef __RCSID
17 __RCSID("$NetBSD: parsedate.y,v 1.29 2016/06/26 07:09:24 kre Exp $");
18 #endif
19 
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <time.h>
25 #include <util.h>
26 #include <stdlib.h>
27 
28 /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
29    releases):
30 
31    We don't want to mess with all the portability hassles of alloca.
32    In particular, most (all?) versions of bison will use alloca in
33    their parser.  If bison works on your system (e.g. it should work
34    with gcc), then go ahead and use it, but the more general solution
35    is to use byacc instead of bison, which should generate a portable
36    parser.  I played with adding "#define alloca dont_use_alloca", to
37    give an error if the parser generator uses alloca (and thus detect
38    unportable parsedate.c's), but that seems to cause as many problems
39    as it solves.  */
40 
41 #define EPOCH		1970
42 #define HOUR(x)		((time_t)((x) * 60))
43 #define SECSPERDAY	(24L * 60L * 60L)
44 
45 #define	MAXREL	16	/* hours mins secs days weeks months years - maybe twice each ...*/
46 
47 #define USE_LOCAL_TIME	99999 /* special case for Convert() and yyTimezone */
48 
49 /*
50 **  An entry in the lexical lookup table.
51 */
52 typedef struct _TABLE {
53     const char	*name;
54     int		type;
55     time_t	value;
56 } TABLE;
57 
58 
59 /*
60 **  Daylight-savings mode:  on, off, or not yet known.
61 */
62 typedef enum _DSTMODE {
63     DSTon, DSToff, DSTmaybe
64 } DSTMODE;
65 
66 /*
67 **  Meridian:  am, pm, or 24-hour style.
68 */
69 typedef enum _MERIDIAN {
70     MERam, MERpm, MER24
71 } MERIDIAN;
72 
73 
74 struct dateinfo {
75 	DSTMODE	yyDSTmode;	/* DST on/off/maybe */
76 	time_t	yyDayOrdinal;
77 	time_t	yyDayNumber;
78 	int	yyHaveDate;
79 	int	yyHaveFullYear;	/* if true, year is not abbreviated. */
80 				/* if false, need to call AdjustYear(). */
81 	int	yyHaveDay;
82 	int	yyHaveRel;
83 	int	yyHaveTime;
84 	int	yyHaveZone;
85 	time_t	yyTimezone;	/* Timezone as minutes ahead/east of UTC */
86 	time_t	yyDay;		/* Day of month [1-31] */
87 	time_t	yyHour;		/* Hour of day [0-24] or [1-12] */
88 	time_t	yyMinutes;	/* Minute of hour [0-59] */
89 	time_t	yyMonth;	/* Month of year [1-12] */
90 	time_t	yySeconds;	/* Second of minute [0-60] */
91 	time_t	yyYear;		/* Year, see also yyHaveFullYear */
92 	MERIDIAN yyMeridian;	/* Interpret yyHour as AM/PM/24 hour clock */
93 	struct {
94 		time_t	yyRelVal;
95 		int	yyRelMonth;
96 	} yyRel[MAXREL];
97 };
98 %}
99 
100 %union {
101     time_t		Number;
102     enum _MERIDIAN	Meridian;
103 }
104 
105 %token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
106 %token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN tTIME
107 
108 %type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
109 %type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE tTIME
110 %type	<Meridian>	tMERIDIAN
111 
112 %type	<Number>	at_number
113 %type	<Meridian>	o_merid
114 
115 %parse-param	{ struct dateinfo *param }
116 %parse-param 	{ const char **yyInput }
117 %lex-param	{ const char **yyInput }
118 %pure-parser
119 
120 %%
121 
122 spec:
123 	  /* empty */
124 	| spec item
125 ;
126 
127 item:
128 	  time			{ param->yyHaveTime++; }
129 	| time_numericzone	{ param->yyHaveTime++; param->yyHaveZone++; }
130 	| zone			{ param->yyHaveZone++; }
131 	| date			{ param->yyHaveDate++; }
132 	| day			{ param->yyHaveDay++; }
133 	| rel			{ param->yyHaveRel++; }
134 	| cvsstamp		{ param->yyHaveTime++; param->yyHaveDate++;
135 				  param->yyHaveZone++; }
136 	| epochdate		{ param->yyHaveTime++; param->yyHaveDate++;
137 				  param->yyHaveZone++; }
138 	| number
139 ;
140 
141 cvsstamp:
142 	tUNUMBER '.' tUNUMBER '.' tUNUMBER '.'
143 				tUNUMBER '.' tUNUMBER '.' tUNUMBER {
144 		param->yyYear = $1;
145 		if (param->yyYear < 100) {
146 			param->yyYear += 1900;
147 		}
148 		param->yyHaveFullYear = 1;
149 		param->yyMonth = $3;
150 		param->yyDay = $5;
151 		param->yyHour = $7;
152 		param->yyMinutes = $9;
153 		param->yySeconds = $11;
154 		param->yyDSTmode = DSToff;
155 		param->yyTimezone = 0;
156 	}
157 ;
158 
159 epochdate:
160 	AT_SIGN at_number {
161 		time_t	when = $2;
162 		struct tm tmbuf;
163 
164 		if (gmtime_r(&when, &tmbuf) != NULL) {
165 			param->yyYear = tmbuf.tm_year + 1900;
166 			param->yyMonth = tmbuf.tm_mon + 1;
167 			param->yyDay = tmbuf.tm_mday;
168 
169 			param->yyHour = tmbuf.tm_hour;
170 			param->yyMinutes = tmbuf.tm_min;
171 			param->yySeconds = tmbuf.tm_sec;
172 		} else {
173 			param->yyYear = EPOCH;
174 			param->yyMonth = 1;
175 			param->yyDay = 1;
176 
177 			param->yyHour = 0;
178 			param->yyMinutes = 0;
179 			param->yySeconds = 0;
180 		}
181 		param->yyHaveFullYear = 1;
182 		param->yyDSTmode = DSToff;
183 		param->yyTimezone = 0;
184 	}
185 ;
186 
187 at_number:
188 	  tUNUMBER
189 	| tSNUMBER
190 ;
191 
192 time:
193 	  tUNUMBER tMERIDIAN {
194 		param->yyHour = $1;
195 		param->yyMinutes = 0;
196 		param->yySeconds = 0;
197 		param->yyMeridian = $2;
198 	  }
199 	| tUNUMBER ':' tUNUMBER o_merid {
200 		param->yyHour = $1;
201 		param->yyMinutes = $3;
202 		param->yySeconds = 0;
203 		param->yyMeridian = $4;
204 	  }
205 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
206 		param->yyHour = $1;
207 		param->yyMinutes = $3;
208 		param->yySeconds = $5;
209 		param->yyMeridian = $6;
210 	  }
211 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
212 		param->yyHour = $1;
213 		param->yyMinutes = $3;
214 		param->yySeconds = $5;
215 		param->yyMeridian = MER24;
216 		/* XXX: Do nothing with millis */
217 	  }
218 	| tTIME {
219 		param->yyHour = $1;
220 		param->yyMinutes = 0;
221 		param->yySeconds = 0;
222 		param->yyMeridian = MER24;
223 		/* Tues midnight --> Weds 00:00, midnight Tues -> Tues 00:00 */
224 		if ($1 == 0 && param->yyHaveDay)
225 			param->yyDayNumber++;
226 	}
227 ;
228 
229 time_numericzone:
230 	  tUNUMBER ':' tUNUMBER tSNUMBER {
231 		param->yyHour = $1;
232 		param->yyMinutes = $3;
233 		param->yyMeridian = MER24;
234 		param->yyDSTmode = DSToff;
235 		param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
236 	  }
237 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
238 		param->yyHour = $1;
239 		param->yyMinutes = $3;
240 		param->yySeconds = $5;
241 		param->yyMeridian = MER24;
242 		param->yyDSTmode = DSToff;
243 		param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
244 	}
245 ;
246 
247 zone:
248 	  tZONE		{ param->yyTimezone = $1; param->yyDSTmode = DSToff; }
249 	| tDAYZONE	{ param->yyTimezone = $1; param->yyDSTmode = DSTon; }
250 	| tZONE tDST	{ param->yyTimezone = $1; param->yyDSTmode = DSTon; }
251 ;
252 
253 day:
254 	  tDAY		{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
255 	| tDAY ','	{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
256 	| tUNUMBER tDAY	{ param->yyDayOrdinal = $1; param->yyDayNumber = $2; }
257 ;
258 
259 date:
260 	  tUNUMBER '/' tUNUMBER {
261 		param->yyMonth = $1;
262 		param->yyDay = $3;
263 	  }
264 	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
265 		if ($1 >= 100) {
266 			param->yyYear = $1;
267 			param->yyMonth = $3;
268 			param->yyDay = $5;
269 		} else {
270 			param->yyMonth = $1;
271 			param->yyDay = $3;
272 			param->yyYear = $5;
273 		}
274 	  }
275 	| tUNUMBER tSNUMBER tSNUMBER {
276 		/* ISO 8601 format.  yyyy-mm-dd.  */
277 		param->yyYear = $1;
278 		param->yyHaveFullYear = 1;
279 		param->yyMonth = -$2;
280 		param->yyDay = -$3;
281 	  }
282 	| tUNUMBER tMONTH tSNUMBER {
283 		/* e.g. 17-JUN-1992.  */
284 		param->yyDay = $1;
285 		param->yyMonth = $2;
286 		param->yyYear = -$3;
287 	  }
288 	| tMONTH tUNUMBER {
289 		param->yyMonth = $1;
290 		param->yyDay = $2;
291 	  }
292 	| tMONTH tUNUMBER ',' tUNUMBER {
293 		param->yyMonth = $1;
294 		param->yyDay = $2;
295 		param->yyYear = $4;
296 	  }
297 	| tUNUMBER tMONTH {
298 		param->yyMonth = $2;
299 		param->yyDay = $1;
300 	  }
301 	| tUNUMBER tMONTH tUNUMBER {
302 		param->yyMonth = $2;
303 		if ($1 < 35) {
304 			param->yyDay = $1;
305 			param->yyYear = $3;
306 		} else {
307 			param->yyDay = $3;
308 			param->yyYear = $1;
309 		}
310 	  }
311 ;
312 
313 rel:
314 	  relunit
315 	| relunit tAGO {
316 		param->yyRel[param->yyHaveRel].yyRelVal =
317 		    -param->yyRel[param->yyHaveRel].yyRelVal;
318 	  }
319 ;
320 
321 relunit:
322 	  tUNUMBER tMINUTE_UNIT	{ RelVal(param, $1 * $2 * 60L, 0); }
323 	| tSNUMBER tMINUTE_UNIT	{ RelVal(param, $1 * $2 * 60L, 0); }
324 	| tMINUTE_UNIT		{ RelVal(param, $1 * 60L, 0); }
325 	| tSNUMBER tSEC_UNIT	{ RelVal(param, $1, 0); }
326 	| tUNUMBER tSEC_UNIT	{ RelVal(param, $1, 0); }
327 	| tSEC_UNIT		{ RelVal(param, 1L, 0);  }
328 	| tSNUMBER tMONTH_UNIT	{ RelVal(param, $1 * $2, 1); }
329 	| tUNUMBER tMONTH_UNIT	{ RelVal(param, $1 * $2, 1); }
330 	| tMONTH_UNIT		{ RelVal(param, $1, 1); }
331 ;
332 
333 number:
334 	tUNUMBER {
335 		if (param->yyHaveTime && param->yyHaveDate &&
336 		    !param->yyHaveRel) {
337 			param->yyYear = $1;
338 		} else {
339 			if ($1 > 10000) {
340 				param->yyHaveDate++;
341 				param->yyDay = ($1)%100;
342 				param->yyMonth = ($1/100)%100;
343 				param->yyYear = $1/10000;
344 			}
345 			else {
346 				param->yyHaveTime++;
347 				if ($1 < 100) {
348 					param->yyHour = $1;
349 					param->yyMinutes = 0;
350 				}
351 				else {
352 					param->yyHour = $1 / 100;
353 					param->yyMinutes = $1 % 100;
354 				}
355 				param->yySeconds = 0;
356 				param->yyMeridian = MER24;
357 			}
358 		}
359 	}
360 ;
361 
362 o_merid:
363 	  /* empty */		{ $$ = MER24; }
364 	| tMERIDIAN		{ $$ = $1; }
365 ;
366 
367 %%
368 
369 static short DaysInMonth[12] = {
370     31, 28, 31, 30, 31, 30,
371     31, 31, 30, 31, 30, 31
372 };
373 
374 /*
375  * works with tm.tm_year (ie: rel to 1900)
376  */
377 #define	isleap(yr)  (((yr) & 3) == 0 && (((yr) % 100) != 0 || \
378 			((1900+(yr)) % 400) == 0))
379 
380 /* Month and day table. */
381 static const TABLE MonthDayTable[] = {
382     { "january",	tMONTH,  1 },
383     { "february",	tMONTH,  2 },
384     { "march",		tMONTH,  3 },
385     { "april",		tMONTH,  4 },
386     { "may",		tMONTH,  5 },
387     { "june",		tMONTH,  6 },
388     { "july",		tMONTH,  7 },
389     { "august",		tMONTH,  8 },
390     { "september",	tMONTH,  9 },
391     { "sept",		tMONTH,  9 },
392     { "october",	tMONTH, 10 },
393     { "november",	tMONTH, 11 },
394     { "december",	tMONTH, 12 },
395     { "sunday",		tDAY, 0 },
396     { "su",		tDAY, 0 },
397     { "monday",		tDAY, 1 },
398     { "mo",		tDAY, 1 },
399     { "tuesday",	tDAY, 2 },
400     { "tues",		tDAY, 2 },
401     { "tu",		tDAY, 2 },
402     { "wednesday",	tDAY, 3 },
403     { "wednes",		tDAY, 3 },
404     { "weds",		tDAY, 3 },
405     { "we",		tDAY, 3 },
406     { "thursday",	tDAY, 4 },
407     { "thurs",		tDAY, 4 },
408     { "thur",		tDAY, 4 },
409     { "th",		tDAY, 4 },
410     { "friday",		tDAY, 5 },
411     { "fr",		tDAY, 5 },
412     { "saturday",	tDAY, 6 },
413     { "sa",		tDAY, 6 },
414     { NULL,		0,    0 }
415 };
416 
417 /* Time units table. */
418 static const TABLE UnitsTable[] = {
419     { "year",		tMONTH_UNIT,	12 },
420     { "month",		tMONTH_UNIT,	1 },
421     { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
422     { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
423     { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
424     { "hour",		tMINUTE_UNIT,	60 },
425     { "minute",		tMINUTE_UNIT,	1 },
426     { "min",		tMINUTE_UNIT,	1 },
427     { "second",		tSEC_UNIT,	1 },
428     { "sec",		tSEC_UNIT,	1 },
429     { NULL,		0,		0 }
430 };
431 
432 /* Assorted relative-time words. */
433 static const TABLE OtherTable[] = {
434     { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
435     { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
436     { "today",		tMINUTE_UNIT,	0 },
437     { "now",		tMINUTE_UNIT,	0 },
438     { "last",		tUNUMBER,	-1 },
439     { "this",		tMINUTE_UNIT,	0 },
440     { "next",		tUNUMBER,	2 },
441     { "first",		tUNUMBER,	1 },
442     { "one",		tUNUMBER,	1 },
443 /*  { "second",		tUNUMBER,	2 }, */
444     { "two",		tUNUMBER,	2 },
445     { "third",		tUNUMBER,	3 },
446     { "three",		tUNUMBER,	3 },
447     { "fourth",		tUNUMBER,	4 },
448     { "four",		tUNUMBER,	4 },
449     { "fifth",		tUNUMBER,	5 },
450     { "five",		tUNUMBER,	5 },
451     { "sixth",		tUNUMBER,	6 },
452     { "six",		tUNUMBER,	6 },
453     { "seventh",	tUNUMBER,	7 },
454     { "seven",		tUNUMBER,	7 },
455     { "eighth",		tUNUMBER,	8 },
456     { "eight",		tUNUMBER,	8 },
457     { "ninth",		tUNUMBER,	9 },
458     { "nine",		tUNUMBER,	9 },
459     { "tenth",		tUNUMBER,	10 },
460     { "ten",		tUNUMBER,	10 },
461     { "eleventh",	tUNUMBER,	11 },
462     { "eleven",		tUNUMBER,	11 },
463     { "twelfth",	tUNUMBER,	12 },
464     { "twelve",		tUNUMBER,	12 },
465     { "ago",		tAGO,	1 },
466     { NULL,		0,	0 }
467 };
468 
469 /* The timezone table. */
470 /* Some of these are commented out because a time_t can't store a float. */
471 static const TABLE TimezoneTable[] = {
472     { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
473     { "ut",	tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
474     { "utc",	tZONE,     HOUR( 0) },
475     { "wet",	tZONE,     HOUR( 0) },	/* Western European */
476     { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
477     { "wat",	tZONE,     HOUR( 1) },	/* West Africa */
478     { "at",	tZONE,     HOUR( 2) },	/* Azores */
479 #if	0
480     /* For completeness.  BST is also British Summer, and GST is
481      * also Guam Standard. */
482     { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
483     { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
484 #endif
485     { "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
486     { "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
487     { "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
488     { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
489     { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
490     { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
491     { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
492     { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
493     { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
494     { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
495     { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
496     { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
497     { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
498     { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
499     { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
500     { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
501     { "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
502     { "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
503     { "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
504     { "nt",	tZONE,     HOUR(11) },	/* Nome */
505     { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
506     { "cet",	tZONE,     -HOUR(1) },	/* Central European */
507     { "met",	tZONE,     -HOUR(1) },	/* Middle European */
508     { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
509     { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
510     { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
511     { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
512     { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
513     { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
514     { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
515     { "bt",	tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
516     { "it",	tZONE,     -HOUR(3.5) },/* Iran */
517     { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
518     { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
519     { "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
520     { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
521 #if	0
522     /* For completeness.  NST is also Newfoundland Stanard, and SST is
523      * also Swedish Summer. */
524     { "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
525     { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
526 #endif	/* 0 */
527     { "ict",	tZONE,     -HOUR(7) },	/* Indo China Time (Thai) */
528 #if 0	/* this one looks to be bogus */
529     { "jt",	tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
530 #endif
531     { "wast",	tZONE,     -HOUR(8) },	/* West Australian Standard */
532     { "awst",	tZONE,     -HOUR(8) },	/* West Australian Standard */
533     { "wadt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
534     { "awdt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
535     { "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
536     { "sgt",	tZONE,     -HOUR(8) },	/* Singapore */
537     { "hkt",	tZONE,     -HOUR(8) },	/* Hong Kong */
538     { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
539     { "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
540     { "acst",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
541     { "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
542     { "acdt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
543     { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
544     { "aest",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
545     { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
546     { "aedt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
547     { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
548     { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
549     { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
550     { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
551     { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
552     {  NULL,	0,	    0 }
553 };
554 
555 /* Military timezone table. */
556 static const TABLE MilitaryTable[] = {
557     { "a",	tZONE,	HOUR(  1) },
558     { "b",	tZONE,	HOUR(  2) },
559     { "c",	tZONE,	HOUR(  3) },
560     { "d",	tZONE,	HOUR(  4) },
561     { "e",	tZONE,	HOUR(  5) },
562     { "f",	tZONE,	HOUR(  6) },
563     { "g",	tZONE,	HOUR(  7) },
564     { "h",	tZONE,	HOUR(  8) },
565     { "i",	tZONE,	HOUR(  9) },
566     { "k",	tZONE,	HOUR( 10) },
567     { "l",	tZONE,	HOUR( 11) },
568     { "m",	tZONE,	HOUR( 12) },
569     { "n",	tZONE,	HOUR(- 1) },
570     { "o",	tZONE,	HOUR(- 2) },
571     { "p",	tZONE,	HOUR(- 3) },
572     { "q",	tZONE,	HOUR(- 4) },
573     { "r",	tZONE,	HOUR(- 5) },
574     { "s",	tZONE,	HOUR(- 6) },
575     { "t",	tZONE,	HOUR(- 7) },
576     { "u",	tZONE,	HOUR(- 8) },
577     { "v",	tZONE,	HOUR(- 9) },
578     { "w",	tZONE,	HOUR(-10) },
579     { "x",	tZONE,	HOUR(-11) },
580     { "y",	tZONE,	HOUR(-12) },
581     { "z",	tZONE,	HOUR(  0) },
582     { NULL,	0,	0 }
583 };
584 
585 static const TABLE TimeNames[] = {
586     { "midnight",	tTIME,		 0 },
587     { "mn",		tTIME,		 0 },
588     { "noon",		tTIME,		12 },
589     { "midday",		tTIME,		12 },
590     { NULL,		0,		 0 }
591 };
592 
593 
594 
595 /* ARGSUSED */
596 static int
597 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
598 {
599   return 0;
600 }
601 
602 /*
603  * Save a relative value, if it fits
604  */
605 static void
606 RelVal(struct dateinfo *param, time_t v, int type)
607 {
608 	int i;
609 
610 	if ((i = param->yyHaveRel) >= MAXREL)
611 		return;
612 	param->yyRel[i].yyRelMonth = type;
613 	param->yyRel[i].yyRelVal = v;
614 }
615 
616 
617 /* Adjust year from a value that might be abbreviated, to a full value.
618  * e.g. convert 70 to 1970.
619  * Input Year is either:
620  *  - A negative number, which means to use its absolute value (why?)
621  *  - A number from 0 to 99, which means a year from 1900 to 1999, or
622  *  - The actual year (>=100).
623  * Returns the full year. */
624 static time_t
625 AdjustYear(time_t Year)
626 {
627     /* XXX Y2K */
628     if (Year < 0)
629 	Year = -Year;
630     if (Year < 70)
631 	Year += 2000;
632     else if (Year < 100)
633 	Year += 1900;
634     return Year;
635 }
636 
637 static time_t
638 Convert(
639     time_t	Month,		/* month of year [1-12] */
640     time_t	Day,		/* day of month [1-31] */
641     time_t	Year,		/* year, not abbreviated in any way */
642     time_t	Hours,		/* Hour of day [0-24] */
643     time_t	Minutes,	/* Minute of hour [0-59] */
644     time_t	Seconds,	/* Second of minute [0-60] */
645     time_t	Timezone,	/* Timezone as minutes east of UTC,
646 				 * or USE_LOCAL_TIME special case */
647     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
648     DSTMODE	DSTmode		/* DST on/off/maybe */
649 )
650 {
651     struct tm tm = {.tm_sec = 0};
652     struct tm otm;
653     time_t result;
654 
655     tm.tm_sec = Seconds;
656     tm.tm_min = Minutes;
657     tm.tm_hour = Hours + (Meridian == MERpm ? 12 : 0);
658     tm.tm_mday = Day;
659     tm.tm_mon = Month - 1;
660     tm.tm_year = Year - 1900;
661     if (Timezone == USE_LOCAL_TIME) {
662 	    switch (DSTmode) {
663 	    case DSTon:  tm.tm_isdst = 1; break;
664 	    case DSToff: tm.tm_isdst = 0; break;
665 	    default:     tm.tm_isdst = -1; break;
666 	    }
667 	    otm = tm;
668 	    result = mktime(&tm);
669     } else {
670 	    /* We rely on mktime_z(NULL, ...) working in UTC */
671 	    tm.tm_isdst = 0;	/* hence cannot be summer time */
672 	    otm = tm;
673 	    errno = 0;
674 	    result = mktime_z(NULL, &tm);
675 	    if (result != -1 || errno == 0) {
676 		    result += Timezone * 60;
677 		    if (DSTmode == DSTon)	/* if specified sumer time */
678 			result -= 3600;		/* UTC is 1 hour earlier XXX */
679 	    }
680     }
681 
682 #if PARSEDATE_DEBUG
683     fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
684 		    " mer=%d DST=%d)",
685 	__func__,
686 	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
687 	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
688 	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
689     fprintf(stderr, " -> %jd", (intmax_t)result);
690     fprintf(stderr, " %s", ctime(&result));
691 #endif
692 
693 #define	TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
694     if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
695 	TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
696 	    /* mktime() "corrected" our tm, so it must have been invalid */
697 	    result = -1;
698 	    errno = EAGAIN;
699     }
700 #undef	TM_NE
701 
702     return result;
703 }
704 
705 
706 static time_t
707 DSTcorrect(
708     time_t	Start,
709     time_t	Future
710 )
711 {
712     time_t	StartDay;
713     time_t	FutureDay;
714     struct tm	tm;
715 
716     if (localtime_r(&Start, &tm) == NULL)
717 	return -1;
718     StartDay = (tm.tm_hour + 1) % 24;
719 
720     if (localtime_r(&Future, &tm) == NULL)
721 	return -1;
722     FutureDay = (tm.tm_hour + 1) % 24;
723 
724     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
725 }
726 
727 
728 static time_t
729 RelativeDate(
730     time_t	Start,
731     time_t	DayOrdinal,
732     time_t	DayNumber
733 )
734 {
735     struct tm	tm;
736     time_t	now;
737 
738     now = Start;
739     if (localtime_r(&now, &tm) == NULL)
740 	return -1;
741     now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
742     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
743     return DSTcorrect(Start, now);
744 }
745 
746 
747 static time_t
748 RelativeMonth(
749     time_t	Start,
750     time_t	RelMonth,
751     time_t	Timezone
752 )
753 {
754     struct tm	tm;
755     time_t	Month;
756     time_t	Then;
757     int		Day;
758 
759     if (RelMonth == 0)
760 	return 0;
761     /*
762      * It doesn't matter what timezone we use to do this computation,
763      * as long as we use the same one to reassemble the time that we
764      * used to disassemble it. So always use localtime and mktime. In
765      * particular, don't use Convert() to reassemble, because it will
766      * not only reassemble with the wrong timezone but it will also
767      * fail if we do e.g. three months from March 31 yielding July 1.
768      */
769     (void)Timezone;
770 
771     if (localtime_r(&Start, &tm) == NULL)
772 	return -1;
773 
774     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
775     tm.tm_year = (Month / 12) - 1900;
776     tm.tm_mon = Month % 12;
777     if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
778 	((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
779 	    tm.tm_mday = Day;
780     errno = 0;
781     Then = mktime(&tm);
782     if (Then == -1 && errno != 0)
783 	return -1;
784     return DSTcorrect(Start, Then);
785 }
786 
787 
788 static int
789 LookupWord(YYSTYPE *yylval, char *buff)
790 {
791     register char	*p;
792     register char	*q;
793     register const TABLE	*tp;
794     int			i;
795     int			abbrev;
796 
797     /* Make it lowercase. */
798     for (p = buff; *p; p++)
799 	if (isupper((unsigned char)*p))
800 	    *p = tolower((unsigned char)*p);
801 
802     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
803 	yylval->Meridian = MERam;
804 	return tMERIDIAN;
805     }
806     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
807 	yylval->Meridian = MERpm;
808 	return tMERIDIAN;
809     }
810 
811     /* See if we have an abbreviation for a month. */
812     if (strlen(buff) == 3)
813 	abbrev = 1;
814     else if (strlen(buff) == 4 && buff[3] == '.') {
815 	abbrev = 1;
816 	buff[3] = '\0';
817     }
818     else
819 	abbrev = 0;
820 
821     for (tp = MonthDayTable; tp->name; tp++) {
822 	if (abbrev) {
823 	    if (strncmp(buff, tp->name, 3) == 0) {
824 		yylval->Number = tp->value;
825 		return tp->type;
826 	    }
827 	}
828 	else if (strcmp(buff, tp->name) == 0) {
829 	    yylval->Number = tp->value;
830 	    return tp->type;
831 	}
832     }
833 
834     for (tp = TimezoneTable; tp->name; tp++)
835 	if (strcmp(buff, tp->name) == 0) {
836 	    yylval->Number = tp->value;
837 	    return tp->type;
838 	}
839 
840     if (strcmp(buff, "dst") == 0)
841 	return tDST;
842 
843     for (tp = TimeNames; tp->name; tp++)
844 	if (strcmp(buff, tp->name) == 0) {
845 	    yylval->Number = tp->value;
846 	    return tp->type;
847 	}
848 
849     for (tp = UnitsTable; tp->name; tp++)
850 	if (strcmp(buff, tp->name) == 0) {
851 	    yylval->Number = tp->value;
852 	    return tp->type;
853 	}
854 
855     /* Strip off any plural and try the units table again. */
856     i = strlen(buff) - 1;
857     if (buff[i] == 's') {
858 	buff[i] = '\0';
859 	for (tp = UnitsTable; tp->name; tp++)
860 	    if (strcmp(buff, tp->name) == 0) {
861 		yylval->Number = tp->value;
862 		return tp->type;
863 	    }
864 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
865     }
866 
867     for (tp = OtherTable; tp->name; tp++)
868 	if (strcmp(buff, tp->name) == 0) {
869 	    yylval->Number = tp->value;
870 	    return tp->type;
871 	}
872 
873     /* Military timezones. */
874     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
875 	for (tp = MilitaryTable; tp->name; tp++)
876 	    if (strcmp(buff, tp->name) == 0) {
877 		yylval->Number = tp->value;
878 		return tp->type;
879 	    }
880     }
881 
882     /* Drop out any periods and try the timezone table again. */
883     for (i = 0, p = q = buff; *q; q++)
884 	if (*q != '.')
885 	    *p++ = *q;
886 	else
887 	    i++;
888     *p = '\0';
889     if (i)
890 	for (tp = TimezoneTable; tp->name; tp++)
891 	    if (strcmp(buff, tp->name) == 0) {
892 		yylval->Number = tp->value;
893 		return tp->type;
894 	    }
895 
896     return tID;
897 }
898 
899 
900 static int
901 yylex(YYSTYPE *yylval, const char **yyInput)
902 {
903     register char	c;
904     register char	*p;
905     char		buff[20];
906     int			Count;
907     int			sign;
908     const char		*inp = *yyInput;
909 
910     for ( ; ; ) {
911 	while (isspace((unsigned char)*inp))
912 	    inp++;
913 
914 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
915 	    if (c == '-' || c == '+') {
916 		sign = c == '-' ? -1 : 1;
917 		if (!isdigit((unsigned char)*++inp))
918 		    /* skip the '-' sign */
919 		    continue;
920 	    }
921 	    else
922 		sign = 0;
923 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
924 		yylval->Number = 10 * yylval->Number + c - '0';
925 	    if (sign < 0)
926 		yylval->Number = -yylval->Number;
927 	    *yyInput = --inp;
928 	    return sign ? tSNUMBER : tUNUMBER;
929 	}
930 	if (isalpha((unsigned char)c)) {
931 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
932 		if (p < &buff[sizeof buff - 1])
933 		    *p++ = c;
934 	    *p = '\0';
935 	    *yyInput = --inp;
936 	    return LookupWord(yylval, buff);
937 	}
938 	if (c == '@') {
939 	    *yyInput = ++inp;
940 	    return AT_SIGN;
941 	}
942 	if (c != '(') {
943 	    *yyInput = ++inp;
944 	    return c;
945 	}
946 	Count = 0;
947 	do {
948 	    c = *inp++;
949 	    if (c == '\0')
950 		return c;
951 	    if (c == '(')
952 		Count++;
953 	    else if (c == ')')
954 		Count--;
955 	} while (Count > 0);
956     }
957 }
958 
959 #define TM_YEAR_ORIGIN 1900
960 
961 time_t
962 parsedate(const char *p, const time_t *now, const int *zone)
963 {
964     struct tm		local, *tm;
965     time_t		nowt;
966     int			zonet;
967     time_t		Start;
968     time_t		tod, rm;
969     struct dateinfo	param;
970     int			saved_errno;
971     int			i;
972 
973     saved_errno = errno;
974     errno = 0;
975 
976     if (now == NULL) {
977         now = &nowt;
978 	(void)time(&nowt);
979     }
980     if (zone == NULL) {
981 	zone = &zonet;
982 	zonet = USE_LOCAL_TIME;
983 	if ((tm = localtime_r(now, &local)) == NULL)
984 	    return -1;
985     } else {
986 	/*
987 	 * Should use the specified zone, not localtime.
988 	 * Fake it using gmtime and arithmetic.
989 	 * This is good enough because we use only the year/month/day,
990 	 * not other fields of struct tm.
991 	 */
992 	time_t fake = *now + (*zone * 60);
993 	if ((tm = gmtime_r(&fake, &local)) == NULL)
994 	    return -1;
995     }
996     param.yyYear = tm->tm_year + 1900;
997     param.yyMonth = tm->tm_mon + 1;
998     param.yyDay = tm->tm_mday;
999     param.yyTimezone = *zone;
1000     param.yyDSTmode = DSTmaybe;
1001     param.yyHour = 0;
1002     param.yyMinutes = 0;
1003     param.yySeconds = 0;
1004     param.yyMeridian = MER24;
1005     param.yyHaveDate = 0;
1006     param.yyHaveFullYear = 0;
1007     param.yyHaveDay = 0;
1008     param.yyHaveRel = 0;
1009     param.yyHaveTime = 0;
1010     param.yyHaveZone = 0;
1011 
1012     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
1013 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
1014 	errno = EINVAL;
1015 	return -1;
1016     }
1017 
1018     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
1019 	if (! param.yyHaveFullYear) {
1020 		param.yyYear = AdjustYear(param.yyYear);
1021 		param.yyHaveFullYear = 1;
1022 	}
1023 	errno = 0;
1024 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
1025 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
1026 	    param.yyMeridian, param.yyDSTmode);
1027 	if (Start == -1 && errno != 0)
1028 	    return -1;
1029     }
1030     else {
1031 	Start = *now;
1032 	if (!param.yyHaveRel)
1033 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
1034     }
1035 
1036     if (param.yyHaveRel > MAXREL) {
1037 	errno = EINVAL;
1038 	return -1;
1039     }
1040     for (i = 0; i < param.yyHaveRel; i++) {
1041 	if (param.yyRel[i].yyRelMonth) {
1042 	    errno = 0;
1043 	    rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
1044 	    if (rm == -1 && errno != 0)
1045 		return -1;
1046 	    Start += rm;
1047 	} else
1048 	    Start += param.yyRel[i].yyRelVal;
1049     }
1050 
1051     if (param.yyHaveDay && !param.yyHaveDate) {
1052 	errno = 0;
1053 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1054 	if (tod == -1 && errno != 0)
1055 	    return -1;
1056 	Start += tod;
1057     }
1058 
1059     errno = saved_errno;
1060     return Start;
1061 }
1062 
1063 
1064 #if	defined(TEST)
1065 
1066 /* ARGSUSED */
1067 int
1068 main(int ac, char *av[])
1069 {
1070     char	buff[128];
1071     time_t	d;
1072 
1073     (void)printf("Enter date, or blank line to exit.\n\t> ");
1074     (void)fflush(stdout);
1075     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1076 	errno = 0;
1077 	d = parsedate(buff, NULL, NULL);
1078 	if (d == -1 && errno != 0)
1079 	    (void)printf("Bad format - couldn't convert: %s\n",
1080 	        strerror(errno));
1081 	else
1082 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1083 	(void)printf("\t> ");
1084 	(void)fflush(stdout);
1085     }
1086     exit(0);
1087     /* NOTREACHED */
1088 }
1089 #endif	/* defined(TEST) */
1090