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