]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - usr.sbin/fifolog/lib/getdate.y
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / usr.sbin / fifolog / lib / getdate.y
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 yyparse getdate_yyparse
28 #define yylex getdate_yylex
29 #define yyerror getdate_yyerror
30
31 static int yyparse(void);
32 static int yylex(void);
33 static 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 */
43 typedef 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 */
53 typedef enum _DSTMODE {
54     DSToff, DSTon, DSTmaybe
55 } DSTMODE;
56
57 /*
58 **  Meridian:  am, pm, or 24-hour style.
59 */
60 typedef 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 */
71 static char     *yyInput;
72 static DSTMODE  yyDSTmode;
73 static time_t   yyDayOrdinal;
74 static time_t   yyDayNumber;
75 static int      yyHaveDate;
76 static int      yyHaveDay;
77 static int      yyHaveRel;
78 static int      yyHaveTime;
79 static int      yyHaveZone;
80 static time_t   yyTimezone;
81 static time_t   yyDay;
82 static time_t   yyHour;
83 static time_t   yyMinutes;
84 static time_t   yyMonth;
85 static time_t   yySeconds;
86 static time_t   yyYear;
87 static MERIDIAN yyMeridian;
88 static time_t   yyRelMonth;
89 static 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
107 spec    : /* NULL */
108         | spec item
109         ;
110
111 item    : 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
134 cvsstamp: 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
147 time    : 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
182 zone    : 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
197 day     : 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
211 date    : 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
258 rel     : relunit tAGO {
259             yyRelSeconds = -yyRelSeconds;
260             yyRelMonth = -yyRelMonth;
261         }
262         | relunit
263         ;
264
265 relunit : 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
294 number  : 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
321 o_merid : /* NULL */ {
322             $$ = MER24;
323         }
324         | tMERIDIAN {
325             $$ = $1;
326         }
327         ;
328
329 %%
330
331 /* Month and day table. */
332 static 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. */
361 static 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. */
376 static 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. */
402 static 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. */
486 static 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 \f
516
517
518 /* ARGSUSED */
519 static int
520 yyerror(const char *s __unused)
521 {
522   return 0;
523 }
524
525
526 static time_t
527 ToSeconds(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).  */
559 static time_t
560 Convert(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);
604 fprintf(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
612 static time_t
613 DSTcorrect(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
624 static time_t
625 RelativeDate(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
638 static time_t
639 RelativeMonth(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
658 static int
659 LookupWord(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
764 static int
765 yylex(void)
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
818 time_t
819 get_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 }