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