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