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