chiark / gitweb /
Commit 2.4.5-5 as unpacked
[innduct.git] / lib / parsedate.y
1 %{
2 /*  $Id: parsedate.y 6372 2003-05-31 19:48:28Z rra $
3 **
4 **  Originally written by Steven M. Bellovin <smb@research.att.com> while
5 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
6 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
7 **  <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
8 **  Further revised (removed obsolete constructs and cleaned up timezone
9 **  names) in August, 1991, by Rich.  Paul Eggert <eggert@twinsun.com>
10 **  helped in September, 1992.
11 **
12 **  This grammar has six shift/reduce conflicts.
13 **
14 **  This code is in the public domain and has no copyright.
15 */
16
17 #include "config.h"
18 #include "clibrary.h"
19 #include <ctype.h>
20
21 #if defined(_HPUX_SOURCE)
22 # include <alloca.h>
23 #endif
24
25 #ifdef TM_IN_SYS_TIME
26 # include <sys/time.h>
27 #else
28 # include <time.h>
29 #endif
30
31 #include "libinn.h"
32
33
34 #define yylhs           date_yylhs
35 #define yylen           date_yylen
36 #define yydefred        date_yydefred
37 #define yydgoto         date_yydgoto
38 #define yysindex        date_yysindex
39 #define yyrindex        date_yyrindex
40 #define yygindex        date_yygindex
41 #define yytable         date_yytable
42 #define yycheck         date_yycheck
43 #define yyparse         date_parse
44 #define yylex           date_lex
45 #define yyerror         date_error
46 #define yymaxdepth      date_yymaxdepth
47
48
49 static int date_lex(void);
50
51 int yyparse(void);
52
53     /* See the LeapYears table in Convert. */
54 #define EPOCH           1970
55 #define END_OF_TIME     2038
56     /* Constants for general time calculations. */
57 #define DST_OFFSET      1
58 #define SECSPERDAY      (24L * 60L * 60L)
59     /* Readability for TABLE stuff. */
60 #define HOUR(x)         (x * 60)
61
62 #define LPAREN          '('
63 #define RPAREN          ')'
64 #define IS7BIT(x)       ((unsigned int)(x) < 0200)
65
66
67 /*
68 **  An entry in the lexical lookup table.
69 */
70 typedef struct _TABLE {
71     const char * name;
72     int          type;
73     time_t       value;
74 } TABLE;
75
76 /*
77 **  Daylight-savings mode:  on, off, or not yet known.
78 */
79 typedef enum _DSTMODE {
80     DSTon, DSToff, DSTmaybe
81 } DSTMODE;
82
83 /*
84 **  Meridian:  am, pm, or 24-hour style.
85 */
86 typedef enum _MERIDIAN {
87     MERam, MERpm, MER24
88 } MERIDIAN;
89
90
91 /*
92 **  Global variables.  We could get rid of most of them by using a yacc
93 **  union, but this is more efficient.  (This routine predates the
94 **  yacc %union construct.)
95 */
96 static char     *yyInput;
97 static DSTMODE  yyDSTmode;
98 static int      yyHaveDate;
99 static int      yyHaveRel;
100 static int      yyHaveTime;
101 static time_t   yyTimezone;
102 static time_t   yyDay;
103 static time_t   yyHour;
104 static time_t   yyMinutes;
105 static time_t   yyMonth;
106 static time_t   yySeconds;
107 static time_t   yyYear;
108 static MERIDIAN yyMeridian;
109 static time_t   yyRelMonth;
110 static time_t   yyRelSeconds;
111
112
113 /* extern struct tm     *localtime(); */
114
115 static void             date_error(const char *s);
116 %}
117
118 %union {
119     time_t              Number;
120     enum _MERIDIAN      Meridian;
121 }
122
123 %token  tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
124 %token  tUNUMBER tZONE
125
126 %type   <Number>        tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
127 %type   <Number>        tSNUMBER tUNUMBER tZONE numzone zone
128 %type   <Meridian>      tMERIDIAN o_merid
129
130 %%
131
132 spec    : /* NULL */
133         | spec item
134         ;
135
136 item    : time {
137             yyHaveTime++;
138 #if     defined(lint)
139             /* I am compulsive about lint natterings... */
140             if (yyHaveTime == -1) {
141                 YYERROR;
142             }
143 #endif  /* defined(lint) */
144         }
145         | time zone {
146             yyHaveTime++;
147             yyTimezone = $2;
148         }
149         | date {
150             yyHaveDate++;
151         }
152         | rel {
153             yyHaveRel = 1;
154         }
155         ;
156
157 time    : tUNUMBER o_merid {
158             if ($1 < 100) {
159                 yyHour = $1;
160                 yyMinutes = 0;
161             }
162             else {
163                 yyHour = $1 / 100;
164                 yyMinutes = $1 % 100;
165             }
166             yySeconds = 0;
167             yyMeridian = $2;
168         }
169         | tUNUMBER ':' tUNUMBER o_merid {
170             yyHour = $1;
171             yyMinutes = $3;
172             yySeconds = 0;
173             yyMeridian = $4;
174         }
175         | tUNUMBER ':' tUNUMBER numzone {
176             yyHour = $1;
177             yyMinutes = $3;
178             yyTimezone = $4;
179             yyMeridian = MER24;
180             yyDSTmode = DSToff;
181         }
182         | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
183             yyHour = $1;
184             yyMinutes = $3;
185             yySeconds = $5;
186             yyMeridian = $6;
187         }
188         | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
189             yyHour = $1;
190             yyMinutes = $3;
191             yySeconds = $5;
192             yyTimezone = $6;
193             yyMeridian = MER24;
194             yyDSTmode = DSToff;
195         }
196         ;
197
198 zone    : tZONE {
199             $$ = $1;
200             yyDSTmode = DSToff;
201         }
202         | tDAYZONE {
203             $$ = $1;
204             yyDSTmode = DSTon;
205         }
206         | tZONE numzone {
207             /* Only allow "GMT+300" and "GMT-0800" */
208             if ($1 != 0) {
209                 YYABORT;
210             }
211             $$ = $2;
212             yyDSTmode = DSToff;
213         }
214         | numzone {
215             $$ = $1;
216             yyDSTmode = DSToff;
217         }
218         ;
219
220 numzone : tSNUMBER {
221             int         i;
222
223             /* Unix and GMT and numeric timezones -- a little confusing. */
224             if ($1 < 0) {
225                 /* Don't work with negative modulus. */
226                 $1 = -$1;
227                 if ($1 > 9999 || (i = $1 % 100) >= 60) {
228                     YYABORT;
229                 }
230                 $$ = ($1 / 100) * 60 + i;
231             }
232             else {
233                 if ($1 > 9999 || (i = $1 % 100) >= 60) {
234                     YYABORT;
235                 }
236                 $$ = -(($1 / 100) * 60 + i);
237             }
238         }
239         ;
240
241 date    : tUNUMBER '/' tUNUMBER {
242             yyMonth = $1;
243             yyDay = $3;
244         }
245         | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
246             if ($1 > 100) {
247                 /* assume YYYY/MM/DD format, so need not to add 1900 */
248                 if ($1 > 999) {
249                     yyYear = $1;
250                 } else {
251                     yyYear = 1900 + $1;
252                 }
253                 yyMonth = $3;
254                 yyDay = $5;
255             }
256             else {
257                 /* assume MM/DD/YY* format */
258                 yyMonth = $1;
259                 yyDay = $3;
260                 if ($5 > 999) {
261                     /* assume year is YYYY format, so need not to add 1900 */
262                     yyYear = $5;
263                 } else if ($5 < 100) {
264                     /* assume year is YY format, so need to add 1900 */
265                     yyYear = $5 + (yyYear / 100 + (yyYear % 100 - $5) / 50) * 100;
266                 } else {
267                     yyYear = 1900 + $5;
268                 }
269             }
270         }
271         | tMONTH tUNUMBER {
272             yyMonth = $1;
273             yyDay = $2;
274         }
275         | tMONTH tUNUMBER ',' tUNUMBER {
276             yyMonth = $1;
277             yyDay = $2;
278             if ($4 > 999) {
279                 /* assume year is YYYY format, so need not to add 1900 */
280                 yyYear = $4;
281             } else if ($4 < 100) {
282                 /* assume year is YY format, so need to add 1900 */
283                 yyYear = $4 + (yyYear / 100 + (yyYear % 100 - $4) / 50) * 100;
284             } else {
285                 yyYear = 1900 + $4;
286             }
287         }
288         | tUNUMBER tMONTH {
289             yyDay = $1;
290             yyMonth = $2;
291         }
292         | tUNUMBER tMONTH tUNUMBER {
293             yyDay = $1;
294             yyMonth = $2;
295             if ($3 > 999) {
296                 /* assume year is YYYY format, so need not to add 1900 */
297                 yyYear = $3;
298             } else if ($3 < 100) {
299                 /* assume year is YY format, so need to add 1900 */
300                 yyYear = $3 + (yyYear / 100 + (yyYear % 100 - $3) / 50) * 100;
301             } else {
302                 yyYear = 1900 + $3;
303             }
304         }
305         | tDAY ',' tUNUMBER tMONTH tUNUMBER {
306             yyDay = $3;
307             yyMonth = $4;
308             if ($5 > 999) {
309                 /* assume year is YYYY format, so need not to add 1900 */
310                 yyYear = $5;
311             } else if ($5 < 100) {
312                 /* assume year is YY format, so need to add 1900 */
313                 yyYear = $5 + (yyYear / 100 + (yyYear % 100 - $5) / 50) * 100;
314             } else {
315                 yyYear = 1900 + $5;
316             }
317         }
318         ;
319
320 rel     : tSNUMBER tSEC_UNIT {
321             yyRelSeconds += $1 * $2;
322         }
323         | tUNUMBER tSEC_UNIT {
324             yyRelSeconds += $1 * $2;
325         }
326         | tSNUMBER tMONTH_UNIT {
327             yyRelMonth += $1 * $2;
328         }
329         | tUNUMBER tMONTH_UNIT {
330             yyRelMonth += $1 * $2;
331         }
332         ;
333
334 o_merid : /* NULL */ {
335             $$ = MER24;
336         }
337         | tMERIDIAN {
338             $$ = $1;
339         }
340         ;
341
342 %%
343
344 /* Month and day table. */
345 static TABLE    MonthDayTable[] = {
346     { "january",        tMONTH,  1 },
347     { "february",       tMONTH,  2 },
348     { "march",          tMONTH,  3 },
349     { "april",          tMONTH,  4 },
350     { "may",            tMONTH,  5 },
351     { "june",           tMONTH,  6 },
352     { "july",           tMONTH,  7 },
353     { "august",         tMONTH,  8 },
354     { "september",      tMONTH,  9 },
355     { "october",        tMONTH, 10 },
356     { "november",       tMONTH, 11 },
357     { "december",       tMONTH, 12 },
358         /* The value of the day isn't used... */
359     { "sunday",         tDAY, 0 },
360     { "monday",         tDAY, 0 },
361     { "tuesday",        tDAY, 0 },
362     { "wednesday",      tDAY, 0 },
363     { "thursday",       tDAY, 0 },
364     { "friday",         tDAY, 0 },
365     { "saturday",       tDAY, 0 },
366 };
367
368 /* Time units table. */
369 static TABLE    UnitsTable[] = {
370     { "year",           tMONTH_UNIT,    12 },
371     { "month",          tMONTH_UNIT,    1 },
372     { "week",           tSEC_UNIT,      7 * 24 * 60 * 60 },
373     { "day",            tSEC_UNIT,      1 * 24 * 60 * 60 },
374     { "hour",           tSEC_UNIT,      60 * 60 },
375     { "minute",         tSEC_UNIT,      60 },
376     { "min",            tSEC_UNIT,      60 },
377     { "second",         tSEC_UNIT,      1 },
378     { "sec",            tSEC_UNIT,      1 },
379 };
380
381 /* Timezone table. */
382 static TABLE    TimezoneTable[] = {
383     { "gmt",    tZONE,     HOUR( 0) },  /* Greenwich Mean */
384     { "ut",     tZONE,     HOUR( 0) },  /* Universal */
385     { "utc",    tZONE,     HOUR( 0) },  /* Universal Coordinated */
386     { "cut",    tZONE,     HOUR( 0) },  /* Coordinated Universal */
387     { "z",      tZONE,     HOUR( 0) },  /* Greenwich Mean */
388     { "wet",    tZONE,     HOUR( 0) },  /* Western European */
389     { "bst",    tDAYZONE,  HOUR( 0) },  /* British Summer */
390     { "nst",    tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
391     { "ndt",    tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
392     { "ast",    tZONE,     HOUR( 4) },  /* Atlantic Standard */
393     { "adt",    tDAYZONE,  HOUR( 4) },  /* Atlantic Daylight */
394     { "est",    tZONE,     HOUR( 5) },  /* Eastern Standard */
395     { "edt",    tDAYZONE,  HOUR( 5) },  /* Eastern Daylight */
396     { "cst",    tZONE,     HOUR( 6) },  /* Central Standard */
397     { "cdt",    tDAYZONE,  HOUR( 6) },  /* Central Daylight */
398     { "mst",    tZONE,     HOUR( 7) },  /* Mountain Standard */
399     { "mdt",    tDAYZONE,  HOUR( 7) },  /* Mountain Daylight */
400     { "pst",    tZONE,     HOUR( 8) },  /* Pacific Standard */
401     { "pdt",    tDAYZONE,  HOUR( 8) },  /* Pacific Daylight */
402     { "yst",    tZONE,     HOUR( 9) },  /* Yukon Standard */
403     { "ydt",    tDAYZONE,  HOUR( 9) },  /* Yukon Daylight */
404     { "akst",   tZONE,     HOUR( 9) },  /* Alaska Standard */
405     { "akdt",   tDAYZONE,  HOUR( 9) },  /* Alaska Daylight */
406     { "hst",    tZONE,     HOUR(10) },  /* Hawaii Standard */
407     { "hast",   tZONE,     HOUR(10) },  /* Hawaii-Aleutian Standard */
408     { "hadt",   tDAYZONE,  HOUR(10) },  /* Hawaii-Aleutian Daylight */
409     { "ces",    tDAYZONE,  -HOUR(1) },  /* Central European Summer */
410     { "cest",   tDAYZONE,  -HOUR(1) },  /* Central European Summer */
411     { "mez",    tZONE,     -HOUR(1) },  /* Middle European */
412     { "mezt",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
413     { "cet",    tZONE,     -HOUR(1) },  /* Central European */
414     { "met",    tZONE,     -HOUR(1) },  /* Middle European */
415     { "eet",    tZONE,     -HOUR(2) },  /* Eastern Europe */
416     { "msk",    tZONE,     -HOUR(3) },  /* Moscow Winter */
417     { "msd",    tDAYZONE,  -HOUR(3) },  /* Moscow Summer */
418     { "wast",   tZONE,     -HOUR(8) },  /* West Australian Standard */
419     { "wadt",   tDAYZONE,  -HOUR(8) },  /* West Australian Daylight */
420     { "hkt",    tZONE,     -HOUR(8) },  /* Hong Kong */
421     { "cct",    tZONE,     -HOUR(8) },  /* China Coast */
422     { "jst",    tZONE,     -HOUR(9) },  /* Japan Standard */
423     { "kst",    tZONE,     -HOUR(9) },  /* Korean Standard */
424     { "kdt",    tZONE,     -HOUR(9) },  /* Korean Daylight */
425     { "cast",   tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
426     { "cadt",   tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
427     { "east",   tZONE,     -HOUR(10) }, /* Eastern Australian Standard */
428     { "eadt",   tDAYZONE,  -HOUR(10) }, /* Eastern Australian Daylight */
429     { "nzst",   tZONE,     -HOUR(12) }, /* New Zealand Standard */
430     { "nzdt",   tDAYZONE,  -HOUR(12) }, /* New Zealand Daylight */
431
432     /* For completeness we include the following entries. */
433 #if     0
434
435     /* Duplicate names.  Either they conflict with a zone listed above
436      * (which is either more likely to be seen or just been in circulation
437      * longer), or they conflict with another zone in this section and
438      * we could not reasonably choose one over the other. */
439     { "fst",    tZONE,     HOUR( 2) },  /* Fernando De Noronha Standard */
440     { "fdt",    tDAYZONE,  HOUR( 2) },  /* Fernando De Noronha Daylight */
441     { "bst",    tZONE,     HOUR( 3) },  /* Brazil Standard */
442     { "est",    tZONE,     HOUR( 3) },  /* Eastern Standard (Brazil) */
443     { "edt",    tDAYZONE,  HOUR( 3) },  /* Eastern Daylight (Brazil) */
444     { "wst",    tZONE,     HOUR( 4) },  /* Western Standard (Brazil) */
445     { "wdt",    tDAYZONE,  HOUR( 4) },  /* Western Daylight (Brazil) */
446     { "cst",    tZONE,     HOUR( 5) },  /* Chile Standard */
447     { "cdt",    tDAYZONE,  HOUR( 5) },  /* Chile Daylight */
448     { "ast",    tZONE,     HOUR( 5) },  /* Acre Standard */
449     { "adt",    tDAYZONE,  HOUR( 5) },  /* Acre Daylight */
450     { "cst",    tZONE,     HOUR( 5) },  /* Cuba Standard */
451     { "cdt",    tDAYZONE,  HOUR( 5) },  /* Cuba Daylight */
452     { "est",    tZONE,     HOUR( 6) },  /* Easter Island Standard */
453     { "edt",    tDAYZONE,  HOUR( 6) },  /* Easter Island Daylight */
454     { "sst",    tZONE,     HOUR(11) },  /* Samoa Standard */
455     { "ist",    tZONE,     -HOUR(2) },  /* Israel Standard */
456     { "idt",    tDAYZONE,  -HOUR(2) },  /* Israel Daylight */
457     { "idt",    tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
458     { "ist",    tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
459     { "cst",     tZONE,     -HOUR(8) }, /* China Standard */
460     { "cdt",     tDAYZONE,  -HOUR(8) }, /* China Daylight */
461     { "sst",     tZONE,     -HOUR(8) }, /* Singapore Standard */
462
463     /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
464     { "gst",    tZONE,     HOUR( 3) },  /* Greenland Standard */
465     { "wat",    tZONE,     -HOUR(1) },  /* West Africa */
466     { "at",     tZONE,     HOUR( 2) },  /* Azores */
467     { "gst",    tZONE,     -HOUR(10) }, /* Guam Standard */
468     { "nft",    tZONE,     HOUR(3)+30 }, /* Newfoundland */
469     { "idlw",   tZONE,     HOUR(12) },  /* International Date Line West */
470     { "mewt",   tZONE,     -HOUR(1) },  /* Middle European Winter */
471     { "mest",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
472     { "swt",    tZONE,     -HOUR(1) },  /* Swedish Winter */
473     { "sst",    tDAYZONE,  -HOUR(1) },  /* Swedish Summer */
474     { "fwt",    tZONE,     -HOUR(1) },  /* French Winter */
475     { "fst",    tDAYZONE,  -HOUR(1) },  /* French Summer */
476     { "bt",     tZONE,     -HOUR(3) },  /* Baghdad */
477     { "it",     tZONE,     -(HOUR(3)+30) }, /* Iran */
478     { "zp4",    tZONE,     -HOUR(4) },  /* USSR Zone 3 */
479     { "zp5",    tZONE,     -HOUR(5) },  /* USSR Zone 4 */
480     { "ist",    tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
481     { "zp6",    tZONE,     -HOUR(6) },  /* USSR Zone 5 */
482     { "nst",    tZONE,     -HOUR(7) },  /* North Sumatra */
483     { "sst",    tZONE,     -HOUR(7) },  /* South Sumatra */
484     { "jt",     tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
485     { "nzt",    tZONE,     -HOUR(12) }, /* New Zealand */
486     { "idle",   tZONE,     -HOUR(12) }, /* International Date Line East */
487     { "cat",    tZONE,     HOUR(10) },  /* -- expired 1967 */
488     { "nt",     tZONE,     HOUR(11) },  /* -- expired 1967 */
489     { "ahst",   tZONE,     HOUR(10) },  /* -- expired 1983 */
490     { "hdt",    tDAYZONE,  HOUR(10) },  /* -- expired 1986 */
491 #endif  /* 0 */
492 };
493
494 \f
495
496 static void
497 date_error(const char *s)
498 {
499     s = s;                      /* ARGSUSED */
500     /* NOTREACHED */
501 }
502
503
504 static time_t
505 ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
506 {
507     if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
508         return -1;
509     if (Meridian == MER24) {
510         if (Hours < 0 || Hours > 23)
511             return -1;
512     }
513     else {
514         if (Hours < 1 || Hours > 12)
515             return -1;
516         if (Hours == 12)
517             Hours = 0;
518         if (Meridian == MERpm)
519             Hours += 12;
520     }
521     return (Hours * 60L + Minutes) * 60L + Seconds;
522 }
523
524
525 static time_t
526 Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes,
527         time_t Seconds, MERIDIAN Meridian, DSTMODE dst)
528 {
529     static int  DaysNormal[13] = {
530         0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
531     };
532     static int  DaysLeap[13] = {
533         0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
534     };
535     static int  LeapYears[] = {
536         1972, 1976, 1980, 1984, 1988, 1992, 1996,
537         2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
538     };
539     int                 *yp;
540     int                 *mp;
541     time_t              Julian;
542     int                 i;
543     time_t              tod;
544
545     /* Year should not be passed as a relative value, but absolute one.
546        so this should not happen, but just ensure it */
547     if (Year < 0)
548         Year = -Year;
549     if (Year < 100) {
550         Year += 1900;
551         if (Year < EPOCH)
552             Year += 100;
553     }
554     for (mp = DaysNormal, yp = LeapYears; yp < ARRAY_END(LeapYears); yp++)
555         if (Year == *yp) {
556             mp = DaysLeap;
557             break;
558         }
559     if (Year < EPOCH || Year > END_OF_TIME
560      || Month < 1 || Month > 12
561      /* NOSTRICT *//* conversion from long may lose accuracy */
562      || Day < 1 || Day > mp[(int)Month])
563         return -1;
564
565     Julian = Day - 1 + (Year - EPOCH) * 365;
566     for (yp = LeapYears; yp < ARRAY_END(LeapYears); yp++, Julian++)
567         if (Year <= *yp)
568             break;
569     for (i = 1; i < Month; i++)
570         Julian += *++mp;
571     Julian *= SECSPERDAY;
572     Julian += yyTimezone * 60L;
573     if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
574         return -1;
575     Julian += tod;
576     tod = Julian;
577     if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
578         Julian -= DST_OFFSET * 60 * 60;
579     return Julian;
580 }
581
582
583 static time_t
584 DSTcorrect(time_t Start, time_t Future)
585 {
586     time_t      StartDay;
587     time_t      FutureDay;
588
589     StartDay = (localtime(&Start)->tm_hour + 1) % 24;
590     FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
591     return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60 * 60;
592 }
593
594
595 static time_t
596 RelativeMonth(time_t Start, time_t RelMonth)
597 {
598     struct tm   *tm;
599     time_t      Month;
600     time_t      Year;
601
602     tm = localtime(&Start);
603     Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
604     Year = Month / 12;
605     Year += 1900;
606     Month = Month % 12 + 1;
607     return DSTcorrect(Start,
608             Convert(Month, (time_t)tm->tm_mday, Year,
609                 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
610                 MER24, DSTmaybe));
611 }
612
613
614 static int
615 LookupWord(char *buff, int length)
616 {
617     char *p;
618     const char *q;
619     TABLE *tp;
620     int c;
621
622     p = buff;
623     c = p[0];
624
625     /* See if we have an abbreviation for a month. */
626     if (length == 3 || (length == 4 && p[3] == '.'))
627         for (tp = MonthDayTable; tp < ARRAY_END(MonthDayTable); tp++) {
628             q = tp->name;
629             if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
630                 yylval.Number = tp->value;
631                 return tp->type;
632             }
633         }
634     else
635         for (tp = MonthDayTable; tp < ARRAY_END(MonthDayTable); tp++)
636             if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
637                 yylval.Number = tp->value;
638                 return tp->type;
639             }
640
641     /* Try for a timezone. */
642     for (tp = TimezoneTable; tp < ARRAY_END(TimezoneTable); tp++)
643         if (c == tp->name[0] && p[1] == tp->name[1]
644          && strcmp(p, tp->name) == 0) {
645             yylval.Number = tp->value;
646             return tp->type;
647         }
648
649     /* Try the units table. */
650     for (tp = UnitsTable; tp < ARRAY_END(UnitsTable); tp++)
651         if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
652             yylval.Number = tp->value;
653             return tp->type;
654         }
655
656     /* Strip off any plural and try the units table again. */
657     if (--length > 0 && p[length] == 's') {
658         p[length] = '\0';
659         for (tp = UnitsTable; tp < ARRAY_END(UnitsTable); tp++)
660             if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
661                 p[length] = 's';
662                 yylval.Number = tp->value;
663                 return tp->type;
664             }
665         p[length] = 's';
666     }
667     length++;
668
669     /* Drop out any periods. */
670     for (p = buff, q = buff; *q; q++)
671         if (*q != '.')
672             *p++ = *q;
673     *p = '\0';
674
675     /* Try the meridians. */
676     if (buff[1] == 'm' && buff[2] == '\0') {
677         if (buff[0] == 'a') {
678             yylval.Meridian = MERam;
679             return tMERIDIAN;
680         }
681         if (buff[0] == 'p') {
682             yylval.Meridian = MERpm;
683             return tMERIDIAN;
684         }
685     }
686
687     /* If we saw any periods, try the timezones again. */
688     if (p - buff != length) {
689         c = buff[0];
690         for (p = buff, tp = TimezoneTable; tp < ARRAY_END(TimezoneTable); tp++)
691             if (c == tp->name[0] && p[1] == tp->name[1]
692             && strcmp(p, tp->name) == 0) {
693                 yylval.Number = tp->value;
694                 return tp->type;
695             }
696     }
697
698     /* Unknown word -- assume GMT timezone. */
699     yylval.Number = 0;
700     return tZONE;
701 }
702
703
704 static int
705 date_lex(void)
706 {
707     char                c;
708     char                *p;
709     char                buff[20];
710     int                 sign;
711     int                 i;
712     int                 nesting;
713
714     for ( ; ; ) {
715         /* Get first character after the whitespace. */
716         for ( ; ; ) {
717             while (CTYPE(isspace, (int)*yyInput))
718                 yyInput++;
719             c = *yyInput;
720
721             /* Ignore RFC 822 comments, typically time zone names. */
722             if (c != LPAREN)
723                 break;
724             for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
725                 if (c == LPAREN)
726                     nesting++;
727                 else if (!IS7BIT(c) || c == '\0' || c == '\r'
728                      || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
729                     /* Lexical error: bad comment. */
730                     return '?';
731             yyInput++;
732         }
733
734         /* A number? */
735         if (CTYPE(isdigit, (int)c) || c == '-' || c == '+') {
736             if (c == '-' || c == '+') {
737                 sign = c == '-' ? -1 : 1;
738                 yyInput++;
739                 if (!CTYPE(isdigit, (int)*yyInput))
740                     /* Skip the plus or minus sign. */
741                     continue;
742             }
743             else
744                 sign = 0;
745             for (i = 0; (c = *yyInput++) != '\0' && CTYPE(isdigit, (int)c); )
746                 i = 10 * i + c - '0';
747             yyInput--;
748             yylval.Number = sign < 0 ? -i : i;
749             return sign ? tSNUMBER : tUNUMBER;
750         }
751
752         /* A word? */
753         if (CTYPE(isalpha, (int)c)) {
754             for (p = buff; (c = *yyInput++) == '.' || CTYPE(isalpha, (int)c); )
755                 if (p < &buff[sizeof buff - 1])
756                     *p++ = CTYPE(isupper, (int)c) ? tolower(c) : c;
757             *p = '\0';
758             yyInput--;
759             return LookupWord(buff, p - buff);
760         }
761
762         return *yyInput++;
763     }
764 }
765
766
767 time_t
768 parsedate(char *p, TIMEINFO *now)
769 {
770     struct tm           *tm;
771     TIMEINFO            ti;
772     time_t              Start;
773
774     yyInput = p;
775     if (now == NULL) {
776         now = &ti;
777         GetTimeInfo(&ti);
778     }
779
780     tm = localtime(&now->time);
781     yyYear = tm->tm_year + 1900;
782     yyMonth = tm->tm_mon + 1;
783     yyDay = tm->tm_mday;
784     yyTimezone = now->tzone;
785     yyDSTmode = DSTmaybe;
786     yyHour = 0;
787     yyMinutes = 0;
788     yySeconds = 0;
789     yyMeridian = MER24;
790     yyRelSeconds = 0;
791     yyRelMonth = 0;
792     yyHaveDate = 0;
793     yyHaveRel = 0;
794     yyHaveTime = 0;
795
796     if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
797         return -1;
798
799     if (yyHaveDate || yyHaveTime) {
800         Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
801                     yyMeridian, yyDSTmode);
802         if (Start < 0)
803             return -1;
804     }
805     else {
806         Start = now->time;
807         if (!yyHaveRel)
808             Start -= (tm->tm_hour * 60L + tm->tm_min) * 60L + tm->tm_sec;
809     }
810
811     Start += yyRelSeconds;
812     if (yyRelMonth)
813         Start += RelativeMonth(Start, yyRelMonth);
814
815     /* Have to do *something* with a legitimate -1 so it's distinguishable
816      * from the error return value.  (Alternately could set errno on error.) */
817     return Start == -1 ? 0 : Start;
818 }
819
820
821 #if     defined(TEST)
822
823 #if     YYDEBUG
824 extern int      yydebug;
825 #endif  /* YYDEBUG */
826
827 /* ARGSUSED */
828 int
829 main(int ac, char *av[])
830 {
831     char        buff[128];
832     time_t      d;
833
834 #if     YYDEBUG
835     yydebug = 1;
836 #endif  /* YYDEBUG */
837
838     printf("Enter date, or blank line to exit.\n\t> ");
839     for ( ; ; ) {
840         printf("\t> ");
841         fflush(stdout);
842         if (gets(buff) == NULL || buff[0] == '\n')
843             break;
844 #if     YYDEBUG
845         if (strcmp(buff, "yydebug") == 0) {
846             yydebug = !yydebug;
847             printf("yydebug = %s\n", yydebug ? "on" : "off");
848             continue;
849         }
850 #endif  /* YYDEBUG */
851         d = parsedate(buff, (TIMEINFO *)NULL);
852         if (d == -1)
853             printf("Bad format - couldn't convert.\n");
854         else
855             printf("%s", ctime(&d));
856     }
857
858     exit(0);
859     /* NOTREACHED */
860 }
861 #endif  /* defined(TEST) */