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