44f2e19c28aaf30d485f87ac3a21a8b0a17157cf
[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 += $1;
351         }
352         | tUNUMBER tMONTH_UNIT {
353             yyRelMonth += $1 * $2;
354         }
355         | tSNUMBER tMONTH_UNIT {
356             yyRelMonth += $1 * $2;
357         }
358         | tMONTH_UNIT {
359             yyRelMonth += $1;
360         }
361         | tUNUMBER tDAY_UNIT {
362             yyRelDay += $1 * $2;
363         }
364         | tSNUMBER tDAY_UNIT {
365             yyRelDay += $1 * $2;
366         }
367         | tDAY_UNIT {
368             yyRelDay += $1;
369         }
370         | tUNUMBER tHOUR_UNIT {
371             yyRelHour += $1 * $2;
372         }
373         | tSNUMBER tHOUR_UNIT {
374             yyRelHour += $1 * $2;
375         }
376         | tHOUR_UNIT {
377             yyRelHour += $1;
378         }
379         | tUNUMBER tMINUTE_UNIT {
380             yyRelMinutes += $1 * $2;
381         }
382         | tSNUMBER tMINUTE_UNIT {
383             yyRelMinutes += $1 * $2;
384         }
385         | tMINUTE_UNIT {
386             yyRelMinutes += $1;
387         }
388         | tUNUMBER tSEC_UNIT {
389             yyRelSeconds += $1 * $2;
390         }
391         | tSNUMBER tSEC_UNIT {
392             yyRelSeconds += $1 * $2;
393         }
394         | tSEC_UNIT {
395             yyRelSeconds += $1;
396         }
397         ;
398
399 number  : tUNUMBER
400           {
401             if (yyHaveTime && yyHaveDate && !yyHaveRel)
402               yyYear = $1;
403             else
404               {
405                 if ($1>10000)
406                   {
407                     yyHaveDate++;
408                     yyDay= ($1)%100;
409                     yyMonth= ($1/100)%100;
410                     yyYear = $1/10000;
411                   }
412                 else
413                   {
414                     yyHaveTime++;
415                     if ($1 < 100)
416                       {
417                         yyHour = $1;
418                         yyMinutes = 0;
419                       }
420                     else
421                       {
422                         yyHour = $1 / 100;
423                         yyMinutes = $1 % 100;
424                       }
425                     yySeconds = 0;
426                     yyMeridian = MER24;
427                   }
428               }
429           }
430         ;
431
432 o_merid : /* NULL */
433           {
434             $$ = MER24;
435           }
436         | tMERIDIAN
437           {
438             $$ = $1;
439           }
440         ;
441
442 %%
443
444 /* Month and day table. */
445 static TABLE const MonthDayTable[] = {
446     { "january",        tMONTH,  1 },
447     { "february",       tMONTH,  2 },
448     { "march",          tMONTH,  3 },
449     { "april",          tMONTH,  4 },
450     { "may",            tMONTH,  5 },
451     { "june",           tMONTH,  6 },
452     { "july",           tMONTH,  7 },
453     { "august",         tMONTH,  8 },
454     { "september",      tMONTH,  9 },
455     { "sept",           tMONTH,  9 },
456     { "october",        tMONTH, 10 },
457     { "november",       tMONTH, 11 },
458     { "december",       tMONTH, 12 },
459     { "sunday",         tDAY, 0 },
460     { "monday",         tDAY, 1 },
461     { "tuesday",        tDAY, 2 },
462     { "tues",           tDAY, 2 },
463     { "wednesday",      tDAY, 3 },
464     { "wednes",         tDAY, 3 },
465     { "thursday",       tDAY, 4 },
466     { "thur",           tDAY, 4 },
467     { "thurs",          tDAY, 4 },
468     { "friday",         tDAY, 5 },
469     { "saturday",       tDAY, 6 },
470     { NULL }
471 };
472
473 /* Time units table. */
474 static TABLE const UnitsTable[] = {
475     { "year",           tYEAR_UNIT,     1 },
476     { "month",          tMONTH_UNIT,    1 },
477     { "fortnight",      tDAY_UNIT,      14 },
478     { "week",           tDAY_UNIT,      7 },
479     { "day",            tDAY_UNIT,      1 },
480     { "hour",           tHOUR_UNIT,     1 },
481     { "minute",         tMINUTE_UNIT,   1 },
482     { "min",            tMINUTE_UNIT,   1 },
483     { "second",         tSEC_UNIT,      1 },
484     { "sec",            tSEC_UNIT,      1 },
485     { NULL }
486 };
487
488 /* Assorted relative-time words. */
489 static TABLE const OtherTable[] = {
490     { "tomorrow",       tMINUTE_UNIT,   1 * 24 * 60 },
491     { "yesterday",      tMINUTE_UNIT,   -1 * 24 * 60 },
492     { "today",          tMINUTE_UNIT,   0 },
493     { "now",            tMINUTE_UNIT,   0 },
494     { "last",           tUNUMBER,       -1 },
495     { "this",           tMINUTE_UNIT,   0 },
496     { "next",           tUNUMBER,       1 },
497     { "first",          tUNUMBER,       1 },
498 /*  { "second",         tUNUMBER,       2 }, */
499     { "third",          tUNUMBER,       3 },
500     { "fourth",         tUNUMBER,       4 },
501     { "fifth",          tUNUMBER,       5 },
502     { "sixth",          tUNUMBER,       6 },
503     { "seventh",        tUNUMBER,       7 },
504     { "eighth",         tUNUMBER,       8 },
505     { "ninth",          tUNUMBER,       9 },
506     { "tenth",          tUNUMBER,       10 },
507     { "eleventh",       tUNUMBER,       11 },
508     { "twelfth",        tUNUMBER,       12 },
509     { "ago",            tAGO,   1 },
510     { NULL }
511 };
512
513 /* The timezone table. */
514 static TABLE const TimezoneTable[] = {
515     { "gmt",    tZONE,     HOUR ( 0) }, /* Greenwich Mean */
516     { "ut",     tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
517     { "utc",    tZONE,     HOUR ( 0) },
518     { "wet",    tZONE,     HOUR ( 0) }, /* Western European */
519     { "bst",    tDAYZONE,  HOUR ( 0) }, /* British Summer */
520     { "wat",    tZONE,     HOUR ( 1) }, /* West Africa */
521     { "at",     tZONE,     HOUR ( 2) }, /* Azores */
522 #if     0
523     /* For completeness.  BST is also British Summer, and GST is
524      * also Guam Standard. */
525     { "bst",    tZONE,     HOUR ( 3) }, /* Brazil Standard */
526     { "gst",    tZONE,     HOUR ( 3) }, /* Greenland Standard */
527 #endif
528 #if 0
529     { "nft",    tZONE,     HOUR (3.5) },        /* Newfoundland */
530     { "nst",    tZONE,     HOUR (3.5) },        /* Newfoundland Standard */
531     { "ndt",    tDAYZONE,  HOUR (3.5) },        /* Newfoundland Daylight */
532 #endif
533     { "ast",    tZONE,     HOUR ( 4) }, /* Atlantic Standard */
534     { "adt",    tDAYZONE,  HOUR ( 4) }, /* Atlantic Daylight */
535     { "est",    tZONE,     HOUR ( 5) }, /* Eastern Standard */
536     { "edt",    tDAYZONE,  HOUR ( 5) }, /* Eastern Daylight */
537     { "cst",    tZONE,     HOUR ( 6) }, /* Central Standard */
538     { "cdt",    tDAYZONE,  HOUR ( 6) }, /* Central Daylight */
539     { "mst",    tZONE,     HOUR ( 7) }, /* Mountain Standard */
540     { "mdt",    tDAYZONE,  HOUR ( 7) }, /* Mountain Daylight */
541     { "pst",    tZONE,     HOUR ( 8) }, /* Pacific Standard */
542     { "pdt",    tDAYZONE,  HOUR ( 8) }, /* Pacific Daylight */
543     { "yst",    tZONE,     HOUR ( 9) }, /* Yukon Standard */
544     { "ydt",    tDAYZONE,  HOUR ( 9) }, /* Yukon Daylight */
545     { "hst",    tZONE,     HOUR (10) }, /* Hawaii Standard */
546     { "hdt",    tDAYZONE,  HOUR (10) }, /* Hawaii Daylight */
547     { "cat",    tZONE,     HOUR (10) }, /* Central Alaska */
548     { "ahst",   tZONE,     HOUR (10) }, /* Alaska-Hawaii Standard */
549     { "nt",     tZONE,     HOUR (11) }, /* Nome */
550     { "idlw",   tZONE,     HOUR (12) }, /* International Date Line West */
551     { "cet",    tZONE,     -HOUR (1) }, /* Central European */
552     { "met",    tZONE,     -HOUR (1) }, /* Middle European */
553     { "mewt",   tZONE,     -HOUR (1) }, /* Middle European Winter */
554     { "mest",   tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
555     { "mesz",   tDAYZONE,  -HOUR (1) }, /* Middle European Summer */
556     { "swt",    tZONE,     -HOUR (1) }, /* Swedish Winter */
557     { "sst",    tDAYZONE,  -HOUR (1) }, /* Swedish Summer */
558     { "fwt",    tZONE,     -HOUR (1) }, /* French Winter */
559     { "fst",    tDAYZONE,  -HOUR (1) }, /* French Summer */
560     { "eet",    tZONE,     -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
561     { "bt",     tZONE,     -HOUR (3) }, /* Baghdad, USSR Zone 2 */
562 #if 0
563     { "it",     tZONE,     -HOUR (3.5) },/* Iran */
564 #endif
565     { "zp4",    tZONE,     -HOUR (4) }, /* USSR Zone 3 */
566     { "zp5",    tZONE,     -HOUR (5) }, /* USSR Zone 4 */
567 #if 0
568     { "ist",    tZONE,     -HOUR (5.5) },/* Indian Standard */
569 #endif
570     { "zp6",    tZONE,     -HOUR (6) }, /* USSR Zone 5 */
571 #if     0
572     /* For completeness.  NST is also Newfoundland Standard, and SST is
573      * also Swedish Summer. */
574     { "nst",    tZONE,     -HOUR (6.5) },/* North Sumatra */
575     { "sst",    tZONE,     -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
576 #endif  /* 0 */
577     { "wast",   tZONE,     -HOUR (7) }, /* West Australian Standard */
578     { "wadt",   tDAYZONE,  -HOUR (7) }, /* West Australian Daylight */
579 #if 0
580     { "jt",     tZONE,     -HOUR (7.5) },/* Java (3pm in Cronusland!) */
581 #endif
582     { "cct",    tZONE,     -HOUR (8) }, /* China Coast, USSR Zone 7 */
583     { "jst",    tZONE,     -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
584 #if 0
585     { "cast",   tZONE,     -HOUR (9.5) },/* Central Australian Standard */
586     { "cadt",   tDAYZONE,  -HOUR (9.5) },/* Central Australian Daylight */
587 #endif
588     { "east",   tZONE,     -HOUR (10) },        /* Eastern Australian Standard */
589     { "eadt",   tDAYZONE,  -HOUR (10) },        /* Eastern Australian Daylight */
590     { "gst",    tZONE,     -HOUR (10) },        /* Guam Standard, USSR Zone 9 */
591     { "nzt",    tZONE,     -HOUR (12) },        /* New Zealand */
592     { "nzst",   tZONE,     -HOUR (12) },        /* New Zealand Standard */
593     { "nzdt",   tDAYZONE,  -HOUR (12) },        /* New Zealand Daylight */
594     { "idle",   tZONE,     -HOUR (12) },        /* International Date Line East */
595     {  NULL  }
596 };
597
598 /* Military timezone table. */
599 static TABLE const MilitaryTable[] = {
600     { "a",      tZONE,  HOUR (  1) },
601     { "b",      tZONE,  HOUR (  2) },
602     { "c",      tZONE,  HOUR (  3) },
603     { "d",      tZONE,  HOUR (  4) },
604     { "e",      tZONE,  HOUR (  5) },
605     { "f",      tZONE,  HOUR (  6) },
606     { "g",      tZONE,  HOUR (  7) },
607     { "h",      tZONE,  HOUR (  8) },
608     { "i",      tZONE,  HOUR (  9) },
609     { "k",      tZONE,  HOUR ( 10) },
610     { "l",      tZONE,  HOUR ( 11) },
611     { "m",      tZONE,  HOUR ( 12) },
612     { "n",      tZONE,  HOUR (- 1) },
613     { "o",      tZONE,  HOUR (- 2) },
614     { "p",      tZONE,  HOUR (- 3) },
615     { "q",      tZONE,  HOUR (- 4) },
616     { "r",      tZONE,  HOUR (- 5) },
617     { "s",      tZONE,  HOUR (- 6) },
618     { "t",      tZONE,  HOUR (- 7) },
619     { "u",      tZONE,  HOUR (- 8) },
620     { "v",      tZONE,  HOUR (- 9) },
621     { "w",      tZONE,  HOUR (-10) },
622     { "x",      tZONE,  HOUR (-11) },
623     { "y",      tZONE,  HOUR (-12) },
624     { "z",      tZONE,  HOUR (  0) },
625     { NULL }
626 };
627
628 \f
629
630
631 /* ARGSUSED */
632 static int
633 yyerror (s)
634      char *s;
635 {
636   return 0;
637 }
638
639 static int
640 ToHour (Hours, Meridian)
641      int Hours;
642      MERIDIAN Meridian;
643 {
644   switch (Meridian)
645     {
646     case MER24:
647       if (Hours < 0 || Hours > 23)
648         return -1;
649       return Hours;
650     case MERam:
651       if (Hours < 1 || Hours > 12)
652         return -1;
653       if (Hours == 12)
654         Hours = 0;
655       return Hours;
656     case MERpm:
657       if (Hours < 1 || Hours > 12)
658         return -1;
659       if (Hours == 12)
660         Hours = 0;
661       return Hours + 12;
662     default:
663       abort ();
664     }
665   /* NOTREACHED */
666 }
667
668 static int
669 ToYear (Year)
670      int Year;
671 {
672   if (Year < 0)
673     Year = -Year;
674
675   /* XPG4 suggests that years 00-68 map to 2000-2068, and
676      years 69-99 map to 1969-1999.  */
677   if (Year < 69)
678     Year += 2000;
679   else if (Year < 100)
680     Year += 1900;
681
682   return Year;
683 }
684
685 static int
686 LookupWord (buff)
687      char *buff;
688 {
689   register char *p;
690   register char *q;
691   register const TABLE *tp;
692   int i;
693   int abbrev;
694
695   /* Make it lowercase. */
696   for (p = buff; *p; p++)
697     if (ISUPPER (*p))
698       *p = tolower (*p);
699
700   if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
701     {
702       yylval.Meridian = MERam;
703       return tMERIDIAN;
704     }
705   if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
706     {
707       yylval.Meridian = MERpm;
708       return tMERIDIAN;
709     }
710
711   /* See if we have an abbreviation for a month. */
712   if (strlen (buff) == 3)
713     abbrev = 1;
714   else if (strlen (buff) == 4 && buff[3] == '.')
715     {
716       abbrev = 1;
717       buff[3] = '\0';
718     }
719   else
720     abbrev = 0;
721
722   for (tp = MonthDayTable; tp->name; tp++)
723     {
724       if (abbrev)
725         {
726           if (strncmp (buff, tp->name, 3) == 0)
727             {
728               yylval.Number = tp->value;
729               return tp->type;
730             }
731         }
732       else if (strcmp (buff, tp->name) == 0)
733         {
734           yylval.Number = tp->value;
735           return tp->type;
736         }
737     }
738
739   for (tp = TimezoneTable; tp->name; tp++)
740     if (strcmp (buff, tp->name) == 0)
741       {
742         yylval.Number = tp->value;
743         return tp->type;
744       }
745
746   if (strcmp (buff, "dst") == 0)
747     return tDST;
748
749   for (tp = UnitsTable; tp->name; tp++)
750     if (strcmp (buff, tp->name) == 0)
751       {
752         yylval.Number = tp->value;
753         return tp->type;
754       }
755
756   /* Strip off any plural and try the units table again. */
757   i = strlen (buff) - 1;
758   if (buff[i] == 's')
759     {
760       buff[i] = '\0';
761       for (tp = UnitsTable; tp->name; tp++)
762         if (strcmp (buff, tp->name) == 0)
763           {
764             yylval.Number = tp->value;
765             return tp->type;
766           }
767       buff[i] = 's';            /* Put back for "this" in OtherTable. */
768     }
769
770   for (tp = OtherTable; tp->name; tp++)
771     if (strcmp (buff, tp->name) == 0)
772       {
773         yylval.Number = tp->value;
774         return tp->type;
775       }
776
777   /* Military timezones. */
778   if (buff[1] == '\0' && ISALPHA (*buff))
779     {
780       for (tp = MilitaryTable; tp->name; tp++)
781         if (strcmp (buff, tp->name) == 0)
782           {
783             yylval.Number = tp->value;
784             return tp->type;
785           }
786     }
787
788   /* Drop out any periods and try the timezone table again. */
789   for (i = 0, p = q = buff; *q; q++)
790     if (*q != '.')
791       *p++ = *q;
792     else
793       i++;
794   *p = '\0';
795   if (i)
796     for (tp = TimezoneTable; tp->name; tp++)
797       if (strcmp (buff, tp->name) == 0)
798         {
799           yylval.Number = tp->value;
800           return tp->type;
801         }
802
803   return tID;
804 }
805
806 static int
807 yylex ()
808 {
809   register char c;
810   register char *p;
811   char buff[20];
812   int Count;
813   int sign;
814
815   for (;;)
816     {
817       while (ISSPACE (*yyInput))
818         yyInput++;
819
820       if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
821         {
822           if (c == '-' || c == '+')
823             {
824               sign = c == '-' ? -1 : 1;
825               if (!ISDIGIT (*++yyInput))
826                 /* skip the '-' sign */
827                 continue;
828             }
829           else
830             sign = 0;
831           for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
832             yylval.Number = 10 * yylval.Number + c - '0';
833           yyInput--;
834           if (sign < 0)
835             yylval.Number = -yylval.Number;
836           return sign ? tSNUMBER : tUNUMBER;
837         }
838       if (ISALPHA (c))
839         {
840           for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
841             if (p < &buff[sizeof buff - 1])
842               *p++ = c;
843           *p = '\0';
844           yyInput--;
845           return LookupWord (buff);
846         }
847       if (c != '(')
848         return *yyInput++;
849       Count = 0;
850       do
851         {
852           c = *yyInput++;
853           if (c == '\0')
854             return c;
855           if (c == '(')
856             Count++;
857           else if (c == ')')
858             Count--;
859         }
860       while (Count > 0);
861     }
862 }
863
864 #define TM_YEAR_ORIGIN 1900
865
866 /* Yield A - B, measured in seconds.  */
867 static long
868 difftm (a, b)
869      struct tm *a, *b;
870 {
871   int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
872   int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
873   long days = (
874   /* difference in day of year */
875                 a->tm_yday - b->tm_yday
876   /* + intervening leap days */
877                 + ((ay >> 2) - (by >> 2))
878                 - (ay / 100 - by / 100)
879                 + ((ay / 100 >> 2) - (by / 100 >> 2))
880   /* + difference in years * 365 */
881                 + (long) (ay - by) * 365
882   );
883   return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
884                 + (a->tm_min - b->tm_min))
885           + (a->tm_sec - b->tm_sec));
886 }
887
888 time_t
889 get_date (p, now)
890      const char *p;
891      const time_t *now;
892 {
893   struct tm tm, tm0, *tmp;
894   time_t Start;
895
896   yyInput = p;
897   Start = now ? *now : time ((time_t *) NULL);
898   tmp = localtime (&Start);
899   yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
900   yyMonth = tmp->tm_mon + 1;
901   yyDay = tmp->tm_mday;
902   yyHour = tmp->tm_hour;
903   yyMinutes = tmp->tm_min;
904   yySeconds = tmp->tm_sec;
905   yyMeridian = MER24;
906   yyRelSeconds = 0;
907   yyRelMinutes = 0;
908   yyRelHour = 0;
909   yyRelDay = 0;
910   yyRelMonth = 0;
911   yyRelYear = 0;
912   yyHaveDate = 0;
913   yyHaveDay = 0;
914   yyHaveRel = 0;
915   yyHaveTime = 0;
916   yyHaveZone = 0;
917
918   if (yyparse ()
919       || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
920     return -1;
921
922   tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
923   tm.tm_mon = yyMonth - 1 + yyRelMonth;
924   tm.tm_mday = yyDay + yyRelDay;
925   if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
926     {
927       tm.tm_hour = ToHour (yyHour, yyMeridian);
928       if (tm.tm_hour < 0)
929         return -1;
930       tm.tm_min = yyMinutes;
931       tm.tm_sec = yySeconds;
932     }
933   else
934     {
935       tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
936     }
937   tm.tm_hour += yyRelHour;
938   tm.tm_min += yyRelMinutes;
939   tm.tm_sec += yyRelSeconds;
940   tm.tm_isdst = -1;
941   tm0 = tm;
942
943   Start = mktime (&tm);
944
945   if (Start == (time_t) -1)
946     {
947
948       /* Guard against falsely reporting errors near the time_t boundaries
949          when parsing times in other time zones.  For example, if the min
950          time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
951          of UTC, then the min localtime value is 1970-01-01 08:00:00; if
952          we apply mktime to 1970-01-01 00:00:00 we will get an error, so
953          we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
954          zone by 24 hours to compensate.  This algorithm assumes that
955          there is no DST transition within a day of the time_t boundaries.  */
956       if (yyHaveZone)
957         {
958           tm = tm0;
959           if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
960             {
961               tm.tm_mday++;
962               yyTimezone -= 24 * 60;
963             }
964           else
965             {
966               tm.tm_mday--;
967               yyTimezone += 24 * 60;
968             }
969           Start = mktime (&tm);
970         }
971
972       if (Start == (time_t) -1)
973         return Start;
974     }
975
976   if (yyHaveDay && !yyHaveDate)
977     {
978       tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
979                      + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
980       Start = mktime (&tm);
981       if (Start == (time_t) -1)
982         return Start;
983     }
984
985   if (yyHaveZone)
986     {
987       long delta = yyTimezone * 60L + difftm (&tm, gmtime (&Start));
988       if ((Start + delta < Start) != (delta < 0))
989         return -1;              /* time_t overflow */
990       Start += delta;
991     }
992
993   return Start;
994 }
995
996 #if     defined (TEST)
997
998 /* ARGSUSED */
999 int
1000 main (ac, av)
1001      int ac;
1002      char *av[];
1003 {
1004   char buff[MAX_BUFF_LEN + 1];
1005   time_t d;
1006
1007   (void) printf ("Enter date, or blank line to exit.\n\t> ");
1008   (void) fflush (stdout);
1009
1010   buff[MAX_BUFF_LEN] = 0;
1011   while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
1012     {
1013       d = get_date (buff, (time_t *) NULL);
1014       if (d == -1)
1015         (void) printf ("Bad format - couldn't convert.\n");
1016       else
1017         (void) printf ("%s", ctime (&d));
1018       (void) printf ("\t> ");
1019       (void) fflush (stdout);
1020     }
1021   exit (0);
1022   /* NOTREACHED */
1023 }
1024 #endif /* defined (TEST) */