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