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