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