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