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