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