(make_path): Quote the other instance, too.
[gnulib.git] / lib / getdate.y
1 %{
2 /* Parse a string into an internal time stamp.
3    Copyright 1999, 2000 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
47 #if HAVE_STDLIB_H
48 # include <stdlib.h> /* for `free'; used by Bison 1.27 */
49 #endif
50
51 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
52 # define IN_CTYPE_DOMAIN(c) 1
53 #else
54 # define IN_CTYPE_DOMAIN(c) isascii (c)
55 #endif
56
57 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
58 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
59 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
60 #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
61
62 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
63    - Its arg may be any int or unsigned int; it need not be an unsigned char.
64    - It's guaranteed to evaluate its argument exactly once.
65    - It's typically faster.
66    Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
67    only '0' through '9' are digits.  Prefer ISDIGIT to ISDIGIT_LOCALE unless
68    it's important to use the locale's definition of `digit' even when the
69    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 "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 }
655
656 static int
657 to_year (textint textyear)
658 {
659   int year = textyear.value;
660
661   if (year < 0)
662     year = -year;
663
664   /* XPG4 suggests that years 00-68 map to 2000-2068, and
665      years 69-99 map to 1969-1999.  */
666   if (textyear.digits == 2)
667     year += year < 69 ? 2000 : 1900;
668
669   return year;
670 }
671
672 static table const *
673 lookup_zone (parser_control const *pc, char const *name)
674 {
675   table const *tp;
676
677   /* Try local zone abbreviations first; they're more likely to be right.  */
678   for (tp = pc->local_time_zone_table; tp->name; tp++)
679     if (strcmp (name, tp->name) == 0)
680       return tp;
681
682   for (tp = time_zone_table; tp->name; tp++)
683     if (strcmp (name, tp->name) == 0)
684       return tp;
685
686   return 0;
687 }
688
689 #if ! HAVE_TM_GMTOFF
690 /* Yield the difference between *A and *B,
691    measured in seconds, ignoring leap seconds.
692    The body of this function is taken directly from the GNU C Library;
693    see src/strftime.c.  */
694 static int
695 tm_diff (struct tm const *a, struct tm const *b)
696 {
697   /* Compute intervening leap days correctly even if year is negative.
698      Take care to avoid int overflow in leap day calculations,
699      but it's OK to assume that A and B are close to each other.  */
700   int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
701   int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
702   int a100 = a4 / 25 - (a4 % 25 < 0);
703   int b100 = b4 / 25 - (b4 % 25 < 0);
704   int a400 = a100 >> 2;
705   int b400 = b100 >> 2;
706   int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
707   int years = a->tm_year - b->tm_year;
708   int days = (365 * years + intervening_leap_days
709               + (a->tm_yday - b->tm_yday));
710   return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
711                 + (a->tm_min - b->tm_min))
712           + (a->tm_sec - b->tm_sec));
713 }
714 #endif /* ! HAVE_TM_GMTOFF */
715
716 static table const *
717 lookup_word (parser_control const *pc, char *word)
718 {
719   char *p;
720   char *q;
721   size_t wordlen;
722   table const *tp;
723   int i;
724   int abbrev;
725
726   /* Make it uppercase.  */
727   for (p = word; *p; p++)
728     if (ISLOWER ((unsigned char) *p))
729       *p = toupper ((unsigned char) *p);
730
731   for (tp = meridian_table; tp->name; tp++)
732     if (strcmp (word, tp->name) == 0)
733       return tp;
734
735   /* See if we have an abbreviation for a month. */
736   wordlen = strlen (word);
737   abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
738
739   for (tp = month_and_day_table; tp->name; tp++)
740     if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
741       return tp;
742
743   if ((tp = lookup_zone (pc, word)))
744     return tp;
745
746   if (strcmp (word, dst_table[0].name) == 0)
747     return dst_table;
748
749   for (tp = time_units_table; tp->name; tp++)
750     if (strcmp (word, tp->name) == 0)
751       return tp;
752
753   /* Strip off any plural and try the units table again. */
754   if (word[wordlen - 1] == 'S')
755     {
756       word[wordlen - 1] = '\0';
757       for (tp = time_units_table; tp->name; tp++)
758         if (strcmp (word, tp->name) == 0)
759           return tp;
760       word[wordlen - 1] = 'S';  /* For "this" in relative_time_table.  */
761     }
762
763   for (tp = relative_time_table; tp->name; tp++)
764     if (strcmp (word, tp->name) == 0)
765       return tp;
766
767   /* Military time zones. */
768   if (wordlen == 1)
769     for (tp = military_table; tp->name; tp++)
770       if (word[0] == tp->name[0])
771         return tp;
772
773   /* Drop out any periods and try the time zone table again. */
774   for (i = 0, p = q = word; (*p = *q); q++)
775     if (*q == '.')
776       i = 1;
777     else
778       p++;
779   if (i && (tp = lookup_zone (pc, word)))
780     return tp;
781
782   return 0;
783 }
784
785 static int
786 yylex (YYSTYPE *lvalp, parser_control *pc)
787 {
788   unsigned char c;
789   int count;
790
791   for (;;)
792     {
793       while (c = *pc->input, ISSPACE (c))
794         pc->input++;
795
796       if (ISDIGIT (c) || c == '-' || c == '+')
797         {
798           char const *p;
799           int sign;
800           int value;
801           if (c == '-' || c == '+')
802             {
803               sign = c == '-' ? -1 : 1;
804               c = *++pc->input;
805               if (! ISDIGIT (c))
806                 /* skip the '-' sign */
807                 continue;
808             }
809           else
810             sign = 0;
811           p = pc->input;
812           value = 0;
813           do
814             {
815               value = 10 * value + c - '0';
816               c = *++p;
817             }
818           while (ISDIGIT (c));
819           lvalp->textintval.value = sign < 0 ? -value : value;
820           lvalp->textintval.digits = p - pc->input;
821           pc->input = p;
822           return sign ? tSNUMBER : tUNUMBER;
823         }
824
825       if (ISALPHA (c))
826         {
827           char buff[20];
828           char *p = buff;
829           table const *tp;
830
831           do
832             {
833               if (p < buff + sizeof buff - 1)
834                 *p++ = c;
835               c = *++pc->input;
836             }
837           while (ISALPHA (c) || c == '.');
838
839           *p = '\0';
840           tp = lookup_word (pc, buff);
841           if (! tp)
842             return '?';
843           lvalp->intval = tp->value;
844           return tp->type;
845         }
846
847       if (c != '(')
848         return *pc->input++;
849       count = 0;
850       do
851         {
852           c = *pc->input++;
853           if (c == '\0')
854             return c;
855           if (c == '(')
856             count++;
857           else if (c == ')')
858             count--;
859         }
860       while (count > 0);
861     }
862 }
863
864 /* Do nothing if the parser reports an error.  */
865 static int
866 yyerror (char *s ATTRIBUTE_UNUSED)
867 {
868   return 0;
869 }
870
871 /* Parse a date/time string P.  Return the corresponding time_t value,
872    or (time_t) -1 if there is an error.  P can be an incomplete or
873    relative time specification; if so, use *NOW as the basis for the
874    returned time.  */
875 time_t
876 get_date (const char *p, const time_t *now)
877 {
878   time_t Start = now ? *now : time (0);
879   struct tm *tmp = localtime (&Start);
880   struct tm tm;
881   struct tm tm0;
882   parser_control pc;
883
884   if (! tmp)
885     return -1;
886
887   pc.input = p;
888   pc.year.value = tmp->tm_year + TM_YEAR_BASE;
889   pc.year.digits = 4;
890   pc.month = tmp->tm_mon + 1;
891   pc.day = tmp->tm_mday;
892   pc.hour = tmp->tm_hour;
893   pc.minutes = tmp->tm_min;
894   pc.seconds = tmp->tm_sec;
895   tm.tm_isdst = tmp->tm_isdst;
896
897   pc.meridian = MER24;
898   pc.rel_seconds = 0;
899   pc.rel_minutes = 0;
900   pc.rel_hour = 0;
901   pc.rel_day = 0;
902   pc.rel_month = 0;
903   pc.rel_year = 0;
904   pc.dates_seen = 0;
905   pc.days_seen = 0;
906   pc.rels_seen = 0;
907   pc.times_seen = 0;
908   pc.local_zones_seen = 0;
909   pc.zones_seen = 0;
910
911 #if HAVE_TM_ZONE
912   pc.local_time_zone_table[0].name = tmp->tm_zone;
913   pc.local_time_zone_table[0].type = tLOCAL_ZONE;
914   pc.local_time_zone_table[0].value = tmp->tm_isdst;
915   pc.local_time_zone_table[1].name = 0;
916
917   /* Probe the names used in the next three calendar quarters, looking
918      for a tm_isdst different from the one we already have.  */
919   {
920     int quarter;
921     for (quarter = 1; quarter <= 3; quarter++)
922       {
923         time_t probe = Start + quarter * (90 * 24 * 60 * 60);
924         struct tm *probe_tm = localtime (&probe);
925         if (probe_tm && probe_tm->tm_zone
926             && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
927           {
928               {
929                 pc.local_time_zone_table[1].name = probe_tm->tm_zone;
930                 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
931                 pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
932                 pc.local_time_zone_table[2].name = 0;
933               }
934             break;
935           }
936       }
937   }
938 #else
939 #if HAVE_TZNAME
940   {
941 # ifndef tzname
942     extern char *tzname[];
943 # endif
944     int i;
945     for (i = 0; i < 2; i++)
946       {
947         pc.local_time_zone_table[i].name = tzname[i];
948         pc.local_time_zone_table[i].type = tLOCAL_ZONE;
949         pc.local_time_zone_table[i].value = i;
950       }
951     pc.local_time_zone_table[i].name = 0;
952   }
953 #else
954   pc.local_time_zone_table[0].name = 0;
955 #endif
956 #endif
957
958   if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
959       && ! strcmp (pc.local_time_zone_table[0].name,
960                    pc.local_time_zone_table[1].name))
961     {
962       /* This locale uses the same abbrevation for standard and
963          daylight times.  So if we see that abbreviation, we don't
964          know whether it's daylight time.  */
965       pc.local_time_zone_table[0].value = -1;
966       pc.local_time_zone_table[1].name = 0;
967     }
968
969   if (yyparse (&pc) != 0
970       || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
971       || 1 < (pc.local_zones_seen + pc.zones_seen)
972       || (pc.local_zones_seen && 1 < pc.local_isdst))
973     return -1;
974
975   tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
976   tm.tm_mon = pc.month - 1 + pc.rel_month;
977   tm.tm_mday = pc.day + pc.rel_day;
978   if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
979     {
980       tm.tm_hour = to_hour (pc.hour, pc.meridian);
981       if (tm.tm_hour < 0)
982         return -1;
983       tm.tm_min = pc.minutes;
984       tm.tm_sec = pc.seconds;
985     }
986   else
987     {
988       tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
989     }
990
991   /* Let mktime deduce tm_isdst if we have an absolute time stamp,
992      or if the relative time stamp mentions days, months, or years.  */
993   if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day | 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       Start = mktime (&tm);
1041       if (Start == (time_t) -1)
1042         return Start;
1043     }
1044
1045   if (pc.zones_seen)
1046     {
1047       int delta = pc.time_zone * 60;
1048 #ifdef HAVE_TM_GMTOFF
1049       delta -= tm.tm_gmtoff;
1050 #else
1051       struct tm *gmt = gmtime (&Start);
1052       if (! gmt)
1053         return -1;
1054       delta -= tm_diff (&tm, gmt);
1055 #endif
1056       if ((Start < Start - delta) != (delta < 0))
1057         return -1;      /* time_t overflow */
1058       Start -= delta;
1059     }
1060
1061   /* Add relative hours, minutes, and seconds.  Ignore leap seconds;
1062      i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1063      leap second.  Typically this is not what the user wants, but it's
1064      too hard to do it the other way, because the time zone indicator
1065      must be applied before relative times, and if mktime is applied
1066      again the time zone will be lost.  */
1067   {
1068     time_t t0 = Start;
1069     long d1 = 60 * 60 * (long) pc.rel_hour;
1070     time_t t1 = t0 + d1;
1071     long d2 = 60 * (long) pc.rel_minutes;
1072     time_t t2 = t1 + d2;
1073     int d3 = pc.rel_seconds;
1074     time_t t3 = t2 + d3;
1075     if ((d1 / (60 * 60) ^ pc.rel_hour)
1076         | (d2 / 60 ^ pc.rel_minutes)
1077         | ((t0 + d1 < t0) ^ (d1 < 0))
1078         | ((t1 + d2 < t1) ^ (d2 < 0))
1079         | ((t2 + d3 < t2) ^ (d3 < 0)))
1080       return -1;
1081     Start = t3;
1082   }
1083
1084   return Start;
1085 }
1086
1087 #if TEST
1088
1089 #include <stdio.h>
1090
1091 int
1092 main (int ac, char **av)
1093 {
1094   char buff[BUFSIZ];
1095   time_t d;
1096
1097   printf ("Enter date, or blank line to exit.\n\t> ");
1098   fflush (stdout);
1099
1100   buff[BUFSIZ - 1] = 0;
1101   while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
1102     {
1103       d = get_date (buff, 0);
1104       if (d == (time_t) -1)
1105         printf ("Bad format - couldn't convert.\n");
1106       else
1107         printf ("%s", ctime (&d));
1108       printf ("\t> ");
1109       fflush (stdout);
1110     }
1111   return 0;
1112 }
1113 #endif /* defined TEST */