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