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)
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. */
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. */
47 # include <stdlib.h> /* for `free'; used by Bison 1.27 */
50 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
51 # define IN_CTYPE_DOMAIN(c) 1
53 # define IN_CTYPE_DOMAIN(c) isascii (c)
56 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
57 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
58 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
59 #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
61 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
62 - Its arg may be any int or unsigned int; it need not be an unsigned char.
63 - It's guaranteed to evaluate its argument exactly once.
64 - It's typically faster.
65 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
66 ISDIGIT_LOCALE unless it's important to use the locale's definition
67 of `digit' even when the host does not conform to POSIX. */
68 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
70 #if STDC_HEADERS || HAVE_STRING_H
74 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
75 # define __attribute__(x)
78 #ifndef ATTRIBUTE_UNUSED
79 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
82 #define EPOCH_YEAR 1970
83 #define TM_YEAR_BASE 1900
85 #define HOUR(x) ((x) * 60)
87 /* An integer value, and the number of digits in its textual
95 /* An entry in the lexical lookup table. */
103 /* Meridian: am, pm, or 24-hour style. */
104 enum { MERam
, MERpm
, MER24
};
106 /* Information passed to and from the parser. */
109 /* The input string remaining to be parsed. */
112 /* N, if this is the Nth Tuesday. */
115 /* Day of week; Sunday is 0. */
118 /* tm_isdst flag for the local zone. */
121 /* Time zone, in minutes east of UTC. */
124 /* Style used for time. */
127 /* Gregorian year, month, day, hour, minutes, and seconds. */
135 /* Relative year, month, day, hour, minutes, and seconds. */
143 /* Counts of nonterminals of various flavors parsed so far. */
146 int local_zones_seen
;
151 /* Table of local time zone abbrevations, terminated by a null entry. */
152 table local_time_zone_table
[3];
155 #define PC (* (parser_control *) parm)
156 #define YYLEX_PARAM parm
157 #define YYPARSE_PARAM parm
159 static int yyerror ();
164 /* We want a reentrant parser. */
167 /* This grammar has 13 shift/reduce conflicts. */
178 %token
<intval
> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
179 %token
<intval
> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
181 %token
<textintval
> tSNUMBER tUNUMBER
183 %type
<intval
> o_merid
196 { PC.local_zones_seen
++; }
216 | tUNUMBER
':' tUNUMBER o_merid
219 PC.minutes
= $3.value
;
223 | tUNUMBER
':' tUNUMBER tSNUMBER
226 PC.minutes
= $3.value
;
229 PC.time_zone
= $4.value %
100 + ($4.value
/ 100) * 60;
231 | tUNUMBER
':' tUNUMBER
':' tUNUMBER o_merid
234 PC.minutes
= $3.value
;
235 PC.seconds
= $5.value
;
238 | tUNUMBER
':' tUNUMBER
':' tUNUMBER tSNUMBER
241 PC.minutes
= $3.value
;
242 PC.seconds
= $5.value
;
245 PC.time_zone
= $6.value %
100 + ($6.value
/ 100) * 60;
251 { PC.local_isdst
= $1; }
253 { PC.local_isdst
= $1 < 0 ?
1 : $1 + 1; }
258 { PC.time_zone
= $1; }
260 { PC.time_zone
= $1 + 60; }
262 { PC.time_zone
= $1 + 60; }
278 PC.day_ordinal
= $1.value
;
284 tUNUMBER
'/' tUNUMBER
289 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
291 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
292 otherwise as MM/DD/YY.
293 The goal in recognizing YYYY/MM/DD is solely to support legacy
294 machine-generated dates like those in an RCS log listing. If
295 you want portability, use the ISO 8601 format. */
309 | tUNUMBER tSNUMBER tSNUMBER
311 /* ISO 8601 format. YYYY-MM-DD. */
313 PC.month
= -$2.value
;
316 | tUNUMBER tMONTH tSNUMBER
318 /* e.g. 17-JUN-1992. */
321 PC.year.value
= -$3.value
;
322 PC.year.digits
= $3.digits
;
329 | tMONTH tUNUMBER
',' tUNUMBER
340 | tUNUMBER tMONTH tUNUMBER
351 PC.rel_seconds
= -PC.rel_seconds
;
352 PC.rel_minutes
= -PC.rel_minutes
;
353 PC.rel_hour
= -PC.rel_hour
;
354 PC.rel_day
= -PC.rel_day
;
355 PC.rel_month
= -PC.rel_month
;
356 PC.rel_year
= -PC.rel_year
;
363 { PC.rel_year
+= $1.value
* $2; }
364 | tSNUMBER tYEAR_UNIT
365 { PC.rel_year
+= $1.value
* $2; }
367 { PC.rel_year
+= $1; }
368 | tUNUMBER tMONTH_UNIT
369 { PC.rel_month
+= $1.value
* $2; }
370 | tSNUMBER tMONTH_UNIT
371 { PC.rel_month
+= $1.value
* $2; }
373 { PC.rel_month
+= $1; }
375 { PC.rel_day
+= $1.value
* $2; }
377 { PC.rel_day
+= $1.value
* $2; }
379 { PC.rel_day
+= $1; }
380 | tUNUMBER tHOUR_UNIT
381 { PC.rel_hour
+= $1.value
* $2; }
382 | tSNUMBER tHOUR_UNIT
383 { PC.rel_hour
+= $1.value
* $2; }
385 { PC.rel_hour
+= $1; }
386 | tUNUMBER tMINUTE_UNIT
387 { PC.rel_minutes
+= $1.value
* $2; }
388 | tSNUMBER tMINUTE_UNIT
389 { PC.rel_minutes
+= $1.value
* $2; }
391 { PC.rel_minutes
+= $1; }
393 { PC.rel_seconds
+= $1.value
* $2; }
395 { PC.rel_seconds
+= $1.value
* $2; }
397 { PC.rel_seconds
+= $1; }
404 && ! PC.rels_seen
&& (PC.times_seen ||
2 < $1.digits
))
411 PC.day
= $1.value %
100;
412 PC.month
= ($1.value
/ 100) %
100;
413 PC.year.value
= $1.value
/ 10000;
414 PC.year.digits
= $1.digits
- 4;
426 PC.hour
= $1.value
/ 100;
427 PC.minutes
= $1.value %
100;
445 /* Include this file down here because bison inserts code above which
446 may define-away `const'. We want the prototype for get_date to have
447 the same signature as the function definition. */
449 #include "unlocked-io.h"
452 struct tm
*gmtime
();
455 struct tm
*localtime
();
461 static table
const meridian_table
[] =
463 { "AM", tMERIDIAN
, MERam
},
464 { "A.M.", tMERIDIAN
, MERam
},
465 { "PM", tMERIDIAN
, MERpm
},
466 { "P.M.", tMERIDIAN
, MERpm
},
470 static table
const dst_table
[] =
475 static table
const month_and_day_table
[] =
477 { "JANUARY", tMONTH
, 1 },
478 { "FEBRUARY", tMONTH
, 2 },
479 { "MARCH", tMONTH
, 3 },
480 { "APRIL", tMONTH
, 4 },
481 { "MAY", tMONTH
, 5 },
482 { "JUNE", tMONTH
, 6 },
483 { "JULY", tMONTH
, 7 },
484 { "AUGUST", tMONTH
, 8 },
485 { "SEPTEMBER",tMONTH
, 9 },
486 { "SEPT", tMONTH
, 9 },
487 { "OCTOBER", tMONTH
, 10 },
488 { "NOVEMBER", tMONTH
, 11 },
489 { "DECEMBER", tMONTH
, 12 },
490 { "SUNDAY", tDAY
, 0 },
491 { "MONDAY", tDAY
, 1 },
492 { "TUESDAY", tDAY
, 2 },
494 { "WEDNESDAY",tDAY
, 3 },
495 { "WEDNES", tDAY
, 3 },
496 { "THURSDAY", tDAY
, 4 },
498 { "THURS", tDAY
, 4 },
499 { "FRIDAY", tDAY
, 5 },
500 { "SATURDAY", tDAY
, 6 },
504 static table
const time_units_table
[] =
506 { "YEAR", tYEAR_UNIT
, 1 },
507 { "MONTH", tMONTH_UNIT
, 1 },
508 { "FORTNIGHT",tDAY_UNIT
, 14 },
509 { "WEEK", tDAY_UNIT
, 7 },
510 { "DAY", tDAY_UNIT
, 1 },
511 { "HOUR", tHOUR_UNIT
, 1 },
512 { "MINUTE", tMINUTE_UNIT
, 1 },
513 { "MIN", tMINUTE_UNIT
, 1 },
514 { "SECOND", tSEC_UNIT
, 1 },
515 { "SEC", tSEC_UNIT
, 1 },
519 /* Assorted relative-time words. */
520 static table
const relative_time_table
[] =
522 { "TOMORROW", tMINUTE_UNIT
, 24 * 60 },
523 { "YESTERDAY",tMINUTE_UNIT
, - (24 * 60) },
524 { "TODAY", tMINUTE_UNIT
, 0 },
525 { "NOW", tMINUTE_UNIT
, 0 },
526 { "LAST", tUNUMBER
, -1 },
527 { "THIS", tUNUMBER
, 0 },
528 { "NEXT", tUNUMBER
, 1 },
529 { "FIRST", tUNUMBER
, 1 },
530 /*{ "SECOND", tUNUMBER, 2 }, */
531 { "THIRD", tUNUMBER
, 3 },
532 { "FOURTH", tUNUMBER
, 4 },
533 { "FIFTH", tUNUMBER
, 5 },
534 { "SIXTH", tUNUMBER
, 6 },
535 { "SEVENTH", tUNUMBER
, 7 },
536 { "EIGHTH", tUNUMBER
, 8 },
537 { "NINTH", tUNUMBER
, 9 },
538 { "TENTH", tUNUMBER
, 10 },
539 { "ELEVENTH", tUNUMBER
, 11 },
540 { "TWELFTH", tUNUMBER
, 12 },
545 /* The time zone table. This table is necessarily incomplete, as time
546 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
547 as Eastern time in Australia, not as US Eastern Standard Time.
548 You cannot rely on getdate to handle arbitrary time zone
549 abbreviations; use numeric abbreviations like `-0500' instead. */
550 static table
const time_zone_table
[] =
552 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
553 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
554 { "UTC", tZONE
, HOUR
( 0) },
555 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
556 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
557 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
558 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
559 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
560 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
561 { "NST", tZONE
, -(HOUR
( 3) + 30) }, /* Newfoundland Standard */
562 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30) }, /* Newfoundland Daylight */
563 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
564 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
565 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
566 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
567 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
568 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
569 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
570 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
571 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
572 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
573 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
574 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
575 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
576 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
577 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
578 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
579 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
580 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
581 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
582 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
583 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
584 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
585 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
586 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
587 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
588 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
589 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
590 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
591 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
592 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
593 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
594 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
595 { "IST", tZONE
, (HOUR
( 5) + 30) }, /* India Standard */
596 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
597 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
598 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
599 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
600 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
601 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
605 /* Military time zone table. */
606 static table
const military_table
[] =
608 { "A", tZONE
, -HOUR
( 1) },
609 { "B", tZONE
, -HOUR
( 2) },
610 { "C", tZONE
, -HOUR
( 3) },
611 { "D", tZONE
, -HOUR
( 4) },
612 { "E", tZONE
, -HOUR
( 5) },
613 { "F", tZONE
, -HOUR
( 6) },
614 { "G", tZONE
, -HOUR
( 7) },
615 { "H", tZONE
, -HOUR
( 8) },
616 { "I", tZONE
, -HOUR
( 9) },
617 { "K", tZONE
, -HOUR
(10) },
618 { "L", tZONE
, -HOUR
(11) },
619 { "M", tZONE
, -HOUR
(12) },
620 { "N", tZONE
, HOUR
( 1) },
621 { "O", tZONE
, HOUR
( 2) },
622 { "P", tZONE
, HOUR
( 3) },
623 { "Q", tZONE
, HOUR
( 4) },
624 { "R", tZONE
, HOUR
( 5) },
625 { "S", tZONE
, HOUR
( 6) },
626 { "T", tZONE
, HOUR
( 7) },
627 { "U", tZONE
, HOUR
( 8) },
628 { "V", tZONE
, HOUR
( 9) },
629 { "W", tZONE
, HOUR
(10) },
630 { "X", tZONE
, HOUR
(11) },
631 { "Y", tZONE
, HOUR
(12) },
632 { "Z", tZONE
, HOUR
( 0) },
639 to_hour
(int hours
, int meridian
)
644 return
0 <= hours
&& hours
< 24 ? hours
: -1;
646 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
648 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
656 to_year
(textint textyear
)
658 int year
= textyear.value
;
663 /* XPG4 suggests that years 00-68 map to 2000-2068, and
664 years 69-99 map to 1969-1999. */
665 if
(textyear.digits
== 2)
666 year
+= year
< 69 ?
2000 : 1900;
672 lookup_zone
(parser_control
const *pc
, char const *name
)
676 /* Try local zone abbreviations first; they're more likely to be right. */
677 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
678 if
(strcmp
(name
, tp
->name
) == 0)
681 for
(tp
= time_zone_table
; tp
->name
; tp
++)
682 if
(strcmp
(name
, tp
->name
) == 0)
689 /* Yield the difference between *A and *B,
690 measured in seconds, ignoring leap seconds.
691 The body of this function is taken directly from the GNU C Library;
692 see src/strftime.c. */
694 tm_diff
(struct tm
const *a
, struct tm
const *b
)
696 /* Compute intervening leap days correctly even if year is negative.
697 Take care to avoid int overflow in leap day calculations,
698 but it's OK to assume that A and B are close to each other. */
699 int a4
= (a
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (a
->tm_year
& 3);
700 int b4
= (b
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (b
->tm_year
& 3);
701 int a100
= a4
/ 25 - (a4 %
25 < 0);
702 int b100
= b4
/ 25 - (b4 %
25 < 0);
703 int a400
= a100
>> 2;
704 int b400
= b100
>> 2;
705 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
706 int years
= a
->tm_year
- b
->tm_year
;
707 int days
= (365 * years
+ intervening_leap_days
708 + (a
->tm_yday
- b
->tm_yday
));
709 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
710 + (a
->tm_min
- b
->tm_min
))
711 + (a
->tm_sec
- b
->tm_sec
));
713 #endif /* ! HAVE_TM_GMTOFF */
716 lookup_word
(parser_control
const *pc
, char *word
)
725 /* Make it uppercase. */
726 for
(p
= word
; *p
; p
++)
727 if
(ISLOWER
((unsigned char) *p
))
728 *p
= toupper
((unsigned char) *p
);
730 for
(tp
= meridian_table
; tp
->name
; tp
++)
731 if
(strcmp
(word
, tp
->name
) == 0)
734 /* See if we have an abbreviation for a month. */
735 wordlen
= strlen
(word
);
736 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
738 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
739 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
742 if
((tp
= lookup_zone
(pc
, word
)))
745 if
(strcmp
(word
, dst_table
[0].name
) == 0)
748 for
(tp
= time_units_table
; tp
->name
; tp
++)
749 if
(strcmp
(word
, tp
->name
) == 0)
752 /* Strip off any plural and try the units table again. */
753 if
(word
[wordlen
- 1] == 'S')
755 word
[wordlen
- 1] = '\0';
756 for
(tp
= time_units_table
; tp
->name
; tp
++)
757 if
(strcmp
(word
, tp
->name
) == 0)
759 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
762 for
(tp
= relative_time_table
; tp
->name
; tp
++)
763 if
(strcmp
(word
, tp
->name
) == 0)
766 /* Military time zones. */
768 for
(tp
= military_table
; tp
->name
; tp
++)
769 if
(word
[0] == tp
->name
[0])
772 /* Drop out any periods and try the time zone table again. */
773 for
(i
= 0, p
= q
= word
; (*p
= *q
); q
++)
778 if
(i
&& (tp
= lookup_zone
(pc
, word
)))
785 yylex (YYSTYPE *lvalp
, parser_control
*pc
)
792 while
(c
= *pc
->input
, ISSPACE
(c
))
795 if
(ISDIGIT
(c
) || c
== '-' || c
== '+')
800 if
(c
== '-' || c
== '+')
802 sign
= c
== '-' ?
-1 : 1;
805 /* skip the '-' sign */
814 value
= 10 * value
+ c
- '0';
818 lvalp
->textintval.value
= sign
< 0 ?
-value
: value
;
819 lvalp
->textintval.digits
= p
- pc
->input
;
821 return sign ? tSNUMBER
: tUNUMBER
;
832 if
(p
< buff
+ sizeof buff
- 1)
836 while
(ISALPHA
(c
) || c
== '.');
839 tp
= lookup_word
(pc
, buff
);
842 lvalp
->intval
= tp
->value
;
863 /* Do nothing if the parser reports an error. */
865 yyerror (char *s ATTRIBUTE_UNUSED
)
870 /* Parse a date/time string P. Return the corresponding time_t value,
871 or (time_t) -1 if there is an error. P can be an incomplete or
872 relative time specification; if so, use *NOW as the basis for the
875 get_date
(const char *p
, const time_t *now
)
877 time_t Start
= now ?
*now
: time
(0);
878 struct tm
*tmp
= localtime
(&Start
);
887 pc.year.value
= tmp
->tm_year
+ TM_YEAR_BASE
;
889 pc.month
= tmp
->tm_mon
+ 1;
890 pc.day
= tmp
->tm_mday
;
891 pc.hour
= tmp
->tm_hour
;
892 pc.minutes
= tmp
->tm_min
;
893 pc.seconds
= tmp
->tm_sec
;
894 tm.tm_isdst
= tmp
->tm_isdst
;
907 pc.local_zones_seen
= 0;
910 #if HAVE_STRUCT_TM_TM_ZONE
911 pc.local_time_zone_table
[0].name
= tmp
->tm_zone
;
912 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
913 pc.local_time_zone_table
[0].value
= tmp
->tm_isdst
;
914 pc.local_time_zone_table
[1].name
= 0;
916 /* Probe the names used in the next three calendar quarters, looking
917 for a tm_isdst different from the one we already have. */
920 for
(quarter
= 1; quarter
<= 3; quarter
++)
922 time_t probe
= Start
+ quarter
* (90 * 24 * 60 * 60);
923 struct tm
*probe_tm
= localtime
(&probe
);
924 if
(probe_tm
&& probe_tm
->tm_zone
925 && probe_tm
->tm_isdst
!= pc.local_time_zone_table
[0].value
)
928 pc.local_time_zone_table
[1].name
= probe_tm
->tm_zone
;
929 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
930 pc.local_time_zone_table
[1].value
= probe_tm
->tm_isdst
;
931 pc.local_time_zone_table
[2].name
= 0;
941 extern
char *tzname
[];
944 for
(i
= 0; i
< 2; i
++)
946 pc.local_time_zone_table
[i
].name
= tzname
[i
];
947 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
948 pc.local_time_zone_table
[i
].value
= i
;
950 pc.local_time_zone_table
[i
].name
= 0;
953 pc.local_time_zone_table
[0].name
= 0;
957 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
958 && ! strcmp
(pc.local_time_zone_table
[0].name
,
959 pc.local_time_zone_table
[1].name
))
961 /* This locale uses the same abbrevation for standard and
962 daylight times. So if we see that abbreviation, we don't
963 know whether it's daylight time. */
964 pc.local_time_zone_table
[0].value
= -1;
965 pc.local_time_zone_table
[1].name
= 0;
968 if
(yyparse (&pc
) != 0
969 ||
1 < pc.times_seen ||
1 < pc.dates_seen ||
1 < pc.days_seen
970 ||
1 < (pc.local_zones_seen
+ pc.zones_seen
)
971 ||
(pc.local_zones_seen
&& 1 < pc.local_isdst
))
974 tm.tm_year
= to_year
(pc.year
) - TM_YEAR_BASE
+ pc.rel_year
;
975 tm.tm_mon
= pc.month
- 1 + pc.rel_month
;
976 tm.tm_mday
= pc.day
+ pc.rel_day
;
977 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
979 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
982 tm.tm_min
= pc.minutes
;
983 tm.tm_sec
= pc.seconds
;
987 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
990 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
991 or if the relative time stamp mentions days, months, or years. */
992 if
(pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
993 | pc.rel_month | pc.rel_year
)
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
;
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. */
1019 if
(tm.tm_year
<= EPOCH_YEAR
- TM_YEAR_BASE
)
1022 pc.time_zone
+= 24 * 60;
1027 pc.time_zone
-= 24 * 60;
1029 Start
= mktime
(&tm
);
1032 if
(Start
== (time_t) -1)
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
)));
1041 Start
= mktime
(&tm
);
1042 if
(Start
== (time_t) -1)
1048 int delta
= pc.time_zone
* 60;
1049 #ifdef HAVE_TM_GMTOFF
1050 delta
-= tm.tm_gmtoff
;
1052 struct tm
*gmt
= gmtime
(&Start
);
1055 delta
-= tm_diff
(&tm
, gmt
);
1057 if
((Start
< Start
- delta
) != (delta
< 0))
1058 return
-1; /* time_t overflow */
1062 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1063 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1064 leap second. Typically this is not what the user wants, but it's
1065 too hard to do it the other way, because the time zone indicator
1066 must be applied before relative times, and if mktime is applied
1067 again the time zone will be lost. */
1070 long d1
= 60 * 60 * (long) pc.rel_hour
;
1071 time_t t1
= t0
+ d1
;
1072 long d2
= 60 * (long) pc.rel_minutes
;
1073 time_t t2
= t1
+ d2
;
1074 int d3
= pc.rel_seconds
;
1075 time_t t3
= t2
+ d3
;
1076 if
((d1
/ (60 * 60) ^ pc.rel_hour
)
1077 |
(d2
/ 60 ^ pc.rel_minutes
)
1078 |
((t0
+ d1
< t0
) ^
(d1
< 0))
1079 |
((t1
+ d2
< t1
) ^
(d2
< 0))
1080 |
((t2
+ d3
< t2
) ^
(d3
< 0)))
1093 main
(int ac
, char **av
)
1098 printf
("Enter date, or blank line to exit.\n\t> ");
1101 buff
[BUFSIZ
- 1] = 0;
1102 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
1104 d
= get_date
(buff
, 0);
1105 if
(d
== (time_t) -1)
1106 printf
("Bad format - couldn't convert.\n");
1108 printf
("%s", ctime
(&d
));
1114 #endif /* defined TEST */