xref: /freebsd/usr.sbin/fifolog/lib/getdate.y (revision 4a0b57b0b4cbf7eb848927cf3f493163c3e76142)
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 ** $FreeBSD$
16 */
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <ctype.h>
21 #include <string.h>
22 #include <sys/types.h>
23 #include <sys/time.h>
24 
25 #include "libfifolog.h"
26 
27 #define yyparse getdate_yyparse
28 #define yylex getdate_yylex
29 #define yyerror getdate_yyerror
30 
31 static int yyparse(void);
32 static int yylex(void);
33 static int yyerror(const char *);
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     DSToff, DSTon, 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 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(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
528 {
529     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
530 	return -1;
531     switch (Meridian) {
532     case MER24:
533 	if (Hours < 0 || Hours > 23)
534 	    return -1;
535 	return (Hours * 60L + Minutes) * 60L + Seconds;
536     case MERam:
537 	if (Hours < 1 || Hours > 12)
538 	    return -1;
539 	if (Hours == 12)
540 	    Hours = 0;
541 	return (Hours * 60L + Minutes) * 60L + Seconds;
542     case MERpm:
543 	if (Hours < 1 || Hours > 12)
544 	    return -1;
545 	if (Hours == 12)
546 	    Hours = 0;
547 	return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
548     default:
549 	abort ();
550     }
551     /* NOTREACHED */
552 }
553 
554 
555 /* Year is either
556    * A negative number, which means to use its absolute value (why?)
557    * A number from 0 to 99, which means a year from 1900 to 1999, or
558    * The actual year (>=100).  */
559 static time_t
560 Convert(time_t Month, time_t Day, time_t Year,
561     time_t Hours, time_t Minutes, time_t Seconds,
562     MERIDIAN Meridian, DSTMODE DSTmode)
563 {
564     static int DaysInMonth[12] = {
565 	31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
566     };
567     time_t	tod;
568     time_t	Julian;
569     int		i;
570     struct tm   *ltm;
571 
572     if (Year < 0)
573 	Year = -Year;
574     if (Year < 69)
575 	Year += 2000;
576     else if (Year < 100)
577 	Year += 1900;
578     DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
579 		    ? 29 : 28;
580     /* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
581        I'm too lazy to try to check for time_t overflow in another way.  */
582     if (Year < EPOCH || Year > 2038
583      || Month < 1 || Month > 12
584      /* Lint fluff:  "conversion from long may lose accuracy" */
585      || Day < 1 || Day > DaysInMonth[(int)--Month])
586 	/* FIXME:
587 	 * It would be nice to set a global error string here.
588 	 * "February 30 is not a valid date" is much more informative than
589 	 * "Can't parse date/time: 100 months" when the user input was
590 	 * "100 months" and addition resolved that to February 30, for
591 	 * example.  See rcs2-7 in src/sanity.sh for more. */
592 	return -1;
593 
594     for (Julian = Day - 1, i = 0; i < Month; i++)
595 	Julian += DaysInMonth[i];
596     for (i = EPOCH; i < Year; i++)
597 	Julian += 365 + (i % 4 == 0);
598     Julian *= SECSPERDAY;
599     Julian += yyTimezone * 60L;
600     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
601 	return -1;
602     Julian += tod;
603     ltm = localtime(&Julian);
604 fprintf(stderr, "DST %d TZ %s %d\n", DSTmode, ltm->tm_zone, ltm->tm_isdst);
605     if (DSTmode == DSTon
606      || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
607 	Julian -= 60 * 60;
608     return Julian;
609 }
610 
611 
612 static time_t
613 DSTcorrect(time_t Start, time_t Future)
614 {
615     time_t	StartDay;
616     time_t	FutureDay;
617 
618     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
619     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
620     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
621 }
622 
623 
624 static time_t
625 RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
626 {
627     struct tm	*tm;
628     time_t	now;
629 
630     now = Start;
631     tm = localtime(&now);
632     now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
633     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
634     return DSTcorrect(Start, now);
635 }
636 
637 
638 static time_t
639 RelativeMonth(time_t Start, time_t RelMonth)
640 {
641     struct tm	*tm;
642     time_t	Month;
643     time_t	Year;
644 
645     if (RelMonth == 0)
646 	return 0;
647     tm = localtime(&Start);
648     Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
649     Year = Month / 12;
650     Month = Month % 12 + 1;
651     return DSTcorrect(Start,
652 	    Convert(Month, (time_t)tm->tm_mday, Year,
653 		(time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
654 		MER24, DSTmaybe));
655 }
656 
657 
658 static int
659 LookupWord(char *buff)
660 {
661     char	*p;
662     char	*q;
663     const TABLE	*tp;
664     int			i;
665     int			abbrev;
666 
667     /* Make it lowercase. */
668     for (p = buff; *p; p++)
669 	if (isupper(*p))
670 	    *p = tolower(*p);
671 
672     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
673 	yylval.Meridian = MERam;
674 	return tMERIDIAN;
675     }
676     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
677 	yylval.Meridian = MERpm;
678 	return tMERIDIAN;
679     }
680 
681     /* See if we have an abbreviation for a month. */
682     if (strlen(buff) == 3)
683 	abbrev = 1;
684     else if (strlen(buff) == 4 && buff[3] == '.') {
685 	abbrev = 1;
686 	buff[3] = '\0';
687     }
688     else
689 	abbrev = 0;
690 
691     for (tp = MonthDayTable; tp->name; tp++) {
692 	if (abbrev) {
693 	    if (strncmp(buff, tp->name, 3) == 0) {
694 		yylval.Number = tp->value;
695 		return tp->type;
696 	    }
697 	}
698 	else if (strcmp(buff, tp->name) == 0) {
699 	    yylval.Number = tp->value;
700 	    return tp->type;
701 	}
702     }
703 
704     for (tp = TimezoneTable; tp->name; tp++)
705 	if (strcmp(buff, tp->name) == 0) {
706 	    yylval.Number = tp->value;
707 	    return tp->type;
708 	}
709 
710     if (strcmp(buff, "dst") == 0)
711 	return tDST;
712 
713     for (tp = UnitsTable; tp->name; tp++)
714 	if (strcmp(buff, tp->name) == 0) {
715 	    yylval.Number = tp->value;
716 	    return tp->type;
717 	}
718 
719     /* Strip off any plural and try the units table again. */
720     i = strlen(buff) - 1;
721     if (buff[i] == 's') {
722 	buff[i] = '\0';
723 	for (tp = UnitsTable; tp->name; tp++)
724 	    if (strcmp(buff, tp->name) == 0) {
725 		yylval.Number = tp->value;
726 		return tp->type;
727 	    }
728 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
729     }
730 
731     for (tp = OtherTable; tp->name; tp++)
732 	if (strcmp(buff, tp->name) == 0) {
733 	    yylval.Number = tp->value;
734 	    return tp->type;
735 	}
736 
737     /* Military timezones. */
738     if (buff[1] == '\0' && isalpha(*buff)) {
739 	for (tp = MilitaryTable; tp->name; tp++)
740 	    if (strcmp(buff, tp->name) == 0) {
741 		yylval.Number = tp->value;
742 		return tp->type;
743 	    }
744     }
745 
746     /* Drop out any periods and try the timezone table again. */
747     for (i = 0, p = q = buff; *q; q++)
748 	if (*q != '.')
749 	    *p++ = *q;
750 	else
751 	    i++;
752     *p = '\0';
753     if (i)
754 	for (tp = TimezoneTable; tp->name; tp++)
755 	    if (strcmp(buff, tp->name) == 0) {
756 		yylval.Number = tp->value;
757 		return tp->type;
758 	    }
759 
760     return tID;
761 }
762 
763 
764 static int
765 yylex(void)
766 {
767     char	c;
768     char	*p;
769     char		buff[20];
770     int			Count;
771     int			sign;
772 
773     for ( ; ; ) {
774 	while (isspace(*yyInput))
775 	    yyInput++;
776 
777 	if (isdigit(c = *yyInput) || c == '-' || c == '+') {
778 	    if (c == '-' || c == '+') {
779 		sign = c == '-' ? -1 : 1;
780 		if (!isdigit(*++yyInput))
781 		    /* skip the '-' sign */
782 		    continue;
783 	    }
784 	    else
785 		sign = 0;
786 	    for (yylval.Number = 0; isdigit(c = *yyInput++); )
787 		yylval.Number = 10 * yylval.Number + c - '0';
788 	    yyInput--;
789 	    if (sign < 0)
790 		yylval.Number = -yylval.Number;
791 	    return sign ? tSNUMBER : tUNUMBER;
792 	}
793 	if (isalpha(c)) {
794 	    for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
795 		if (p < &buff[sizeof buff - 1])
796 		    *p++ = c;
797 	    *p = '\0';
798 	    yyInput--;
799 	    return LookupWord(buff);
800 	}
801 	if (c != '(')
802 	    return *yyInput++;
803 	Count = 0;
804 	do {
805 	    c = *yyInput++;
806 	    if (c == '\0')
807 		return c;
808 	    if (c == '(')
809 		Count++;
810 	    else if (c == ')')
811 		Count--;
812 	} while (Count > 0);
813     }
814 }
815 
816 #define TM_YEAR_ORIGIN 1900
817 
818 time_t
819 get_date(char *p)
820 {
821     struct tm		*tm;
822     time_t		Start;
823     time_t		tod;
824     time_t nowtime;
825 
826     yyInput = p;
827 
828     (void)time (&nowtime);
829 
830     if (! (tm = localtime (&nowtime)))
831 	return -1;
832 
833     yyYear = tm->tm_year + 1900;
834     yyMonth = tm->tm_mon + 1;
835     yyDay = tm->tm_mday;
836     yyTimezone = tm->tm_gmtoff;
837     yyDSTmode = DSTmaybe;
838     yyHour = 0;
839     yyMinutes = 0;
840     yySeconds = 0;
841     yyMeridian = MER24;
842     yyRelSeconds = 0;
843     yyRelMonth = 0;
844     yyHaveDate = 0;
845     yyHaveDay = 0;
846     yyHaveRel = 0;
847     yyHaveTime = 0;
848     yyHaveZone = 0;
849 
850     if (yyparse()
851      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
852 	return -1;
853 
854     if (yyHaveDate || yyHaveTime || yyHaveDay) {
855 	Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
856 		    yyMeridian, yyDSTmode);
857 	if (Start < 0)
858 	    return -1;
859     }
860     else {
861 	Start = nowtime;
862 	if (!yyHaveRel)
863 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
864     }
865 
866     Start += yyRelSeconds;
867     Start += RelativeMonth(Start, yyRelMonth);
868 
869     if (yyHaveDay && !yyHaveDate) {
870 	tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
871 	Start += tod;
872     }
873 
874     /* Have to do *something* with a legitimate -1 so it's distinguishable
875      * from the error return value.  (Alternately could set errno on error.) */
876     return Start == -1 ? 0 : Start;
877 }
878