date.y revision 1.23
1%{
2/*	$OpenBSD: date.y,v 1.23 2013/04/19 17:28:06 deraadt 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 <sys/timeb.h>
18
19#include <ctype.h>
20#include <string.h>
21
22#include "cvs.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 */
31typedef 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. */
39typedef enum _DSTMODE {
40	DSTon, DSToff, DSTmaybe
41} DSTMODE;
42
43/*  Meridian:  am, pm, or 24-hour style. */
44typedef 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 */
55static const char	*yyInput;
56static DSTMODE	yyDSTmode;
57static time_t	yyDayOrdinal;
58static time_t	yyDayNumber;
59static int	yyHaveDate;
60static int	yyHaveDay;
61static int	yyHaveRel;
62static int	yyHaveTime;
63static int	yyHaveZone;
64static time_t	yyTimezone;
65static time_t	yyDay;
66static time_t	yyHour;
67static time_t	yyMinutes;
68static time_t	yyMonth;
69static time_t	yySeconds;
70static time_t	yyYear;
71static MERIDIAN	yyMeridian;
72static time_t	yyRelMonth;
73static time_t	yyRelSeconds;
74
75
76static int	yyerror(const char *);
77static int	yylex(void);
78static int	yyparse(void);
79static 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
97spec	: /* NULL */
98	| spec item
99	;
100
101item	: 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
119time	: 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
154zone	: 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
168day	: 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
182date	: 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
229rel	: relunit tAGO {
230		yyRelSeconds = -yyRelSeconds;
231		yyRelMonth = -yyRelMonth;
232	}
233	| relunit
234	;
235
236relunit	: 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
265number	: 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
290o_merid	: /* NULL */ {
291		$$ = MER24;
292	}
293	| tMERIDIAN {
294		$$ = $1;
295	}
296	;
297
298%%
299
300/* Month and day table. */
301static 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. */
330static 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. */
345static 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. */
371static 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. */
455static 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
485static int
486yyerror(const char *s)
487{
488#if !defined(TEST)
489	char *str;
490
491	(void)xasprintf(&str, "parsing date string: %s", s);
492	cvs_log(LP_ERR, "%s", str);
493	xfree(str);
494#endif
495	return (0);
496}
497
498
499static time_t
500ToSeconds(time_t Hours, time_t Minutes, time_t	Seconds, MERIDIAN Meridian)
501{
502	if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
503		return (-1);
504
505	switch (Meridian) {
506	case MER24:
507		if (Hours < 0 || Hours > 23)
508			return (-1);
509		return (Hours * 60L + Minutes) * 60L + Seconds;
510	case MERam:
511		if (Hours < 1 || Hours > 12)
512			return (-1);
513		if (Hours == 12)
514			Hours = 0;
515		return (Hours * 60L + Minutes) * 60L + Seconds;
516	case MERpm:
517		if (Hours < 1 || Hours > 12)
518			return (-1);
519		if (Hours == 12)
520			Hours = 0;
521		return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
522	default:
523		return (-1);
524	}
525	/* NOTREACHED */
526}
527
528
529/* Year is either
530 * A negative number, which means to use its absolute value (why?)
531 * A number from 0 to 99, which means a year from 1900 to 1999, or
532 * The actual year (>=100).
533 */
534static time_t
535Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes,
536    time_t Seconds, MERIDIAN Meridian, DSTMODE DSTmode)
537{
538	static int DaysInMonth[12] = {
539		31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
540	};
541	time_t	tod;
542	time_t	julian;
543	int	i;
544
545	if (Year < 0)
546		Year = -Year;
547	if (Year < 69)
548		Year += 2000;
549	else if (Year < 100) {
550		Year += 1900;
551		if (Year < YEAR_EPOCH)
552			Year += 100;
553	}
554	DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
555	    ? 29 : 28;
556	/* XXX Sloppily check for 2038 if time_t is 32 bits */
557	if (Year < YEAR_EPOCH ||
558	    (sizeof(time_t) == sizeof(int) && Year > 2038) ||
559	    Month < 1 || Month > 12 ||
560	    /* Lint fluff:  "conversion from long may lose accuracy" */
561	     Day < 1 || Day > DaysInMonth[(int)--Month])
562		return (-1);
563
564	for (julian = Day - 1, i = 0; i < Month; i++)
565		julian += DaysInMonth[i];
566
567	for (i = YEAR_EPOCH; i < Year; i++)
568		julian += 365 + (i % 4 == 0);
569	julian *= SECSPERDAY;
570	julian += yyTimezone * 60L;
571
572	if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
573		return (-1);
574	julian += tod;
575	if ((DSTmode == DSTon) ||
576	    (DSTmode == DSTmaybe && localtime(&julian)->tm_isdst))
577	julian -= 60 * 60;
578	return (julian);
579}
580
581
582static time_t
583DSTcorrect(time_t Start, time_t Future)
584{
585	time_t	StartDay;
586	time_t	FutureDay;
587
588	StartDay = (localtime(&Start)->tm_hour + 1) % 24;
589	FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
590	return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
591}
592
593
594static time_t
595RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
596{
597	struct tm	*tm;
598	time_t	now;
599
600	now = Start;
601	tm = localtime(&now);
602	now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
603	now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
604	return DSTcorrect(Start, now);
605}
606
607
608static time_t
609RelativeMonth(time_t Start, time_t RelMonth)
610{
611	struct tm	*tm;
612	time_t	Month;
613	time_t	Year;
614
615	if (RelMonth == 0)
616		return (0);
617	tm = localtime(&Start);
618	Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
619	Year = Month / 12;
620	Month = Month % 12 + 1;
621	return DSTcorrect(Start,
622	    Convert(Month, (time_t)tm->tm_mday, Year,
623	    (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
624	    MER24, DSTmaybe));
625}
626
627
628static int
629lookup(char *buff)
630{
631	size_t		len;
632	char		*p, *q;
633	int		i, abbrev;
634	const TABLE	*tp;
635
636	/* Make it lowercase. */
637	for (p = buff; *p; p++)
638		if (isupper(*p))
639			*p = tolower(*p);
640
641	if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
642		yylval.Meridian = MERam;
643		return (tMERIDIAN);
644	}
645	if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
646		yylval.Meridian = MERpm;
647		return (tMERIDIAN);
648	}
649
650	len = strlen(buff);
651	/* See if we have an abbreviation for a month. */
652	if (len == 3)
653		abbrev = 1;
654	else if (len == 4 && buff[3] == '.') {
655		abbrev = 1;
656		buff[3] = '\0';
657		--len;
658	} else
659		abbrev = 0;
660
661	for (tp = MonthDayTable; tp->name; tp++) {
662		if (abbrev) {
663			if (strncmp(buff, tp->name, 3) == 0) {
664				yylval.Number = tp->value;
665				return (tp->type);
666			}
667		} else if (strcmp(buff, tp->name) == 0) {
668			yylval.Number = tp->value;
669			return (tp->type);
670		}
671	}
672
673	for (tp = TimezoneTable; tp->name; tp++)
674		if (strcmp(buff, tp->name) == 0) {
675			yylval.Number = tp->value;
676			return (tp->type);
677		}
678
679	if (strcmp(buff, "dst") == 0)
680		return (tDST);
681
682	for (tp = UnitsTable; tp->name; tp++)
683		if (strcmp(buff, tp->name) == 0) {
684			yylval.Number = tp->value;
685			return (tp->type);
686		}
687
688	/* Strip off any plural and try the units table again. */
689	if (len != 0 && buff[len - 1] == 's') {
690		buff[len - 1] = '\0';
691		for (tp = UnitsTable; tp->name; tp++)
692			if (strcmp(buff, tp->name) == 0) {
693				yylval.Number = tp->value;
694				return (tp->type);
695			}
696		buff[len - 1] = 's';	/* Put back for "this" in OtherTable. */
697	}
698
699	for (tp = OtherTable; tp->name; tp++)
700		if (strcmp(buff, tp->name) == 0) {
701			yylval.Number = tp->value;
702			return (tp->type);
703		}
704
705	/* Military timezones. */
706	if (len == 1 && isalpha(*buff)) {
707		for (tp = MilitaryTable; tp->name; tp++)
708			if (strcmp(buff, tp->name) == 0) {
709				yylval.Number = tp->value;
710				return (tp->type);
711			}
712	}
713
714	/* Drop out any periods and try the timezone table again. */
715	for (i = 0, p = q = buff; *q; q++)
716		if (*q != '.')
717			*p++ = *q;
718		else
719			i++;
720	*p = '\0';
721	if (i)
722		for (tp = TimezoneTable; tp->name; tp++)
723			if (strcmp(buff, tp->name) == 0) {
724				yylval.Number = tp->value;
725				return (tp->type);
726			}
727
728	return (tID);
729}
730
731
732static int
733yylex(void)
734{
735	char	c, *p, buff[20];
736	int	count, sign;
737
738	for (;;) {
739		while (isspace(*yyInput))
740			yyInput++;
741
742		if (isdigit(c = *yyInput) || c == '-' || c == '+') {
743			if (c == '-' || c == '+') {
744				sign = c == '-' ? -1 : 1;
745				if (!isdigit(*++yyInput))
746					/* skip the '-' sign */
747					continue;
748			}
749			else
750				sign = 0;
751
752			for (yylval.Number = 0; isdigit(c = *yyInput++); )
753				yylval.Number = 10 * yylval.Number + c - '0';
754			yyInput--;
755			if (sign < 0)
756				yylval.Number = -yylval.Number;
757			return sign ? tSNUMBER : tUNUMBER;
758		}
759
760		if (isalpha(c)) {
761			for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
762				if (p < &buff[sizeof buff - 1])
763					*p++ = c;
764			*p = '\0';
765			yyInput--;
766			return lookup(buff);
767		}
768		if (c != '(')
769			return *yyInput++;
770
771		count = 0;
772		do {
773			c = *yyInput++;
774			if (c == '\0')
775				return (c);
776			if (c == '(')
777				count++;
778			else if (c == ')')
779				count--;
780		} while (count > 0);
781	}
782}
783
784/* Yield A - B, measured in seconds.  */
785static long
786difftm(struct tm *a, struct tm *b)
787{
788	int ay = a->tm_year + (YEAR_TMORIGIN - 1);
789	int by = b->tm_year + (YEAR_TMORIGIN - 1);
790	int days = (
791	    /* difference in day of year */
792	    a->tm_yday - b->tm_yday
793	    /* + intervening leap days */
794	    +  ((ay >> 2) - (by >> 2))
795	    -  (ay/100 - by/100)
796	    +  ((ay/100 >> 2) - (by/100 >> 2))
797	    /* + difference in years * 365 */
798	    +  (long)(ay-by) * 365);
799	return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
800	    + (a->tm_min - b->tm_min)) + (a->tm_sec - b->tm_sec));
801}
802
803/*
804 * date_parse()
805 *
806 * Returns the number of seconds since the Epoch corresponding to the date.
807 */
808time_t
809date_parse(const char *p)
810{
811	struct tm	gmt, tm;
812	time_t		Start, tod, nowtime, tz;
813
814	yyInput = p;
815
816	if (time(&nowtime) == -1 || !gmtime_r(&nowtime, &gmt) ||
817	    !localtime_r(&nowtime, &tm))
818		return -1;
819
820	tz = difftm(&gmt, &tm) / 60;
821
822	if (tm.tm_isdst)
823		tz += 60;
824
825	yyYear = tm.tm_year + 1900;
826	yyMonth = tm.tm_mon + 1;
827	yyDay = tm.tm_mday;
828	yyTimezone = tz;
829	yyDSTmode = DSTmaybe;
830	yyHour = 0;
831	yyMinutes = 0;
832	yySeconds = 0;
833	yyMeridian = MER24;
834	yyRelSeconds = 0;
835	yyRelMonth = 0;
836	yyHaveDate = 0;
837	yyHaveDay = 0;
838	yyHaveRel = 0;
839	yyHaveTime = 0;
840	yyHaveZone = 0;
841
842	if (yyparse() || yyHaveTime > 1 || yyHaveZone > 1 ||
843	    yyHaveDate > 1 || yyHaveDay > 1)
844		return (-1);
845
846	if (yyHaveDate || yyHaveTime || yyHaveDay) {
847		Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes,
848		    yySeconds, yyMeridian, yyDSTmode);
849		if (Start < 0)
850			return (-1);
851	} else {
852		Start = nowtime;
853		if (!yyHaveRel)
854			Start -= ((tm.tm_hour * 60L + tm.tm_min) * 60L) +
855			    tm.tm_sec;
856	}
857
858	Start += yyRelSeconds;
859	Start += RelativeMonth(Start, yyRelMonth);
860
861	if (yyHaveDay && !yyHaveDate) {
862		tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
863		Start += tod;
864	}
865
866	return Start;
867}
868
869#if defined(TEST)
870/* ARGSUSED */
871int
872main(int argc, char **argv)
873{
874	char	buff[128];
875	time_t	d;
876
877	(void)printf("Enter date, or blank line to exit.\n\t> ");
878	(void)fflush(stdout);
879	while (fgets(buff, sizeof(buff), stdin) && buff[0]) {
880		d = date_parse(buff);
881		if (d == -1)
882			(void)printf("Bad format - couldn't convert.\n");
883		else
884			(void)printf("%s", ctime(&d));
885		(void)printf("\t> ");
886		(void)fflush(stdout);
887	}
888
889	return (0);
890}
891#endif	/* defined(TEST) */
892