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