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