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