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