2 /* Parse a string into an internal time stamp.
3 Copyright (C) 1999, 2000, 2002, 2003, 2004 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)
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. Also modified by Paul Eggert
26 <eggert@cs.ucla.edu> in February 2004 to support
27 nanosecond-resolution time stamps, and in October 2004 to support
28 TZ strings in dates. */
30 /* FIXME: Check for arithmetic overflow in all cases, not just
39 /* There's no need to extend the stack, so there's no need to involve
41 #define YYSTACK_USE_ALLOCA 0
43 /* Tell Bison how much stack space is needed. 20 should be plenty for
44 this grammar, which is not right recursive. Beware setting it too
45 high, since that might cause problems on machines whose
46 implementations have lame stack-overflow checking. */
48 #define YYINITDEPTH YYMAXDEPTH
50 /* Since the code of getdate.y is not included in the Emacs executable
51 itself, there is no need to #define static in this file. Even if
52 the code were included in the Emacs executable, it probably
53 wouldn't do any harm to #undef it here; this will only cause
54 problems if we try to write to a static variable, which I don't
55 think this code needs to do. */
69 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
70 # define IN_CTYPE_DOMAIN(c) 1
72 # define IN_CTYPE_DOMAIN(c) isascii (c)
75 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
76 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
77 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
79 /* ISDIGIT differs from isdigit, as follows:
80 - Its arg may be any int or unsigned int; it need not be an unsigned char.
81 - It's guaranteed to evaluate its argument exactly once.
82 - It's typically faster.
83 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
84 isdigit unless it's important to use the locale's definition
85 of `digit' even when the host does not conform to POSIX. */
86 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
88 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
89 # define __attribute__(x)
92 #ifndef ATTRIBUTE_UNUSED
93 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
96 /* Shift A right by B bits portably, by dividing A by 2**B and
97 truncating towards minus infinity. A and B should be free of side
98 effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
99 INT_BITS is the number of useful bits in an int. GNU code can
100 assume that INT_BITS is at least 32.
102 ISO C99 says that A >> B is implementation-defined if A < 0. Some
103 implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
104 right in the usual way when A < 0, so SHR falls back on division if
105 ordinary A >> B doesn't seem to be the usual signed shift. */
109 : (a
) / (1 << (b
)) - ((a
) %
(1 << (b
)) < 0))
111 #define EPOCH_YEAR 1970
112 #define TM_YEAR_BASE 1900
114 #define HOUR(x) ((x) * 60)
116 /* An integer value, and the number of digits in its textual
125 /* An entry in the lexical lookup table. */
133 /* Meridian: am, pm, or 24-hour style. */
134 enum { MERam
, MERpm
, MER24
};
136 enum { BILLION
= 1000000000, LOG10_BILLION
= 9 };
138 /* Information passed to and from the parser. */
141 /* The input string remaining to be parsed. */
144 /* N, if this is the Nth Tuesday. */
145 long int day_ordinal
;
147 /* Day of week; Sunday is 0. */
150 /* tm_isdst flag for the local zone. */
153 /* Time zone, in minutes east of UTC. */
156 /* Style used for time. */
159 /* Gregorian year, month, day, hour, minutes, seconds, and nanoseconds. */
165 struct timespec seconds
; /* includes nanoseconds */
167 /* Relative year, month, day, hour, minutes, seconds, and nanoseconds. */
172 long int rel_minutes
;
173 long int rel_seconds
;
176 /* Counts of nonterminals of various flavors parsed so far. */
180 size_t local_zones_seen
;
185 /* Table of local time zone abbrevations, terminated by a null entry. */
186 table local_time_zone_table
[3];
190 static int yylex (union YYSTYPE *, parser_control
*);
191 static int yyerror (parser_control
*, char *);
192 static long int time_zone_hhmm
(textint
, long int);
196 /* We want a reentrant parser, even if the TZ manipulation and the calls to
197 localtime and gmtime are not reentrant. */
199 %parse
-param
{ parser_control
*pc
}
200 %lex
-param
{ parser_control
*pc
}
202 /* This grammar has 14 shift/reduce conflicts. */
209 struct timespec timespec
;
214 %token
<intval
> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
215 %token
<intval
> tMINUTE_UNIT tMONTH tMONTH_UNIT tORDINAL
216 %token
<intval
> tSEC_UNIT tYEAR_UNIT tZONE
218 %token
<textintval
> tSNUMBER tUNUMBER
219 %token
<timespec
> tSDECIMAL_NUMBER tUDECIMAL_NUMBER
221 %type
<intval
> o_colon_minutes o_merid
222 %type
<timespec
> seconds signed_seconds unsigned_seconds
235 pc
->timespec_seen
= true
;
246 { pc
->times_seen
++; }
248 { pc
->local_zones_seen
++; }
250 { pc
->zones_seen
++; }
252 { pc
->dates_seen
++; }
265 pc
->seconds.tv_sec
= 0;
266 pc
->seconds.tv_nsec
= 0;
269 | tUNUMBER
':' tUNUMBER o_merid
272 pc
->minutes
= $3.value
;
273 pc
->seconds.tv_sec
= 0;
274 pc
->seconds.tv_nsec
= 0;
277 | tUNUMBER
':' tUNUMBER tSNUMBER o_colon_minutes
280 pc
->minutes
= $3.value
;
281 pc
->seconds.tv_sec
= 0;
282 pc
->seconds.tv_nsec
= 0;
283 pc
->meridian
= MER24
;
285 pc
->time_zone
= time_zone_hhmm
($4, $5);
287 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds o_merid
290 pc
->minutes
= $3.value
;
294 | tUNUMBER
':' tUNUMBER
':' unsigned_seconds tSNUMBER o_colon_minutes
297 pc
->minutes
= $3.value
;
299 pc
->meridian
= MER24
;
301 pc
->time_zone
= time_zone_hhmm
($6, $7);
307 { pc
->local_isdst
= $1; }
309 { pc
->local_isdst
= $1 < 0 ?
1 : $1 + 1; }
314 { pc
->time_zone
= $1; }
315 | tZONE tSNUMBER o_colon_minutes
316 { pc
->time_zone
= $1 + time_zone_hhmm
($2, $3); }
318 { pc
->time_zone
= $1 + 60; }
320 { pc
->time_zone
= $1 + 60; }
336 pc
->day_ordinal
= $1;
341 pc
->day_ordinal
= $1.value
;
347 tUNUMBER
'/' tUNUMBER
349 pc
->month
= $1.value
;
352 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
354 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
355 otherwise as MM/DD/YY.
356 The goal in recognizing YYYY/MM/DD is solely to support legacy
357 machine-generated dates like those in an RCS log listing. If
358 you want portability, use the ISO 8601 format. */
362 pc
->month
= $3.value
;
367 pc
->month
= $1.value
;
372 | tUNUMBER tSNUMBER tSNUMBER
374 /* ISO 8601 format. YYYY-MM-DD. */
376 pc
->month
= -$2.value
;
379 | tUNUMBER tMONTH tSNUMBER
381 /* e.g. 17-JUN-1992. */
384 pc
->year.value
= -$3.value
;
385 pc
->year.digits
= $3.digits
;
387 | tMONTH tSNUMBER tSNUMBER
389 /* e.g. JUN-17-1992. */
392 pc
->year.value
= -$3.value
;
393 pc
->year.digits
= $3.digits
;
400 | tMONTH tUNUMBER
',' tUNUMBER
411 | tUNUMBER tMONTH tUNUMBER
422 pc
->rel_ns
= -pc
->rel_ns
;
423 pc
->rel_seconds
= -pc
->rel_seconds
;
424 pc
->rel_minutes
= -pc
->rel_minutes
;
425 pc
->rel_hour
= -pc
->rel_hour
;
426 pc
->rel_day
= -pc
->rel_day
;
427 pc
->rel_month
= -pc
->rel_month
;
428 pc
->rel_year
= -pc
->rel_year
;
435 { pc
->rel_year
+= $1 * $2; }
436 | tUNUMBER tYEAR_UNIT
437 { pc
->rel_year
+= $1.value
* $2; }
438 | tSNUMBER tYEAR_UNIT
439 { pc
->rel_year
+= $1.value
* $2; }
441 { pc
->rel_year
+= $1; }
442 | tORDINAL tMONTH_UNIT
443 { pc
->rel_month
+= $1 * $2; }
444 | tUNUMBER tMONTH_UNIT
445 { pc
->rel_month
+= $1.value
* $2; }
446 | tSNUMBER tMONTH_UNIT
447 { pc
->rel_month
+= $1.value
* $2; }
449 { pc
->rel_month
+= $1; }
451 { pc
->rel_day
+= $1 * $2; }
453 { pc
->rel_day
+= $1.value
* $2; }
455 { pc
->rel_day
+= $1.value
* $2; }
457 { pc
->rel_day
+= $1; }
458 | tORDINAL tHOUR_UNIT
459 { pc
->rel_hour
+= $1 * $2; }
460 | tUNUMBER tHOUR_UNIT
461 { pc
->rel_hour
+= $1.value
* $2; }
462 | tSNUMBER tHOUR_UNIT
463 { pc
->rel_hour
+= $1.value
* $2; }
465 { pc
->rel_hour
+= $1; }
466 | tORDINAL tMINUTE_UNIT
467 { pc
->rel_minutes
+= $1 * $2; }
468 | tUNUMBER tMINUTE_UNIT
469 { pc
->rel_minutes
+= $1.value
* $2; }
470 | tSNUMBER tMINUTE_UNIT
471 { pc
->rel_minutes
+= $1.value
* $2; }
473 { pc
->rel_minutes
+= $1; }
475 { pc
->rel_seconds
+= $1 * $2; }
477 { pc
->rel_seconds
+= $1.value
* $2; }
479 { pc
->rel_seconds
+= $1.value
* $2; }
480 | tSDECIMAL_NUMBER tSEC_UNIT
481 { pc
->rel_seconds
+= $1.tv_sec
* $2; pc
->rel_ns
+= $1.tv_nsec
* $2; }
482 | tUDECIMAL_NUMBER tSEC_UNIT
483 { pc
->rel_seconds
+= $1.tv_sec
* $2; pc
->rel_ns
+= $1.tv_nsec
* $2; }
485 { pc
->rel_seconds
+= $1; }
488 seconds: signed_seconds | unsigned_seconds
;
493 { $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
499 { $$.tv_sec
= $1.value
; $$.tv_nsec
= 0; }
506 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < $1.digits
))
513 pc
->day
= $1.value %
100;
514 pc
->month
= ($1.value
/ 100) %
100;
515 pc
->year.value
= $1.value
/ 10000;
516 pc
->year.digits
= $1.digits
- 4;
528 pc
->hour
= $1.value
/ 100;
529 pc
->minutes
= $1.value %
100;
531 pc
->seconds.tv_sec
= 0;
532 pc
->seconds.tv_nsec
= 0;
533 pc
->meridian
= MER24
;
555 static table
const meridian_table
[] =
557 { "AM", tMERIDIAN
, MERam
},
558 { "A.M.", tMERIDIAN
, MERam
},
559 { "PM", tMERIDIAN
, MERpm
},
560 { "P.M.", tMERIDIAN
, MERpm
},
564 static table
const dst_table
[] =
569 static table
const month_and_day_table
[] =
571 { "JANUARY", tMONTH
, 1 },
572 { "FEBRUARY", tMONTH
, 2 },
573 { "MARCH", tMONTH
, 3 },
574 { "APRIL", tMONTH
, 4 },
575 { "MAY", tMONTH
, 5 },
576 { "JUNE", tMONTH
, 6 },
577 { "JULY", tMONTH
, 7 },
578 { "AUGUST", tMONTH
, 8 },
579 { "SEPTEMBER",tMONTH
, 9 },
580 { "SEPT", tMONTH
, 9 },
581 { "OCTOBER", tMONTH
, 10 },
582 { "NOVEMBER", tMONTH
, 11 },
583 { "DECEMBER", tMONTH
, 12 },
584 { "SUNDAY", tDAY
, 0 },
585 { "MONDAY", tDAY
, 1 },
586 { "TUESDAY", tDAY
, 2 },
588 { "WEDNESDAY",tDAY
, 3 },
589 { "WEDNES", tDAY
, 3 },
590 { "THURSDAY", tDAY
, 4 },
592 { "THURS", tDAY
, 4 },
593 { "FRIDAY", tDAY
, 5 },
594 { "SATURDAY", tDAY
, 6 },
598 static table
const time_units_table
[] =
600 { "YEAR", tYEAR_UNIT
, 1 },
601 { "MONTH", tMONTH_UNIT
, 1 },
602 { "FORTNIGHT",tDAY_UNIT
, 14 },
603 { "WEEK", tDAY_UNIT
, 7 },
604 { "DAY", tDAY_UNIT
, 1 },
605 { "HOUR", tHOUR_UNIT
, 1 },
606 { "MINUTE", tMINUTE_UNIT
, 1 },
607 { "MIN", tMINUTE_UNIT
, 1 },
608 { "SECOND", tSEC_UNIT
, 1 },
609 { "SEC", tSEC_UNIT
, 1 },
613 /* Assorted relative-time words. */
614 static table
const relative_time_table
[] =
616 { "TOMORROW", tDAY_UNIT
, 1 },
617 { "YESTERDAY",tDAY_UNIT
, -1 },
618 { "TODAY", tDAY_UNIT
, 0 },
619 { "NOW", tDAY_UNIT
, 0 },
620 { "LAST", tORDINAL
, -1 },
621 { "THIS", tORDINAL
, 0 },
622 { "NEXT", tORDINAL
, 1 },
623 { "FIRST", tORDINAL
, 1 },
624 /*{ "SECOND", tORDINAL, 2 }, */
625 { "THIRD", tORDINAL
, 3 },
626 { "FOURTH", tORDINAL
, 4 },
627 { "FIFTH", tORDINAL
, 5 },
628 { "SIXTH", tORDINAL
, 6 },
629 { "SEVENTH", tORDINAL
, 7 },
630 { "EIGHTH", tORDINAL
, 8 },
631 { "NINTH", tORDINAL
, 9 },
632 { "TENTH", tORDINAL
, 10 },
633 { "ELEVENTH", tORDINAL
, 11 },
634 { "TWELFTH", tORDINAL
, 12 },
639 /* The time zone table. This table is necessarily incomplete, as time
640 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
641 as Eastern time in Australia, not as US Eastern Standard Time.
642 You cannot rely on getdate to handle arbitrary time zone
643 abbreviations; use numeric abbreviations like `-0500' instead. */
644 static table
const time_zone_table
[] =
646 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
647 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
648 { "UTC", tZONE
, HOUR
( 0) },
649 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
650 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
651 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
652 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
653 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
654 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
655 { "NST", tZONE
, -(HOUR
( 3) + 30) }, /* Newfoundland Standard */
656 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30) }, /* Newfoundland Daylight */
657 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
658 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
659 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
660 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
661 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
662 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
663 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
664 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
665 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
666 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
667 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
668 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
669 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
670 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
671 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
672 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
673 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
674 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
675 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
676 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
677 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
678 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
679 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
680 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
681 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
682 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
683 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
684 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
685 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
686 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
687 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
688 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
689 { "IST", tZONE
, (HOUR
( 5) + 30) }, /* India Standard */
690 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
691 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
692 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
693 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
694 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
695 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
699 /* Military time zone table. */
700 static table
const military_table
[] =
702 { "A", tZONE
, -HOUR
( 1) },
703 { "B", tZONE
, -HOUR
( 2) },
704 { "C", tZONE
, -HOUR
( 3) },
705 { "D", tZONE
, -HOUR
( 4) },
706 { "E", tZONE
, -HOUR
( 5) },
707 { "F", tZONE
, -HOUR
( 6) },
708 { "G", tZONE
, -HOUR
( 7) },
709 { "H", tZONE
, -HOUR
( 8) },
710 { "I", tZONE
, -HOUR
( 9) },
711 { "K", tZONE
, -HOUR
(10) },
712 { "L", tZONE
, -HOUR
(11) },
713 { "M", tZONE
, -HOUR
(12) },
714 { "N", tZONE
, HOUR
( 1) },
715 { "O", tZONE
, HOUR
( 2) },
716 { "P", tZONE
, HOUR
( 3) },
717 { "Q", tZONE
, HOUR
( 4) },
718 { "R", tZONE
, HOUR
( 5) },
719 { "S", tZONE
, HOUR
( 6) },
720 { "T", tZONE
, HOUR
( 7) },
721 { "U", tZONE
, HOUR
( 8) },
722 { "V", tZONE
, HOUR
( 9) },
723 { "W", tZONE
, HOUR
(10) },
724 { "X", tZONE
, HOUR
(11) },
725 { "Y", tZONE
, HOUR
(12) },
726 { "Z", tZONE
, HOUR
( 0) },
732 /* Convert a time zone expressed as HH:MM into an integer count of
733 minutes. If MM is negative, then S is of the form HHMM and needs
734 to be picked apart; otherwise, S is of the form HH. */
737 time_zone_hhmm
(textint s
, long int mm
)
740 return
(s.value
/ 100) * 60 + s.value %
100;
742 return s.value
* 60 + (s.negative ?
-mm
: mm
);
746 to_hour
(long int hours
, int meridian
)
750 default
: /* Pacify GCC. */
752 return
0 <= hours
&& hours
< 24 ? hours
: -1;
754 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
756 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
761 to_year
(textint textyear
)
763 long int year
= textyear.value
;
768 /* XPG4 suggests that years 00-68 map to 2000-2068, and
769 years 69-99 map to 1969-1999. */
770 else if
(textyear.digits
== 2)
771 year
+= year
< 69 ?
2000 : 1900;
777 lookup_zone
(parser_control
const *pc
, char const *name
)
781 /* Try local zone abbreviations first; they're more likely to be right. */
782 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
783 if
(strcmp
(name
, tp
->name
) == 0)
786 for
(tp
= time_zone_table
; tp
->name
; tp
++)
787 if
(strcmp
(name
, tp
->name
) == 0)
794 /* Yield the difference between *A and *B,
795 measured in seconds, ignoring leap seconds.
796 The body of this function is taken directly from the GNU C Library;
797 see src/strftime.c. */
799 tm_diff
(struct tm
const *a
, struct tm
const *b
)
801 /* Compute intervening leap days correctly even if year is negative.
802 Take care to avoid int overflow in leap day calculations. */
803 int a4
= SHR
(a
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (a
->tm_year
& 3);
804 int b4
= SHR
(b
->tm_year
, 2) + SHR
(TM_YEAR_BASE
, 2) - ! (b
->tm_year
& 3);
805 int a100
= a4
/ 25 - (a4 %
25 < 0);
806 int b100
= b4
/ 25 - (b4 %
25 < 0);
807 int a400
= SHR
(a100
, 2);
808 int b400
= SHR
(b100
, 2);
809 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
810 long int ayear
= a
->tm_year
;
811 long int years
= ayear
- b
->tm_year
;
812 long int days
= (365 * years
+ intervening_leap_days
813 + (a
->tm_yday
- b
->tm_yday
));
814 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
815 + (a
->tm_min
- b
->tm_min
))
816 + (a
->tm_sec
- b
->tm_sec
));
818 #endif /* ! HAVE_TM_GMTOFF */
821 lookup_word
(parser_control
const *pc
, char *word
)
830 /* Make it uppercase. */
831 for
(p
= word
; *p
; p
++)
833 unsigned char ch
= *p
;
838 for
(tp
= meridian_table
; tp
->name
; tp
++)
839 if
(strcmp
(word
, tp
->name
) == 0)
842 /* See if we have an abbreviation for a month. */
843 wordlen
= strlen
(word
);
844 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
846 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
847 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
850 if
((tp
= lookup_zone
(pc
, word
)))
853 if
(strcmp
(word
, dst_table
[0].name
) == 0)
856 for
(tp
= time_units_table
; tp
->name
; tp
++)
857 if
(strcmp
(word
, tp
->name
) == 0)
860 /* Strip off any plural and try the units table again. */
861 if
(word
[wordlen
- 1] == 'S')
863 word
[wordlen
- 1] = '\0';
864 for
(tp
= time_units_table
; tp
->name
; tp
++)
865 if
(strcmp
(word
, tp
->name
) == 0)
867 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
870 for
(tp
= relative_time_table
; tp
->name
; tp
++)
871 if
(strcmp
(word
, tp
->name
) == 0)
874 /* Military time zones. */
876 for
(tp
= military_table
; tp
->name
; tp
++)
877 if
(word
[0] == tp
->name
[0])
880 /* Drop out any periods and try the time zone table again. */
881 for
(period_found
= false
, p
= q
= word
; (*p
= *q
); q
++)
886 if
(period_found
&& (tp
= lookup_zone
(pc
, word
)))
893 yylex (YYSTYPE *lvalp
, parser_control
*pc
)
900 while
(c
= *pc
->input
, ISSPACE
(c
))
903 if
(ISDIGIT
(c
) || c
== '-' || c
== '+')
907 unsigned long int value
;
908 if
(c
== '-' || c
== '+')
910 sign
= c
== '-' ?
-1 : 1;
911 while
(c
= *++pc
->input
, ISSPACE
(c
))
914 /* skip the '-' sign */
920 for
(value
= 0; ; value
*= 10)
922 unsigned long int value1
= value
+ (c
- '0');
929 if
(ULONG_MAX
/ 10 < value
)
932 if
((c
== '.' || c
== ',') && ISDIGIT
(p
[1]))
937 unsigned long int value1
;
939 /* Check for overflow when converting value to time_t. */
957 /* Accumulate fraction, to ns precision. */
960 for
(digits
= 2; digits
<= LOG10_BILLION
; digits
++)
967 /* Skip excess digits, truncating toward -Infinity. */
969 for
(; ISDIGIT
(*p
); p
++)
978 /* Adjust to the timespec convention, which is that
979 tv_nsec is always a positive offset even if tv_sec is
989 lvalp
->timespec.tv_sec
= s
;
990 lvalp
->timespec.tv_nsec
= ns
;
992 return sign ? tSDECIMAL_NUMBER
: tUDECIMAL_NUMBER
;
996 lvalp
->textintval.negative
= sign
< 0;
999 lvalp
->textintval.value
= - value
;
1000 if
(0 < lvalp
->textintval.value
)
1005 lvalp
->textintval.value
= value
;
1006 if
(lvalp
->textintval.value
< 0)
1009 lvalp
->textintval.digits
= p
- pc
->input
;
1011 return sign ? tSNUMBER
: tUNUMBER
;
1023 if
(p
< buff
+ sizeof buff
- 1)
1027 while
(ISALPHA
(c
) || c
== '.');
1030 tp
= lookup_word
(pc
, buff
);
1033 lvalp
->intval
= tp
->value
;
1038 return
*pc
->input
++;
1054 /* Do nothing if the parser reports an error. */
1056 yyerror (parser_control
*pc ATTRIBUTE_UNUSED
, char *s ATTRIBUTE_UNUSED
)
1061 /* If *TM0 is the old and *TM1 is the new value of a struct tm after
1062 passing it to mktime, return true if it's OK that mktime returned T.
1063 It's not OK if *TM0 has out-of-range members. */
1066 mktime_ok
(struct tm
const *tm0
, struct tm
const *tm1
, time_t t
)
1068 if
(t
== (time_t) -1)
1070 /* Guard against falsely reporting an error when parsing a time
1071 stamp that happens to equal (time_t) -1, on a host that
1072 supports such a time stamp. */
1073 tm1
= localtime
(&t
);
1078 return
! ((tm0
->tm_sec ^ tm1
->tm_sec
)
1079 |
(tm0
->tm_min ^ tm1
->tm_min
)
1080 |
(tm0
->tm_hour ^ tm1
->tm_hour
)
1081 |
(tm0
->tm_mday ^ tm1
->tm_mday
)
1082 |
(tm0
->tm_mon ^ tm1
->tm_mon
)
1083 |
(tm0
->tm_year ^ tm1
->tm_year
));
1086 /* A reasonable upper bound for the size of ordinary TZ strings.
1087 Use heap allocation if TZ's length exceeds this. */
1088 enum { TZBUFSIZE
= 100 };
1090 /* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
1093 get_tz
(char tzbuf
[TZBUFSIZE
])
1095 char *tz
= getenv
("TZ");
1098 size_t tzsize
= strlen
(tz
) + 1;
1099 tz
= (tzsize
<= TZBUFSIZE
1100 ? memcpy
(tzbuf
, tz
, tzsize
)
1101 : xmemdup
(tz
, tzsize
));
1106 /* Parse a date/time string, storing the resulting time value into *RESULT.
1107 The string itself is pointed to by P. Return true if successful.
1108 P can be an incomplete or relative time specification; if so, use
1109 *NOW as the basis for the returned time. */
1111 get_date
(struct timespec
*result
, char const *p
, struct timespec
const *now
)
1115 struct tm
const *tmp
;
1119 struct timespec gettime_buffer
;
1121 bool tz_was_altered
= false
;
1123 char tz0buf
[TZBUFSIZE
];
1128 if
(gettime
(&gettime_buffer
) != 0)
1130 now
= &gettime_buffer
;
1133 Start
= now
->tv_sec
;
1134 Start_ns
= now
->tv_nsec
;
1136 tmp
= localtime
(&now
->tv_sec
);
1140 while
(c
= *p
, ISSPACE
(c
))
1143 if
(strncmp
(p
, "TZ=\"", 4) == 0)
1145 char const *tzbase
= p
+ 4;
1149 for
(s
= tzbase
; *s
; s
++, tzsize
++)
1153 if
(! (*s
== '\\' ||
*s
== '"'))
1160 char tz1buf
[TZBUFSIZE
];
1161 bool large_tz
= TZBUFSIZE
< tzsize
;
1163 tz0
= get_tz
(tz0buf
);
1164 z
= tz1
= large_tz ? xmalloc
(tzsize
) : tz1buf
;
1165 for
(s
= tzbase
; *s
!= '"'; s
++)
1166 *z
++ = *(s
+= *s
== '\\');
1168 setenv_ok
= setenv
("TZ", tz1
, 1) == 0;
1173 tz_was_altered
= true
;
1179 pc.year.value
= tmp
->tm_year
;
1180 pc.year.value
+= TM_YEAR_BASE
;
1182 pc.month
= tmp
->tm_mon
+ 1;
1183 pc.day
= tmp
->tm_mday
;
1184 pc.hour
= tmp
->tm_hour
;
1185 pc.minutes
= tmp
->tm_min
;
1186 pc.seconds.tv_sec
= tmp
->tm_sec
;
1187 pc.seconds.tv_nsec
= Start_ns
;
1188 tm.tm_isdst
= tmp
->tm_isdst
;
1190 pc.meridian
= MER24
;
1198 pc.timespec_seen
= false
;
1203 pc.local_zones_seen
= 0;
1206 #if HAVE_STRUCT_TM_TM_ZONE
1207 pc.local_time_zone_table
[0].name
= tmp
->tm_zone
;
1208 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
1209 pc.local_time_zone_table
[0].value
= tmp
->tm_isdst
;
1210 pc.local_time_zone_table
[1].name
= NULL
;
1212 /* Probe the names used in the next three calendar quarters, looking
1213 for a tm_isdst different from the one we already have. */
1216 for
(quarter
= 1; quarter
<= 3; quarter
++)
1218 time_t probe
= Start
+ quarter
* (90 * 24 * 60 * 60);
1219 struct tm
const *probe_tm
= localtime
(&probe
);
1220 if
(probe_tm
&& probe_tm
->tm_zone
1221 && probe_tm
->tm_isdst
!= pc.local_time_zone_table
[0].value
)
1224 pc.local_time_zone_table
[1].name
= probe_tm
->tm_zone
;
1225 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
1226 pc.local_time_zone_table
[1].value
= probe_tm
->tm_isdst
;
1227 pc.local_time_zone_table
[2].name
= NULL
;
1237 extern
char *tzname
[];
1240 for
(i
= 0; i
< 2; i
++)
1242 pc.local_time_zone_table
[i
].name
= tzname
[i
];
1243 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
1244 pc.local_time_zone_table
[i
].value
= i
;
1246 pc.local_time_zone_table
[i
].name
= NULL
;
1249 pc.local_time_zone_table
[0].name
= NULL
;
1253 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
1254 && ! strcmp
(pc.local_time_zone_table
[0].name
,
1255 pc.local_time_zone_table
[1].name
))
1257 /* This locale uses the same abbrevation for standard and
1258 daylight times. So if we see that abbreviation, we don't
1259 know whether it's daylight time. */
1260 pc.local_time_zone_table
[0].value
= -1;
1261 pc.local_time_zone_table
[1].name
= NULL
;
1264 if
(yyparse (&pc
) != 0)
1267 if
(pc.timespec_seen
)
1268 *result
= pc.seconds
;
1271 if
(1 < pc.times_seen ||
1 < pc.dates_seen ||
1 < pc.days_seen
1272 ||
1 < (pc.local_zones_seen
+ pc.zones_seen
)
1273 ||
(pc.local_zones_seen
&& 1 < pc.local_isdst
))
1276 tm.tm_year
= to_year
(pc.year
) - TM_YEAR_BASE
;
1277 tm.tm_mon
= pc.month
- 1;
1278 tm.tm_mday
= pc.day
;
1279 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
1281 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
1284 tm.tm_min
= pc.minutes
;
1285 tm.tm_sec
= pc.seconds.tv_sec
;
1289 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
1290 pc.seconds.tv_nsec
= 0;
1293 /* Let mktime deduce tm_isdst if we have an absolute time stamp. */
1294 if
(pc.dates_seen | pc.days_seen | pc.times_seen
)
1297 /* But if the input explicitly specifies local time with or without
1298 DST, give mktime that information. */
1299 if
(pc.local_zones_seen
)
1300 tm.tm_isdst
= pc.local_isdst
;
1304 Start
= mktime
(&tm
);
1306 if
(! mktime_ok
(&tm0
, &tm
, Start
))
1308 if
(! pc.zones_seen
)
1312 /* Guard against falsely reporting errors near the time_t
1313 boundaries when parsing times in other time zones. For
1314 example, suppose the input string "1969-12-31 23:00:00 -0100",
1315 the current time zone is 8 hours ahead of UTC, and the min
1316 time_t value is 1970-01-01 00:00:00 UTC. Then the min
1317 localtime value is 1970-01-01 08:00:00, and mktime will
1318 therefore fail on 1969-12-31 23:00:00. To work around the
1319 problem, set the time zone to 1 hour behind UTC temporarily
1320 by setting TZ="XXX1:00" and try mktime again. */
1322 long int time_zone
= pc.time_zone
;
1323 long int abs_time_zone
= time_zone
< 0 ?
- time_zone
: time_zone
;
1324 long int abs_time_zone_hour
= abs_time_zone
/ 60;
1325 int abs_time_zone_min
= abs_time_zone %
60;
1326 char tz1buf
[sizeof
"XXX+0:00"
1327 + sizeof pc.time_zone
* CHAR_BIT
/ 3];
1328 if
(!tz_was_altered
)
1329 tz0
= get_tz
(tz0buf
);
1330 sprintf
(tz1buf
, "XXX%s%ld:%02d", "-" + (time_zone
< 0),
1331 abs_time_zone_hour
, abs_time_zone_min
);
1332 if
(setenv
("TZ", tz1buf
, 1) != 0)
1334 tz_was_altered
= true
;
1336 Start
= mktime
(&tm
);
1337 if
(! mktime_ok
(&tm0
, &tm
, Start
))
1342 if
(pc.days_seen
&& ! pc.dates_seen
)
1344 tm.tm_mday
+= ((pc.day_number
- tm.tm_wday
+ 7) %
7
1345 + 7 * (pc.day_ordinal
- (0 < pc.day_ordinal
)));
1347 Start
= mktime
(&tm
);
1348 if
(Start
== (time_t) -1)
1354 long int delta
= pc.time_zone
* 60;
1356 #ifdef HAVE_TM_GMTOFF
1357 delta
-= tm.tm_gmtoff
;
1360 struct tm
const *gmt
= gmtime
(&t
);
1363 delta
-= tm_diff
(&tm
, gmt
);
1366 if
((Start
< t1
) != (delta
< 0))
1367 goto fail
; /* time_t overflow */
1371 /* Add relative date. */
1372 if
(pc.rel_year | pc.rel_month | pc.rel_day
)
1374 int year
= tm.tm_year
+ pc.rel_year
;
1375 int month
= tm.tm_mon
+ pc.rel_month
;
1376 int day
= tm.tm_mday
+ pc.rel_day
;
1377 if
(((year
< tm.tm_year
) ^
(pc.rel_year
< 0))
1378 |
((month
< tm.tm_mon
) ^
(pc.rel_month
< 0))
1379 |
((day
< tm.tm_mday
) ^
(pc.rel_day
< 0)))
1384 Start
= mktime
(&tm
);
1385 if
(Start
== (time_t) -1)
1389 /* Add relative hours, minutes, and seconds. On hosts that support
1390 leap seconds, ignore the possibility of leap seconds; e.g.,
1391 "+ 10 minutes" adds 600 seconds, even if one of them is a
1392 leap second. Typically this is not what the user wants, but it's
1393 too hard to do it the other way, because the time zone indicator
1394 must be applied before relative times, and if mktime is applied
1395 again the time zone will be lost. */
1397 long int sum_ns
= pc.seconds.tv_nsec
+ pc.rel_ns
;
1398 long int normalized_ns
= (sum_ns % BILLION
+ BILLION
) % BILLION
;
1400 long int d1
= 60 * 60 * pc.rel_hour
;
1401 time_t t1
= t0
+ d1
;
1402 long int d2
= 60 * pc.rel_minutes
;
1403 time_t t2
= t1
+ d2
;
1404 long int d3
= pc.rel_seconds
;
1405 time_t t3
= t2
+ d3
;
1406 long int d4
= (sum_ns
- normalized_ns
) / BILLION
;
1407 time_t t4
= t3
+ d4
;
1409 if
((d1
/ (60 * 60) ^ pc.rel_hour
)
1410 |
(d2
/ 60 ^ pc.rel_minutes
)
1411 |
((t1
< t0
) ^
(d1
< 0))
1412 |
((t2
< t1
) ^
(d2
< 0))
1413 |
((t3
< t2
) ^
(d3
< 0))
1414 |
((t4
< t3
) ^
(d4
< 0)))
1417 result
->tv_sec
= t4
;
1418 result
->tv_nsec
= normalized_ns
;
1428 ok
&= (tz0 ? setenv
("TZ", tz0
, 1) : unsetenv
("TZ")) == 0;
1437 main
(int ac
, char **av
)
1441 printf
("Enter date, or blank line to exit.\n\t> ");
1444 buff
[BUFSIZ
- 1] = '\0';
1445 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
1448 struct tm
const *tm
;
1449 if
(! get_date
(&d
, buff
, NULL
))
1450 printf
("Bad format - couldn't convert.\n");
1451 else if
(! (tm
= localtime
(&d.tv_sec
)))
1453 long int sec
= d.tv_sec
;
1454 printf
("localtime (%ld) failed\n", sec
);
1459 printf
("%04ld-%02d-%02d %02d:%02d:%02d.%09d\n",
1460 tm
->tm_year
+ 1900L, tm
->tm_mon
+ 1, tm
->tm_mday
,
1461 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
, ns
);