xref: /netbsd-src/lib/libutil/parsedate.y (revision a24efa7dea9f1f56c3bdb15a927d3516792ace1c)
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.28 2016/05/03 18:14:54 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     { "dawn",		tTIME,		 6 },
591     { "sunup",		tTIME,		 6 },
592     { "sunset",		tTIME,		18 },
593     { "sundown",	tTIME,		18 },
594     { NULL,		0,		 0 }
595 };
596 
597 
598 
599 /* ARGSUSED */
600 static int
601 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
602 {
603   return 0;
604 }
605 
606 /*
607  * Save a relative value, if it fits
608  */
609 static void
610 RelVal(struct dateinfo *param, time_t v, int type)
611 {
612 	int i;
613 
614 	if ((i = param->yyHaveRel) >= MAXREL)
615 		return;
616 	param->yyRel[i].yyRelMonth = type;
617 	param->yyRel[i].yyRelVal = v;
618 }
619 
620 
621 /* Adjust year from a value that might be abbreviated, to a full value.
622  * e.g. convert 70 to 1970.
623  * Input Year is either:
624  *  - A negative number, which means to use its absolute value (why?)
625  *  - A number from 0 to 99, which means a year from 1900 to 1999, or
626  *  - The actual year (>=100).
627  * Returns the full year. */
628 static time_t
629 AdjustYear(time_t Year)
630 {
631     /* XXX Y2K */
632     if (Year < 0)
633 	Year = -Year;
634     if (Year < 70)
635 	Year += 2000;
636     else if (Year < 100)
637 	Year += 1900;
638     return Year;
639 }
640 
641 static time_t
642 Convert(
643     time_t	Month,		/* month of year [1-12] */
644     time_t	Day,		/* day of month [1-31] */
645     time_t	Year,		/* year, not abbreviated in any way */
646     time_t	Hours,		/* Hour of day [0-24] */
647     time_t	Minutes,	/* Minute of hour [0-59] */
648     time_t	Seconds,	/* Second of minute [0-60] */
649     time_t	Timezone,	/* Timezone as minutes east of UTC,
650 				 * or USE_LOCAL_TIME special case */
651     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
652     DSTMODE	DSTmode		/* DST on/off/maybe */
653 )
654 {
655     struct tm tm = {.tm_sec = 0};
656     struct tm otm;
657     time_t result;
658 
659     tm.tm_sec = Seconds;
660     tm.tm_min = Minutes;
661     tm.tm_hour = Hours + (Meridian == MERpm ? 12 : 0);
662     tm.tm_mday = Day;
663     tm.tm_mon = Month - 1;
664     tm.tm_year = Year - 1900;
665     if (Timezone == USE_LOCAL_TIME) {
666 	    switch (DSTmode) {
667 	    case DSTon:  tm.tm_isdst = 1; break;
668 	    case DSToff: tm.tm_isdst = 0; break;
669 	    default:     tm.tm_isdst = -1; break;
670 	    }
671 	    otm = tm;
672 	    result = mktime(&tm);
673     } else {
674 	    /* We rely on mktime_z(NULL, ...) working in UTC */
675 	    tm.tm_isdst = 0;	/* hence cannot be summer time */
676 	    otm = tm;
677 	    errno = 0;
678 	    result = mktime_z(NULL, &tm);
679 	    if (result != -1 || errno == 0) {
680 		    result += Timezone * 60;
681 		    if (DSTmode == DSTon)	/* if specified sumer time */
682 			result -= 3600;		/* UTC is 1 hour earlier XXX */
683 	    }
684     }
685 
686 #if PARSEDATE_DEBUG
687     fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
688 		    " mer=%d DST=%d)",
689 	__func__,
690 	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
691 	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
692 	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
693     fprintf(stderr, " -> %jd", (intmax_t)result);
694     fprintf(stderr, " %s", ctime(&result));
695 #endif
696 
697 #define	TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
698     if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
699 	TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
700 	    /* mktime() "corrected" our tm, so it must have been invalid */
701 	    result = -1;
702 	    errno = EAGAIN;
703     }
704 #undef	TM_NE
705 
706     return result;
707 }
708 
709 
710 static time_t
711 DSTcorrect(
712     time_t	Start,
713     time_t	Future
714 )
715 {
716     time_t	StartDay;
717     time_t	FutureDay;
718     struct tm	tm;
719 
720     if (localtime_r(&Start, &tm) == NULL)
721 	return -1;
722     StartDay = (tm.tm_hour + 1) % 24;
723 
724     if (localtime_r(&Future, &tm) == NULL)
725 	return -1;
726     FutureDay = (tm.tm_hour + 1) % 24;
727 
728     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
729 }
730 
731 
732 static time_t
733 RelativeDate(
734     time_t	Start,
735     time_t	DayOrdinal,
736     time_t	DayNumber
737 )
738 {
739     struct tm	tm;
740     time_t	now;
741 
742     now = Start;
743     if (localtime_r(&now, &tm) == NULL)
744 	return -1;
745     now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
746     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
747     return DSTcorrect(Start, now);
748 }
749 
750 
751 static time_t
752 RelativeMonth(
753     time_t	Start,
754     time_t	RelMonth,
755     time_t	Timezone
756 )
757 {
758     struct tm	tm;
759     time_t	Month;
760     time_t	Then;
761     int		Day;
762 
763     if (RelMonth == 0)
764 	return 0;
765     /*
766      * It doesn't matter what timezone we use to do this computation,
767      * as long as we use the same one to reassemble the time that we
768      * used to disassemble it. So always use localtime and mktime. In
769      * particular, don't use Convert() to reassemble, because it will
770      * not only reassemble with the wrong timezone but it will also
771      * fail if we do e.g. three months from March 31 yielding July 1.
772      */
773     (void)Timezone;
774 
775     if (localtime_r(&Start, &tm) == NULL)
776 	return -1;
777 
778     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
779     tm.tm_year = (Month / 12) - 1900;
780     tm.tm_mon = Month % 12;
781     if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
782 	((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
783 	    tm.tm_mday = Day;
784     errno = 0;
785     Then = mktime(&tm);
786     if (Then == -1 && errno != 0)
787 	return -1;
788     return DSTcorrect(Start, Then);
789 }
790 
791 
792 static int
793 LookupWord(YYSTYPE *yylval, char *buff)
794 {
795     register char	*p;
796     register char	*q;
797     register const TABLE	*tp;
798     int			i;
799     int			abbrev;
800 
801     /* Make it lowercase. */
802     for (p = buff; *p; p++)
803 	if (isupper((unsigned char)*p))
804 	    *p = tolower((unsigned char)*p);
805 
806     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
807 	yylval->Meridian = MERam;
808 	return tMERIDIAN;
809     }
810     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
811 	yylval->Meridian = MERpm;
812 	return tMERIDIAN;
813     }
814 
815     /* See if we have an abbreviation for a month. */
816     if (strlen(buff) == 3)
817 	abbrev = 1;
818     else if (strlen(buff) == 4 && buff[3] == '.') {
819 	abbrev = 1;
820 	buff[3] = '\0';
821     }
822     else
823 	abbrev = 0;
824 
825     for (tp = MonthDayTable; tp->name; tp++) {
826 	if (abbrev) {
827 	    if (strncmp(buff, tp->name, 3) == 0) {
828 		yylval->Number = tp->value;
829 		return tp->type;
830 	    }
831 	}
832 	else if (strcmp(buff, tp->name) == 0) {
833 	    yylval->Number = tp->value;
834 	    return tp->type;
835 	}
836     }
837 
838     for (tp = TimezoneTable; tp->name; tp++)
839 	if (strcmp(buff, tp->name) == 0) {
840 	    yylval->Number = tp->value;
841 	    return tp->type;
842 	}
843 
844     if (strcmp(buff, "dst") == 0)
845 	return tDST;
846 
847     for (tp = TimeNames; tp->name; tp++)
848 	if (strcmp(buff, tp->name) == 0) {
849 	    yylval->Number = tp->value;
850 	    return tp->type;
851 	}
852 
853     for (tp = UnitsTable; tp->name; tp++)
854 	if (strcmp(buff, tp->name) == 0) {
855 	    yylval->Number = tp->value;
856 	    return tp->type;
857 	}
858 
859     /* Strip off any plural and try the units table again. */
860     i = strlen(buff) - 1;
861     if (buff[i] == 's') {
862 	buff[i] = '\0';
863 	for (tp = UnitsTable; tp->name; tp++)
864 	    if (strcmp(buff, tp->name) == 0) {
865 		yylval->Number = tp->value;
866 		return tp->type;
867 	    }
868 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
869     }
870 
871     for (tp = OtherTable; tp->name; tp++)
872 	if (strcmp(buff, tp->name) == 0) {
873 	    yylval->Number = tp->value;
874 	    return tp->type;
875 	}
876 
877     /* Military timezones. */
878     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
879 	for (tp = MilitaryTable; tp->name; tp++)
880 	    if (strcmp(buff, tp->name) == 0) {
881 		yylval->Number = tp->value;
882 		return tp->type;
883 	    }
884     }
885 
886     /* Drop out any periods and try the timezone table again. */
887     for (i = 0, p = q = buff; *q; q++)
888 	if (*q != '.')
889 	    *p++ = *q;
890 	else
891 	    i++;
892     *p = '\0';
893     if (i)
894 	for (tp = TimezoneTable; tp->name; tp++)
895 	    if (strcmp(buff, tp->name) == 0) {
896 		yylval->Number = tp->value;
897 		return tp->type;
898 	    }
899 
900     return tID;
901 }
902 
903 
904 static int
905 yylex(YYSTYPE *yylval, const char **yyInput)
906 {
907     register char	c;
908     register char	*p;
909     char		buff[20];
910     int			Count;
911     int			sign;
912     const char		*inp = *yyInput;
913 
914     for ( ; ; ) {
915 	while (isspace((unsigned char)*inp))
916 	    inp++;
917 
918 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
919 	    if (c == '-' || c == '+') {
920 		sign = c == '-' ? -1 : 1;
921 		if (!isdigit((unsigned char)*++inp))
922 		    /* skip the '-' sign */
923 		    continue;
924 	    }
925 	    else
926 		sign = 0;
927 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
928 		yylval->Number = 10 * yylval->Number + c - '0';
929 	    if (sign < 0)
930 		yylval->Number = -yylval->Number;
931 	    *yyInput = --inp;
932 	    return sign ? tSNUMBER : tUNUMBER;
933 	}
934 	if (isalpha((unsigned char)c)) {
935 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
936 		if (p < &buff[sizeof buff - 1])
937 		    *p++ = c;
938 	    *p = '\0';
939 	    *yyInput = --inp;
940 	    return LookupWord(yylval, buff);
941 	}
942 	if (c == '@') {
943 	    *yyInput = ++inp;
944 	    return AT_SIGN;
945 	}
946 	if (c != '(') {
947 	    *yyInput = ++inp;
948 	    return c;
949 	}
950 	Count = 0;
951 	do {
952 	    c = *inp++;
953 	    if (c == '\0')
954 		return c;
955 	    if (c == '(')
956 		Count++;
957 	    else if (c == ')')
958 		Count--;
959 	} while (Count > 0);
960     }
961 }
962 
963 #define TM_YEAR_ORIGIN 1900
964 
965 time_t
966 parsedate(const char *p, const time_t *now, const int *zone)
967 {
968     struct tm		local, *tm;
969     time_t		nowt;
970     int			zonet;
971     time_t		Start;
972     time_t		tod, rm;
973     struct dateinfo	param;
974     int			saved_errno;
975     int			i;
976 
977     saved_errno = errno;
978     errno = 0;
979 
980     if (now == NULL) {
981         now = &nowt;
982 	(void)time(&nowt);
983     }
984     if (zone == NULL) {
985 	zone = &zonet;
986 	zonet = USE_LOCAL_TIME;
987 	if ((tm = localtime_r(now, &local)) == NULL)
988 	    return -1;
989     } else {
990 	/*
991 	 * Should use the specified zone, not localtime.
992 	 * Fake it using gmtime and arithmetic.
993 	 * This is good enough because we use only the year/month/day,
994 	 * not other fields of struct tm.
995 	 */
996 	time_t fake = *now + (*zone * 60);
997 	if ((tm = gmtime_r(&fake, &local)) == NULL)
998 	    return -1;
999     }
1000     param.yyYear = tm->tm_year + 1900;
1001     param.yyMonth = tm->tm_mon + 1;
1002     param.yyDay = tm->tm_mday;
1003     param.yyTimezone = *zone;
1004     param.yyDSTmode = DSTmaybe;
1005     param.yyHour = 0;
1006     param.yyMinutes = 0;
1007     param.yySeconds = 0;
1008     param.yyMeridian = MER24;
1009     param.yyHaveDate = 0;
1010     param.yyHaveFullYear = 0;
1011     param.yyHaveDay = 0;
1012     param.yyHaveRel = 0;
1013     param.yyHaveTime = 0;
1014     param.yyHaveZone = 0;
1015 
1016     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
1017 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
1018 	errno = EINVAL;
1019 	return -1;
1020     }
1021 
1022     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
1023 	if (! param.yyHaveFullYear) {
1024 		param.yyYear = AdjustYear(param.yyYear);
1025 		param.yyHaveFullYear = 1;
1026 	}
1027 	errno = 0;
1028 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
1029 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
1030 	    param.yyMeridian, param.yyDSTmode);
1031 	if (Start == -1 && errno != 0)
1032 	    return -1;
1033     }
1034     else {
1035 	Start = *now;
1036 	if (!param.yyHaveRel)
1037 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
1038     }
1039 
1040     if (param.yyHaveRel > MAXREL) {
1041 	errno = EINVAL;
1042 	return -1;
1043     }
1044     for (i = 0; i < param.yyHaveRel; i++) {
1045 	if (param.yyRel[i].yyRelMonth) {
1046 	    errno = 0;
1047 	    rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
1048 	    if (rm == -1 && errno != 0)
1049 		return -1;
1050 	    Start += rm;
1051 	} else
1052 	    Start += param.yyRel[i].yyRelVal;
1053     }
1054 
1055     if (param.yyHaveDay && !param.yyHaveDate) {
1056 	errno = 0;
1057 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1058 	if (tod == -1 && errno != 0)
1059 	    return -1;
1060 	Start += tod;
1061     }
1062 
1063     errno = saved_errno;
1064     return Start;
1065 }
1066 
1067 
1068 #if	defined(TEST)
1069 
1070 /* ARGSUSED */
1071 int
1072 main(int ac, char *av[])
1073 {
1074     char	buff[128];
1075     time_t	d;
1076 
1077     (void)printf("Enter date, or blank line to exit.\n\t> ");
1078     (void)fflush(stdout);
1079     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1080 	errno = 0;
1081 	d = parsedate(buff, NULL, NULL);
1082 	if (d == -1 && errno != 0)
1083 	    (void)printf("Bad format - couldn't convert: %s\n",
1084 	        strerror(errno));
1085 	else
1086 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1087 	(void)printf("\t> ");
1088 	(void)fflush(stdout);
1089     }
1090     exit(0);
1091     /* NOTREACHED */
1092 }
1093 #endif	/* defined(TEST) */
1094