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