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