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