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