Optimization: Avoid unnecessary stat() calls.
[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 tUNUMBER
325       {
326         PC.month = $1;
327         PC.day = $2.value;
328       }
329   | tMONTH tUNUMBER ',' tUNUMBER
330       {
331         PC.month = $1;
332         PC.day = $2.value;
333         PC.year = $4;
334       }
335   | tUNUMBER tMONTH
336       {
337         PC.day = $1.value;
338         PC.month = $2;
339       }
340   | tUNUMBER tMONTH tUNUMBER
341       {
342         PC.day = $1.value;
343         PC.month = $2;
344         PC.year = $3;
345       }
346   ;
347
348 rel:
349     relunit tAGO
350       {
351         PC.rel_seconds = -PC.rel_seconds;
352         PC.rel_minutes = -PC.rel_minutes;
353         PC.rel_hour = -PC.rel_hour;
354         PC.rel_day = -PC.rel_day;
355         PC.rel_month = -PC.rel_month;
356         PC.rel_year = -PC.rel_year;
357       }
358   | relunit
359   ;
360
361 relunit:
362     tUNUMBER tYEAR_UNIT
363       { PC.rel_year += $1.value * $2; }
364   | tSNUMBER tYEAR_UNIT
365       { PC.rel_year += $1.value * $2; }
366   | tYEAR_UNIT
367       { PC.rel_year += $1; }
368   | tUNUMBER tMONTH_UNIT
369       { PC.rel_month += $1.value * $2; }
370   | tSNUMBER tMONTH_UNIT
371       { PC.rel_month += $1.value * $2; }
372   | tMONTH_UNIT
373       { PC.rel_month += $1; }
374   | tUNUMBER tDAY_UNIT
375       { PC.rel_day += $1.value * $2; }
376   | tSNUMBER tDAY_UNIT
377       { PC.rel_day += $1.value * $2; }
378   | tDAY_UNIT
379       { PC.rel_day += $1; }
380   | tUNUMBER tHOUR_UNIT
381       { PC.rel_hour += $1.value * $2; }
382   | tSNUMBER tHOUR_UNIT
383       { PC.rel_hour += $1.value * $2; }
384   | tHOUR_UNIT
385       { PC.rel_hour += $1; }
386   | tUNUMBER tMINUTE_UNIT
387       { PC.rel_minutes += $1.value * $2; }
388   | tSNUMBER tMINUTE_UNIT
389       { PC.rel_minutes += $1.value * $2; }
390   | tMINUTE_UNIT
391       { PC.rel_minutes += $1; }
392   | tUNUMBER tSEC_UNIT
393       { PC.rel_seconds += $1.value * $2; }
394   | tSNUMBER tSEC_UNIT
395       { PC.rel_seconds += $1.value * $2; }
396   | tSEC_UNIT
397       { PC.rel_seconds += $1; }
398   ;
399
400 number:
401     tUNUMBER
402       {
403         if (PC.dates_seen
404             && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
405           PC.year = $1;
406         else
407           {
408             if (4 < $1.digits)
409               {
410                 PC.dates_seen++;
411                 PC.day = $1.value % 100;
412                 PC.month = ($1.value / 100) % 100;
413                 PC.year.value = $1.value / 10000;
414                 PC.year.digits = $1.digits - 4;
415               }
416             else
417               {
418                 PC.times_seen++;
419                 if ($1.digits <= 2)
420                   {
421                     PC.hour = $1.value;
422                     PC.minutes = 0;
423                   }
424                 else
425                   {
426                     PC.hour = $1.value / 100;
427                     PC.minutes = $1.value % 100;
428                   }
429                 PC.seconds = 0;
430                 PC.meridian = MER24;
431               }
432           }
433       }
434   ;
435
436 o_merid:
437     /* empty */
438       { $$ = MER24; }
439   | tMERIDIAN
440       { $$ = $1; }
441   ;
442
443 %%
444
445 /* Include this file down here because bison inserts code above which
446    may define-away `const'.  We want the prototype for get_date to have
447    the same signature as the function definition.  */
448 #include "getdate.h"
449 #include "unlocked-io.h"
450
451 #ifndef gmtime
452 struct tm *gmtime ();
453 #endif
454 #ifndef localtime
455 struct tm *localtime ();
456 #endif
457 #ifndef mktime
458 time_t mktime ();
459 #endif
460
461 static table const meridian_table[] =
462 {
463   { "AM",   tMERIDIAN, MERam },
464   { "A.M.", tMERIDIAN, MERam },
465   { "PM",   tMERIDIAN, MERpm },
466   { "P.M.", tMERIDIAN, MERpm },
467   { 0, 0, 0 }
468 };
469
470 static table const dst_table[] =
471 {
472   { "DST", tDST, 0 }
473 };
474
475 static table const month_and_day_table[] =
476 {
477   { "JANUARY",  tMONTH,  1 },
478   { "FEBRUARY", tMONTH,  2 },
479   { "MARCH",    tMONTH,  3 },
480   { "APRIL",    tMONTH,  4 },
481   { "MAY",      tMONTH,  5 },
482   { "JUNE",     tMONTH,  6 },
483   { "JULY",     tMONTH,  7 },
484   { "AUGUST",   tMONTH,  8 },
485   { "SEPTEMBER",tMONTH,  9 },
486   { "SEPT",     tMONTH,  9 },
487   { "OCTOBER",  tMONTH, 10 },
488   { "NOVEMBER", tMONTH, 11 },
489   { "DECEMBER", tMONTH, 12 },
490   { "SUNDAY",   tDAY,    0 },
491   { "MONDAY",   tDAY,    1 },
492   { "TUESDAY",  tDAY,    2 },
493   { "TUES",     tDAY,    2 },
494   { "WEDNESDAY",tDAY,    3 },
495   { "WEDNES",   tDAY,    3 },
496   { "THURSDAY", tDAY,    4 },
497   { "THUR",     tDAY,    4 },
498   { "THURS",    tDAY,    4 },
499   { "FRIDAY",   tDAY,    5 },
500   { "SATURDAY", tDAY,    6 },
501   { 0, 0, 0 }
502 };
503
504 static table const time_units_table[] =
505 {
506   { "YEAR",     tYEAR_UNIT,      1 },
507   { "MONTH",    tMONTH_UNIT,     1 },
508   { "FORTNIGHT",tDAY_UNIT,      14 },
509   { "WEEK",     tDAY_UNIT,       7 },
510   { "DAY",      tDAY_UNIT,       1 },
511   { "HOUR",     tHOUR_UNIT,      1 },
512   { "MINUTE",   tMINUTE_UNIT,    1 },
513   { "MIN",      tMINUTE_UNIT,    1 },
514   { "SECOND",   tSEC_UNIT,       1 },
515   { "SEC",      tSEC_UNIT,       1 },
516   { 0, 0, 0 }
517 };
518
519 /* Assorted relative-time words. */
520 static table const relative_time_table[] =
521 {
522   { "TOMORROW", tMINUTE_UNIT,   24 * 60 },
523   { "YESTERDAY",tMINUTE_UNIT,   - (24 * 60) },
524   { "TODAY",    tMINUTE_UNIT,    0 },
525   { "NOW",      tMINUTE_UNIT,    0 },
526   { "LAST",     tUNUMBER,       -1 },
527   { "THIS",     tUNUMBER,        0 },
528   { "NEXT",     tUNUMBER,        1 },
529   { "FIRST",    tUNUMBER,        1 },
530 /*{ "SECOND",   tUNUMBER,        2 }, */
531   { "THIRD",    tUNUMBER,        3 },
532   { "FOURTH",   tUNUMBER,        4 },
533   { "FIFTH",    tUNUMBER,        5 },
534   { "SIXTH",    tUNUMBER,        6 },
535   { "SEVENTH",  tUNUMBER,        7 },
536   { "EIGHTH",   tUNUMBER,        8 },
537   { "NINTH",    tUNUMBER,        9 },
538   { "TENTH",    tUNUMBER,       10 },
539   { "ELEVENTH", tUNUMBER,       11 },
540   { "TWELFTH",  tUNUMBER,       12 },
541   { "AGO",      tAGO,            1 },
542   { 0, 0, 0 }
543 };
544
545 /* The time zone table.  This table is necessarily incomplete, as time
546    zone abbreviations are ambiguous; e.g. Australians interpret "EST"
547    as Eastern time in Australia, not as US Eastern Standard Time.
548    You cannot rely on getdate to handle arbitrary time zone
549    abbreviations; use numeric abbreviations like `-0500' instead.  */
550 static table const time_zone_table[] =
551 {
552   { "GMT",      tZONE,     HOUR ( 0) }, /* Greenwich Mean */
553   { "UT",       tZONE,     HOUR ( 0) }, /* Universal (Coordinated) */
554   { "UTC",      tZONE,     HOUR ( 0) },
555   { "WET",      tZONE,     HOUR ( 0) }, /* Western European */
556   { "WEST",     tDAYZONE,  HOUR ( 0) }, /* Western European Summer */
557   { "BST",      tDAYZONE,  HOUR ( 0) }, /* British Summer */
558   { "ART",      tZONE,    -HOUR ( 3) }, /* Argentina */
559   { "BRT",      tZONE,    -HOUR ( 3) }, /* Brazil */
560   { "BRST",     tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
561   { "NST",      tZONE,   -(HOUR ( 3) + 30) },   /* Newfoundland Standard */
562   { "NDT",      tDAYZONE,-(HOUR ( 3) + 30) },   /* Newfoundland Daylight */
563   { "AST",      tZONE,    -HOUR ( 4) }, /* Atlantic Standard */
564   { "ADT",      tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
565   { "CLT",      tZONE,    -HOUR ( 4) }, /* Chile */
566   { "CLST",     tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
567   { "EST",      tZONE,    -HOUR ( 5) }, /* Eastern Standard */
568   { "EDT",      tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
569   { "CST",      tZONE,    -HOUR ( 6) }, /* Central Standard */
570   { "CDT",      tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
571   { "MST",      tZONE,    -HOUR ( 7) }, /* Mountain Standard */
572   { "MDT",      tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
573   { "PST",      tZONE,    -HOUR ( 8) }, /* Pacific Standard */
574   { "PDT",      tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
575   { "AKST",     tZONE,    -HOUR ( 9) }, /* Alaska Standard */
576   { "AKDT",     tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
577   { "HST",      tZONE,    -HOUR (10) }, /* Hawaii Standard */
578   { "HAST",     tZONE,    -HOUR (10) }, /* Hawaii-Aleutian Standard */
579   { "HADT",     tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
580   { "SST",      tZONE,    -HOUR (12) }, /* Samoa Standard */
581   { "WAT",      tZONE,     HOUR ( 1) }, /* West Africa */
582   { "CET",      tZONE,     HOUR ( 1) }, /* Central European */
583   { "CEST",     tDAYZONE,  HOUR ( 1) }, /* Central European Summer */
584   { "MET",      tZONE,     HOUR ( 1) }, /* Middle European */
585   { "MEZ",      tZONE,     HOUR ( 1) }, /* Middle European */
586   { "MEST",     tDAYZONE,  HOUR ( 1) }, /* Middle European Summer */
587   { "MESZ",     tDAYZONE,  HOUR ( 1) }, /* Middle European Summer */
588   { "EET",      tZONE,     HOUR ( 2) }, /* Eastern European */
589   { "EEST",     tDAYZONE,  HOUR ( 2) }, /* Eastern European Summer */
590   { "CAT",      tZONE,     HOUR ( 2) }, /* Central Africa */
591   { "SAST",     tZONE,     HOUR ( 2) }, /* South Africa Standard */
592   { "EAT",      tZONE,     HOUR ( 3) }, /* East Africa */
593   { "MSK",      tZONE,     HOUR ( 3) }, /* Moscow */
594   { "MSD",      tDAYZONE,  HOUR ( 3) }, /* Moscow Daylight */
595   { "IST",      tZONE,    (HOUR ( 5) + 30) },   /* India Standard */
596   { "SGT",      tZONE,     HOUR ( 8) }, /* Singapore */
597   { "KST",      tZONE,     HOUR ( 9) }, /* Korea Standard */
598   { "JST",      tZONE,     HOUR ( 9) }, /* Japan Standard */
599   { "GST",      tZONE,     HOUR (10) }, /* Guam Standard */
600   { "NZST",     tZONE,     HOUR (12) }, /* New Zealand Standard */
601   { "NZDT",     tDAYZONE,  HOUR (12) }, /* New Zealand Daylight */
602   { 0, 0, 0  }
603 };
604
605 /* Military time zone table. */
606 static table const military_table[] =
607 {
608   { "A", tZONE, -HOUR ( 1) },
609   { "B", tZONE, -HOUR ( 2) },
610   { "C", tZONE, -HOUR ( 3) },
611   { "D", tZONE, -HOUR ( 4) },
612   { "E", tZONE, -HOUR ( 5) },
613   { "F", tZONE, -HOUR ( 6) },
614   { "G", tZONE, -HOUR ( 7) },
615   { "H", tZONE, -HOUR ( 8) },
616   { "I", tZONE, -HOUR ( 9) },
617   { "K", tZONE, -HOUR (10) },
618   { "L", tZONE, -HOUR (11) },
619   { "M", tZONE, -HOUR (12) },
620   { "N", tZONE,  HOUR ( 1) },
621   { "O", tZONE,  HOUR ( 2) },
622   { "P", tZONE,  HOUR ( 3) },
623   { "Q", tZONE,  HOUR ( 4) },
624   { "R", tZONE,  HOUR ( 5) },
625   { "S", tZONE,  HOUR ( 6) },
626   { "T", tZONE,  HOUR ( 7) },
627   { "U", tZONE,  HOUR ( 8) },
628   { "V", tZONE,  HOUR ( 9) },
629   { "W", tZONE,  HOUR (10) },
630   { "X", tZONE,  HOUR (11) },
631   { "Y", tZONE,  HOUR (12) },
632   { "Z", tZONE,  HOUR ( 0) },
633   { 0, 0, 0 }
634 };
635
636 \f
637
638 static int
639 to_hour (int hours, int meridian)
640 {
641   switch (meridian)
642     {
643     case MER24:
644       return 0 <= hours && hours < 24 ? hours : -1;
645     case MERam:
646       return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
647     case MERpm:
648       return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
649     default:
650       abort ();
651     }
652   /* NOTREACHED */
653 }
654
655 static int
656 to_year (textint textyear)
657 {
658   int year = textyear.value;
659
660   if (year < 0)
661     year = -year;
662
663   /* XPG4 suggests that years 00-68 map to 2000-2068, and
664      years 69-99 map to 1969-1999.  */
665   if (textyear.digits == 2)
666     year += year < 69 ? 2000 : 1900;
667
668   return year;
669 }
670
671 static table const *
672 lookup_zone (parser_control const *pc, char const *name)
673 {
674   table const *tp;
675
676   /* Try local zone abbreviations first; they're more likely to be right.  */
677   for (tp = pc->local_time_zone_table; tp->name; tp++)
678     if (strcmp (name, tp->name) == 0)
679       return tp;
680
681   for (tp = time_zone_table; tp->name; tp++)
682     if (strcmp (name, tp->name) == 0)
683       return tp;
684
685   return 0;
686 }
687
688 #if ! HAVE_TM_GMTOFF
689 /* Yield the difference between *A and *B,
690    measured in seconds, ignoring leap seconds.
691    The body of this function is taken directly from the GNU C Library;
692    see src/strftime.c.  */
693 static int
694 tm_diff (struct tm const *a, struct tm const *b)
695 {
696   /* Compute intervening leap days correctly even if year is negative.
697      Take care to avoid int overflow in leap day calculations,
698      but it's OK to assume that A and B are close to each other.  */
699   int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
700   int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
701   int a100 = a4 / 25 - (a4 % 25 < 0);
702   int b100 = b4 / 25 - (b4 % 25 < 0);
703   int a400 = a100 >> 2;
704   int b400 = b100 >> 2;
705   int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
706   int years = a->tm_year - b->tm_year;
707   int days = (365 * years + intervening_leap_days
708               + (a->tm_yday - b->tm_yday));
709   return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
710                 + (a->tm_min - b->tm_min))
711           + (a->tm_sec - b->tm_sec));
712 }
713 #endif /* ! HAVE_TM_GMTOFF */
714
715 static table const *
716 lookup_word (parser_control const *pc, char *word)
717 {
718   char *p;
719   char *q;
720   size_t wordlen;
721   table const *tp;
722   int i;
723   int abbrev;
724
725   /* Make it uppercase.  */
726   for (p = word; *p; p++)
727     if (ISLOWER ((unsigned char) *p))
728       *p = toupper ((unsigned char) *p);
729
730   for (tp = meridian_table; tp->name; tp++)
731     if (strcmp (word, tp->name) == 0)
732       return tp;
733
734   /* See if we have an abbreviation for a month. */
735   wordlen = strlen (word);
736   abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
737
738   for (tp = month_and_day_table; tp->name; tp++)
739     if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
740       return tp;
741
742   if ((tp = lookup_zone (pc, word)))
743     return tp;
744
745   if (strcmp (word, dst_table[0].name) == 0)
746     return dst_table;
747
748   for (tp = time_units_table; tp->name; tp++)
749     if (strcmp (word, tp->name) == 0)
750       return tp;
751
752   /* Strip off any plural and try the units table again. */
753   if (word[wordlen - 1] == 'S')
754     {
755       word[wordlen - 1] = '\0';
756       for (tp = time_units_table; tp->name; tp++)
757         if (strcmp (word, tp->name) == 0)
758           return tp;
759       word[wordlen - 1] = 'S';  /* For "this" in relative_time_table.  */
760     }
761
762   for (tp = relative_time_table; tp->name; tp++)
763     if (strcmp (word, tp->name) == 0)
764       return tp;
765
766   /* Military time zones. */
767   if (wordlen == 1)
768     for (tp = military_table; tp->name; tp++)
769       if (word[0] == tp->name[0])
770         return tp;
771
772   /* Drop out any periods and try the time zone table again. */
773   for (i = 0, p = q = word; (*p = *q); q++)
774     if (*q == '.')
775       i = 1;
776     else
777       p++;
778   if (i && (tp = lookup_zone (pc, word)))
779     return tp;
780
781   return 0;
782 }
783
784 static int
785 yylex (YYSTYPE *lvalp, parser_control *pc)
786 {
787   unsigned char c;
788   int count;
789
790   for (;;)
791     {
792       while (c = *pc->input, ISSPACE (c))
793         pc->input++;
794
795       if (ISDIGIT (c) || c == '-' || c == '+')
796         {
797           char const *p;
798           int sign;
799           int value;
800           if (c == '-' || c == '+')
801             {
802               sign = c == '-' ? -1 : 1;
803               c = *++pc->input;
804               if (! ISDIGIT (c))
805                 /* skip the '-' sign */
806                 continue;
807             }
808           else
809             sign = 0;
810           p = pc->input;
811           value = 0;
812           do
813             {
814               value = 10 * value + c - '0';
815               c = *++p;
816             }
817           while (ISDIGIT (c));
818           lvalp->textintval.value = sign < 0 ? -value : value;
819           lvalp->textintval.digits = p - pc->input;
820           pc->input = p;
821           return sign ? tSNUMBER : tUNUMBER;
822         }
823
824       if (ISALPHA (c))
825         {
826           char buff[20];
827           char *p = buff;
828           table const *tp;
829
830           do
831             {
832               if (p < buff + sizeof buff - 1)
833                 *p++ = c;
834               c = *++pc->input;
835             }
836           while (ISALPHA (c) || c == '.');
837
838           *p = '\0';
839           tp = lookup_word (pc, buff);
840           if (! tp)
841             return '?';
842           lvalp->intval = tp->value;
843           return tp->type;
844         }
845
846       if (c != '(')
847         return *pc->input++;
848       count = 0;
849       do
850         {
851           c = *pc->input++;
852           if (c == '\0')
853             return c;
854           if (c == '(')
855             count++;
856           else if (c == ')')
857             count--;
858         }
859       while (count > 0);
860     }
861 }
862
863 /* Do nothing if the parser reports an error.  */
864 static int
865 yyerror (char *s ATTRIBUTE_UNUSED)
866 {
867   return 0;
868 }
869
870 /* Parse a date/time string P.  Return the corresponding time_t value,
871    or (time_t) -1 if there is an error.  P can be an incomplete or
872    relative time specification; if so, use *NOW as the basis for the
873    returned time.  */
874 time_t
875 get_date (const char *p, const time_t *now)
876 {
877   time_t Start = now ? *now : time (0);
878   struct tm *tmp = localtime (&Start);
879   struct tm tm;
880   struct tm tm0;
881   parser_control pc;
882
883   if (! tmp)
884     return -1;
885
886   pc.input = p;
887   pc.year.value = tmp->tm_year + TM_YEAR_BASE;
888   pc.year.digits = 4;
889   pc.month = tmp->tm_mon + 1;
890   pc.day = tmp->tm_mday;
891   pc.hour = tmp->tm_hour;
892   pc.minutes = tmp->tm_min;
893   pc.seconds = tmp->tm_sec;
894   tm.tm_isdst = tmp->tm_isdst;
895
896   pc.meridian = MER24;
897   pc.rel_seconds = 0;
898   pc.rel_minutes = 0;
899   pc.rel_hour = 0;
900   pc.rel_day = 0;
901   pc.rel_month = 0;
902   pc.rel_year = 0;
903   pc.dates_seen = 0;
904   pc.days_seen = 0;
905   pc.rels_seen = 0;
906   pc.times_seen = 0;
907   pc.local_zones_seen = 0;
908   pc.zones_seen = 0;
909
910 #if HAVE_STRUCT_TM_TM_ZONE
911   pc.local_time_zone_table[0].name = tmp->tm_zone;
912   pc.local_time_zone_table[0].type = tLOCAL_ZONE;
913   pc.local_time_zone_table[0].value = tmp->tm_isdst;
914   pc.local_time_zone_table[1].name = 0;
915
916   /* Probe the names used in the next three calendar quarters, looking
917      for a tm_isdst different from the one we already have.  */
918   {
919     int quarter;
920     for (quarter = 1; quarter <= 3; quarter++)
921       {
922         time_t probe = Start + quarter * (90 * 24 * 60 * 60);
923         struct tm *probe_tm = localtime (&probe);
924         if (probe_tm && probe_tm->tm_zone
925             && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
926           {
927               {
928                 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
929                 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
930                 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
931                 pc.local_time_zone_table[2].name = 0;
932               }
933             break;
934           }
935       }
936   }
937 #else
938 #if HAVE_TZNAME
939   {
940 # ifndef tzname
941     extern char *tzname[];
942 # endif
943     int i;
944     for (i = 0; i < 2; i++)
945       {
946         pc.local_time_zone_table[i].name = tzname[i];
947         pc.local_time_zone_table[i].type = tLOCAL_ZONE;
948         pc.local_time_zone_table[i].value = i;
949       }
950     pc.local_time_zone_table[i].name = 0;
951   }
952 #else
953   pc.local_time_zone_table[0].name = 0;
954 #endif
955 #endif
956
957   if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
958       && ! strcmp (pc.local_time_zone_table[0].name,
959                    pc.local_time_zone_table[1].name))
960     {
961       /* This locale uses the same abbrevation for standard and
962          daylight times.  So if we see that abbreviation, we don't
963          know whether it's daylight time.  */
964       pc.local_time_zone_table[0].value = -1;
965       pc.local_time_zone_table[1].name = 0;
966     }
967
968   if (yyparse (&pc) != 0
969       || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
970       || 1 < (pc.local_zones_seen + pc.zones_seen)
971       || (pc.local_zones_seen && 1 < pc.local_isdst))
972     return -1;
973
974   tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
975   tm.tm_mon = pc.month - 1 + pc.rel_month;
976   tm.tm_mday = pc.day + pc.rel_day;
977   if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
978     {
979       tm.tm_hour = to_hour (pc.hour, pc.meridian);
980       if (tm.tm_hour < 0)
981         return -1;
982       tm.tm_min = pc.minutes;
983       tm.tm_sec = pc.seconds;
984     }
985   else
986     {
987       tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
988     }
989
990   /* Let mktime deduce tm_isdst if we have an absolute time stamp,
991      or if the relative time stamp mentions days, months, or years.  */
992   if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
993       | pc.rel_month | pc.rel_year)
994     tm.tm_isdst = -1;
995
996   /* But if the input explicitly specifies local time with or without
997      DST, give mktime that information.  */
998   if (pc.local_zones_seen)
999     tm.tm_isdst = pc.local_isdst;
1000
1001   tm0 = tm;
1002
1003   Start = mktime (&tm);
1004
1005   if (Start == (time_t) -1)
1006     {
1007
1008       /* Guard against falsely reporting errors near the time_t boundaries
1009          when parsing times in other time zones.  For example, if the min
1010          time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1011          of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1012          we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1013          we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1014          zone by 24 hours to compensate.  This algorithm assumes that
1015          there is no DST transition within a day of the time_t boundaries.  */
1016       if (pc.zones_seen)
1017         {
1018           tm = tm0;
1019           if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1020             {
1021               tm.tm_mday++;
1022               pc.time_zone += 24 * 60;
1023             }
1024           else
1025             {
1026               tm.tm_mday--;
1027               pc.time_zone -= 24 * 60;
1028             }
1029           Start = mktime (&tm);
1030         }
1031
1032       if (Start == (time_t) -1)
1033         return Start;
1034     }
1035
1036   if (pc.days_seen && ! pc.dates_seen)
1037     {
1038       tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
1039                      + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
1040       tm.tm_isdst = -1;
1041       Start = mktime (&tm);
1042       if (Start == (time_t) -1)
1043         return Start;
1044     }
1045
1046   if (pc.zones_seen)
1047     {
1048       int delta = pc.time_zone * 60;
1049 #ifdef HAVE_TM_GMTOFF
1050       delta -= tm.tm_gmtoff;
1051 #else
1052       struct tm *gmt = gmtime (&Start);
1053       if (! gmt)
1054         return -1;
1055       delta -= tm_diff (&tm, gmt);
1056 #endif
1057       if ((Start < Start - delta) != (delta < 0))
1058         return -1;      /* time_t overflow */
1059       Start -= delta;
1060     }
1061
1062   /* Add relative hours, minutes, and seconds.  Ignore leap seconds;
1063      i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1064      leap second.  Typically this is not what the user wants, but it's
1065      too hard to do it the other way, because the time zone indicator
1066      must be applied before relative times, and if mktime is applied
1067      again the time zone will be lost.  */
1068   {
1069     time_t t0 = Start;
1070     long d1 = 60 * 60 * (long) pc.rel_hour;
1071     time_t t1 = t0 + d1;
1072     long d2 = 60 * (long) pc.rel_minutes;
1073     time_t t2 = t1 + d2;
1074     int d3 = pc.rel_seconds;
1075     time_t t3 = t2 + d3;
1076     if ((d1 / (60 * 60) ^ pc.rel_hour)
1077         | (d2 / 60 ^ pc.rel_minutes)
1078         | ((t0 + d1 < t0) ^ (d1 < 0))
1079         | ((t1 + d2 < t1) ^ (d2 < 0))
1080         | ((t2 + d3 < t2) ^ (d3 < 0)))
1081       return -1;
1082     Start = t3;
1083   }
1084
1085   return Start;
1086 }
1087
1088 #if TEST
1089
1090 #include <stdio.h>
1091
1092 int
1093 main (int ac, char **av)
1094 {
1095   char buff[BUFSIZ];
1096   time_t d;
1097
1098   printf ("Enter date, or blank line to exit.\n\t> ");
1099   fflush (stdout);
1100
1101   buff[BUFSIZ - 1] = 0;
1102   while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1103     {
1104       d = get_date (buff, 0);
1105       if (d == (time_t) -1)
1106         printf ("Bad format - couldn't convert.\n");
1107       else
1108         printf ("%s", ctime (&d));
1109       printf ("\t> ");
1110       fflush (stdout);
1111     }
1112   return 0;
1113 }
1114 #endif /* defined TEST */