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