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