.
[coreutils.git] / lib / getdate.y
blob3210e53676f768d391a60742351d518818489cf1
1 %{
2 /* Parse a string into an internal time stamp.
3 Copyright 1999, 2000 Free Software Foundation, Inc.
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.
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.
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. */
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.
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. */
28 #ifdef HAVE_CONFIG_H
29 # include <config.h>
30 # ifdef HAVE_ALLOCA_H
31 # include <alloca.h>
32 # endif
33 #endif
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
45 #include <ctype.h>
47 #if HAVE_STDLIB_H
48 # include <stdlib.h> /* for `free'; used by Bison 1.27 */
49 #endif
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
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))
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)
72 #if STDC_HEADERS || HAVE_STRING_H
73 # include <string.h>
74 #endif
76 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
77 # define __attribute__(x)
78 #endif
80 #ifndef ATTRIBUTE_UNUSED
81 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
82 #endif
84 #define EPOCH_YEAR 1970
85 #define TM_YEAR_BASE 1900
87 #define HOUR(x) ((x) * 60)
89 /* An integer value, and the number of digits in its textual
90 representation. */
91 typedef struct
93 int value;
94 int digits;
95 } textint;
97 /* An entry in the lexical lookup table. */
98 typedef struct
100 char const *name;
101 int type;
102 int value;
103 } table;
105 /* Meridian: am, pm, or 24-hour style. */
106 enum { MERam, MERpm, MER24 };
108 /* Information passed to and from the parser. */
109 typedef struct
111 /* The input string remaining to be parsed. */
112 const char *input;
114 /* N, if this is the Nth Tuesday. */
115 int day_ordinal;
117 /* Day of week; Sunday is 0. */
118 int day_number;
120 /* tm_isdst flag for the local zone. */
121 int local_isdst;
123 /* Time zone, in minutes east of UTC. */
124 int time_zone;
126 /* Style used for time. */
127 int meridian;
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;
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;
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;
153 /* Table of local time zone abbrevations, terminated by a null entry. */
154 table local_time_zone_table[3];
155 } parser_control;
157 #define PC (* (parser_control *) parm)
158 #define YYLEX_PARAM parm
159 #define YYPARSE_PARAM parm
161 static int yyerror ();
162 static int yylex ();
166 /* We want a reentrant parser. */
167 %pure_parser
169 /* This grammar has 13 shift/reduce conflicts. */
170 %expect 13
172 %union
174 int intval;
175 textint textintval;
178 %token tAGO tDST
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
183 %token <textintval> tSNUMBER tUNUMBER
185 %type <intval> o_merid
189 spec:
190 /* empty */
191 | spec item
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
210 time:
211 tUNUMBER tMERIDIAN
213 PC.hour = $1.value;
214 PC.minutes = 0;
215 PC.seconds = 0;
216 PC.meridian = $2;
218 | tUNUMBER ':' tUNUMBER o_merid
220 PC.hour = $1.value;
221 PC.minutes = $3.value;
222 PC.seconds = 0;
223 PC.meridian = $4;
225 | tUNUMBER ':' tUNUMBER tSNUMBER
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;
233 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
235 PC.hour = $1.value;
236 PC.minutes = $3.value;
237 PC.seconds = $5.value;
238 PC.meridian = $6;
240 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER
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;
251 local_zone:
252 tLOCAL_ZONE
253 { PC.local_isdst = $1; }
254 | tLOCAL_ZONE tDST
255 { PC.local_isdst = $1 < 0 ? 1 : $1 + 1; }
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; }
267 day:
268 tDAY
270 PC.day_ordinal = 1;
271 PC.day_number = $1;
273 | tDAY ','
275 PC.day_ordinal = 1;
276 PC.day_number = $1;
278 | tUNUMBER tDAY
280 PC.day_ordinal = $1.value;
281 PC.day_number = $2;
285 date:
286 tUNUMBER '/' tUNUMBER
288 PC.month = $1.value;
289 PC.day = $3.value;
291 | tUNUMBER '/' tUNUMBER '/' tUNUMBER
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)
300 PC.year = $1;
301 PC.month = $3.value;
302 PC.day = $5.value;
304 else
306 PC.month = $1.value;
307 PC.day = $3.value;
308 PC.year = $5;
311 | tUNUMBER tSNUMBER tSNUMBER
313 /* ISO 8601 format. YYYY-MM-DD. */
314 PC.year = $1;
315 PC.month = -$2.value;
316 PC.day = -$3.value;
318 | tUNUMBER tMONTH tSNUMBER
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;
326 | tMONTH tUNUMBER
328 PC.month = $1;
329 PC.day = $2.value;
331 | tMONTH tUNUMBER ',' tUNUMBER
333 PC.month = $1;
334 PC.day = $2.value;
335 PC.year = $4;
337 | tUNUMBER tMONTH
339 PC.day = $1.value;
340 PC.month = $2;
342 | tUNUMBER tMONTH tUNUMBER
344 PC.day = $1.value;
345 PC.month = $2;
346 PC.year = $3;
350 rel:
351 relunit tAGO
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;
360 | relunit
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; }
402 number:
403 tUNUMBER
405 if (PC.dates_seen
406 && ! PC.rels_seen && (PC.times_seen || 2 < $1.digits))
407 PC.year = $1;
408 else
410 if (4 < $1.digits)
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;
418 else
420 PC.times_seen++;
421 if ($1.digits <= 2)
423 PC.hour = $1.value;
424 PC.minutes = 0;
426 else
428 PC.hour = $1.value / 100;
429 PC.minutes = $1.value % 100;
431 PC.seconds = 0;
432 PC.meridian = MER24;
438 o_merid:
439 /* empty */
440 { $$ = MER24; }
441 | tMERIDIAN
442 { $$ = $1; }
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"
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
462 static table const meridian_table[] =
464 { "AM", tMERIDIAN, MERam },
465 { "A.M.", tMERIDIAN, MERam },
466 { "PM", tMERIDIAN, MERpm },
467 { "P.M.", tMERIDIAN, MERpm },
468 { 0, 0, 0 }
471 static table const dst_table[] =
473 { "DST", tDST, 0 }
476 static table const month_and_day_table[] =
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 }
505 static table const time_units_table[] =
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 }
520 /* Assorted relative-time words. */
521 static table const relative_time_table[] =
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 }
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[] =
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 }
606 /* Military time zone table. */
607 static table const military_table[] =
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 }
639 static int
640 to_hour (int hours, int meridian)
642 switch (meridian)
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 ();
653 /* NOTREACHED */
656 static int
657 to_year (textint textyear)
659 int year = textyear.value;
661 if (year < 0)
662 year = -year;
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;
669 return year;
672 static table const *
673 lookup_zone (parser_control const *pc, char const *name)
675 table const *tp;
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;
682 for (tp = time_zone_table; tp->name; tp++)
683 if (strcmp (name, tp->name) == 0)
684 return tp;
686 return 0;
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)
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));
714 #endif /* ! HAVE_TM_GMTOFF */
716 static table const *
717 lookup_word (parser_control const *pc, char *word)
719 char *p;
720 char *q;
721 size_t wordlen;
722 table const *tp;
723 int i;
724 int abbrev;
726 /* Make it uppercase. */
727 for (p = word; *p; p++)
728 if (ISLOWER ((unsigned char) *p))
729 *p = toupper ((unsigned char) *p);
731 for (tp = meridian_table; tp->name; tp++)
732 if (strcmp (word, tp->name) == 0)
733 return tp;
735 /* See if we have an abbreviation for a month. */
736 wordlen = strlen (word);
737 abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
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;
743 if ((tp = lookup_zone (pc, word)))
744 return tp;
746 if (strcmp (word, dst_table[0].name) == 0)
747 return dst_table;
749 for (tp = time_units_table; tp->name; tp++)
750 if (strcmp (word, tp->name) == 0)
751 return tp;
753 /* Strip off any plural and try the units table again. */
754 if (word[wordlen - 1] == 'S')
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. */
763 for (tp = relative_time_table; tp->name; tp++)
764 if (strcmp (word, tp->name) == 0)
765 return tp;
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;
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;
782 return 0;
785 static int
786 yylex (YYSTYPE *lvalp, parser_control *pc)
788 unsigned char c;
789 int count;
791 for (;;)
793 while (c = *pc->input, ISSPACE (c))
794 pc->input++;
796 if (ISDIGIT (c) || c == '-' || c == '+')
798 char const *p;
799 int sign;
800 int value;
801 if (c == '-' || c == '+')
803 sign = c == '-' ? -1 : 1;
804 c = *++pc->input;
805 if (! ISDIGIT (c))
806 /* skip the '-' sign */
807 continue;
809 else
810 sign = 0;
811 p = pc->input;
812 value = 0;
815 value = 10 * value + c - '0';
816 c = *++p;
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;
825 if (ISALPHA (c))
827 char buff[20];
828 char *p = buff;
829 table const *tp;
833 if (p < buff + sizeof buff - 1)
834 *p++ = c;
835 c = *++pc->input;
837 while (ISALPHA (c) || c == '.');
839 *p = '\0';
840 tp = lookup_word (pc, buff);
841 if (! tp)
842 return '?';
843 lvalp->intval = tp->value;
844 return tp->type;
847 if (c != '(')
848 return *pc->input++;
849 count = 0;
852 c = *pc->input++;
853 if (c == '\0')
854 return c;
855 if (c == '(')
856 count++;
857 else if (c == ')')
858 count--;
860 while (count > 0);
864 /* Do nothing if the parser reports an error. */
865 static int
866 yyerror (char *s ATTRIBUTE_UNUSED)
868 return 0;
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)
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;
884 if (! tmp)
885 return -1;
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;
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;
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;
917 /* Probe the names used in the next three calendar quarters, looking
918 for a tm_isdst different from the one we already have. */
920 int quarter;
921 for (quarter = 1; quarter <= 3; quarter++)
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)
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;
934 break;
938 #else
939 #if HAVE_TZNAME
941 # ifndef tzname
942 extern char *tzname[];
943 # endif
944 int i;
945 for (i = 0; i < 2; i++)
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;
951 pc.local_time_zone_table[i].name = 0;
953 #else
954 pc.local_time_zone_table[0].name = 0;
955 #endif
956 #endif
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))
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;
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;
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))
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;
986 else
988 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
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;
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;
1001 tm0 = tm;
1003 Start = mktime (&tm);
1005 if (Start == (time_t) -1)
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)
1018 tm = tm0;
1019 if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
1021 tm.tm_mday++;
1022 pc.time_zone += 24 * 60;
1024 else
1026 tm.tm_mday--;
1027 pc.time_zone -= 24 * 60;
1029 Start = mktime (&tm);
1032 if (Start == (time_t) -1)
1033 return Start;
1036 if (pc.days_seen && ! pc.dates_seen)
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;
1045 if (pc.zones_seen)
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;
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. */
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;
1084 return Start;
1087 #if TEST
1089 #include <stdio.h>
1092 main (int ac, char **av)
1094 char buff[BUFSIZ];
1095 time_t d;
1097 printf ("Enter date, or blank line to exit.\n\t> ");
1098 fflush (stdout);
1100 buff[BUFSIZ - 1] = 0;
1101 while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
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);
1111 return 0;
1113 #endif /* defined TEST */