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