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