2 /* Parse a string into an internal time stamp.
3 Copyright (C) 1999, 2000, 2002 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, see <http://www.gnu.org/licenses/>. */
18 /* Originally written by Steven M. Bellovin <smb@research.att.com> while
19 at the University of North Carolina at Chapel Hill. Later tweaked by
20 a couple of people on Usenet. Completely overhauled by Rich $alz
21 <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
23 Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
24 the right thing about local DST. Unlike previous versions, this
25 version is reentrant. */
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. */
48 # include <stdlib.h> /* for `free'; used by Bison 1.27 */
51 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
52 # define IN_CTYPE_DOMAIN(c) 1
54 # define IN_CTYPE_DOMAIN(c) isascii (c)
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 says that only '0' through '9' are digits. Prefer ISDIGIT to
67 ISDIGIT_LOCALE unless it's important to use the locale's definition
68 of `digit' even when the host does not conform to POSIX. */
69 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
71 #if STDC_HEADERS || HAVE_STRING_H
75 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
76 # define __attribute__(x)
79 #ifndef ATTRIBUTE_UNUSED
80 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
83 #define EPOCH_YEAR 1970
84 #define TM_YEAR_BASE 1900
86 #define HOUR(x) ((x) * 60)
88 /* An integer value, and the number of digits in its textual
96 /* An entry in the lexical lookup table. */
104 /* Meridian: am, pm, or 24-hour style. */
105 enum { MERam
, MERpm
, MER24
};
107 /* Information passed to and from the parser. */
110 /* The input string remaining to be parsed. */
113 /* N, if this is the Nth Tuesday. */
116 /* Day of week; Sunday is 0. */
119 /* tm_isdst flag for the local zone. */
122 /* Time zone, in minutes east of UTC. */
125 /* Style used for time. */
128 /* Gregorian year, month, day, hour, minutes, and seconds. */
136 /* Relative year, month, day, hour, minutes, and seconds. */
144 /* Counts of nonterminals of various flavors parsed so far. */
147 int local_zones_seen
;
152 /* Table of local time zone abbrevations, terminated by a null entry. */
153 table local_time_zone_table
[3];
156 #define PC (* (parser_control *) parm)
157 #define YYLEX_PARAM parm
158 #define YYPARSE_PARAM parm
162 /* We want a reentrant parser. */
165 /* This grammar has 13 shift/reduce conflicts. */
176 static int yyerror(const char *);
177 static int yylex(YYSTYPE *, parser_control
*);
183 %token
<intval
> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
184 %token
<intval
> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
186 %token
<textintval
> tSNUMBER tUNUMBER
188 %type
<intval
> o_merid
201 { PC.local_zones_seen
++; }
221 | tUNUMBER
':' tUNUMBER o_merid
224 PC.minutes
= $3.value
;
228 | tUNUMBER
':' tUNUMBER tSNUMBER
231 PC.minutes
= $3.value
;
234 PC.time_zone
= $4.value %
100 + ($4.value
/ 100) * 60;
236 | tUNUMBER
':' tUNUMBER
':' tUNUMBER o_merid
239 PC.minutes
= $3.value
;
240 PC.seconds
= $5.value
;
243 | tUNUMBER
':' tUNUMBER
':' tUNUMBER tSNUMBER
246 PC.minutes
= $3.value
;
247 PC.seconds
= $5.value
;
250 PC.time_zone
= $6.value %
100 + ($6.value
/ 100) * 60;
256 { PC.local_isdst
= $1; }
258 { PC.local_isdst
= $1 < 0 ?
1 : $1 + 1; }
263 { PC.time_zone
= $1; }
265 { PC.time_zone
= $1 + 60; }
267 { PC.time_zone
= $1 + 60; }
283 PC.day_ordinal
= $1.value
;
289 tUNUMBER
'/' tUNUMBER
294 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
296 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
297 otherwise as MM/DD/YY.
298 The goal in recognizing YYYY/MM/DD is solely to support legacy
299 machine-generated dates like those in an RCS log listing. If
300 you want portability, use the ISO 8601 format. */
314 | tUNUMBER tSNUMBER tSNUMBER
316 /* ISO 8601 format. YYYY-MM-DD. */
318 PC.month
= -$2.value
;
321 | tUNUMBER tMONTH tSNUMBER
323 /* e.g. 17-JUN-1992. */
326 PC.year.value
= -$3.value
;
327 PC.year.digits
= $3.digits
;
334 | tMONTH tUNUMBER
',' tUNUMBER
345 | tUNUMBER tMONTH tUNUMBER
356 PC.rel_seconds
= -PC.rel_seconds
;
357 PC.rel_minutes
= -PC.rel_minutes
;
358 PC.rel_hour
= -PC.rel_hour
;
359 PC.rel_day
= -PC.rel_day
;
360 PC.rel_month
= -PC.rel_month
;
361 PC.rel_year
= -PC.rel_year
;
368 { PC.rel_year
+= $1.value
* $2; }
369 | tSNUMBER tYEAR_UNIT
370 { PC.rel_year
+= $1.value
* $2; }
372 { PC.rel_year
+= $1; }
373 | tUNUMBER tMONTH_UNIT
374 { PC.rel_month
+= $1.value
* $2; }
375 | tSNUMBER tMONTH_UNIT
376 { PC.rel_month
+= $1.value
* $2; }
378 { PC.rel_month
+= $1; }
380 { PC.rel_day
+= $1.value
* $2; }
382 { PC.rel_day
+= $1.value
* $2; }
384 { PC.rel_day
+= $1; }
385 | tUNUMBER tHOUR_UNIT
386 { PC.rel_hour
+= $1.value
* $2; }
387 | tSNUMBER tHOUR_UNIT
388 { PC.rel_hour
+= $1.value
* $2; }
390 { PC.rel_hour
+= $1; }
391 | tUNUMBER tMINUTE_UNIT
392 { PC.rel_minutes
+= $1.value
* $2; }
393 | tSNUMBER tMINUTE_UNIT
394 { PC.rel_minutes
+= $1.value
* $2; }
396 { PC.rel_minutes
+= $1; }
398 { PC.rel_seconds
+= $1.value
* $2; }
400 { PC.rel_seconds
+= $1.value
* $2; }
402 { PC.rel_seconds
+= $1; }
409 && ! PC.rels_seen
&& (PC.times_seen ||
2 < $1.digits
))
416 PC.day
= $1.value %
100;
417 PC.month
= ($1.value
/ 100) %
100;
418 PC.year.value
= $1.value
/ 10000;
419 PC.year.digits
= $1.digits
- 4;
431 PC.hour
= $1.value
/ 100;
432 PC.minutes
= $1.value %
100;
450 /* Include this file down here because bison inserts code above which
451 may define-away `const'. We want the prototype for get_date to have
452 the same signature as the function definition. */
453 #include "modules/getdate.h"
456 struct tm
*gmtime
(const time_t *);
459 struct tm
*localtime
(const time_t *);
462 time_t mktime
(struct tm
*);
465 static table
const meridian_table
[] =
467 { "AM", tMERIDIAN
, MERam
},
468 { "A.M.", tMERIDIAN
, MERam
},
469 { "PM", tMERIDIAN
, MERpm
},
470 { "P.M.", tMERIDIAN
, MERpm
},
474 static table
const dst_table
[] =
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 },
498 { "WEDNESDAY",tDAY
, 3 },
499 { "WEDNES", tDAY
, 3 },
500 { "THURSDAY", tDAY
, 4 },
502 { "THURS", tDAY
, 4 },
503 { "FRIDAY", tDAY
, 5 },
504 { "SATURDAY", tDAY
, 6 },
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 },
523 /* Assorted relative-time words. */
524 static table
const relative_time_table
[] =
526 { "TOMORROW", tMINUTE_UNIT
, 24 * 60 },
527 { "YESTERDAY",tMINUTE_UNIT
, - (24 * 60) },
528 { "TODAY", tMINUTE_UNIT
, 0 },
529 { "NOW", tMINUTE_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 },
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 */
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) },
643 to_hour
(int hours
, int meridian
)
648 return
0 <= hours
&& hours
< 24 ? hours
: -1;
650 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
652 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
661 to_year
(textint textyear
)
663 int year
= textyear.value
;
668 /* XPG4 suggests that years 00-68 map to 2000-2068, and
669 years 69-99 map to 1969-1999. */
670 if
(textyear.digits
== 2)
671 year
+= year
< 69 ?
2000 : 1900;
677 lookup_zone
(parser_control
const *pc
, char const *name
)
681 /* Try local zone abbreviations first; they're more likely to be right. */
682 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
683 if
(strcmp
(name
, tp
->name
) == 0)
686 for
(tp
= time_zone_table
; tp
->name
; tp
++)
687 if
(strcmp
(name
, tp
->name
) == 0)
694 /* Yield the difference between *A and *B,
695 measured in seconds, ignoring leap seconds.
696 The body of this function is taken directly from the GNU C Library;
697 see src/strftime.c. */
699 tm_diff
(struct tm
const *a
, struct tm
const *b
)
701 /* Compute intervening leap days correctly even if year is negative.
702 Take care to avoid int overflow in leap day calculations,
703 but it's OK to assume that A and B are close to each other. */
704 int a4
= (a
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (a
->tm_year
& 3);
705 int b4
= (b
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (b
->tm_year
& 3);
706 int a100
= a4
/ 25 - (a4 %
25 < 0);
707 int b100
= b4
/ 25 - (b4 %
25 < 0);
708 int a400
= a100
>> 2;
709 int b400
= b100
>> 2;
710 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
711 int years
= a
->tm_year
- b
->tm_year
;
712 int days
= (365 * years
+ intervening_leap_days
713 + (a
->tm_yday
- b
->tm_yday
));
714 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
715 + (a
->tm_min
- b
->tm_min
))
716 + (a
->tm_sec
- b
->tm_sec
));
718 #endif /* ! HAVE_TM_GMTOFF */
721 lookup_word
(parser_control
const *pc
, char *word
)
730 /* Make it uppercase. */
731 for
(p
= word
; *p
; p
++)
732 if
(ISLOWER
((unsigned char) *p
))
733 *p
= toupper
((unsigned char) *p
);
735 for
(tp
= meridian_table
; tp
->name
; tp
++)
736 if
(strcmp
(word
, tp
->name
) == 0)
739 /* See if we have an abbreviation for a month. */
740 wordlen
= strlen
(word
);
741 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
743 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
744 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
747 if
((tp
= lookup_zone
(pc
, word
)))
750 if
(strcmp
(word
, dst_table
[0].name
) == 0)
753 for
(tp
= time_units_table
; tp
->name
; tp
++)
754 if
(strcmp
(word
, tp
->name
) == 0)
757 /* Strip off any plural and try the units table again. */
758 if
(word
[wordlen
- 1] == 'S')
760 word
[wordlen
- 1] = '\0';
761 for
(tp
= time_units_table
; tp
->name
; tp
++)
762 if
(strcmp
(word
, tp
->name
) == 0)
764 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
767 for
(tp
= relative_time_table
; tp
->name
; tp
++)
768 if
(strcmp
(word
, tp
->name
) == 0)
771 /* Military time zones. */
773 for
(tp
= military_table
; tp
->name
; tp
++)
774 if
(word
[0] == tp
->name
[0])
777 /* Drop out any periods and try the time zone table again. */
778 for
(i
= 0, p
= q
= word
; (*p
= *q
); q
++)
783 if
(i
&& (tp
= lookup_zone
(pc
, word
)))
790 yylex (YYSTYPE *lvalp
, parser_control
*pc
)
797 while
(c
= *pc
->input
, ISSPACE
(c
))
800 if
(ISDIGIT
(c
) || c
== '-' || c
== '+')
805 if
(c
== '-' || c
== '+')
807 sign
= c
== '-' ?
-1 : 1;
810 /* skip the '-' sign */
819 value
= 10 * value
+ c
- '0';
823 lvalp
->textintval.value
= sign
< 0 ?
-value
: value
;
824 lvalp
->textintval.digits
= p
- pc
->input
;
826 return sign ? tSNUMBER
: tUNUMBER
;
837 if
(p
< buff
+ sizeof buff
- 1)
841 while
(ISALPHA
(c
) || c
== '.');
844 tp
= lookup_word
(pc
, buff
);
847 lvalp
->intval
= tp
->value
;
868 /* Do nothing if the parser reports an error. */
870 yyerror (const char *s ATTRIBUTE_UNUSED
)
875 /* Parse a date/time string P. Return the corresponding time_t value,
876 or (time_t) -1 if there is an error. P can be an incomplete or
877 relative time specification; if so, use *NOW as the basis for the
880 get_date
(const char *p
, const time_t *now
)
882 time_t Start
= now ?
*now
: time
(0);
883 struct tm
*tmp
= localtime
(&Start
);
892 pc.year.value
= tmp
->tm_year
+ TM_YEAR_BASE
;
894 pc.month
= tmp
->tm_mon
+ 1;
895 pc.day
= tmp
->tm_mday
;
896 pc.hour
= tmp
->tm_hour
;
897 pc.minutes
= tmp
->tm_min
;
898 pc.seconds
= tmp
->tm_sec
;
899 tm.tm_isdst
= tmp
->tm_isdst
;
912 pc.local_zones_seen
= 0;
915 #if HAVE_STRUCT_TM_TM_ZONE
916 pc.local_time_zone_table
[0].name
= tmp
->tm_zone
;
917 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
918 pc.local_time_zone_table
[0].value
= tmp
->tm_isdst
;
919 pc.local_time_zone_table
[1].name
= 0;
921 /* Probe the names used in the next three calendar quarters, looking
922 for a tm_isdst different from the one we already have. */
925 for
(quarter
= 1; quarter
<= 3; quarter
++)
927 time_t probe
= Start
+ quarter
* (90 * 24 * 60 * 60);
928 struct tm
*probe_tm
= localtime
(&probe
);
929 if
(probe_tm
&& probe_tm
->tm_zone
930 && probe_tm
->tm_isdst
!= pc.local_time_zone_table
[0].value
)
933 pc.local_time_zone_table
[1].name
= probe_tm
->tm_zone
;
934 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
935 pc.local_time_zone_table
[1].value
= probe_tm
->tm_isdst
;
936 pc.local_time_zone_table
[2].name
= 0;
946 extern
char *tzname
[];
949 for
(i
= 0; i
< 2; i
++)
951 pc.local_time_zone_table
[i
].name
= tzname
[i
];
952 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
953 pc.local_time_zone_table
[i
].value
= i
;
955 pc.local_time_zone_table
[i
].name
= 0;
958 pc.local_time_zone_table
[0].name
= 0;
962 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
963 && ! strcmp
(pc.local_time_zone_table
[0].name
,
964 pc.local_time_zone_table
[1].name
))
966 /* This locale uses the same abbrevation for standard and
967 daylight times. So if we see that abbreviation, we don't
968 know whether it's daylight time. */
969 pc.local_time_zone_table
[0].value
= -1;
970 pc.local_time_zone_table
[1].name
= 0;
973 if
(yyparse (&pc
) != 0
974 ||
1 < pc.times_seen ||
1 < pc.dates_seen ||
1 < pc.days_seen
975 ||
1 < (pc.local_zones_seen
+ pc.zones_seen
)
976 ||
(pc.local_zones_seen
&& 1 < pc.local_isdst
))
979 tm.tm_year
= to_year
(pc.year
) - TM_YEAR_BASE
+ pc.rel_year
;
980 tm.tm_mon
= pc.month
- 1 + pc.rel_month
;
981 tm.tm_mday
= pc.day
+ pc.rel_day
;
982 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
984 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
987 tm.tm_min
= pc.minutes
;
988 tm.tm_sec
= pc.seconds
;
992 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
995 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
996 or if the relative time stamp mentions days, months, or years. */
997 if
(pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
998 | pc.rel_month | pc.rel_year
)
1001 /* But if the input explicitly specifies local time with or without
1002 DST, give mktime that information. */
1003 if
(pc.local_zones_seen
)
1004 tm.tm_isdst
= pc.local_isdst
;
1008 Start
= mktime
(&tm
);
1010 if
(Start
== (time_t) -1)
1013 /* Guard against falsely reporting errors near the time_t boundaries
1014 when parsing times in other time zones. For example, if the min
1015 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1016 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1017 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1018 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1019 zone by 24 hours to compensate. This algorithm assumes that
1020 there is no DST transition within a day of the time_t boundaries. */
1024 if
(tm.tm_year
<= EPOCH_YEAR
- TM_YEAR_BASE
)
1027 pc.time_zone
+= 24 * 60;
1032 pc.time_zone
-= 24 * 60;
1034 Start
= mktime
(&tm
);
1037 if
(Start
== (time_t) -1)
1041 if
(pc.days_seen
&& ! pc.dates_seen
)
1043 tm.tm_mday
+= ((pc.day_number
- tm.tm_wday
+ 7) %
7
1044 + 7 * (pc.day_ordinal
- (0 < pc.day_ordinal
)));
1046 Start
= mktime
(&tm
);
1047 if
(Start
== (time_t) -1)
1053 int delta
= pc.time_zone
* 60;
1054 #ifdef HAVE_TM_GMTOFF
1055 delta
-= tm.tm_gmtoff
;
1057 struct tm
*gmt
= gmtime
(&Start
);
1060 delta
-= tm_diff
(&tm
, gmt
);
1062 if
((Start
< Start
- delta
) != (delta
< 0))
1063 return
-1; /* time_t overflow */
1067 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1068 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1069 leap second. Typically this is not what the user wants, but it's
1070 too hard to do it the other way, because the time zone indicator
1071 must be applied before relative times, and if mktime is applied
1072 again the time zone will be lost. */
1075 long d1
= 60 * 60 * (long) pc.rel_hour
;
1076 time_t t1
= t0
+ d1
;
1077 long d2
= 60 * (long) pc.rel_minutes
;
1078 time_t t2
= t1
+ d2
;
1079 int d3
= pc.rel_seconds
;
1080 time_t t3
= t2
+ d3
;
1081 if
((d1
/ (60 * 60) ^ pc.rel_hour
)
1082 |
(d2
/ 60 ^ pc.rel_minutes
)
1083 |
((t0
+ d1
< t0
) ^
(d1
< 0))
1084 |
((t1
+ d2
< t1
) ^
(d2
< 0))
1085 |
((t2
+ d3
< t2
) ^
(d3
< 0)))
1098 main
(int ac
, char **av
)
1103 printf
("Enter date, or blank line to exit.\n\t> ");
1106 buff
[BUFSIZ
- 1] = 0;
1107 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
1109 d
= get_date
(buff
, 0);
1110 if
(d
== (time_t) -1)
1111 printf
("Bad format - couldn't convert.\n");
1113 printf
("%s", ctime
(&d
));
1119 #endif /* defined TEST */