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