chiark / gitweb /
Fix bug in relative time specifications. Caused `now', `tomrrow',
[cfd] / 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 13 shift/reduce conflicts.
9 **
10 **  This code is in the public domain and has no copyright.
11 **
12 **  Since butchered by Mark Wooding, 1999-03-07.
13 */
14
15 #include <stdio.h>
16 #include <ctype.h>
17
18 #define ISSPACE(c) (isspace ((unsigned char)c))
19 #define ISALPHA(c) (isalpha ((unsigned char)c))
20 #define ISUPPER(c) (isupper ((unsigned char)c))
21 #define ISDIGIT_LOCALE(c) ((unsigned char)isdigit (c))
22
23 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
24    - Its arg may be any int or unsigned int; it need not be an unsigned char.
25    - It's guaranteed to evaluate its argument exactly once.
26    - It's typically faster.
27    Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
28    only '0' through '9' are digits.  Prefer ISDIGIT to ISDIGIT_LOCALE unless
29    it's important to use the locale's definition of `digit' even when the
30    host does not conform to Posix.  */
31 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
32
33 #include "getdate.h"
34
35 #include <string.h>
36
37 /* Some old versions of bison generate parsers that use bcopy.
38    That loses on systems that don't provide the function, so we have
39    to redefine it here.  (Assume everyone has memcpy -- [mdw]) */
40
41 #define bcopy(from, to, len) memcpy ((to), (from), (len))
42
43 /* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc),
44    as well as gratuitiously global symbol names, so we can have multiple
45    yacc generated parsers in the same program.  Note that these are only
46    the variables produced by yacc.  If other parser generators (bison,
47    byacc, etc) produce additional global names that conflict at link time,
48    then those parser generators need to be fixed instead of adding those
49    names to this list. */
50
51 #define yymaxdepth gd_maxdepth
52 #define yyparse gd_parse
53 #define yylex   gd_lex
54 #define yyerror gd_error
55 #define yylval  gd_lval
56 #define yychar  gd_char
57 #define yydebug gd_debug
58 #define yypact  gd_pact
59 #define yyr1    gd_r1
60 #define yyr2    gd_r2
61 #define yydef   gd_def
62 #define yychk   gd_chk
63 #define yypgo   gd_pgo
64 #define yyact   gd_act
65 #define yyexca  gd_exca
66 #define yyerrflag gd_errflag
67 #define yynerrs gd_nerrs
68 #define yyps    gd_ps
69 #define yypv    gd_pv
70 #define yys     gd_s
71 #define yy_yys  gd_yys
72 #define yystate gd_state
73 #define yytmp   gd_tmp
74 #define yyv     gd_v
75 #define yy_yyv  gd_yyv
76 #define yyval   gd_val
77 #define yylloc  gd_lloc
78 #define yyreds  gd_reds          /* With YYDEBUG defined */
79 #define yytoks  gd_toks          /* With YYDEBUG defined */
80 #define yylhs   gd_yylhs
81 #define yylen   gd_yylen
82 #define yydefred gd_yydefred
83 #define yydgoto gd_yydgoto
84 #define yysindex gd_yysindex
85 #define yyrindex gd_yyrindex
86 #define yygindex gd_yygindex
87 #define yytable  gd_yytable
88 #define yycheck  gd_yycheck
89
90 static int yylex ();
91 static int yyerror ();
92
93 #define EPOCH           1970
94 #define HOUR(x)         ((time_t)(x) * 60)
95 #define SECSPERDAY      (24L * 60L * 60L)
96
97 #define MAX_BUFF_LEN    128   /* size of buffer to read the date into */
98
99 /*
100 **  An entry in the lexical lookup table.
101 */
102 typedef struct _TABLE {
103     const char  *name;
104     int         type;
105     int         value;
106 } TABLE;
107
108
109 /*
110 **  Meridian:  am, pm, or 24-hour style.
111 */
112 typedef enum _MERIDIAN {
113     MERam, MERpm, MER24
114 } MERIDIAN;
115
116
117 /*
118 **  Global variables.  We could get rid of most of these by using a good
119 **  union as the yacc stack.  (This routine was originally written before
120 **  yacc had the %union construct.)  Maybe someday; right now we only use
121 **  the %union very rarely.
122 */
123 static const char       *yyInput;
124 static int      yyDayOrdinal;
125 static int      yyDayNumber;
126 static int      yyHaveDate;
127 static int      yyHaveDay;
128 static int      yyHaveRel;
129 static int      yyHaveTime;
130 static int      yyHaveZone;
131 static int      yyTimezone;
132 static int      yyDay;
133 static int      yyHour;
134 static int      yyMinutes;
135 static int      yyMonth;
136 static int      yySeconds;
137 static int      yyYear;
138 static MERIDIAN yyMeridian;
139 static int      yyRelDay;
140 static int      yyRelHour;
141 static int      yyRelMinutes;
142 static int      yyRelMonth;
143 static int      yyRelSeconds;
144 static int      yyRelYear;
145
146 %}
147
148 %union {
149     int                 Number;
150     enum _MERIDIAN      Meridian;
151 }
152
153 %token  tAGO tDAY tDAY_UNIT tDAYZONE tDST tHOUR_UNIT tID
154 %token  tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
155 %token  tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
156
157 %type   <Number>        tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tMINUTE_UNIT
158 %type   <Number>        tMONTH tMONTH_UNIT
159 %type   <Number>        tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
160 %type   <Meridian>      tMERIDIAN o_merid
161
162 %%
163
164 spec    : /* NULL */
165         | spec item
166         ;
167
168 item    : time {
169             yyHaveTime++;
170         }
171         | zone {
172             yyHaveZone++;
173         }
174         | date {
175             yyHaveDate++;
176         }
177         | day {
178             yyHaveDay++;
179         }
180         | rel {
181             yyHaveRel++;
182         }
183         | number
184         ;
185
186 time    : tUNUMBER tMERIDIAN {
187             yyHour = $1;
188             yyMinutes = 0;
189             yySeconds = 0;
190             yyMeridian = $2;
191         }
192         | tUNUMBER ':' tUNUMBER o_merid {
193             yyHour = $1;
194             yyMinutes = $3;
195             yySeconds = 0;
196             yyMeridian = $4;
197         }
198         | tUNUMBER ':' tUNUMBER tSNUMBER {
199             yyHour = $1;
200             yyMinutes = $3;
201             yyMeridian = MER24;
202             yyHaveZone++;
203             yyTimezone = ($4 < 0
204                           ? -$4 % 100 + (-$4 / 100) * 60
205                           : - ($4 % 100 + ($4 / 100) * 60));
206         }
207         | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
208             yyHour = $1;
209             yyMinutes = $3;
210             yySeconds = $5;
211             yyMeridian = $6;
212         }
213         | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
214             yyHour = $1;
215             yyMinutes = $3;
216             yySeconds = $5;
217             yyMeridian = MER24;
218             yyHaveZone++;
219             yyTimezone = ($6 < 0
220                           ? -$6 % 100 + (-$6 / 100) * 60
221                           : - ($6 % 100 + ($6 / 100) * 60));
222         }
223         ;
224
225 zone    : tZONE {
226             yyTimezone = $1;
227         }
228         | tDAYZONE {
229             yyTimezone = $1 - 60;
230         }
231         |
232           tZONE tDST {
233             yyTimezone = $1 - 60;
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           /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
257              The goal in recognizing YYYY/MM/DD is solely to support legacy
258              machine-generated dates like those in an RCS log listing.  If
259              you want portability, use the ISO 8601 format.  */
260           if ($1 >= 1000)
261             {
262               yyYear = $1;
263               yyMonth = $3;
264               yyDay = $5;
265             }
266           else
267             {
268               yyMonth = $1;
269               yyDay = $3;
270               yyYear = $5;
271             }
272         }
273         | tUNUMBER tSNUMBER tSNUMBER {
274             /* ISO 8601 format.  yyyy-mm-dd.  */
275             yyYear = $1;
276             yyMonth = -$2;
277             yyDay = -$3;
278         }
279         | tUNUMBER tMONTH tSNUMBER {
280             /* e.g. 17-JUN-1992.  */
281             yyDay = $1;
282             yyMonth = $2;
283             yyYear = -$3;
284         }
285         | tMONTH tUNUMBER {
286             yyMonth = $1;
287             yyDay = $2;
288         }
289         | tMONTH tUNUMBER ',' tUNUMBER {
290             yyMonth = $1;
291             yyDay = $2;
292             yyYear = $4;
293         }
294         | tUNUMBER tMONTH {
295             yyMonth = $2;
296             yyDay = $1;
297         }
298         | tUNUMBER tMONTH tUNUMBER {
299             yyMonth = $2;
300             yyDay = $1;
301             yyYear = $3;
302         }
303         ;
304
305 rel     : relunit tAGO {
306             yyRelSeconds = -yyRelSeconds;
307             yyRelMinutes = -yyRelMinutes;
308             yyRelHour = -yyRelHour;
309             yyRelDay = -yyRelDay;
310             yyRelMonth = -yyRelMonth;
311             yyRelYear = -yyRelYear;
312         }
313         | relunit
314         ;
315
316 relunit : tUNUMBER tYEAR_UNIT {
317             yyRelYear += $1 * $2;
318         }
319         | tSNUMBER tYEAR_UNIT {
320             yyRelYear += $1 * $2;
321         }
322         | tYEAR_UNIT {
323             yyRelYear += $1;
324         }
325         | tUNUMBER tMONTH_UNIT {
326             yyRelMonth += $1 * $2;
327         }
328         | tSNUMBER tMONTH_UNIT {
329             yyRelMonth += $1 * $2;
330         }
331         | tMONTH_UNIT {
332             yyRelMonth += $1;
333         }
334         | tUNUMBER tDAY_UNIT {
335             yyRelDay += $1 * $2;
336         }
337         | tSNUMBER tDAY_UNIT {
338             yyRelDay += $1 * $2;
339         }
340         | tDAY_UNIT {
341             yyRelDay += $1;
342         }
343         | tUNUMBER tHOUR_UNIT {
344             yyRelHour += $1 * $2;
345         }
346         | tSNUMBER tHOUR_UNIT {
347             yyRelHour += $1 * $2;
348         }
349         | tHOUR_UNIT {
350             yyRelHour += $1;
351         }
352         | tUNUMBER tMINUTE_UNIT {
353             yyRelMinutes += $1 * $2;
354         }
355         | tSNUMBER tMINUTE_UNIT {
356             yyRelMinutes += $1 * $2;
357         }
358         | tMINUTE_UNIT {
359             yyRelMinutes += $1;
360         }
361         | tUNUMBER tSEC_UNIT {
362             yyRelSeconds += $1 * $2;
363         }
364         | tSNUMBER tSEC_UNIT {
365             yyRelSeconds += $1 * $2;
366         }
367         | tSEC_UNIT {
368             yyRelSeconds += $1;
369         }
370         ;
371
372 number  : tUNUMBER
373           {
374             if (yyHaveTime && yyHaveDate && !yyHaveRel)
375               yyYear = $1;
376             else
377               {
378                 if ($1>10000)
379                   {
380                     yyHaveDate++;
381                     yyDay= ($1)%100;
382                     yyMonth= ($1/100)%100;
383                     yyYear = $1/10000;
384                   }
385                 else
386                   {
387                     yyHaveTime++;
388                     if ($1 < 100)
389                       {
390                         yyHour = $1;
391                         yyMinutes = 0;
392                       }
393                     else
394                       {
395                         yyHour = $1 / 100;
396                         yyMinutes = $1 % 100;
397                       }
398                     yySeconds = 0;
399                     yyMeridian = MER24;
400                   }
401               }
402           }
403         ;
404
405 o_merid : /* NULL */
406           {
407             $$ = MER24;
408           }
409         | tMERIDIAN
410           {
411             $$ = $1;
412           }
413         ;
414
415 %%
416
417 /* Month and day table. */
418 static TABLE const MonthDayTable[] = {
419     { "january",        tMONTH,  1 },
420     { "february",       tMONTH,  2 },
421     { "march",          tMONTH,  3 },
422     { "april",          tMONTH,  4 },
423     { "may",            tMONTH,  5 },
424     { "june",           tMONTH,  6 },
425     { "july",           tMONTH,  7 },
426     { "august",         tMONTH,  8 },
427     { "september",      tMONTH,  9 },
428     { "sept",           tMONTH,  9 },
429     { "october",        tMONTH, 10 },
430     { "november",       tMONTH, 11 },
431     { "december",       tMONTH, 12 },
432     { "sunday",         tDAY, 0 },
433     { "monday",         tDAY, 1 },
434     { "tuesday",        tDAY, 2 },
435     { "tues",           tDAY, 2 },
436     { "wednesday",      tDAY, 3 },
437     { "wednes",         tDAY, 3 },
438     { "thursday",       tDAY, 4 },
439     { "thur",           tDAY, 4 },
440     { "thurs",          tDAY, 4 },
441     { "friday",         tDAY, 5 },
442     { "saturday",       tDAY, 6 },
443     { NULL }
444 };
445
446 /* Time units table. */
447 static TABLE const UnitsTable[] = {
448     { "year",           tYEAR_UNIT,     1 },
449     { "month",          tMONTH_UNIT,    1 },
450     { "fortnight",      tDAY_UNIT,      14 },
451     { "week",           tDAY_UNIT,      7 },
452     { "day",            tDAY_UNIT,      1 },
453     { "hour",           tHOUR_UNIT,     1 },
454     { "minute",         tMINUTE_UNIT,   1 },
455     { "min",            tMINUTE_UNIT,   1 },
456     { "second",         tSEC_UNIT,      1 },
457     { "sec",            tSEC_UNIT,      1 },
458     { NULL }
459 };
460
461 /* Assorted relative-time words. */
462 static TABLE const OtherTable[] = {
463     { "tomorrow",       tMINUTE_UNIT,   1 * 24 * 60 },
464     { "yesterday",      tMINUTE_UNIT,   -1 * 24 * 60 },
465     { "today",          tMINUTE_UNIT,   0 },
466     { "now",            tMINUTE_UNIT,   0 },
467     { "last",           tUNUMBER,       -1 },
468     { "this",           tMINUTE_UNIT,   0 },
469     { "next",           tUNUMBER,       1 /* Was bogusly 2 [mdw] */ },
470     { "first",          tUNUMBER,       1 },
471 /*  { "second",         tUNUMBER,       2 }, */
472     { "third",          tUNUMBER,       3 },
473     { "fourth",         tUNUMBER,       4 },
474     { "fifth",          tUNUMBER,       5 },
475     { "sixth",          tUNUMBER,       6 },
476     { "seventh",        tUNUMBER,       7 },
477     { "eighth",         tUNUMBER,       8 },
478     { "ninth",          tUNUMBER,       9 },
479     { "tenth",          tUNUMBER,       10 },
480     { "eleventh",       tUNUMBER,       11 },
481     { "twelfth",        tUNUMBER,       12 },
482     { "ago",            tAGO,   1 },
483     { NULL }
484 };
485
486 /* The timezone table. */
487 static TABLE const TimezoneTable[] = {
488     { "gmt",    tZONE,     HOUR ( 0) }, /* Greenwich Mean */
489     { "ut",     tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
490     { "utc",    tZONE,     HOUR ( 0) },
491     { "wet",    tZONE,     HOUR ( 0) }, /* Western European */
492     { "bst",    tDAYZONE,  HOUR ( 0) }, /* British Summer */
493     { "wat",    tZONE,     HOUR ( 1) }, /* West Africa */
494     { "at",     tZONE,     HOUR ( 2) }, /* Azores */
495 #if     0
496     /* For completeness.  BST is also British Summer, and GST is
497      * also Guam Standard. */
498     { "bst",    tZONE,     HOUR ( 3) }, /* Brazil Standard */
499     { "gst",    tZONE,     HOUR ( 3) }, /* Greenland Standard */
500 #endif
501 #if 0
502     { "nft",    tZONE,     HOUR (3.5) },        /* Newfoundland */
503     { "nst",    tZONE,     HOUR (3.5) },        /* Newfoundland Standard */
504     { "ndt",    tDAYZONE,  HOUR (3.5) },        /* Newfoundland Daylight */
505 #endif
506     { "ast",    tZONE,     HOUR ( 4) }, /* Atlantic Standard */
507     { "adt",    tDAYZONE,  HOUR ( 4) }, /* Atlantic Daylight */
508     { "est",    tZONE,     HOUR ( 5) }, /* Eastern Standard */
509     { "edt",    tDAYZONE,  HOUR ( 5) }, /* Eastern Daylight */
510     { "cst",    tZONE,     HOUR ( 6) }, /* Central Standard */
511     { "cdt",    tDAYZONE,  HOUR ( 6) }, /* Central Daylight */
512     { "mst",    tZONE,     HOUR ( 7) }, /* Mountain Standard */
513     { "mdt",    tDAYZONE,  HOUR ( 7) }, /* Mountain Daylight */
514     { "pst",    tZONE,     HOUR ( 8) }, /* Pacific Standard */
515     { "pdt",    tDAYZONE,  HOUR ( 8) }, /* Pacific Daylight */
516     { "yst",    tZONE,     HOUR ( 9) }, /* Yukon Standard */
517     { "ydt",    tDAYZONE,  HOUR ( 9) }, /* Yukon Daylight */
518     { "hst",    tZONE,     HOUR (10) }, /* Hawaii Standard */
519     { "hdt",    tDAYZONE,  HOUR (10) }, /* Hawaii Daylight */
520     { "cat",    tZONE,     HOUR (10) }, /* Central Alaska */
521     { "ahst",   tZONE,     HOUR (10) }, /* Alaska-Hawaii Standard */
522     { "nt",     tZONE,     HOUR (11) }, /* Nome */
523     { "idlw",   tZONE,     HOUR (12) }, /* International Date Line West */
524     { "cet",    tZONE,     -HOUR (1) }, /* Central European */
525     { "met",    tZONE,     -HOUR (1) }, /* Middle European */
526     { "mewt",   tZONE,     -HOUR (1) }, /* Middle European Winter */
527     { "mest",   tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
528     { "mesz",   tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
529     { "swt",    tZONE,     -HOUR (1) }, /* Swedish Winter */
530     { "sst",    tDAYZONE,  -HOUR (1) }, /* Swedish Summer */
531     { "fwt",    tZONE,     -HOUR (1) }, /* French Winter */
532     { "fst",    tDAYZONE,  -HOUR (1) }, /* French Summer */
533     { "eet",    tZONE,     -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
534     { "bt",     tZONE,     -HOUR (3) }, /* Baghdad, USSR Zone 2 */
535 #if 0
536     { "it",     tZONE,     -HOUR (3.5) },/* Iran */
537 #endif
538     { "zp4",    tZONE,     -HOUR (4) }, /* USSR Zone 3 */
539     { "zp5",    tZONE,     -HOUR (5) }, /* USSR Zone 4 */
540 #if 0
541     { "ist",    tZONE,     -HOUR (5.5) },/* Indian Standard */
542 #endif
543     { "zp6",    tZONE,     -HOUR (6) }, /* USSR Zone 5 */
544 #if     0
545     /* For completeness.  NST is also Newfoundland Standard, and SST is
546      * also Swedish Summer. */
547     { "nst",    tZONE,     -HOUR (6.5) },/* North Sumatra */
548     { "sst",    tZONE,     -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
549 #endif  /* 0 */
550     { "wast",   tZONE,     -HOUR (7) }, /* West Australian Standard */
551     { "wadt",   tDAYZONE,  -HOUR (7) }, /* West Australian Daylight */
552 #if 0
553     { "jt",     tZONE,     -HOUR (7.5) },/* Java (3pm in Cronusland!) */
554 #endif
555     { "cct",    tZONE,     -HOUR (8) }, /* China Coast, USSR Zone 7 */
556     { "jst",    tZONE,     -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
557 #if 0
558     { "cast",   tZONE,     -HOUR (9.5) },/* Central Australian Standard */
559     { "cadt",   tDAYZONE,  -HOUR (9.5) },/* Central Australian Daylight */
560 #endif
561     { "east",   tZONE,     -HOUR (10) },        /* Eastern Australian Standard */
562     { "eadt",   tDAYZONE,  -HOUR (10) },        /* Eastern Australian Daylight */
563     { "gst",    tZONE,     -HOUR (10) },        /* Guam Standard, USSR Zone 9 */
564     { "nzt",    tZONE,     -HOUR (12) },        /* New Zealand */
565     { "nzst",   tZONE,     -HOUR (12) },        /* New Zealand Standard */
566     { "nzdt",   tDAYZONE,  -HOUR (12) },        /* New Zealand Daylight */
567     { "idle",   tZONE,     -HOUR (12) },        /* International Date Line East */
568     {  NULL  }
569 };
570
571 /* Military timezone table. */
572 static TABLE const MilitaryTable[] = {
573     { "a",      tZONE,  HOUR (  1) },
574     { "b",      tZONE,  HOUR (  2) },
575     { "c",      tZONE,  HOUR (  3) },
576     { "d",      tZONE,  HOUR (  4) },
577     { "e",      tZONE,  HOUR (  5) },
578     { "f",      tZONE,  HOUR (  6) },
579     { "g",      tZONE,  HOUR (  7) },
580     { "h",      tZONE,  HOUR (  8) },
581     { "i",      tZONE,  HOUR (  9) },
582     { "k",      tZONE,  HOUR ( 10) },
583     { "l",      tZONE,  HOUR ( 11) },
584     { "m",      tZONE,  HOUR ( 12) },
585     { "n",      tZONE,  HOUR (- 1) },
586     { "o",      tZONE,  HOUR (- 2) },
587     { "p",      tZONE,  HOUR (- 3) },
588     { "q",      tZONE,  HOUR (- 4) },
589     { "r",      tZONE,  HOUR (- 5) },
590     { "s",      tZONE,  HOUR (- 6) },
591     { "t",      tZONE,  HOUR (- 7) },
592     { "u",      tZONE,  HOUR (- 8) },
593     { "v",      tZONE,  HOUR (- 9) },
594     { "w",      tZONE,  HOUR (-10) },
595     { "x",      tZONE,  HOUR (-11) },
596     { "y",      tZONE,  HOUR (-12) },
597     { "z",      tZONE,  HOUR (  0) },
598     { NULL }
599 };
600
601 \f
602
603
604 /* ARGSUSED */
605 static int
606 yyerror (s)
607      char *s;
608 {
609   return 0;
610 }
611
612 static int
613 ToHour (Hours, Meridian)
614      int Hours;
615      MERIDIAN Meridian;
616 {
617   switch (Meridian)
618     {
619     case MER24:
620       if (Hours < 0 || Hours > 23)
621         return -1;
622       return Hours;
623     case MERam:
624       if (Hours < 1 || Hours > 12)
625         return -1;
626       if (Hours == 12)
627         Hours = 0;
628       return Hours;
629     case MERpm:
630       if (Hours < 1 || Hours > 12)
631         return -1;
632       if (Hours == 12)
633         Hours = 0;
634       return Hours + 12;
635     default:
636       abort ();
637     }
638   /* NOTREACHED */
639 }
640
641 static int
642 ToYear (Year)
643      int Year;
644 {
645   if (Year < 0)
646     Year = -Year;
647
648   /* XPG4 suggests that years 00-68 map to 2000-2068, and
649      years 69-99 map to 1969-1999.  */
650   if (Year < 69)
651     Year += 2000;
652   else if (Year < 100)
653     Year += 1900;
654
655   return Year;
656 }
657
658 static int
659 LookupWord (buff)
660      char *buff;
661 {
662   register char *p;
663   register char *q;
664   register const TABLE *tp;
665   int i;
666   int abbrev;
667
668   /* Make it lowercase. */
669   for (p = buff; *p; p++)
670     if (ISUPPER (*p))
671       *p = tolower (*p);
672
673   if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
674     {
675       yylval.Meridian = MERam;
676       return tMERIDIAN;
677     }
678   if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
679     {
680       yylval.Meridian = MERpm;
681       return tMERIDIAN;
682     }
683
684   /* See if we have an abbreviation for a month. */
685   if (strlen (buff) == 3)
686     abbrev = 1;
687   else if (strlen (buff) == 4 && buff[3] == '.')
688     {
689       abbrev = 1;
690       buff[3] = '\0';
691     }
692   else
693     abbrev = 0;
694
695   for (tp = MonthDayTable; tp->name; tp++)
696     {
697       if (abbrev)
698         {
699           if (strncmp (buff, tp->name, 3) == 0)
700             {
701               yylval.Number = tp->value;
702               return tp->type;
703             }
704         }
705       else if (strcmp (buff, tp->name) == 0)
706         {
707           yylval.Number = tp->value;
708           return tp->type;
709         }
710     }
711
712   for (tp = TimezoneTable; tp->name; tp++)
713     if (strcmp (buff, tp->name) == 0)
714       {
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       {
725         yylval.Number = tp->value;
726         return tp->type;
727       }
728
729   /* Strip off any plural and try the units table again. */
730   i = strlen (buff) - 1;
731   if (buff[i] == 's')
732     {
733       buff[i] = '\0';
734       for (tp = UnitsTable; tp->name; tp++)
735         if (strcmp (buff, tp->name) == 0)
736           {
737             yylval.Number = tp->value;
738             return tp->type;
739           }
740       buff[i] = 's';            /* Put back for "this" in OtherTable. */
741     }
742
743   for (tp = OtherTable; tp->name; tp++)
744     if (strcmp (buff, tp->name) == 0)
745       {
746         yylval.Number = tp->value;
747         return tp->type;
748       }
749
750   /* Military timezones. */
751   if (buff[1] == '\0' && ISALPHA (*buff))
752     {
753       for (tp = MilitaryTable; tp->name; tp++)
754         if (strcmp (buff, tp->name) == 0)
755           {
756             yylval.Number = tp->value;
757             return tp->type;
758           }
759     }
760
761   /* Drop out any periods and try the timezone table again. */
762   for (i = 0, p = q = buff; *q; q++)
763     if (*q != '.')
764       *p++ = *q;
765     else
766       i++;
767   *p = '\0';
768   if (i)
769     for (tp = TimezoneTable; tp->name; tp++)
770       if (strcmp (buff, tp->name) == 0)
771         {
772           yylval.Number = tp->value;
773           return tp->type;
774         }
775
776   return tID;
777 }
778
779 static int
780 yylex ()
781 {
782   register char c;
783   register char *p;
784   char buff[20];
785   int Count;
786   int sign;
787
788   for (;;)
789     {
790       while (ISSPACE (*yyInput))
791         yyInput++;
792
793       if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
794         {
795           if (c == '-' || c == '+')
796             {
797               sign = c == '-' ? -1 : 1;
798               if (!ISDIGIT (*++yyInput))
799                 /* skip the '-' sign */
800                 continue;
801             }
802           else
803             sign = 0;
804           for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
805             yylval.Number = 10 * yylval.Number + c - '0';
806           yyInput--;
807           if (sign < 0)
808             yylval.Number = -yylval.Number;
809           return sign ? tSNUMBER : tUNUMBER;
810         }
811       if (ISALPHA (c))
812         {
813           for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
814             if (p < &buff[sizeof buff - 1])
815               *p++ = c;
816           *p = '\0';
817           yyInput--;
818           return LookupWord (buff);
819         }
820       if (c != '(')
821         return *yyInput++;
822       Count = 0;
823       do
824         {
825           c = *yyInput++;
826           if (c == '\0')
827             return c;
828           if (c == '(')
829             Count++;
830           else if (c == ')')
831             Count--;
832         }
833       while (Count > 0);
834     }
835 }
836
837 #define TM_YEAR_ORIGIN 1900
838
839 /* Yield A - B, measured in seconds.  */
840 static long
841 difftm (a, b)
842      struct tm *a, *b;
843 {
844   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
845   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
846   long days = (
847   /* difference in day of year */
848                 a->tm_yday - b->tm_yday
849   /* + intervening leap days */
850                 + ((ay >> 2) - (by >> 2))
851                 - (ay / 100 - by / 100)
852                 + ((ay / 100 >> 2) - (by / 100 >> 2))
853   /* + difference in years * 365 */
854                 + (long) (ay - by) * 365
855   );
856   return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
857                 + (a->tm_min - b->tm_min))
858           + (a->tm_sec - b->tm_sec));
859 }
860
861 time_t
862 get_date (p, now)
863      const char *p;
864      const time_t *now;
865 {
866   struct tm tm, tm0, *tmp;
867   time_t Start;
868
869   yyInput = p;
870   Start = now ? *now : time ((time_t *) NULL);
871   tmp = localtime (&Start);
872   yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
873   yyMonth = tmp->tm_mon + 1;
874   yyDay = tmp->tm_mday;
875   yyHour = tmp->tm_hour;
876   yyMinutes = tmp->tm_min;
877   yySeconds = tmp->tm_sec;
878   yyMeridian = MER24;
879   yyRelSeconds = 0;
880   yyRelMinutes = 0;
881   yyRelHour = 0;
882   yyRelDay = 0;
883   yyRelMonth = 0;
884   yyRelYear = 0;
885   yyHaveDate = 0;
886   yyHaveDay = 0;
887   yyHaveRel = 0;
888   yyHaveTime = 0;
889   yyHaveZone = 0;
890
891   if (yyparse ()
892       || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
893     return -1;
894
895   tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
896   tm.tm_mon = yyMonth - 1 + yyRelMonth;
897   tm.tm_mday = yyDay + yyRelDay;
898   if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
899     {
900       tm.tm_hour = ToHour (yyHour, yyMeridian);
901       if (tm.tm_hour < 0)
902         return -1;
903       tm.tm_min = yyMinutes;
904       tm.tm_sec = yySeconds;
905     }
906   else
907     {
908       tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
909     }
910   tm.tm_hour += yyRelHour;
911   tm.tm_min += yyRelMinutes;
912   tm.tm_sec += yyRelSeconds;
913   tm.tm_isdst = -1;
914   tm0 = tm;
915
916   Start = mktime (&tm);
917
918   if (Start == (time_t) -1)
919     {
920
921       /* Guard against falsely reporting errors near the time_t boundaries
922          when parsing times in other time zones.  For example, if the min
923          time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
924          of UTC, then the min localtime value is 1970-01-01 08:00:00; if
925          we apply mktime to 1970-01-01 00:00:00 we will get an error, so
926          we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
927          zone by 24 hours to compensate.  This algorithm assumes that
928          there is no DST transition within a day of the time_t boundaries.  */
929
930       if (yyHaveZone)
931         {
932           tm = tm0;
933           if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
934             {
935               tm.tm_mday++;
936               yyTimezone -= 24 * 60;
937             }
938           else
939             {
940               tm.tm_mday--;
941               yyTimezone += 24 * 60;
942             }
943           Start = mktime (&tm);
944         }
945
946       if (Start == (time_t) -1)
947         return Start;
948     }
949
950   if (yyHaveDay && !yyHaveDate)
951     {
952       tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
953                      + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
954       Start = mktime (&tm);
955       if (Start == (time_t) -1)
956         return Start;
957     }
958
959   if (yyHaveZone)
960     {
961       long delta = yyTimezone * 60L + difftm (&tm, gmtime (&Start));
962       if ((Start + delta < Start) != (delta < 0))
963         return -1;              /* time_t overflow */
964       Start += delta;
965     }
966
967   return Start;
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     {
987       d = get_date (buff, (time_t *) NULL);
988       if (d == -1)
989         (void) printf ("Bad format - couldn't convert.\n");
990       else
991         (void) printf ("%s", ctime (&d));
992       (void) printf ("\t> ");
993       (void) fflush (stdout);
994     }
995   exit (0);
996   /* NOTREACHED */
997 }
998 #endif /* defined (TEST) */