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