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