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