Merge changes from FSF.
[gnulib.git] / lib / getdate.y
1 %{
2 /*
3 **  Originally written by Steven M. Bellovin <smb@research.att.com> while
4 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
5 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
6 **  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
7 **  send any email to Rich.
8 **
9 **  This grammar has 10 shift/reduce conflicts.
10 **
11 **  This code is in the public domain and has no copyright.
12 */
13 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
14 /* SUPPRESS 288 on yyerrlab *//* Label unused */
15
16 #ifdef HAVE_CONFIG_H
17 #include <config.h>
18
19 #ifdef FORCE_ALLOCA_H
20 #include <alloca.h>
21 #endif
22 #endif
23
24 /* Since the code of getdate.y is not included in the Emacs executable
25    itself, there is no need to #define static in this file.  Even if
26    the code were included in the Emacs executable, it probably
27    wouldn't do any harm to #undef it here; this will only cause
28    problems if we try to write to a static variable, which I don't
29    think this code needs to do.  */
30 #ifdef emacs
31 #undef static
32 #endif
33
34 #include <stdio.h>
35 #include <ctype.h>
36
37 #if     defined (vms)
38 #include <types.h>
39 #include <time.h>
40 #else
41 #include <sys/types.h>
42 #ifdef TIME_WITH_SYS_TIME
43 #include <sys/time.h>
44 #include <time.h>
45 #else
46 #ifdef HAVE_SYS_TIME_H
47 #include <sys/time.h>
48 #else
49 #include <time.h>
50 #endif
51 #endif
52
53 #ifdef timezone
54 #undef timezone /* needed for sgi */
55 #endif
56
57 #if defined (HAVE_SYS_TIMEB_H)
58 #include <sys/timeb.h>
59 #else
60
61 /* get_date uses the obsolete `struct timeb' in its interface!  FIXME.
62    Since some systems don't have it, we define it here;
63    callers must do likewise.  */
64 struct timeb
65   {
66     time_t              time;           /* Seconds since the epoch      */
67     unsigned short      millitm;        /* Field not used               */
68     short               timezone;       /* Minutes west of GMT          */
69     short               dstflag;        /* Field not used               */
70 };
71 #endif /* defined (HAVE_SYS_TIMEB_H) */
72
73 #endif  /* defined (vms) */
74
75 #if defined (STDC_HEADERS) || defined (USG)
76 #include <string.h>
77 #endif
78
79 /* Some old versions of bison generate parsers that use bcopy.
80    That loses on systems that don't provide the function, so we have
81    to redefine it here.  */
82 #if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
83 #define bcopy(from, to, len) memcpy ((to), (from), (len))
84 #endif
85
86 extern struct tm        *gmtime ();
87 extern struct tm        *localtime ();
88
89 #define yyparse getdate_yyparse
90 #define yylex getdate_yylex
91 #define yyerror getdate_yyerror
92
93 static int yylex ();
94 static int yyerror ();
95
96 #define EPOCH           1970
97 #define DOOMSDAY        2038
98 #define HOUR(x)         ((time_t)(x) * 60)
99 #define SECSPERDAY      (24L * 60L * 60L)
100
101 #define MAX_BUFF_LEN    128   /* size of buffer to read the date into */
102
103 /*
104 **  An entry in the lexical lookup table.
105 */
106 typedef struct _TABLE {
107     const char  *name;
108     int         type;
109     time_t      value;
110 } TABLE;
111
112
113 /*
114 **  Daylight-savings mode:  on, off, or not yet known.
115 */
116 typedef enum _DSTMODE {
117     DSTon, DSToff, DSTmaybe
118 } DSTMODE;
119
120 /*
121 **  Meridian:  am, pm, or 24-hour style.
122 */
123 typedef enum _MERIDIAN {
124     MERam, MERpm, MER24
125 } MERIDIAN;
126
127
128 /*
129 **  Global variables.  We could get rid of most of these by using a good
130 **  union as the yacc stack.  (This routine was originally written before
131 **  yacc had the %union construct.)  Maybe someday; right now we only use
132 **  the %union very rarely.
133 */
134 static char     *yyInput;
135 static DSTMODE  yyDSTmode;
136 static time_t   yyDayOrdinal;
137 static time_t   yyDayNumber;
138 static int      yyHaveDate;
139 static int      yyHaveDay;
140 static int      yyHaveRel;
141 static int      yyHaveTime;
142 static int      yyHaveZone;
143 static time_t   yyTimezone;
144 static time_t   yyDay;
145 static time_t   yyHour;
146 static time_t   yyMinutes;
147 static time_t   yyMonth;
148 static time_t   yySeconds;
149 static time_t   yyYear;
150 static MERIDIAN yyMeridian;
151 static time_t   yyRelMonth;
152 static time_t   yyRelSeconds;
153
154 %}
155
156 %union {
157     time_t              Number;
158     enum _MERIDIAN      Meridian;
159 }
160
161 %token  tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
162 %token  tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
163
164 %type   <Number>        tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
165 %type   <Number>        tSEC_UNIT tSNUMBER tUNUMBER tZONE
166 %type   <Meridian>      tMERIDIAN o_merid
167
168 %%
169
170 spec    : /* NULL */
171         | spec item
172         ;
173
174 item    : time {
175             yyHaveTime++;
176         }
177         | zone {
178             yyHaveZone++;
179         }
180         | date {
181             yyHaveDate++;
182         }
183         | day {
184             yyHaveDay++;
185         }
186         | rel {
187             yyHaveRel++;
188         }
189         | number
190         ;
191
192 time    : tUNUMBER tMERIDIAN {
193             yyHour = $1;
194             yyMinutes = 0;
195             yySeconds = 0;
196             yyMeridian = $2;
197         }
198         | tUNUMBER ':' tUNUMBER o_merid {
199             yyHour = $1;
200             yyMinutes = $3;
201             yySeconds = 0;
202             yyMeridian = $4;
203         }
204         | tUNUMBER ':' tUNUMBER tSNUMBER {
205             yyHour = $1;
206             yyMinutes = $3;
207             yyMeridian = MER24;
208             yyDSTmode = DSToff;
209             yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
210         }
211         | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
212             yyHour = $1;
213             yyMinutes = $3;
214             yySeconds = $5;
215             yyMeridian = $6;
216         }
217         | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
218             yyHour = $1;
219             yyMinutes = $3;
220             yySeconds = $5;
221             yyMeridian = MER24;
222             yyDSTmode = DSToff;
223             yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
224         }
225         ;
226
227 zone    : tZONE {
228             yyTimezone = $1;
229             yyDSTmode = DSToff;
230         }
231         | tDAYZONE {
232             yyTimezone = $1;
233             yyDSTmode = DSTon;
234         }
235         |
236           tZONE tDST {
237             yyTimezone = $1;
238             yyDSTmode = DSTon;
239         }
240         ;
241
242 day     : tDAY {
243             yyDayOrdinal = 1;
244             yyDayNumber = $1;
245         }
246         | tDAY ',' {
247             yyDayOrdinal = 1;
248             yyDayNumber = $1;
249         }
250         | tUNUMBER tDAY {
251             yyDayOrdinal = $1;
252             yyDayNumber = $2;
253         }
254         ;
255
256 date    : tUNUMBER '/' tUNUMBER {
257             yyMonth = $1;
258             yyDay = $3;
259         }
260         | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
261             yyMonth = $1;
262             yyDay = $3;
263             yyYear = $5;
264         }
265         | tUNUMBER tSNUMBER tSNUMBER {
266             /* ISO 8601 format.  yyyy-mm-dd.  */
267             yyYear = $1;
268             yyMonth = -$2;
269             yyDay = -$3;
270         }
271         | tUNUMBER tMONTH tSNUMBER {
272             /* e.g. 17-JUN-1992.  */
273             yyDay = $1;
274             yyMonth = $2;
275             yyYear = -$3;
276         }
277         | tMONTH tUNUMBER {
278             yyMonth = $1;
279             yyDay = $2;
280         }
281         | tMONTH tUNUMBER ',' tUNUMBER {
282             yyMonth = $1;
283             yyDay = $2;
284             yyYear = $4;
285         }
286         | tUNUMBER tMONTH {
287             yyMonth = $2;
288             yyDay = $1;
289         }
290         | tUNUMBER tMONTH tUNUMBER {
291             yyMonth = $2;
292             yyDay = $1;
293             yyYear = $3;
294         }
295         ;
296
297 rel     : relunit tAGO {
298             yyRelSeconds = -yyRelSeconds;
299             yyRelMonth = -yyRelMonth;
300         }
301         | relunit
302         ;
303
304 relunit : tUNUMBER tMINUTE_UNIT {
305             yyRelSeconds += $1 * $2 * 60L;
306         }
307         | tSNUMBER tMINUTE_UNIT {
308             yyRelSeconds += $1 * $2 * 60L;
309         }
310         | tMINUTE_UNIT {
311             yyRelSeconds += $1 * 60L;
312         }
313         | tSNUMBER tSEC_UNIT {
314             yyRelSeconds += $1;
315         }
316         | tUNUMBER tSEC_UNIT {
317             yyRelSeconds += $1;
318         }
319         | tSEC_UNIT {
320             yyRelSeconds++;
321         }
322         | tSNUMBER tMONTH_UNIT {
323             yyRelMonth += $1 * $2;
324         }
325         | tUNUMBER tMONTH_UNIT {
326             yyRelMonth += $1 * $2;
327         }
328         | tMONTH_UNIT {
329             yyRelMonth += $1;
330         }
331         ;
332
333 number  : tUNUMBER {
334             if (yyHaveTime && yyHaveDate && !yyHaveRel)
335                 yyYear = $1;
336             else {
337                 if ($1>10000) {
338                     yyHaveDate++;
339                     yyDay= ($1)%100;
340                     yyMonth= ($1/100)%100;
341                     yyYear = $1/10000;
342                 }
343                 else {
344                     yyHaveTime++;
345                     if ($1 < 100) {
346                         yyHour = $1;
347                         yyMinutes = 0;
348                     }
349                     else {
350                         yyHour = $1 / 100;
351                         yyMinutes = $1 % 100;
352                     }
353                     yySeconds = 0;
354                     yyMeridian = MER24;
355                 }
356             }
357         }
358         ;
359
360 o_merid : /* NULL */ {
361             $$ = MER24;
362         }
363         | tMERIDIAN {
364             $$ = $1;
365         }
366         ;
367
368 %%
369
370 /* Month and day table. */
371 static TABLE const MonthDayTable[] = {
372     { "january",        tMONTH,  1 },
373     { "february",       tMONTH,  2 },
374     { "march",          tMONTH,  3 },
375     { "april",          tMONTH,  4 },
376     { "may",            tMONTH,  5 },
377     { "june",           tMONTH,  6 },
378     { "july",           tMONTH,  7 },
379     { "august",         tMONTH,  8 },
380     { "september",      tMONTH,  9 },
381     { "sept",           tMONTH,  9 },
382     { "october",        tMONTH, 10 },
383     { "november",       tMONTH, 11 },
384     { "december",       tMONTH, 12 },
385     { "sunday",         tDAY, 0 },
386     { "monday",         tDAY, 1 },
387     { "tuesday",        tDAY, 2 },
388     { "tues",           tDAY, 2 },
389     { "wednesday",      tDAY, 3 },
390     { "wednes",         tDAY, 3 },
391     { "thursday",       tDAY, 4 },
392     { "thur",           tDAY, 4 },
393     { "thurs",          tDAY, 4 },
394     { "friday",         tDAY, 5 },
395     { "saturday",       tDAY, 6 },
396     { NULL }
397 };
398
399 /* Time units table. */
400 static TABLE const UnitsTable[] = {
401     { "year",           tMONTH_UNIT,    12 },
402     { "month",          tMONTH_UNIT,    1 },
403     { "fortnight",      tMINUTE_UNIT,   14 * 24 * 60 },
404     { "week",           tMINUTE_UNIT,   7 * 24 * 60 },
405     { "day",            tMINUTE_UNIT,   1 * 24 * 60 },
406     { "hour",           tMINUTE_UNIT,   60 },
407     { "minute",         tMINUTE_UNIT,   1 },
408     { "min",            tMINUTE_UNIT,   1 },
409     { "second",         tSEC_UNIT,      1 },
410     { "sec",            tSEC_UNIT,      1 },
411     { NULL }
412 };
413
414 /* Assorted relative-time words. */
415 static TABLE const OtherTable[] = {
416     { "tomorrow",       tMINUTE_UNIT,   1 * 24 * 60 },
417     { "yesterday",      tMINUTE_UNIT,   -1 * 24 * 60 },
418     { "today",          tMINUTE_UNIT,   0 },
419     { "now",            tMINUTE_UNIT,   0 },
420     { "last",           tUNUMBER,       -1 },
421     { "this",           tMINUTE_UNIT,   0 },
422     { "next",           tUNUMBER,       2 },
423     { "first",          tUNUMBER,       1 },
424 /*  { "second",         tUNUMBER,       2 }, */
425     { "third",          tUNUMBER,       3 },
426     { "fourth",         tUNUMBER,       4 },
427     { "fifth",          tUNUMBER,       5 },
428     { "sixth",          tUNUMBER,       6 },
429     { "seventh",        tUNUMBER,       7 },
430     { "eighth",         tUNUMBER,       8 },
431     { "ninth",          tUNUMBER,       9 },
432     { "tenth",          tUNUMBER,       10 },
433     { "eleventh",       tUNUMBER,       11 },
434     { "twelfth",        tUNUMBER,       12 },
435     { "ago",            tAGO,   1 },
436     { NULL }
437 };
438
439 /* The timezone table. */
440 /* Some of these are commented out because a time_t can't store a float. */
441 static TABLE const TimezoneTable[] = {
442     { "gmt",    tZONE,     HOUR ( 0) }, /* Greenwich Mean */
443     { "ut",     tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
444     { "utc",    tZONE,     HOUR ( 0) },
445     { "wet",    tZONE,     HOUR ( 0) }, /* Western European */
446     { "bst",    tDAYZONE,  HOUR ( 0) }, /* British Summer */
447     { "wat",    tZONE,     HOUR ( 1) }, /* West Africa */
448     { "at",     tZONE,     HOUR ( 2) }, /* Azores */
449 #if     0
450     /* For completeness.  BST is also British Summer, and GST is
451      * also Guam Standard. */
452     { "bst",    tZONE,     HOUR ( 3) }, /* Brazil Standard */
453     { "gst",    tZONE,     HOUR ( 3) }, /* Greenland Standard */
454 #endif
455 #if 0
456     { "nft",    tZONE,     HOUR (3.5) },        /* Newfoundland */
457     { "nst",    tZONE,     HOUR (3.5) },        /* Newfoundland Standard */
458     { "ndt",    tDAYZONE,  HOUR (3.5) },        /* Newfoundland Daylight */
459 #endif
460     { "ast",    tZONE,     HOUR ( 4) }, /* Atlantic Standard */
461     { "adt",    tDAYZONE,  HOUR ( 4) }, /* Atlantic Daylight */
462     { "est",    tZONE,     HOUR ( 5) }, /* Eastern Standard */
463     { "edt",    tDAYZONE,  HOUR ( 5) }, /* Eastern Daylight */
464     { "cst",    tZONE,     HOUR ( 6) }, /* Central Standard */
465     { "cdt",    tDAYZONE,  HOUR ( 6) }, /* Central Daylight */
466     { "mst",    tZONE,     HOUR ( 7) }, /* Mountain Standard */
467     { "mdt",    tDAYZONE,  HOUR ( 7) }, /* Mountain Daylight */
468     { "pst",    tZONE,     HOUR ( 8) }, /* Pacific Standard */
469     { "pdt",    tDAYZONE,  HOUR ( 8) }, /* Pacific Daylight */
470     { "yst",    tZONE,     HOUR ( 9) }, /* Yukon Standard */
471     { "ydt",    tDAYZONE,  HOUR ( 9) }, /* Yukon Daylight */
472     { "hst",    tZONE,     HOUR (10) }, /* Hawaii Standard */
473     { "hdt",    tDAYZONE,  HOUR (10) }, /* Hawaii Daylight */
474     { "cat",    tZONE,     HOUR (10) }, /* Central Alaska */
475     { "ahst",   tZONE,     HOUR (10) }, /* Alaska-Hawaii Standard */
476     { "nt",     tZONE,     HOUR (11) }, /* Nome */
477     { "idlw",   tZONE,     HOUR (12) }, /* International Date Line West */
478     { "cet",    tZONE,     -HOUR (1) }, /* Central European */
479     { "met",    tZONE,     -HOUR (1) }, /* Middle European */
480     { "mewt",   tZONE,     -HOUR (1) }, /* Middle European Winter */
481     { "mest",   tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
482     { "mesz",   tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
483     { "swt",    tZONE,     -HOUR (1) }, /* Swedish Winter */
484     { "sst",    tDAYZONE,  -HOUR (1) }, /* Swedish Summer */
485     { "fwt",    tZONE,     -HOUR (1) }, /* French Winter */
486     { "fst",    tDAYZONE,  -HOUR (1) }, /* French Summer */
487     { "eet",    tZONE,     -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
488     { "bt",     tZONE,     -HOUR (3) }, /* Baghdad, USSR Zone 2 */
489 #if 0
490     { "it",     tZONE,     -HOUR (3.5) },/* Iran */
491 #endif
492     { "zp4",    tZONE,     -HOUR (4) }, /* USSR Zone 3 */
493     { "zp5",    tZONE,     -HOUR (5) }, /* USSR Zone 4 */
494 #if 0
495     { "ist",    tZONE,     -HOUR (5.5) },/* Indian Standard */
496 #endif
497     { "zp6",    tZONE,     -HOUR (6) }, /* USSR Zone 5 */
498 #if     0
499     /* For completeness.  NST is also Newfoundland Standard, and SST is
500      * also Swedish Summer. */
501     { "nst",    tZONE,     -HOUR (6.5) },/* North Sumatra */
502     { "sst",    tZONE,     -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
503 #endif  /* 0 */
504     { "wast",   tZONE,     -HOUR (7) }, /* West Australian Standard */
505     { "wadt",   tDAYZONE,  -HOUR (7) }, /* West Australian Daylight */
506 #if 0
507     { "jt",     tZONE,     -HOUR (7.5) },/* Java (3pm in Cronusland!) */
508 #endif
509     { "cct",    tZONE,     -HOUR (8) }, /* China Coast, USSR Zone 7 */
510     { "jst",    tZONE,     -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
511 #if 0
512     { "cast",   tZONE,     -HOUR (9.5) },/* Central Australian Standard */
513     { "cadt",   tDAYZONE,  -HOUR (9.5) },/* Central Australian Daylight */
514 #endif
515     { "east",   tZONE,     -HOUR (10) },        /* Eastern Australian Standard */
516     { "eadt",   tDAYZONE,  -HOUR (10) },        /* Eastern Australian Daylight */
517     { "gst",    tZONE,     -HOUR (10) },        /* Guam Standard, USSR Zone 9 */
518     { "nzt",    tZONE,     -HOUR (12) },        /* New Zealand */
519     { "nzst",   tZONE,     -HOUR (12) },        /* New Zealand Standard */
520     { "nzdt",   tDAYZONE,  -HOUR (12) },        /* New Zealand Daylight */
521     { "idle",   tZONE,     -HOUR (12) },        /* International Date Line East */
522     {  NULL  }
523 };
524
525 /* Military timezone table. */
526 static TABLE const MilitaryTable[] = {
527     { "a",      tZONE,  HOUR (  1) },
528     { "b",      tZONE,  HOUR (  2) },
529     { "c",      tZONE,  HOUR (  3) },
530     { "d",      tZONE,  HOUR (  4) },
531     { "e",      tZONE,  HOUR (  5) },
532     { "f",      tZONE,  HOUR (  6) },
533     { "g",      tZONE,  HOUR (  7) },
534     { "h",      tZONE,  HOUR (  8) },
535     { "i",      tZONE,  HOUR (  9) },
536     { "k",      tZONE,  HOUR ( 10) },
537     { "l",      tZONE,  HOUR ( 11) },
538     { "m",      tZONE,  HOUR ( 12) },
539     { "n",      tZONE,  HOUR (- 1) },
540     { "o",      tZONE,  HOUR (- 2) },
541     { "p",      tZONE,  HOUR (- 3) },
542     { "q",      tZONE,  HOUR (- 4) },
543     { "r",      tZONE,  HOUR (- 5) },
544     { "s",      tZONE,  HOUR (- 6) },
545     { "t",      tZONE,  HOUR (- 7) },
546     { "u",      tZONE,  HOUR (- 8) },
547     { "v",      tZONE,  HOUR (- 9) },
548     { "w",      tZONE,  HOUR (-10) },
549     { "x",      tZONE,  HOUR (-11) },
550     { "y",      tZONE,  HOUR (-12) },
551     { "z",      tZONE,  HOUR (  0) },
552     { NULL }
553 };
554
555 \f
556
557
558 /* ARGSUSED */
559 static int
560 yyerror (s)
561     char        *s;
562 {
563   return 0;
564 }
565
566
567 static time_t
568 ToSeconds (Hours, Minutes, Seconds, Meridian)
569     time_t      Hours;
570     time_t      Minutes;
571     time_t      Seconds;
572     MERIDIAN    Meridian;
573 {
574   if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
575     return -1;
576   switch (Meridian) {
577   case MER24:
578     if (Hours < 0 || Hours > 23)
579       return -1;
580     return (Hours * 60L + Minutes) * 60L + Seconds;
581   case MERam:
582     if (Hours < 1 || Hours > 12)
583       return -1;
584     if (Hours == 12)
585       Hours = 0;
586     return (Hours * 60L + Minutes) * 60L + Seconds;
587   case MERpm:
588     if (Hours < 1 || Hours > 12)
589       return -1;
590     if (Hours == 12)
591       Hours = 0;
592     return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
593   default:
594     abort ();
595   }
596   /* NOTREACHED */
597 }
598
599
600 static time_t
601 Convert (Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
602     time_t      Month;
603     time_t      Day;
604     time_t      Year;
605     time_t      Hours;
606     time_t      Minutes;
607     time_t      Seconds;
608     MERIDIAN    Meridian;
609     DSTMODE     DSTmode;
610 {
611   static int DaysInMonth[12] = {
612     31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
613   };
614   time_t        tod;
615   time_t        Julian;
616   int           i;
617
618   if (Year < 0)
619     Year = -Year;
620   if (Year < DOOMSDAY-2000)
621     Year += 2000;
622   else if (Year < 100)
623     Year += 1900;
624   DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
625     ? 29 : 28;
626   if (Year < EPOCH || Year >= DOOMSDAY
627       || Month < 1 || Month > 12
628       /* Lint fluff:  "conversion from long may lose accuracy" */
629       || Day < 1 || Day > DaysInMonth[(int)--Month])
630     return -1;
631
632   for (Julian = Day - 1, i = 0; i < Month; i++)
633     Julian += DaysInMonth[i];
634   for (i = EPOCH; i < Year; i++)
635     Julian += 365 + (i % 4 == 0);
636   Julian *= SECSPERDAY;
637   Julian += yyTimezone * 60L;
638   if ((tod = ToSeconds (Hours, Minutes, Seconds, Meridian)) < 0)
639     return -1;
640   Julian += tod;
641   if (DSTmode == DSTon
642       || (DSTmode == DSTmaybe && localtime (&Julian)->tm_isdst))
643     Julian -= 60 * 60;
644   return Julian;
645 }
646
647
648 static time_t
649 DSTcorrect (Start, Future)
650     time_t      Start;
651     time_t      Future;
652 {
653   time_t        StartDay;
654   time_t        FutureDay;
655
656   StartDay = (localtime (&Start)->tm_hour + 1) % 24;
657   FutureDay = (localtime (&Future)->tm_hour + 1) % 24;
658   return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
659 }
660
661
662 static time_t
663 RelativeDate (Start, DayOrdinal, DayNumber)
664     time_t      Start;
665     time_t      DayOrdinal;
666     time_t      DayNumber;
667 {
668   struct tm     *tm;
669   time_t        now;
670
671   now = Start;
672   tm = localtime (&now);
673   now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
674   now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
675   return DSTcorrect (Start, now);
676 }
677
678
679 static time_t
680 RelativeMonth (Start, RelMonth)
681     time_t      Start;
682     time_t      RelMonth;
683 {
684   struct tm     *tm;
685   time_t        Month;
686   time_t        Year;
687
688   if (RelMonth == 0)
689     return 0;
690   tm = localtime (&Start);
691   Month = 12 * (1900 + tm->tm_year) + tm->tm_mon + RelMonth;
692   Year = Month / 12;
693   Month = Month % 12 + 1;
694   return DSTcorrect (Start,
695                      Convert (Month, (time_t)tm->tm_mday, Year,
696                               (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
697                               MER24, DSTmaybe));
698 }
699
700
701 static int
702 LookupWord (buff)
703     char                *buff;
704 {
705   register char *p;
706   register char *q;
707   register const TABLE  *tp;
708   int                   i;
709   int                   abbrev;
710
711   /* Make it lowercase. */
712   for (p = buff; *p; p++)
713     if (isupper (*p))
714       *p = tolower (*p);
715
716   if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0) {
717     yylval.Meridian = MERam;
718     return tMERIDIAN;
719   }
720   if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0) {
721     yylval.Meridian = MERpm;
722     return tMERIDIAN;
723   }
724
725   /* See if we have an abbreviation for a month. */
726   if (strlen (buff) == 3)
727     abbrev = 1;
728   else if (strlen (buff) == 4 && buff[3] == '.') {
729     abbrev = 1;
730     buff[3] = '\0';
731   }
732   else
733     abbrev = 0;
734
735   for (tp = MonthDayTable; tp->name; tp++) {
736     if (abbrev) {
737       if (strncmp (buff, tp->name, 3) == 0) {
738         yylval.Number = tp->value;
739         return tp->type;
740       }
741     }
742     else if (strcmp (buff, tp->name) == 0) {
743       yylval.Number = tp->value;
744       return tp->type;
745     }
746   }
747
748   for (tp = TimezoneTable; tp->name; tp++)
749     if (strcmp (buff, tp->name) == 0) {
750       yylval.Number = tp->value;
751       return tp->type;
752     }
753
754   if (strcmp (buff, "dst") == 0)
755     return tDST;
756
757   for (tp = UnitsTable; tp->name; tp++)
758     if (strcmp (buff, tp->name) == 0) {
759       yylval.Number = tp->value;
760       return tp->type;
761     }
762
763   /* Strip off any plural and try the units table again. */
764   i = strlen (buff) - 1;
765   if (buff[i] == 's') {
766     buff[i] = '\0';
767     for (tp = UnitsTable; tp->name; tp++)
768       if (strcmp (buff, tp->name) == 0) {
769         yylval.Number = tp->value;
770         return tp->type;
771       }
772     buff[i] = 's';              /* Put back for "this" in OtherTable. */
773   }
774
775   for (tp = OtherTable; tp->name; tp++)
776     if (strcmp (buff, tp->name) == 0) {
777       yylval.Number = tp->value;
778       return tp->type;
779     }
780
781   /* Military timezones. */
782   if (buff[1] == '\0' && isalpha (*buff)) {
783     for (tp = MilitaryTable; tp->name; tp++)
784       if (strcmp (buff, tp->name) == 0) {
785         yylval.Number = tp->value;
786         return tp->type;
787       }
788   }
789
790   /* Drop out any periods and try the timezone table again. */
791   for (i = 0, p = q = buff; *q; q++)
792     if (*q != '.')
793       *p++ = *q;
794     else
795       i++;
796   *p = '\0';
797   if (i)
798     for (tp = TimezoneTable; tp->name; tp++)
799       if (strcmp (buff, tp->name) == 0) {
800         yylval.Number = tp->value;
801         return tp->type;
802       }
803
804   return tID;
805 }
806
807
808 static int
809 yylex ()
810 {
811   register char c;
812   register char *p;
813   char          buff[20];
814   int                   Count;
815   int                   sign;
816
817   for ( ; ; ) {
818     while (isspace (*yyInput))
819       yyInput++;
820
821     if (isdigit (c = *yyInput) || c == '-' || c == '+') {
822       if (c == '-' || c == '+') {
823         sign = c == '-' ? -1 : 1;
824         if (!isdigit (*++yyInput))
825           /* skip the '-' sign */
826           continue;
827       }
828       else
829         sign = 0;
830       for (yylval.Number = 0; isdigit (c = *yyInput++); )
831         yylval.Number = 10 * yylval.Number + c - '0';
832       yyInput--;
833       if (sign < 0)
834         yylval.Number = -yylval.Number;
835       return sign ? tSNUMBER : tUNUMBER;
836     }
837     if (isalpha (c)) {
838       for (p = buff; isalpha (c = *yyInput++) || c == '.'; )
839         if (p < &buff[sizeof buff - 1])
840           *p++ = c;
841       *p = '\0';
842       yyInput--;
843       return LookupWord (buff);
844     }
845     if (c != '(')
846       return *yyInput++;
847     Count = 0;
848     do {
849       c = *yyInput++;
850       if (c == '\0')
851         return c;
852       if (c == '(')
853         Count++;
854       else if (c == ')')
855         Count--;
856     } while (Count > 0);
857   }
858 }
859
860 #define TM_YEAR_ORIGIN 1900
861
862 /* Yield A - B, measured in seconds.  */
863 static long
864 difftm (a, b)
865      struct tm *a, *b;
866 {
867   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
868   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
869   long days = (
870                /* difference in day of year */
871                a->tm_yday - b->tm_yday
872                /* + intervening leap days */
873                +  ((ay >> 2) - (by >> 2))
874                -  (ay/100 - by/100)
875                +  ((ay/100 >> 2) - (by/100 >> 2))
876                /* + difference in years * 365 */
877                +  (long)(ay-by) * 365
878                );
879   return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
880               + (a->tm_min - b->tm_min))
881           + (a->tm_sec - b->tm_sec));
882 }
883
884 time_t
885 get_date (p, now)
886     char                *p;
887     struct timeb        *now;
888 {
889   struct tm             *tm, gmt;
890   struct timeb  ftz;
891   time_t                Start;
892   time_t                tod;
893
894   yyInput = p;
895   if (now == NULL) {
896     now = &ftz;
897     (void)time (&ftz.time);
898
899     if (! (tm = gmtime (&ftz.time)))
900       return -1;
901     gmt = *tm;                  /* Make a copy, in case localtime modifies *tm.  */
902
903     if (! (tm = localtime (&ftz.time)))
904       return -1;
905
906     ftz.timezone = difftm (&gmt, tm) / 60;
907     if (tm->tm_isdst)
908       ftz.timezone += 60;
909   }
910
911   tm = localtime (&now->time);
912   yyYear = tm->tm_year;
913   yyMonth = tm->tm_mon + 1;
914   yyDay = tm->tm_mday;
915   yyTimezone = now->timezone;
916   yyDSTmode = DSTmaybe;
917   yyHour = 0;
918   yyMinutes = 0;
919   yySeconds = 0;
920   yyMeridian = MER24;
921   yyRelSeconds = 0;
922   yyRelMonth = 0;
923   yyHaveDate = 0;
924   yyHaveDay = 0;
925   yyHaveRel = 0;
926   yyHaveTime = 0;
927   yyHaveZone = 0;
928
929   if (yyparse ()
930       || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
931     return -1;
932
933   if (yyHaveDate || yyHaveTime || yyHaveDay) {
934     Start = Convert (yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
935                      yyMeridian, yyDSTmode);
936     if (Start < 0)
937       return -1;
938   }
939   else {
940     Start = now->time;
941     if (!yyHaveRel)
942       Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
943   }
944
945   Start += yyRelSeconds;
946   Start += RelativeMonth (Start, yyRelMonth);
947
948   if (yyHaveDay && !yyHaveDate) {
949     tod = RelativeDate (Start, yyDayOrdinal, yyDayNumber);
950     Start += tod;
951   }
952
953   /* Have to do *something* with a legitimate -1 so it's distinguishable
954    * from the error return value.  (Alternately could set errno on error.) */
955   return Start == -1 ? 0 : Start;
956 }
957
958
959 #if     defined (TEST)
960
961 /* ARGSUSED */
962 int
963 main (ac, av)
964     int         ac;
965     char        *av[];
966 {
967   char buff[MAX_BUFF_LEN + 1];
968   time_t d;
969
970   (void)printf ("Enter date, or blank line to exit.\n\t> ");
971   (void)fflush (stdout);
972
973   buff[MAX_BUFF_LEN] = 0;
974   while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0]) {
975     d = get_date (buff, (struct timeb *)NULL);
976     if (d == -1)
977       (void)printf ("Bad format - couldn't convert.\n");
978     else
979       (void)printf ("%s", ctime (&d));
980     (void)printf ("\t> ");
981     (void)fflush (stdout);
982   }
983   exit (0);
984   /* NOTREACHED */
985 }
986 #endif  /* defined (TEST) */