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