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