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