xref: /dragonfly/usr.bin/find/getdate.y (revision 333227be)
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 /* $FreeBSD: src/usr.bin/find/getdate.y,v 1.2.4.1 2003/01/22 03:26:34 peter Exp $ */
16 /* $DragonFly: src/usr.bin/find/getdate.y,v 1.2 2003/06/17 04:29:26 dillon Exp $ */
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 # include <sys/timeb.h>
32 #endif	/* !defined(vms) */
33 
34 #if defined (__STDC__) || defined (USG)
35 #include <string.h>
36 #endif
37 
38 /* Some old versions of bison generate parsers that use bcopy.
39    That loses on systems that don't provide the function, so we have
40    to redefine it here.  */
41 #if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
42 #define bcopy(from, to, len) memcpy ((to), (from), (len))
43 #endif
44 
45 #if defined (__STDC__)
46 #include <stdlib.h>
47 #endif
48 
49 /* NOTES on rebuilding getdate.c (particularly for inclusion in CVS
50    releases):
51 
52    We don't want to mess with all the portability hassles of alloca.
53    In particular, most (all?) versions of bison will use alloca in
54    their parser.  If bison works on your system (e.g. it should work
55    with gcc), then go ahead and use it, but the more general solution
56    is to use byacc instead of bison, which should generate a portable
57    parser.  I played with adding "#define alloca dont_use_alloca", to
58    give an error if the parser generator uses alloca (and thus detect
59    unportable getdate.c's), but that seems to cause as many problems
60    as it solves.  */
61 
62 extern struct tm	*gmtime();
63 extern struct tm	*localtime();
64 
65 #define yyparse getdate_yyparse
66 #define yylex getdate_yylex
67 #define yyerror getdate_yyerror
68 
69 static int yyparse ();
70 static int yylex ();
71 static int yyerror ();
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     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 }
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 }
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 }
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  }
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 }
533 };
534 
535 
536 
537 
538 /* ARGSUSED */
539 static int
540 yyerror(s)
541     char	*s __unused;
542 {
543   return 0;
544 }
545 
546 
547 static time_t
548 ToSeconds(Hours, Minutes, Seconds, Meridian)
549     time_t	Hours;
550     time_t	Minutes;
551     time_t	Seconds;
552     MERIDIAN	Meridian;
553 {
554     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
555 	return -1;
556     switch (Meridian) {
557     case MER24:
558 	if (Hours < 0 || Hours > 23)
559 	    return -1;
560 	return (Hours * 60L + Minutes) * 60L + Seconds;
561     case MERam:
562 	if (Hours < 1 || Hours > 12)
563 	    return -1;
564 	if (Hours == 12)
565 	    Hours = 0;
566 	return (Hours * 60L + Minutes) * 60L + Seconds;
567     case MERpm:
568 	if (Hours < 1 || Hours > 12)
569 	    return -1;
570 	if (Hours == 12)
571 	    Hours = 0;
572 	return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
573     default:
574 	abort ();
575     }
576     /* NOTREACHED */
577 }
578 
579 
580 /* Year is either
581    * A negative number, which means to use its absolute value (why?)
582    * A number from 0 to 99, which means a year from 1900 to 1999, or
583    * The actual year (>=100).  */
584 static time_t
585 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
586     time_t	Month;
587     time_t	Day;
588     time_t	Year;
589     time_t	Hours;
590     time_t	Minutes;
591     time_t	Seconds;
592     MERIDIAN	Meridian;
593     DSTMODE	DSTmode;
594 {
595     static int DaysInMonth[12] = {
596 	31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
597     };
598     time_t	tod;
599     time_t	Julian;
600     int		i;
601 
602     if (Year < 0)
603 	Year = -Year;
604     if (Year < 69)
605 	Year += 2000;
606     else if (Year < 100)
607 	Year += 1900;
608     DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
609 		    ? 29 : 28;
610     /* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
611        I'm too lazy to try to check for time_t overflow in another way.  */
612     if (Year < EPOCH || Year > 2038
613      || Month < 1 || Month > 12
614      /* Lint fluff:  "conversion from long may lose accuracy" */
615      || Day < 1 || Day > DaysInMonth[(int)--Month])
616 	return -1;
617 
618     for (Julian = Day - 1, i = 0; i < Month; i++)
619 	Julian += DaysInMonth[i];
620     for (i = EPOCH; i < Year; i++)
621 	Julian += 365 + (i % 4 == 0);
622     Julian *= SECSPERDAY;
623     Julian += yyTimezone * 60L;
624     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
625 	return -1;
626     Julian += tod;
627     if (DSTmode == DSTon
628      || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
629 	Julian -= 60 * 60;
630     return Julian;
631 }
632 
633 
634 static time_t
635 DSTcorrect(Start, Future)
636     time_t	Start;
637     time_t	Future;
638 {
639     time_t	StartDay;
640     time_t	FutureDay;
641 
642     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
643     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
644     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
645 }
646 
647 
648 static time_t
649 RelativeDate(Start, DayOrdinal, DayNumber)
650     time_t	Start;
651     time_t	DayOrdinal;
652     time_t	DayNumber;
653 {
654     struct tm	*tm;
655     time_t	now;
656 
657     now = Start;
658     tm = localtime(&now);
659     now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
660     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
661     return DSTcorrect(Start, now);
662 }
663 
664 
665 static time_t
666 RelativeMonth(Start, RelMonth)
667     time_t	Start;
668     time_t	RelMonth;
669 {
670     struct tm	*tm;
671     time_t	Month;
672     time_t	Year;
673 
674     if (RelMonth == 0)
675 	return 0;
676     tm = localtime(&Start);
677     Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
678     Year = Month / 12;
679     Month = Month % 12 + 1;
680     return DSTcorrect(Start,
681 	    Convert(Month, (time_t)tm->tm_mday, Year,
682 		(time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
683 		MER24, DSTmaybe));
684 }
685 
686 
687 static int
688 LookupWord(buff)
689     char		*buff;
690 {
691     register char	*p;
692     register char	*q;
693     register const TABLE	*tp;
694     int			i;
695     int			abbrev;
696 
697     /* Make it lowercase. */
698     for (p = buff; *p; p++)
699 	if (isupper(*p))
700 	    *p = tolower(*p);
701 
702     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
703 	yylval.Meridian = MERam;
704 	return tMERIDIAN;
705     }
706     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
707 	yylval.Meridian = MERpm;
708 	return tMERIDIAN;
709     }
710 
711     /* See if we have an abbreviation for a month. */
712     if (strlen(buff) == 3)
713 	abbrev = 1;
714     else if (strlen(buff) == 4 && buff[3] == '.') {
715 	abbrev = 1;
716 	buff[3] = '\0';
717     }
718     else
719 	abbrev = 0;
720 
721     for (tp = MonthDayTable; tp->name; tp++) {
722 	if (abbrev) {
723 	    if (strncmp(buff, tp->name, 3) == 0) {
724 		yylval.Number = tp->value;
725 		return tp->type;
726 	    }
727 	}
728 	else if (strcmp(buff, tp->name) == 0) {
729 	    yylval.Number = tp->value;
730 	    return tp->type;
731 	}
732     }
733 
734     for (tp = TimezoneTable; tp->name; tp++)
735 	if (strcmp(buff, tp->name) == 0) {
736 	    yylval.Number = tp->value;
737 	    return tp->type;
738 	}
739 
740     if (strcmp(buff, "dst") == 0)
741 	return tDST;
742 
743     for (tp = UnitsTable; tp->name; tp++)
744 	if (strcmp(buff, tp->name) == 0) {
745 	    yylval.Number = tp->value;
746 	    return tp->type;
747 	}
748 
749     /* Strip off any plural and try the units table again. */
750     i = strlen(buff) - 1;
751     if (buff[i] == 's') {
752 	buff[i] = '\0';
753 	for (tp = UnitsTable; tp->name; tp++)
754 	    if (strcmp(buff, tp->name) == 0) {
755 		yylval.Number = tp->value;
756 		return tp->type;
757 	    }
758 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
759     }
760 
761     for (tp = OtherTable; tp->name; tp++)
762 	if (strcmp(buff, tp->name) == 0) {
763 	    yylval.Number = tp->value;
764 	    return tp->type;
765 	}
766 
767     /* Military timezones. */
768     if (buff[1] == '\0' && isalpha(*buff)) {
769 	for (tp = MilitaryTable; tp->name; tp++)
770 	    if (strcmp(buff, tp->name) == 0) {
771 		yylval.Number = tp->value;
772 		return tp->type;
773 	    }
774     }
775 
776     /* Drop out any periods and try the timezone table again. */
777     for (i = 0, p = q = buff; *q; q++)
778 	if (*q != '.')
779 	    *p++ = *q;
780 	else
781 	    i++;
782     *p = '\0';
783     if (i)
784 	for (tp = TimezoneTable; tp->name; tp++)
785 	    if (strcmp(buff, tp->name) == 0) {
786 		yylval.Number = tp->value;
787 		return tp->type;
788 	    }
789 
790     return tID;
791 }
792 
793 
794 static int
795 yylex()
796 {
797     register char	c;
798     register char	*p;
799     char		buff[20];
800     int			Count;
801     int			sign;
802 
803     for ( ; ; ) {
804 	while (isspace(*yyInput))
805 	    yyInput++;
806 
807 	if (isdigit(c = *yyInput) || c == '-' || c == '+') {
808 	    if (c == '-' || c == '+') {
809 		sign = c == '-' ? -1 : 1;
810 		if (!isdigit(*++yyInput))
811 		    /* skip the '-' sign */
812 		    continue;
813 	    }
814 	    else
815 		sign = 0;
816 	    for (yylval.Number = 0; isdigit(c = *yyInput++); )
817 		yylval.Number = 10 * yylval.Number + c - '0';
818 	    yyInput--;
819 	    if (sign < 0)
820 		yylval.Number = -yylval.Number;
821 	    return sign ? tSNUMBER : tUNUMBER;
822 	}
823 	if (isalpha(c)) {
824 	    for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
825 		if (p < &buff[sizeof buff - 1])
826 		    *p++ = c;
827 	    *p = '\0';
828 	    yyInput--;
829 	    return LookupWord(buff);
830 	}
831 	if (c != '(')
832 	    return *yyInput++;
833 	Count = 0;
834 	do {
835 	    c = *yyInput++;
836 	    if (c == '\0')
837 		return c;
838 	    if (c == '(')
839 		Count++;
840 	    else if (c == ')')
841 		Count--;
842 	} while (Count > 0);
843     }
844 }
845 
846 #define TM_YEAR_ORIGIN 1900
847 
848 /* Yield A - B, measured in seconds.  */
849 static long
850 difftm (a, b)
851      struct tm *a, *b;
852 {
853   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
854   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
855   int days = (
856 	      /* difference in day of year */
857 	      a->tm_yday - b->tm_yday
858 	      /* + intervening leap days */
859 	      +  ((ay >> 2) - (by >> 2))
860 	      -  (ay/100 - by/100)
861 	      +  ((ay/100 >> 2) - (by/100 >> 2))
862 	      /* + difference in years * 365 */
863 	      +  (long)(ay-by) * 365
864 	      );
865   return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
866 	      + (a->tm_min - b->tm_min))
867 	  + (a->tm_sec - b->tm_sec));
868 }
869 
870 time_t
871 get_date(p, now)
872     char		*p;
873     struct timeb	*now;
874 {
875     struct tm		*tm, gmt;
876     struct timeb	ftz;
877     time_t		Start;
878     time_t		tod;
879     time_t nowtime;
880 
881     yyInput = p;
882     if (now == NULL) {
883 	struct tm *gmt_ptr;
884 
885         now = &ftz;
886 	(void)time (&nowtime);
887 
888 	gmt_ptr = gmtime (&nowtime);
889 	if (gmt_ptr != NULL)
890 	{
891 	    /* Make a copy, in case localtime modifies *tm (I think
892 	       that comment now applies to *gmt_ptr, but I am too
893 	       lazy to dig into how gmtime and locatime allocate the
894 	       structures they return pointers to).  */
895 	    gmt = *gmt_ptr;
896 	}
897 
898 	if (! (tm = localtime (&nowtime)))
899 	    return -1;
900 
901 	if (gmt_ptr != NULL)
902 	    ftz.timezone = difftm (&gmt, tm) / 60;
903 	else
904 	    /* We are on a system like VMS, where the system clock is
905 	       in local time and the system has no concept of timezones.
906 	       Hopefully we can fake this out (for the case in which the
907 	       user specifies no timezone) by just saying the timezone
908 	       is zero.  */
909 	    ftz.timezone = 0;
910 
911 	if(tm->tm_isdst)
912 	    ftz.timezone += 60;
913     }
914     else
915     {
916 	nowtime = now->time;
917     }
918 
919     tm = localtime(&nowtime);
920     yyYear = tm->tm_year + 1900;
921     yyMonth = tm->tm_mon + 1;
922     yyDay = tm->tm_mday;
923     yyTimezone = now->timezone;
924     yyDSTmode = DSTmaybe;
925     yyHour = 0;
926     yyMinutes = 0;
927     yySeconds = 0;
928     yyMeridian = MER24;
929     yyRelSeconds = 0;
930     yyRelMonth = 0;
931     yyHaveDate = 0;
932     yyHaveDay = 0;
933     yyHaveRel = 0;
934     yyHaveTime = 0;
935     yyHaveZone = 0;
936 
937     if (yyparse()
938      || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
939 	return -1;
940 
941     if (yyHaveDate || yyHaveTime || yyHaveDay) {
942 	Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
943 		    yyMeridian, yyDSTmode);
944 	if (Start < 0)
945 	    return -1;
946     }
947     else {
948 	Start = nowtime;
949 	if (!yyHaveRel)
950 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
951     }
952 
953     Start += yyRelSeconds;
954     Start += RelativeMonth(Start, yyRelMonth);
955 
956     if (yyHaveDay && !yyHaveDate) {
957 	tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
958 	Start += tod;
959     }
960 
961     /* Have to do *something* with a legitimate -1 so it's distinguishable
962      * from the error return value.  (Alternately could set errno on error.) */
963     return Start == -1 ? 0 : Start;
964 }
965 
966 
967 #if	defined(TEST)
968 
969 /* ARGSUSED */
970 int
971 main(ac, av)
972     int		ac;
973     char	*av[];
974 {
975     char	buff[128];
976     time_t	d;
977 
978     (void)printf("Enter date, or blank line to exit.\n\t> ");
979     (void)fflush(stdout);
980     while (gets(buff) && buff[0]) {
981 	d = get_date(buff, (struct timeb *)NULL);
982 	if (d == -1)
983 	    (void)printf("Bad format - couldn't convert.\n");
984 	else
985 	    (void)printf("%s", ctime(&d));
986 	(void)printf("\t> ");
987 	(void)fflush(stdout);
988     }
989     exit(0);
990     /* NOTREACHED */
991 }
992 #endif	/* defined(TEST) */
993