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**  Picked up from CVS and slightly cleaned up by to WARNS=5 level by
13**  Poul-Henning Kamp <phk@FreeBSD.org>
14**
15** $FreeBSD$
16*/
17
18#include <stdio.h>
19#include <stdlib.h>
20#include <ctype.h>
21#include <string.h>
22#include <sys/types.h>
23#include <sys/time.h>
24
25#include "libfifolog.h"
26
27#define yylex getdate_yylex
28#define yyerror getdate_yyerror
29
30static int yylex(void);
31static int yyerror(const char *);
32
33#define EPOCH		1970
34#define HOUR(x)		((time_t)(x) * 60)
35#define SECSPERDAY	(24L * 60L * 60L)
36
37
38/*
39**  An entry in the lexical lookup table.
40*/
41typedef struct _TABLE {
42    const char	*name;
43    int		type;
44    time_t	value;
45} TABLE;
46
47
48/*
49**  Daylight-savings mode:  on, off, or not yet known.
50*/
51typedef enum _DSTMODE {
52    DSToff, DSTon, DSTmaybe
53} DSTMODE;
54
55/*
56**  Meridian:  am, pm, or 24-hour style.
57*/
58typedef enum _MERIDIAN {
59    MERam, MERpm, MER24
60} MERIDIAN;
61
62
63/*
64**  Global variables.  We could get rid of most of these by using a good
65**  union as the yacc stack.  (This routine was originally written before
66**  yacc had the %union construct.)  Maybe someday; right now we only use
67**  the %union very rarely.
68*/
69static char	*yyInput;
70static DSTMODE	yyDSTmode;
71static time_t	yyDayOrdinal;
72static time_t	yyDayNumber;
73static int	yyHaveDate;
74static int	yyHaveDay;
75static int	yyHaveRel;
76static int	yyHaveTime;
77static int	yyHaveZone;
78static time_t	yyTimezone;
79static time_t	yyDay;
80static time_t	yyHour;
81static time_t	yyMinutes;
82static time_t	yyMonth;
83static time_t	yySeconds;
84static time_t	yyYear;
85static MERIDIAN	yyMeridian;
86static time_t	yyRelMonth;
87static time_t	yyRelSeconds;
88
89%}
90
91%union {
92    time_t		Number;
93    enum _MERIDIAN	Meridian;
94}
95
96%token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
97%token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
98
99%type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
100%type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE
101%type	<Meridian>	tMERIDIAN o_merid
102
103%%
104
105spec	: /* NULL */
106	| spec item
107	;
108
109item	: time {
110	    yyHaveTime++;
111	}
112	| zone {
113	    yyHaveZone++;
114	}
115	| date {
116	    yyHaveDate++;
117	}
118	| day {
119	    yyHaveDay++;
120	}
121	| rel {
122	    yyHaveRel++;
123	}
124	| cvsstamp {
125	    yyHaveTime++;
126	    yyHaveDate++;
127	    yyHaveZone++;
128	}
129	| number
130	;
131
132cvsstamp: tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER '.' tUNUMBER {
133	    yyYear = $1;
134	    if (yyYear < 100) yyYear += 1900;
135	    yyMonth = $3;
136	    yyDay = $5;
137	    yyHour = $7;
138	    yyMinutes = $9;
139	    yySeconds = $11;
140	    yyDSTmode = DSToff;
141	    yyTimezone = 0;
142	}
143	;
144
145time	: tUNUMBER tMERIDIAN {
146	    yyHour = $1;
147	    yyMinutes = 0;
148	    yySeconds = 0;
149	    yyMeridian = $2;
150	}
151	| tUNUMBER ':' tUNUMBER o_merid {
152	    yyHour = $1;
153	    yyMinutes = $3;
154	    yySeconds = 0;
155	    yyMeridian = $4;
156	}
157	| tUNUMBER ':' tUNUMBER tSNUMBER {
158	    yyHour = $1;
159	    yyMinutes = $3;
160	    yyMeridian = MER24;
161	    yyDSTmode = DSToff;
162	    yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
163	}
164	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
165	    yyHour = $1;
166	    yyMinutes = $3;
167	    yySeconds = $5;
168	    yyMeridian = $6;
169	}
170	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
171	    yyHour = $1;
172	    yyMinutes = $3;
173	    yySeconds = $5;
174	    yyMeridian = MER24;
175	    yyDSTmode = DSToff;
176	    yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
177	}
178	;
179
180zone	: tZONE {
181	    yyTimezone = $1;
182	    yyDSTmode = DSToff;
183	}
184	| tDAYZONE {
185	    yyTimezone = $1;
186	    yyDSTmode = DSTon;
187	}
188	|
189	  tZONE tDST {
190	    yyTimezone = $1;
191	    yyDSTmode = DSTon;
192	}
193	;
194
195day	: tDAY {
196	    yyDayOrdinal = 1;
197	    yyDayNumber = $1;
198	}
199	| tDAY ',' {
200	    yyDayOrdinal = 1;
201	    yyDayNumber = $1;
202	}
203	| tUNUMBER tDAY {
204	    yyDayOrdinal = $1;
205	    yyDayNumber = $2;
206	}
207	;
208
209date	: tUNUMBER '/' tUNUMBER {
210	    yyMonth = $1;
211	    yyDay = $3;
212	}
213	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
214	    if ($1 >= 100) {
215		yyYear = $1;
216		yyMonth = $3;
217		yyDay = $5;
218	    } else {
219		yyMonth = $1;
220		yyDay = $3;
221		yyYear = $5;
222	    }
223	}
224	| tUNUMBER tSNUMBER tSNUMBER {
225	    /* ISO 8601 format.  yyyy-mm-dd.  */
226	    yyYear = $1;
227	    yyMonth = -$2;
228	    yyDay = -$3;
229	}
230	| tUNUMBER tMONTH tSNUMBER {
231	    /* e.g. 17-JUN-1992.  */
232	    yyDay = $1;
233	    yyMonth = $2;
234	    yyYear = -$3;
235	}
236	| tMONTH tUNUMBER {
237	    yyMonth = $1;
238	    yyDay = $2;
239	}
240	| tMONTH tUNUMBER ',' tUNUMBER {
241	    yyMonth = $1;
242	    yyDay = $2;
243	    yyYear = $4;
244	}
245	| tUNUMBER tMONTH {
246	    yyMonth = $2;
247	    yyDay = $1;
248	}
249	| tUNUMBER tMONTH tUNUMBER {
250	    yyMonth = $2;
251	    yyDay = $1;
252	    yyYear = $3;
253	}
254	;
255
256rel	: relunit tAGO {
257	    yyRelSeconds = -yyRelSeconds;
258	    yyRelMonth = -yyRelMonth;
259	}
260	| relunit
261	;
262
263relunit	: tUNUMBER tMINUTE_UNIT {
264	    yyRelSeconds += $1 * $2 * 60L;
265	}
266	| tSNUMBER tMINUTE_UNIT {
267	    yyRelSeconds += $1 * $2 * 60L;
268	}
269	| tMINUTE_UNIT {
270	    yyRelSeconds += $1 * 60L;
271	}
272	| tSNUMBER tSEC_UNIT {
273	    yyRelSeconds += $1;
274	}
275	| tUNUMBER tSEC_UNIT {
276	    yyRelSeconds += $1;
277	}
278	| tSEC_UNIT {
279	    yyRelSeconds++;
280	}
281	| tSNUMBER tMONTH_UNIT {
282	    yyRelMonth += $1 * $2;
283	}
284	| tUNUMBER tMONTH_UNIT {
285	    yyRelMonth += $1 * $2;
286	}
287	| tMONTH_UNIT {
288	    yyRelMonth += $1;
289	}
290	;
291
292number	: tUNUMBER {
293	    if (yyHaveTime && yyHaveDate && !yyHaveRel)
294		yyYear = $1;
295	    else {
296		if($1>10000) {
297		    yyHaveDate++;
298		    yyDay= ($1)%100;
299		    yyMonth= ($1/100)%100;
300		    yyYear = $1/10000;
301		}
302		else {
303		    yyHaveTime++;
304		    if ($1 < 100) {
305			yyHour = $1;
306			yyMinutes = 0;
307		    }
308		    else {
309		    	yyHour = $1 / 100;
310		    	yyMinutes = $1 % 100;
311		    }
312		    yySeconds = 0;
313		    yyMeridian = MER24;
314	        }
315	    }
316	}
317	;
318
319o_merid	: /* NULL */ {
320	    $$ = MER24;
321	}
322	| tMERIDIAN {
323	    $$ = $1;
324	}
325	;
326
327%%
328
329/* Month and day table. */
330static TABLE const MonthDayTable[] = {
331    { "january",	tMONTH,  1 },
332    { "february",	tMONTH,  2 },
333    { "march",		tMONTH,  3 },
334    { "april",		tMONTH,  4 },
335    { "may",		tMONTH,  5 },
336    { "june",		tMONTH,  6 },
337    { "july",		tMONTH,  7 },
338    { "august",		tMONTH,  8 },
339    { "september",	tMONTH,  9 },
340    { "sept",		tMONTH,  9 },
341    { "october",	tMONTH, 10 },
342    { "november",	tMONTH, 11 },
343    { "december",	tMONTH, 12 },
344    { "sunday",		tDAY, 0 },
345    { "monday",		tDAY, 1 },
346    { "tuesday",	tDAY, 2 },
347    { "tues",		tDAY, 2 },
348    { "wednesday",	tDAY, 3 },
349    { "wednes",		tDAY, 3 },
350    { "thursday",	tDAY, 4 },
351    { "thur",		tDAY, 4 },
352    { "thurs",		tDAY, 4 },
353    { "friday",		tDAY, 5 },
354    { "saturday",	tDAY, 6 },
355    { NULL,		0,    0 }
356};
357
358/* Time units table. */
359static TABLE const UnitsTable[] = {
360    { "year",		tMONTH_UNIT,	12 },
361    { "month",		tMONTH_UNIT,	1 },
362    { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
363    { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
364    { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
365    { "hour",		tMINUTE_UNIT,	60 },
366    { "minute",		tMINUTE_UNIT,	1 },
367    { "min",		tMINUTE_UNIT,	1 },
368    { "second",		tSEC_UNIT,	1 },
369    { "sec",		tSEC_UNIT,	1 },
370    { NULL,		0,		0 }
371};
372
373/* Assorted relative-time words. */
374static TABLE const OtherTable[] = {
375    { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
376    { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
377    { "today",		tMINUTE_UNIT,	0 },
378    { "now",		tMINUTE_UNIT,	0 },
379    { "last",		tUNUMBER,	-1 },
380    { "this",		tMINUTE_UNIT,	0 },
381    { "next",		tUNUMBER,	2 },
382    { "first",		tUNUMBER,	1 },
383/*  { "second",		tUNUMBER,	2 }, */
384    { "third",		tUNUMBER,	3 },
385    { "fourth",		tUNUMBER,	4 },
386    { "fifth",		tUNUMBER,	5 },
387    { "sixth",		tUNUMBER,	6 },
388    { "seventh",	tUNUMBER,	7 },
389    { "eighth",		tUNUMBER,	8 },
390    { "ninth",		tUNUMBER,	9 },
391    { "tenth",		tUNUMBER,	10 },
392    { "eleventh",	tUNUMBER,	11 },
393    { "twelfth",	tUNUMBER,	12 },
394    { "ago",		tAGO,		1 },
395    { NULL,		0,		0 }
396};
397
398/* The timezone table. */
399/* Some of these are commented out because a time_t can't store a float. */
400static TABLE const TimezoneTable[] = {
401    { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
402    { "ut",	tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
403    { "utc",	tZONE,     HOUR( 0) },
404    { "wet",	tZONE,     HOUR( 0) },	/* Western European */
405    { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
406    { "wat",	tZONE,     HOUR( 1) },	/* West Africa */
407    { "at",	tZONE,     HOUR( 2) },	/* Azores */
408#if	0
409    /* For completeness.  BST is also British Summer, and GST is
410     * also Guam Standard. */
411    { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
412    { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
413#endif
414#if 0
415    { "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
416    { "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
417    { "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
418#endif
419    { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
420    { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
421    { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
422    { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
423    { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
424    { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
425    { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
426    { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
427    { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
428    { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
429    { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
430    { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
431    { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
432    { "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
433    { "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
434    { "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
435    { "nt",	tZONE,     HOUR(11) },	/* Nome */
436    { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
437    { "cet",	tZONE,     -HOUR(1) },	/* Central European */
438    { "met",	tZONE,     -HOUR(1) },	/* Middle European */
439    { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
440    { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
441    { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
442    { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
443    { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
444    { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
445    { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
446    { "bt",	tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
447#if 0
448    { "it",	tZONE,     -HOUR(3.5) },/* Iran */
449#endif
450    { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
451    { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
452#if 0
453    { "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
454#endif
455    { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
456#if	0
457    /* For completeness.  NST is also Newfoundland Stanard, and SST is
458     * also Swedish Summer. */
459    { "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
460    { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
461#endif	/* 0 */
462    { "wast",	tZONE,     -HOUR(7) },	/* West Australian Standard */
463    { "wadt",	tDAYZONE,  -HOUR(7) },	/* West Australian Daylight */
464#if 0
465    { "jt",	tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
466#endif
467    { "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
468    { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
469#if 0
470    { "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
471    { "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
472#endif
473    { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
474    { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
475    { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
476    { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
477    { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
478    { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
479    { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
480    {  NULL,	0,	   0 }
481};
482
483/* Military timezone table. */
484static TABLE const MilitaryTable[] = {
485    { "a",	tZONE,	HOUR(  1) },
486    { "b",	tZONE,	HOUR(  2) },
487    { "c",	tZONE,	HOUR(  3) },
488    { "d",	tZONE,	HOUR(  4) },
489    { "e",	tZONE,	HOUR(  5) },
490    { "f",	tZONE,	HOUR(  6) },
491    { "g",	tZONE,	HOUR(  7) },
492    { "h",	tZONE,	HOUR(  8) },
493    { "i",	tZONE,	HOUR(  9) },
494    { "k",	tZONE,	HOUR( 10) },
495    { "l",	tZONE,	HOUR( 11) },
496    { "m",	tZONE,	HOUR( 12) },
497    { "n",	tZONE,	HOUR(- 1) },
498    { "o",	tZONE,	HOUR(- 2) },
499    { "p",	tZONE,	HOUR(- 3) },
500    { "q",	tZONE,	HOUR(- 4) },
501    { "r",	tZONE,	HOUR(- 5) },
502    { "s",	tZONE,	HOUR(- 6) },
503    { "t",	tZONE,	HOUR(- 7) },
504    { "u",	tZONE,	HOUR(- 8) },
505    { "v",	tZONE,	HOUR(- 9) },
506    { "w",	tZONE,	HOUR(-10) },
507    { "x",	tZONE,	HOUR(-11) },
508    { "y",	tZONE,	HOUR(-12) },
509    { "z",	tZONE,	HOUR(  0) },
510    { NULL,	0,  0 }
511};
512
513
514
515
516/* ARGSUSED */
517static int
518yyerror(const char *s __unused)
519{
520  return 0;
521}
522
523
524static time_t
525ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
526{
527    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
528	return -1;
529    switch (Meridian) {
530    case MER24:
531	if (Hours < 0 || Hours > 23)
532	    return -1;
533	return (Hours * 60L + Minutes) * 60L + Seconds;
534    case MERam:
535	if (Hours < 1 || Hours > 12)
536	    return -1;
537	if (Hours == 12)
538	    Hours = 0;
539	return (Hours * 60L + Minutes) * 60L + Seconds;
540    case MERpm:
541	if (Hours < 1 || Hours > 12)
542	    return -1;
543	if (Hours == 12)
544	    Hours = 0;
545	return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
546    default:
547	abort ();
548    }
549    /* NOTREACHED */
550}
551
552
553/* Year is either
554   * A negative number, which means to use its absolute value (why?)
555   * A number from 0 to 99, which means a year from 1900 to 1999, or
556   * The actual year (>=100).  */
557static time_t
558Convert(time_t Month, time_t Day, time_t Year,
559    time_t Hours, time_t Minutes, time_t Seconds,
560    MERIDIAN Meridian, DSTMODE DSTmode)
561{
562    static int DaysInMonth[12] = {
563	31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
564    };
565    time_t	tod;
566    time_t	Julian;
567    int		i;
568    struct tm   *ltm;
569
570    if (Year < 0)
571	Year = -Year;
572    if (Year < 69)
573	Year += 2000;
574    else if (Year < 100)
575	Year += 1900;
576    DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
577		    ? 29 : 28;
578    /* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
579       I'm too lazy to try to check for time_t overflow in another way.  */
580    if (Year < EPOCH || Year > 2038
581     || Month < 1 || Month > 12
582     /* Lint fluff:  "conversion from long may lose accuracy" */
583     || Day < 1 || Day > DaysInMonth[(int)--Month])
584	/* FIXME:
585	 * It would be nice to set a global error string here.
586	 * "February 30 is not a valid date" is much more informative than
587	 * "Can't parse date/time: 100 months" when the user input was
588	 * "100 months" and addition resolved that to February 30, for
589	 * example.  See rcs2-7 in src/sanity.sh for more. */
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    ltm = localtime(&Julian);
602fprintf(stderr, "DST %d TZ %s %d\n", DSTmode, ltm->tm_zone, ltm->tm_isdst);
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
816time_t
817get_date(char *p)
818{
819    struct tm		*tm, gmt;
820    time_t		Start;
821    time_t		tod;
822    time_t nowtime;
823    struct tm *gmt_ptr;
824
825    yyInput = p;
826
827    (void)time (&nowtime);
828
829    gmt_ptr = gmtime (&nowtime);
830    if (gmt_ptr != NULL)
831    {
832	/* Make a copy, in case localtime modifies *tm (I think
833	   that comment now applies to *gmt_ptr, but I am too
834	   lazy to dig into how gmtime and locatime allocate the
835	   structures they return pointers to).  */
836	gmt = *gmt_ptr;
837    }
838
839    if (! (tm = localtime (&nowtime)))
840	return -1;
841
842    tm = localtime(&nowtime);
843    yyYear = tm->tm_year + 1900;
844    yyMonth = tm->tm_mon + 1;
845    yyDay = tm->tm_mday;
846    yyTimezone = tm->tm_gmtoff;
847    yyDSTmode = DSTmaybe;
848    yyHour = 0;
849    yyMinutes = 0;
850    yySeconds = 0;
851    yyMeridian = MER24;
852    yyRelSeconds = 0;
853    yyRelMonth = 0;
854    yyHaveDate = 0;
855    yyHaveDay = 0;
856    yyHaveRel = 0;
857    yyHaveTime = 0;
858    yyHaveZone = 0;
859
860    if (yyparse()
861     || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
862	return -1;
863
864    if (yyHaveDate || yyHaveTime || yyHaveDay) {
865	Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
866		    yyMeridian, yyDSTmode);
867	if (Start < 0)
868	    return -1;
869    }
870    else {
871	Start = nowtime;
872	if (!yyHaveRel)
873	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
874    }
875
876    Start += yyRelSeconds;
877    Start += RelativeMonth(Start, yyRelMonth);
878
879    if (yyHaveDay && !yyHaveDate) {
880	tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
881	Start += tod;
882    }
883
884    /* Have to do *something* with a legitimate -1 so it's distinguishable
885     * from the error return value.  (Alternately could set errno on error.) */
886    return Start == -1 ? 0 : Start;
887}
888