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