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