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