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