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