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