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