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