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