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