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