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