(date): Also accept dates like May-23-2003; suggestion
[gnulib.git] / lib / getdate.y
1 %{
2 /* Parse a string into an internal time stamp.
3    Copyright (C) 1999, 2000, 2002, 2003 Free Software Foundation, Inc.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
20    at the University of North Carolina at Chapel Hill.  Later tweaked by
21    a couple of people on Usenet.  Completely overhauled by Rich $alz
22    <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
23
24    Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
25    the right thing about local DST.  Unlike previous versions, this
26    version is reentrant.  */
27
28 #ifdef HAVE_CONFIG_H
29 # include <config.h>
30 #endif
31
32 #include <alloca.h>
33
34 /* Since the code of getdate.y is not included in the Emacs executable
35    itself, there is no need to #define static in this file.  Even if
36    the code were included in the Emacs executable, it probably
37    wouldn't do any harm to #undef it here; this will only cause
38    problems if we try to write to a static variable, which I don't
39    think this code needs to do.  */
40 #ifdef emacs
41 # undef static
42 #endif
43
44 #include <ctype.h>
45
46 #if HAVE_STDLIB_H
47 # include <stdlib.h> /* for `free'; used by Bison 1.27 */
48 #endif
49
50 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
51 # define IN_CTYPE_DOMAIN(c) 1
52 #else
53 # define IN_CTYPE_DOMAIN(c) isascii (c)
54 #endif
55
56 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
57 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
58 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
59 #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
60
61 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
62    - Its arg may be any int or unsigned int; it need not be an unsigned char.
63    - It's guaranteed to evaluate its argument exactly once.
64    - It's typically faster.
65    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
66    ISDIGIT_LOCALE unless it's important to use the locale's definition
67    of `digit' even when the host does not conform to POSIX.  */
68 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
69
70 #if STDC_HEADERS || HAVE_STRING_H
71 # include <string.h>
72 #endif
73
74 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
75 # define __attribute__(x)
76 #endif
77
78 #ifndef ATTRIBUTE_UNUSED
79 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
80 #endif
81
82 #define EPOCH_YEAR 1970
83 #define TM_YEAR_BASE 1900
84
85 #define HOUR(x) ((x) * 60)
86
87 /* An integer value, and the number of digits in its textual
88    representation.  */
89 typedef struct
90 {
91   int value;
92   int digits;
93 } textint;
94
95 /* An entry in the lexical lookup table.  */
96 typedef struct
97 {
98   char const *name;
99   int type;
100   int value;
101 } table;
102
103 /* Meridian: am, pm, or 24-hour style.  */
104 enum { MERam, MERpm, MER24 };
105
106 /* Information passed to and from the parser.  */
107 typedef struct
108 {
109   /* The input string remaining to be parsed. */
110   const char *input;
111
112   /* N, if this is the Nth Tuesday.  */
113   int day_ordinal;
114
115   /* Day of week; Sunday is 0.  */
116   int day_number;
117
118   /* tm_isdst flag for the local zone.  */
119   int local_isdst;
120
121   /* Time zone, in minutes east of UTC.  */
122   int time_zone;
123
124   /* Style used for time.  */
125   int meridian;
126
127   /* Gregorian year, month, day, hour, minutes, and seconds.  */
128   textint year;
129   int month;
130   int day;
131   int hour;
132   int minutes;
133   int seconds;
134
135   /* Relative year, month, day, hour, minutes, and seconds.  */
136   int rel_year;
137   int rel_month;
138   int rel_day;
139   int rel_hour;
140   int rel_minutes;
141   int rel_seconds;
142
143   /* Counts of nonterminals of various flavors parsed so far.  */
144   int dates_seen;
145   int days_seen;
146   int local_zones_seen;
147   int rels_seen;
148   int times_seen;
149   int zones_seen;
150
151   /* Table of local time zone abbrevations, terminated by a null entry.  */
152   table local_time_zone_table[3];
153 } parser_control;
154
155 #define PC (* (parser_control *) parm)
156 #define YYLEX_PARAM parm
157 #define YYPARSE_PARAM parm
158
159 static int yyerror ();
160 static int yylex ();
161
162 %}
163
164 /* We want a reentrant parser.  */
165 %pure_parser
166
167 /* This grammar has 13 shift/reduce conflicts. */
168 %expect 13
169
170 %union
171 {
172   int intval;
173   textint textintval;
174 }
175
176 %token tAGO tDST
177
178 %token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
179 %token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
180
181 %token <textintval> tSNUMBER tUNUMBER
182
183 %type <intval> o_merid
184
185 %%
186
187 spec:
188     /* empty */
189   | spec item
190   ;
191
192 item:
193     time
194       { PC.times_seen++; }
195   | local_zone
196       { PC.local_zones_seen++; }
197   | zone
198       { PC.zones_seen++; }
199   | date
200       { PC.dates_seen++; }
201   | day
202       { PC.days_seen++; }
203   | rel
204       { PC.rels_seen++; }
205   | number
206   ;
207
208 time:
209     tUNUMBER tMERIDIAN
210       {
211         PC.hour = $1.value;
212         PC.minutes = 0;
213         PC.seconds = 0;
214         PC.meridian = $2;
215       }
216   | tUNUMBER ':' tUNUMBER o_merid
217       {
218         PC.hour = $1.value;
219         PC.minutes = $3.value;
220         PC.seconds = 0;
221         PC.meridian = $4;
222       }
223   | tUNUMBER ':' tUNUMBER tSNUMBER
224       {
225         PC.hour = $1.value;
226         PC.minutes = $3.value;
227         PC.meridian = MER24;
228         PC.zones_seen++;
229         PC.time_zone = $4.value % 100 + ($4.value / 100) * 60;
230       }
231   | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
232       {
233         PC.hour = $1.value;
234         PC.minutes = $3.value;
235         PC.seconds = $5.value;
236         PC.meridian = $6;
237       }
238   | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER
239       {
240         PC.hour = $1.value;
241         PC.minutes = $3.value;
242         PC.seconds = $5.value;
243         PC.meridian = MER24;
244         PC.zones_seen++;
245         PC.time_zone = $6.value % 100 + ($6.value / 100) * 60;
246       }
247   ;
248
249 local_zone:
250     tLOCAL_ZONE
251       { PC.local_isdst = $1; }
252   | tLOCAL_ZONE tDST
253       { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
254   ;
255
256 zone:
257     tZONE
258       { PC.time_zone = $1; }
259   | tDAYZONE
260       { PC.time_zone = $1 + 60; }
261   | tZONE tDST
262       { PC.time_zone = $1 + 60; }
263   ;
264
265 day:
266     tDAY
267       {
268         PC.day_ordinal = 1;
269         PC.day_number = $1;
270       }
271   | tDAY ','
272       {
273         PC.day_ordinal = 1;
274         PC.day_number = $1;
275       }
276   | tUNUMBER tDAY
277       {
278         PC.day_ordinal = $1.value;
279         PC.day_number = $2;
280       }
281   ;
282
283 date:
284     tUNUMBER '/' tUNUMBER
285       {
286         PC.month = $1.value;
287         PC.day = $3.value;
288       }
289   | tUNUMBER '/' tUNUMBER '/' tUNUMBER
290       {
291         /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
292            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 (4 <= $1.digits)
297           {
298             PC.year = $1;
299             PC.month = $3.value;
300             PC.day = $5.value;
301           }
302         else
303           {
304             PC.month = $1.value;
305             PC.day = $3.value;
306             PC.year = $5;
307           }
308       }
309   | tUNUMBER tSNUMBER tSNUMBER
310       {
311         /* ISO 8601 format.  YYYY-MM-DD.  */
312         PC.year = $1;
313         PC.month = -$2.value;
314         PC.day = -$3.value;
315       }
316   | tUNUMBER tMONTH tSNUMBER
317       {
318         /* e.g. 17-JUN-1992.  */
319         PC.day = $1.value;
320         PC.month = $2;
321         PC.year.value = -$3.value;
322         PC.year.digits = $3.digits;
323       }
324   | tMONTH tSNUMBER tSNUMBER
325       {
326         /* e.g. JUN-17-1992.  */
327         PC.month = $1;
328         PC.day = -$2.value;
329         PC.year.value = -$3.value;
330         PC.year.digits = $3.digits;
331       }
332   | tMONTH tUNUMBER
333       {
334         PC.month = $1;
335         PC.day = $2.value;
336       }
337   | tMONTH tUNUMBER ',' tUNUMBER
338       {
339         PC.month = $1;
340         PC.day = $2.value;
341         PC.year = $4;
342       }
343   | tUNUMBER tMONTH
344       {
345         PC.day = $1.value;
346         PC.month = $2;
347       }
348   | tUNUMBER tMONTH tUNUMBER
349       {
350         PC.day = $1.value;
351         PC.month = $2;
352         PC.year = $3;
353       }
354   ;
355
356 rel:
357     relunit tAGO
358       {
359         PC.rel_seconds = -PC.rel_seconds;
360         PC.rel_minutes = -PC.rel_minutes;
361         PC.rel_hour = -PC.rel_hour;
362         PC.rel_day = -PC.rel_day;
363         PC.rel_month = -PC.rel_month;
364         PC.rel_year = -PC.rel_year;
365       }
366   | relunit
367   ;
368
369 relunit:
370     tUNUMBER tYEAR_UNIT
371       { PC.rel_year += $1.value * $2; }
372   | tSNUMBER tYEAR_UNIT
373       { PC.rel_year += $1.value * $2; }
374   | tYEAR_UNIT
375       { PC.rel_year += $1; }
376   | tUNUMBER tMONTH_UNIT
377       { PC.rel_month += $1.value * $2; }
378   | tSNUMBER tMONTH_UNIT
379       { PC.rel_month += $1.value * $2; }
380   | tMONTH_UNIT
381       { PC.rel_month += $1; }
382   | tUNUMBER tDAY_UNIT
383       { PC.rel_day += $1.value * $2; }
384   | tSNUMBER tDAY_UNIT
385       { PC.rel_day += $1.value * $2; }
386   | tDAY_UNIT
387       { PC.rel_day += $1; }
388   | tUNUMBER tHOUR_UNIT
389       { PC.rel_hour += $1.value * $2; }
390   | tSNUMBER tHOUR_UNIT
391       { PC.rel_hour += $1.value * $2; }
392   | tHOUR_UNIT
393       { PC.rel_hour += $1; }
394   | tUNUMBER tMINUTE_UNIT
395       { PC.rel_minutes += $1.value * $2; }
396   | tSNUMBER tMINUTE_UNIT
397       { PC.rel_minutes += $1.value * $2; }
398   | tMINUTE_UNIT
399       { PC.rel_minutes += $1; }
400   | tUNUMBER tSEC_UNIT
401       { PC.rel_seconds += $1.value * $2; }
402   | tSNUMBER tSEC_UNIT
403       { PC.rel_seconds += $1.value * $2; }
404   | tSEC_UNIT
405       { PC.rel_seconds += $1; }
406   ;
407
408 number:
409     tUNUMBER
410       {
411         if (PC.dates_seen
412             && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
413           PC.year = $1;
414         else
415           {
416             if (4 < $1.digits)
417               {
418                 PC.dates_seen++;
419                 PC.day = $1.value % 100;
420                 PC.month = ($1.value / 100) % 100;
421                 PC.year.value = $1.value / 10000;
422                 PC.year.digits = $1.digits - 4;
423               }
424             else
425               {
426                 PC.times_seen++;
427                 if ($1.digits <= 2)
428                   {
429                     PC.hour = $1.value;
430                     PC.minutes = 0;
431                   }
432                 else
433                   {
434                     PC.hour = $1.value / 100;
435                     PC.minutes = $1.value % 100;
436                   }
437                 PC.seconds = 0;
438                 PC.meridian = MER24;
439               }
440           }
441       }
442   ;
443
444 o_merid:
445     /* empty */
446       { $$ = MER24; }
447   | tMERIDIAN
448       { $$ = $1; }
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.  */
456 #include "getdate.h"
457 #include "unlocked-io.h"
458
459 #ifndef gmtime
460 struct tm *gmtime ();
461 #endif
462 #ifndef localtime
463 struct tm *localtime ();
464 #endif
465 #ifndef mktime
466 time_t mktime ();
467 #endif
468
469 static table const meridian_table[] =
470 {
471   { "AM",   tMERIDIAN, MERam },
472   { "A.M.", tMERIDIAN, MERam },
473   { "PM",   tMERIDIAN, MERpm },
474   { "P.M.", tMERIDIAN, MERpm },
475   { 0, 0, 0 }
476 };
477
478 static table const dst_table[] =
479 {
480   { "DST", tDST, 0 }
481 };
482
483 static table const month_and_day_table[] =
484 {
485   { "JANUARY",  tMONTH,  1 },
486   { "FEBRUARY", tMONTH,  2 },
487   { "MARCH",    tMONTH,  3 },
488   { "APRIL",    tMONTH,  4 },
489   { "MAY",      tMONTH,  5 },
490   { "JUNE",     tMONTH,  6 },
491   { "JULY",     tMONTH,  7 },
492   { "AUGUST",   tMONTH,  8 },
493   { "SEPTEMBER",tMONTH,  9 },
494   { "SEPT",     tMONTH,  9 },
495   { "OCTOBER",  tMONTH, 10 },
496   { "NOVEMBER", tMONTH, 11 },
497   { "DECEMBER", tMONTH, 12 },
498   { "SUNDAY",   tDAY,    0 },
499   { "MONDAY",   tDAY,    1 },
500   { "TUESDAY",  tDAY,    2 },
501   { "TUES",     tDAY,    2 },
502   { "WEDNESDAY",tDAY,    3 },
503   { "WEDNES",   tDAY,    3 },
504   { "THURSDAY", tDAY,    4 },
505   { "THUR",     tDAY,    4 },
506   { "THURS",    tDAY,    4 },
507   { "FRIDAY",   tDAY,    5 },
508   { "SATURDAY", tDAY,    6 },
509   { 0, 0, 0 }
510 };
511
512 static table const time_units_table[] =
513 {
514   { "YEAR",     tYEAR_UNIT,      1 },
515   { "MONTH",    tMONTH_UNIT,     1 },
516   { "FORTNIGHT",tDAY_UNIT,      14 },
517   { "WEEK",     tDAY_UNIT,       7 },
518   { "DAY",      tDAY_UNIT,       1 },
519   { "HOUR",     tHOUR_UNIT,      1 },
520   { "MINUTE",   tMINUTE_UNIT,    1 },
521   { "MIN",      tMINUTE_UNIT,    1 },
522   { "SECOND",   tSEC_UNIT,       1 },
523   { "SEC",      tSEC_UNIT,       1 },
524   { 0, 0, 0 }
525 };
526
527 /* Assorted relative-time words. */
528 static table const relative_time_table[] =
529 {
530   { "TOMORROW", tMINUTE_UNIT,   24 * 60 },
531   { "YESTERDAY",tMINUTE_UNIT,   - (24 * 60) },
532   { "TODAY",    tMINUTE_UNIT,    0 },
533   { "NOW",      tMINUTE_UNIT,    0 },
534   { "LAST",     tUNUMBER,       -1 },
535   { "THIS",     tUNUMBER,        0 },
536   { "NEXT",     tUNUMBER,        1 },
537   { "FIRST",    tUNUMBER,        1 },
538 /*{ "SECOND",   tUNUMBER,        2 }, */
539   { "THIRD",    tUNUMBER,        3 },
540   { "FOURTH",   tUNUMBER,        4 },
541   { "FIFTH",    tUNUMBER,        5 },
542   { "SIXTH",    tUNUMBER,        6 },
543   { "SEVENTH",  tUNUMBER,        7 },
544   { "EIGHTH",   tUNUMBER,        8 },
545   { "NINTH",    tUNUMBER,        9 },
546   { "TENTH",    tUNUMBER,       10 },
547   { "ELEVENTH", tUNUMBER,       11 },
548   { "TWELFTH",  tUNUMBER,       12 },
549   { "AGO",      tAGO,            1 },
550   { 0, 0, 0 }
551 };
552
553 /* The time zone table.  This table is necessarily incomplete, as time
554    zone abbreviations are ambiguous; e.g. Australians interpret "EST"
555    as Eastern time in Australia, not as US Eastern Standard Time.
556    You cannot rely on getdate to handle arbitrary time zone
557    abbreviations; use numeric abbreviations like `-0500' instead.  */
558 static table const time_zone_table[] =
559 {
560   { "GMT",      tZONE,     HOUR ( 0) }, /* Greenwich Mean */
561   { "UT",       tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
562   { "UTC",      tZONE,     HOUR ( 0) },
563   { "WET",      tZONE,     HOUR ( 0) }, /* Western European */
564   { "WEST",     tDAYZONE,  HOUR ( 0) }, /* Western European Summer */
565   { "BST",      tDAYZONE,  HOUR ( 0) }, /* British Summer */
566   { "ART",      tZONE,    -HOUR ( 3) }, /* Argentina */
567   { "BRT",      tZONE,    -HOUR ( 3) }, /* Brazil */
568   { "BRST",     tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
569   { "NST",      tZONE,   -(HOUR ( 3) + 30) },   /* Newfoundland Standard */
570   { "NDT",      tDAYZONE,-(HOUR ( 3) + 30) },   /* Newfoundland Daylight */
571   { "AST",      tZONE,    -HOUR ( 4) }, /* Atlantic Standard */
572   { "ADT",      tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
573   { "CLT",      tZONE,    -HOUR ( 4) }, /* Chile */
574   { "CLST",     tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
575   { "EST",      tZONE,    -HOUR ( 5) }, /* Eastern Standard */
576   { "EDT",      tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
577   { "CST",      tZONE,    -HOUR ( 6) }, /* Central Standard */
578   { "CDT",      tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
579   { "MST",      tZONE,    -HOUR ( 7) }, /* Mountain Standard */
580   { "MDT",      tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
581   { "PST",      tZONE,    -HOUR ( 8) }, /* Pacific Standard */
582   { "PDT",      tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
583   { "AKST",     tZONE,    -HOUR ( 9) }, /* Alaska Standard */
584   { "AKDT",     tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
585   { "HST",      tZONE,    -HOUR (10) }, /* Hawaii Standard */
586   { "HAST",     tZONE,    -HOUR (10) }, /* Hawaii-Aleutian Standard */
587   { "HADT",     tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
588   { "SST",      tZONE,    -HOUR (12) }, /* Samoa Standard */
589   { "WAT",      tZONE,     HOUR ( 1) }, /* West Africa */
590   { "CET",      tZONE,     HOUR ( 1) }, /* Central European */
591   { "CEST",     tDAYZONE,  HOUR ( 1) }, /* Central European Summer */
592   { "MET",      tZONE,     HOUR ( 1) }, /* Middle European */
593   { "MEZ",      tZONE,     HOUR ( 1) }, /* Middle European */
594   { "MEST",     tDAYZONE,  HOUR ( 1) }, /* Middle European Summer */
595   { "MESZ",     tDAYZONE,  HOUR ( 1) }, /* Middle European Summer */
596   { "EET",      tZONE,     HOUR ( 2) }, /* Eastern European */
597   { "EEST",     tDAYZONE,  HOUR ( 2) }, /* Eastern European Summer */
598   { "CAT",      tZONE,     HOUR ( 2) }, /* Central Africa */
599   { "SAST",     tZONE,     HOUR ( 2) }, /* South Africa Standard */
600   { "EAT",      tZONE,     HOUR ( 3) }, /* East Africa */
601   { "MSK",      tZONE,     HOUR ( 3) }, /* Moscow */
602   { "MSD",      tDAYZONE,  HOUR ( 3) }, /* Moscow Daylight */
603   { "IST",      tZONE,    (HOUR ( 5) + 30) },   /* India Standard */
604   { "SGT",      tZONE,     HOUR ( 8) }, /* Singapore */
605   { "KST",      tZONE,     HOUR ( 9) }, /* Korea Standard */
606   { "JST",      tZONE,     HOUR ( 9) }, /* Japan Standard */
607   { "GST",      tZONE,     HOUR (10) }, /* Guam Standard */
608   { "NZST",     tZONE,     HOUR (12) }, /* New Zealand Standard */
609   { "NZDT",     tDAYZONE,  HOUR (12) }, /* New Zealand Daylight */
610   { 0, 0, 0  }
611 };
612
613 /* Military time zone table. */
614 static table const military_table[] =
615 {
616   { "A", tZONE, -HOUR ( 1) },
617   { "B", tZONE, -HOUR ( 2) },
618   { "C", tZONE, -HOUR ( 3) },
619   { "D", tZONE, -HOUR ( 4) },
620   { "E", tZONE, -HOUR ( 5) },
621   { "F", tZONE, -HOUR ( 6) },
622   { "G", tZONE, -HOUR ( 7) },
623   { "H", tZONE, -HOUR ( 8) },
624   { "I", tZONE, -HOUR ( 9) },
625   { "K", tZONE, -HOUR (10) },
626   { "L", tZONE, -HOUR (11) },
627   { "M", tZONE, -HOUR (12) },
628   { "N", tZONE,  HOUR ( 1) },
629   { "O", tZONE,  HOUR ( 2) },
630   { "P", tZONE,  HOUR ( 3) },
631   { "Q", tZONE,  HOUR ( 4) },
632   { "R", tZONE,  HOUR ( 5) },
633   { "S", tZONE,  HOUR ( 6) },
634   { "T", tZONE,  HOUR ( 7) },
635   { "U", tZONE,  HOUR ( 8) },
636   { "V", tZONE,  HOUR ( 9) },
637   { "W", tZONE,  HOUR (10) },
638   { "X", tZONE,  HOUR (11) },
639   { "Y", tZONE,  HOUR (12) },
640   { "Z", tZONE,  HOUR ( 0) },
641   { 0, 0, 0 }
642 };
643
644 \f
645
646 static int
647 to_hour (int hours, int meridian)
648 {
649   switch (meridian)
650     {
651     case MER24:
652       return 0 <= hours && hours < 24 ? hours : -1;
653     case MERam:
654       return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
655     case MERpm:
656       return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
657     default:
658       abort ();
659     }
660   /* NOTREACHED */
661 }
662
663 static int
664 to_year (textint textyear)
665 {
666   int year = textyear.value;
667
668   if (year < 0)
669     year = -year;
670
671   /* XPG4 suggests that years 00-68 map to 2000-2068, and
672      years 69-99 map to 1969-1999.  */
673   if (textyear.digits == 2)
674     year += year < 69 ? 2000 : 1900;
675
676   return year;
677 }
678
679 static table const *
680 lookup_zone (parser_control const *pc, char const *name)
681 {
682   table const *tp;
683
684   /* Try local zone abbreviations first; they're more likely to be right.  */
685   for (tp = pc->local_time_zone_table; tp->name; tp++)
686     if (strcmp (name, tp->name) == 0)
687       return tp;
688
689   for (tp = time_zone_table; tp->name; tp++)
690     if (strcmp (name, tp->name) == 0)
691       return tp;
692
693   return 0;
694 }
695
696 #if ! HAVE_TM_GMTOFF
697 /* Yield the difference between *A and *B,
698    measured in seconds, ignoring leap seconds.
699    The body of this function is taken directly from the GNU C Library;
700    see src/strftime.c.  */
701 static int
702 tm_diff (struct tm const *a, struct tm const *b)
703 {
704   /* Compute intervening leap days correctly even if year is negative.
705      Take care to avoid int overflow in leap day calculations,
706      but it's OK to assume that A and B are close to each other.  */
707   int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
708   int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
709   int a100 = a4 / 25 - (a4 % 25 < 0);
710   int b100 = b4 / 25 - (b4 % 25 < 0);
711   int a400 = a100 >> 2;
712   int b400 = b100 >> 2;
713   int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
714   int years = a->tm_year - b->tm_year;
715   int days = (365 * years + intervening_leap_days
716               + (a->tm_yday - b->tm_yday));
717   return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
718                 + (a->tm_min - b->tm_min))
719           + (a->tm_sec - b->tm_sec));
720 }
721 #endif /* ! HAVE_TM_GMTOFF */
722
723 static table const *
724 lookup_word (parser_control const *pc, char *word)
725 {
726   char *p;
727   char *q;
728   size_t wordlen;
729   table const *tp;
730   int i;
731   int abbrev;
732
733   /* Make it uppercase.  */
734   for (p = word; *p; p++)
735     if (ISLOWER ((unsigned char) *p))
736       *p = toupper ((unsigned char) *p);
737
738   for (tp = meridian_table; tp->name; tp++)
739     if (strcmp (word, tp->name) == 0)
740       return tp;
741
742   /* See if we have an abbreviation for a month. */
743   wordlen = strlen (word);
744   abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
745
746   for (tp = month_and_day_table; tp->name; tp++)
747     if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
748       return tp;
749
750   if ((tp = lookup_zone (pc, word)))
751     return tp;
752
753   if (strcmp (word, dst_table[0].name) == 0)
754     return dst_table;
755
756   for (tp = time_units_table; tp->name; tp++)
757     if (strcmp (word, tp->name) == 0)
758       return tp;
759
760   /* Strip off any plural and try the units table again. */
761   if (word[wordlen - 1] == 'S')
762     {
763       word[wordlen - 1] = '\0';
764       for (tp = time_units_table; tp->name; tp++)
765         if (strcmp (word, tp->name) == 0)
766           return tp;
767       word[wordlen - 1] = 'S';  /* For "this" in relative_time_table.  */
768     }
769
770   for (tp = relative_time_table; tp->name; tp++)
771     if (strcmp (word, tp->name) == 0)
772       return tp;
773
774   /* Military time zones. */
775   if (wordlen == 1)
776     for (tp = military_table; tp->name; tp++)
777       if (word[0] == tp->name[0])
778         return tp;
779
780   /* Drop out any periods and try the time zone table again. */
781   for (i = 0, p = q = word; (*p = *q); q++)
782     if (*q == '.')
783       i = 1;
784     else
785       p++;
786   if (i && (tp = lookup_zone (pc, word)))
787     return tp;
788
789   return 0;
790 }
791
792 static int
793 yylex (YYSTYPE *lvalp, parser_control *pc)
794 {
795   unsigned char c;
796   int count;
797
798   for (;;)
799     {
800       while (c = *pc->input, ISSPACE (c))
801         pc->input++;
802
803       if (ISDIGIT (c) || c == '-' || c == '+')
804         {
805           char const *p;
806           int sign;
807           int value;
808           if (c == '-' || c == '+')
809             {
810               sign = c == '-' ? -1 : 1;
811               c = *++pc->input;
812               if (! ISDIGIT (c))
813                 /* skip the '-' sign */
814                 continue;
815             }
816           else
817             sign = 0;
818           p = pc->input;
819           value = 0;
820           do
821             {
822               value = 10 * value + c - '0';
823               c = *++p;
824             }
825           while (ISDIGIT (c));
826           lvalp->textintval.value = sign < 0 ? -value : value;
827           lvalp->textintval.digits = p - pc->input;
828           pc->input = p;
829           return sign ? tSNUMBER : tUNUMBER;
830         }
831
832       if (ISALPHA (c))
833         {
834           char buff[20];
835           char *p = buff;
836           table const *tp;
837
838           do
839             {
840               if (p < buff + sizeof buff - 1)
841                 *p++ = c;
842               c = *++pc->input;
843             }
844           while (ISALPHA (c) || c == '.');
845
846           *p = '\0';
847           tp = lookup_word (pc, buff);
848           if (! tp)
849             return '?';
850           lvalp->intval = tp->value;
851           return tp->type;
852         }
853
854       if (c != '(')
855         return *pc->input++;
856       count = 0;
857       do
858         {
859           c = *pc->input++;
860           if (c == '\0')
861             return c;
862           if (c == '(')
863             count++;
864           else if (c == ')')
865             count--;
866         }
867       while (count > 0);
868     }
869 }
870
871 /* Do nothing if the parser reports an error.  */
872 static int
873 yyerror (char *s ATTRIBUTE_UNUSED)
874 {
875   return 0;
876 }
877
878 /* Parse a date/time string P.  Return the corresponding time_t value,
879    or (time_t) -1 if there is an error.  P can be an incomplete or
880    relative time specification; if so, use *NOW as the basis for the
881    returned time.  */
882 time_t
883 get_date (const char *p, const time_t *now)
884 {
885   time_t Start = now ? *now : time (0);
886   struct tm *tmp = localtime (&Start);
887   struct tm tm;
888   struct tm tm0;
889   parser_control pc;
890
891   if (! tmp)
892     return -1;
893
894   pc.input = p;
895   pc.year.value = tmp->tm_year + TM_YEAR_BASE;
896   pc.year.digits = 4;
897   pc.month = tmp->tm_mon + 1;
898   pc.day = tmp->tm_mday;
899   pc.hour = tmp->tm_hour;
900   pc.minutes = tmp->tm_min;
901   pc.seconds = tmp->tm_sec;
902   tm.tm_isdst = tmp->tm_isdst;
903
904   pc.meridian = MER24;
905   pc.rel_seconds = 0;
906   pc.rel_minutes = 0;
907   pc.rel_hour = 0;
908   pc.rel_day = 0;
909   pc.rel_month = 0;
910   pc.rel_year = 0;
911   pc.dates_seen = 0;
912   pc.days_seen = 0;
913   pc.rels_seen = 0;
914   pc.times_seen = 0;
915   pc.local_zones_seen = 0;
916   pc.zones_seen = 0;
917
918 #if HAVE_STRUCT_TM_TM_ZONE
919   pc.local_time_zone_table[0].name = tmp->tm_zone;
920   pc.local_time_zone_table[0].type = tLOCAL_ZONE;
921   pc.local_time_zone_table[0].value = tmp->tm_isdst;
922   pc.local_time_zone_table[1].name = 0;
923
924   /* Probe the names used in the next three calendar quarters, looking
925      for a tm_isdst different from the one we already have.  */
926   {
927     int quarter;
928     for (quarter = 1; quarter <= 3; quarter++)
929       {
930         time_t probe = Start + quarter * (90 * 24 * 60 * 60);
931         struct tm *probe_tm = localtime (&probe);
932         if (probe_tm && probe_tm->tm_zone
933             && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
934           {
935               {
936                 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
937                 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
938                 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
939                 pc.local_time_zone_table[2].name = 0;
940               }
941             break;
942           }
943       }
944   }
945 #else
946 #if HAVE_TZNAME
947   {
948 # ifndef tzname
949     extern char *tzname[];
950 # endif
951     int i;
952     for (i = 0; i < 2; i++)
953       {
954         pc.local_time_zone_table[i].name = tzname[i];
955         pc.local_time_zone_table[i].type = tLOCAL_ZONE;
956         pc.local_time_zone_table[i].value = i;
957       }
958     pc.local_time_zone_table[i].name = 0;
959   }
960 #else
961   pc.local_time_zone_table[0].name = 0;
962 #endif
963 #endif
964
965   if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
966       && ! strcmp (pc.local_time_zone_table[0].name,
967                    pc.local_time_zone_table[1].name))
968     {
969       /* This locale uses the same abbrevation for standard and
970          daylight times.  So if we see that abbreviation, we don't
971          know whether it's daylight time.  */
972       pc.local_time_zone_table[0].value = -1;
973       pc.local_time_zone_table[1].name = 0;
974     }
975
976   if (yyparse (&pc) != 0
977       || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
978       || 1 < (pc.local_zones_seen + pc.zones_seen)
979       || (pc.local_zones_seen && 1 < pc.local_isdst))
980     return -1;
981
982   tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
983   tm.tm_mon = pc.month - 1 + pc.rel_month;
984   tm.tm_mday = pc.day + pc.rel_day;
985   if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
986     {
987       tm.tm_hour = to_hour (pc.hour, pc.meridian);
988       if (tm.tm_hour < 0)
989         return -1;
990       tm.tm_min = pc.minutes;
991       tm.tm_sec = pc.seconds;
992     }
993   else
994     {
995       tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
996     }
997
998   /* Let mktime deduce tm_isdst if we have an absolute time stamp,
999      or if the relative time stamp mentions days, months, or years.  */
1000   if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
1001       | pc.rel_month | pc.rel_year)
1002     tm.tm_isdst = -1;
1003
1004   /* But if the input explicitly specifies local time with or without
1005      DST, give mktime that information.  */
1006   if (pc.local_zones_seen)
1007     tm.tm_isdst = pc.local_isdst;
1008
1009   tm0 = tm;
1010
1011   Start = mktime (&tm);
1012
1013   if (Start == (time_t) -1)
1014     {
1015
1016       /* Guard against falsely reporting errors near the time_t boundaries
1017          when parsing times in other time zones.  For example, if the min
1018          time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1019          of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1020          we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1021          we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1022          zone by 24 hours to compensate.  This algorithm assumes that
1023          there is no DST transition within a day of the time_t boundaries.  */
1024       if (pc.zones_seen)
1025         {
1026           tm = tm0;
1027           if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1028             {
1029               tm.tm_mday++;
1030               pc.time_zone += 24 * 60;
1031             }
1032           else
1033             {
1034               tm.tm_mday--;
1035               pc.time_zone -= 24 * 60;
1036             }
1037           Start = mktime (&tm);
1038         }
1039
1040       if (Start == (time_t) -1)
1041         return Start;
1042     }
1043
1044   if (pc.days_seen && ! pc.dates_seen)
1045     {
1046       tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1047                      + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1048       tm.tm_isdst = -1;
1049       Start = mktime (&tm);
1050       if (Start == (time_t) -1)
1051         return Start;
1052     }
1053
1054   if (pc.zones_seen)
1055     {
1056       int delta = pc.time_zone * 60;
1057 #ifdef HAVE_TM_GMTOFF
1058       delta -= tm.tm_gmtoff;
1059 #else
1060       struct tm *gmt = gmtime (&Start);
1061       if (! gmt)
1062         return -1;
1063       delta -= tm_diff (&tm, gmt);
1064 #endif
1065       if ((Start < Start - delta) != (delta < 0))
1066         return -1;      /* time_t overflow */
1067       Start -= delta;
1068     }
1069
1070   /* Add relative hours, minutes, and seconds.  Ignore leap seconds;
1071      i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1072      leap second.  Typically this is not what the user wants, but it's
1073      too hard to do it the other way, because the time zone indicator
1074      must be applied before relative times, and if mktime is applied
1075      again the time zone will be lost.  */
1076   {
1077     time_t t0 = Start;
1078     long d1 = 60 * 60 * (long) pc.rel_hour;
1079     time_t t1 = t0 + d1;
1080     long d2 = 60 * (long) pc.rel_minutes;
1081     time_t t2 = t1 + d2;
1082     int d3 = pc.rel_seconds;
1083     time_t t3 = t2 + d3;
1084     if ((d1 / (60 * 60) ^ pc.rel_hour)
1085         | (d2 / 60 ^ pc.rel_minutes)
1086         | ((t0 + d1 < t0) ^ (d1 < 0))
1087         | ((t1 + d2 < t1) ^ (d2 < 0))
1088         | ((t2 + d3 < t2) ^ (d3 < 0)))
1089       return -1;
1090     Start = t3;
1091   }
1092
1093   return Start;
1094 }
1095
1096 #if TEST
1097
1098 #include <stdio.h>
1099
1100 int
1101 main (int ac, char **av)
1102 {
1103   char buff[BUFSIZ];
1104   time_t d;
1105
1106   printf ("Enter date, or blank line to exit.\n\t> ");
1107   fflush (stdout);
1108
1109   buff[BUFSIZ - 1] = 0;
1110   while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1111     {
1112       d = get_date (buff, 0);
1113       if (d == (time_t) -1)
1114         printf ("Bad format - couldn't convert.\n");
1115       else
1116         printf ("%s", ctime (&d));
1117       printf ("\t> ");
1118       fflush (stdout);
1119     }
1120   return 0;
1121 }
1122 #endif /* defined TEST */