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