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