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