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