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