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, 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. */
35 /* Since the code of getdate.y is not included in the Emacs executable
36 itself, there is no need to #define static in this file. Even if
37 the code were included in the Emacs executable, it probably
38 wouldn't do any harm to #undef it here; this will only cause
39 problems if we try to write to a static variable, which I don't
40 think this code needs to do. */
49 # include <stdlib.h> /* for `free'; used by Bison 1.27 */
52 #if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
53 # define IN_CTYPE_DOMAIN(c) 1
55 # define IN_CTYPE_DOMAIN(c) isascii (c)
58 #define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
59 #define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
60 #define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
61 #define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
63 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
64 - Its arg may be any int or unsigned int; it need not be an unsigned char.
65 - It's guaranteed to evaluate its argument exactly once.
66 - It's typically faster.
67 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
68 ISDIGIT_LOCALE unless it's important to use the locale's definition
69 of `digit' even when the host does not conform to POSIX. */
70 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
72 #if STDC_HEADERS || HAVE_STRING_H
76 #if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
77 # define __attribute__(x)
80 #ifndef ATTRIBUTE_UNUSED
81 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
84 #define EPOCH_YEAR 1970
85 #define TM_YEAR_BASE 1900
87 #define HOUR(x) ((x) * 60)
89 /* An integer value, and the number of digits in its textual
97 /* An entry in the lexical lookup table. */
105 /* Meridian: am, pm, or 24-hour style. */
106 enum { MERam
, MERpm
, MER24
};
108 /* Information passed to and from the parser. */
111 /* The input string remaining to be parsed. */
114 /* N, if this is the Nth Tuesday. */
117 /* Day of week; Sunday is 0. */
120 /* tm_isdst flag for the local zone. */
123 /* Time zone, in minutes east of UTC. */
126 /* Style used for time. */
129 /* Gregorian year, month, day, hour, minutes, and seconds. */
137 /* Relative year, month, day, hour, minutes, and seconds. */
145 /* Counts of nonterminals of various flavors parsed so far. */
148 int local_zones_seen
;
153 /* Table of local time zone abbrevations, terminated by a null entry. */
154 table local_time_zone_table
[3];
157 #define PC (* (parser_control *) parm)
158 #define YYLEX_PARAM parm
159 #define YYPARSE_PARAM parm
161 static int yyerror ();
166 /* We want a reentrant parser. */
169 /* This grammar has 13 shift/reduce conflicts. */
180 %token
<intval
> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
181 %token
<intval
> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
183 %token
<textintval
> tSNUMBER tUNUMBER
185 %type
<intval
> o_merid
198 { PC.local_zones_seen
++; }
218 | tUNUMBER
':' tUNUMBER o_merid
221 PC.minutes
= $3.value
;
225 | tUNUMBER
':' tUNUMBER tSNUMBER
228 PC.minutes
= $3.value
;
231 PC.time_zone
= $4.value %
100 + ($4.value
/ 100) * 60;
233 | tUNUMBER
':' tUNUMBER
':' tUNUMBER o_merid
236 PC.minutes
= $3.value
;
237 PC.seconds
= $5.value
;
240 | tUNUMBER
':' tUNUMBER
':' tUNUMBER tSNUMBER
243 PC.minutes
= $3.value
;
244 PC.seconds
= $5.value
;
247 PC.time_zone
= $6.value %
100 + ($6.value
/ 100) * 60;
253 { PC.local_isdst
= $1; }
255 { PC.local_isdst
= $1 < 0 ?
1 : $1 + 1; }
260 { PC.time_zone
= $1; }
262 { PC.time_zone
= $1 + 60; }
264 { PC.time_zone
= $1 + 60; }
280 PC.day_ordinal
= $1.value
;
286 tUNUMBER
'/' tUNUMBER
291 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
293 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
294 otherwise as MM/DD/YY.
295 The goal in recognizing YYYY/MM/DD is solely to support legacy
296 machine-generated dates like those in an RCS log listing. If
297 you want portability, use the ISO 8601 format. */
311 | tUNUMBER tSNUMBER tSNUMBER
313 /* ISO 8601 format. YYYY-MM-DD. */
315 PC.month
= -$2.value
;
318 | tUNUMBER tMONTH tSNUMBER
320 /* e.g. 17-JUN-1992. */
323 PC.year.value
= -$3.value
;
324 PC.year.digits
= $3.digits
;
331 | tMONTH tUNUMBER
',' tUNUMBER
342 | tUNUMBER tMONTH tUNUMBER
353 PC.rel_seconds
= -PC.rel_seconds
;
354 PC.rel_minutes
= -PC.rel_minutes
;
355 PC.rel_hour
= -PC.rel_hour
;
356 PC.rel_day
= -PC.rel_day
;
357 PC.rel_month
= -PC.rel_month
;
358 PC.rel_year
= -PC.rel_year
;
365 { PC.rel_year
+= $1.value
* $2; }
366 | tSNUMBER tYEAR_UNIT
367 { PC.rel_year
+= $1.value
* $2; }
369 { PC.rel_year
+= $1; }
370 | tUNUMBER tMONTH_UNIT
371 { PC.rel_month
+= $1.value
* $2; }
372 | tSNUMBER tMONTH_UNIT
373 { PC.rel_month
+= $1.value
* $2; }
375 { PC.rel_month
+= $1; }
377 { PC.rel_day
+= $1.value
* $2; }
379 { PC.rel_day
+= $1.value
* $2; }
381 { PC.rel_day
+= $1; }
382 | tUNUMBER tHOUR_UNIT
383 { PC.rel_hour
+= $1.value
* $2; }
384 | tSNUMBER tHOUR_UNIT
385 { PC.rel_hour
+= $1.value
* $2; }
387 { PC.rel_hour
+= $1; }
388 | tUNUMBER tMINUTE_UNIT
389 { PC.rel_minutes
+= $1.value
* $2; }
390 | tSNUMBER tMINUTE_UNIT
391 { PC.rel_minutes
+= $1.value
* $2; }
393 { PC.rel_minutes
+= $1; }
395 { PC.rel_seconds
+= $1.value
* $2; }
397 { PC.rel_seconds
+= $1.value
* $2; }
399 { PC.rel_seconds
+= $1; }
406 && ! PC.rels_seen
&& (PC.times_seen ||
2 < $1.digits
))
413 PC.day
= $1.value %
100;
414 PC.month
= ($1.value
/ 100) %
100;
415 PC.year.value
= $1.value
/ 10000;
416 PC.year.digits
= $1.digits
- 4;
428 PC.hour
= $1.value
/ 100;
429 PC.minutes
= $1.value %
100;
447 /* Include this file down here because bison inserts code above which
448 may define-away `const'. We want the prototype for get_date to have
449 the same signature as the function definition. */
450 #include "modules/getdate.h"
453 struct tm
*gmtime
();
456 struct tm
*localtime
();
462 static table
const meridian_table
[] =
464 { "AM", tMERIDIAN
, MERam
},
465 { "A.M.", tMERIDIAN
, MERam
},
466 { "PM", tMERIDIAN
, MERpm
},
467 { "P.M.", tMERIDIAN
, MERpm
},
471 static table
const dst_table
[] =
476 static table
const month_and_day_table
[] =
478 { "JANUARY", tMONTH
, 1 },
479 { "FEBRUARY", tMONTH
, 2 },
480 { "MARCH", tMONTH
, 3 },
481 { "APRIL", tMONTH
, 4 },
482 { "MAY", tMONTH
, 5 },
483 { "JUNE", tMONTH
, 6 },
484 { "JULY", tMONTH
, 7 },
485 { "AUGUST", tMONTH
, 8 },
486 { "SEPTEMBER",tMONTH
, 9 },
487 { "SEPT", tMONTH
, 9 },
488 { "OCTOBER", tMONTH
, 10 },
489 { "NOVEMBER", tMONTH
, 11 },
490 { "DECEMBER", tMONTH
, 12 },
491 { "SUNDAY", tDAY
, 0 },
492 { "MONDAY", tDAY
, 1 },
493 { "TUESDAY", tDAY
, 2 },
495 { "WEDNESDAY",tDAY
, 3 },
496 { "WEDNES", tDAY
, 3 },
497 { "THURSDAY", tDAY
, 4 },
499 { "THURS", tDAY
, 4 },
500 { "FRIDAY", tDAY
, 5 },
501 { "SATURDAY", tDAY
, 6 },
505 static table
const time_units_table
[] =
507 { "YEAR", tYEAR_UNIT
, 1 },
508 { "MONTH", tMONTH_UNIT
, 1 },
509 { "FORTNIGHT",tDAY_UNIT
, 14 },
510 { "WEEK", tDAY_UNIT
, 7 },
511 { "DAY", tDAY_UNIT
, 1 },
512 { "HOUR", tHOUR_UNIT
, 1 },
513 { "MINUTE", tMINUTE_UNIT
, 1 },
514 { "MIN", tMINUTE_UNIT
, 1 },
515 { "SECOND", tSEC_UNIT
, 1 },
516 { "SEC", tSEC_UNIT
, 1 },
520 /* Assorted relative-time words. */
521 static table
const relative_time_table
[] =
523 { "TOMORROW", tMINUTE_UNIT
, 24 * 60 },
524 { "YESTERDAY",tMINUTE_UNIT
, - (24 * 60) },
525 { "TODAY", tMINUTE_UNIT
, 0 },
526 { "NOW", tMINUTE_UNIT
, 0 },
527 { "LAST", tUNUMBER
, -1 },
528 { "THIS", tUNUMBER
, 0 },
529 { "NEXT", tUNUMBER
, 1 },
530 { "FIRST", tUNUMBER
, 1 },
531 /*{ "SECOND", tUNUMBER, 2 }, */
532 { "THIRD", tUNUMBER
, 3 },
533 { "FOURTH", tUNUMBER
, 4 },
534 { "FIFTH", tUNUMBER
, 5 },
535 { "SIXTH", tUNUMBER
, 6 },
536 { "SEVENTH", tUNUMBER
, 7 },
537 { "EIGHTH", tUNUMBER
, 8 },
538 { "NINTH", tUNUMBER
, 9 },
539 { "TENTH", tUNUMBER
, 10 },
540 { "ELEVENTH", tUNUMBER
, 11 },
541 { "TWELFTH", tUNUMBER
, 12 },
546 /* The time zone table. This table is necessarily incomplete, as time
547 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
548 as Eastern time in Australia, not as US Eastern Standard Time.
549 You cannot rely on getdate to handle arbitrary time zone
550 abbreviations; use numeric abbreviations like `-0500' instead. */
551 static table
const time_zone_table
[] =
553 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
554 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
555 { "UTC", tZONE
, HOUR
( 0) },
556 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
557 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
558 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
559 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
560 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
561 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
562 { "NST", tZONE
, -(HOUR
( 3) + 30) }, /* Newfoundland Standard */
563 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30) }, /* Newfoundland Daylight */
564 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
565 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
566 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
567 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
568 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
569 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
570 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
571 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
572 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
573 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
574 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
575 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
576 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
577 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
578 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
579 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
580 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
581 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
582 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
583 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
584 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
585 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
586 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
587 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
588 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
589 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
590 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
591 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
592 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
593 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
594 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
595 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
596 { "IST", tZONE
, (HOUR
( 5) + 30) }, /* India Standard */
597 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
598 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
599 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
600 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
601 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
602 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
606 /* Military time zone table. */
607 static table
const military_table
[] =
609 { "A", tZONE
, -HOUR
( 1) },
610 { "B", tZONE
, -HOUR
( 2) },
611 { "C", tZONE
, -HOUR
( 3) },
612 { "D", tZONE
, -HOUR
( 4) },
613 { "E", tZONE
, -HOUR
( 5) },
614 { "F", tZONE
, -HOUR
( 6) },
615 { "G", tZONE
, -HOUR
( 7) },
616 { "H", tZONE
, -HOUR
( 8) },
617 { "I", tZONE
, -HOUR
( 9) },
618 { "K", tZONE
, -HOUR
(10) },
619 { "L", tZONE
, -HOUR
(11) },
620 { "M", tZONE
, -HOUR
(12) },
621 { "N", tZONE
, HOUR
( 1) },
622 { "O", tZONE
, HOUR
( 2) },
623 { "P", tZONE
, HOUR
( 3) },
624 { "Q", tZONE
, HOUR
( 4) },
625 { "R", tZONE
, HOUR
( 5) },
626 { "S", tZONE
, HOUR
( 6) },
627 { "T", tZONE
, HOUR
( 7) },
628 { "U", tZONE
, HOUR
( 8) },
629 { "V", tZONE
, HOUR
( 9) },
630 { "W", tZONE
, HOUR
(10) },
631 { "X", tZONE
, HOUR
(11) },
632 { "Y", tZONE
, HOUR
(12) },
633 { "Z", tZONE
, HOUR
( 0) },
640 to_hour
(int hours
, int meridian
)
645 return
0 <= hours
&& hours
< 24 ? hours
: -1;
647 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
649 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
658 to_year
(textint textyear
)
660 int year
= textyear.value
;
665 /* XPG4 suggests that years 00-68 map to 2000-2068, and
666 years 69-99 map to 1969-1999. */
667 if
(textyear.digits
== 2)
668 year
+= year
< 69 ?
2000 : 1900;
674 lookup_zone
(parser_control
const *pc
, char const *name
)
678 /* Try local zone abbreviations first; they're more likely to be right. */
679 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
680 if
(strcmp
(name
, tp
->name
) == 0)
683 for
(tp
= time_zone_table
; tp
->name
; tp
++)
684 if
(strcmp
(name
, tp
->name
) == 0)
691 /* Yield the difference between *A and *B,
692 measured in seconds, ignoring leap seconds.
693 The body of this function is taken directly from the GNU C Library;
694 see src/strftime.c. */
696 tm_diff
(struct tm
const *a
, struct tm
const *b
)
698 /* Compute intervening leap days correctly even if year is negative.
699 Take care to avoid int overflow in leap day calculations,
700 but it's OK to assume that A and B are close to each other. */
701 int a4
= (a
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (a
->tm_year
& 3);
702 int b4
= (b
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (b
->tm_year
& 3);
703 int a100
= a4
/ 25 - (a4 %
25 < 0);
704 int b100
= b4
/ 25 - (b4 %
25 < 0);
705 int a400
= a100
>> 2;
706 int b400
= b100
>> 2;
707 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
708 int years
= a
->tm_year
- b
->tm_year
;
709 int days
= (365 * years
+ intervening_leap_days
710 + (a
->tm_yday
- b
->tm_yday
));
711 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
712 + (a
->tm_min
- b
->tm_min
))
713 + (a
->tm_sec
- b
->tm_sec
));
715 #endif /* ! HAVE_TM_GMTOFF */
718 lookup_word
(parser_control
const *pc
, char *word
)
727 /* Make it uppercase. */
728 for
(p
= word
; *p
; p
++)
729 if
(ISLOWER
((unsigned char) *p
))
730 *p
= toupper
((unsigned char) *p
);
732 for
(tp
= meridian_table
; tp
->name
; tp
++)
733 if
(strcmp
(word
, tp
->name
) == 0)
736 /* See if we have an abbreviation for a month. */
737 wordlen
= strlen
(word
);
738 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
740 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
741 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
744 if
((tp
= lookup_zone
(pc
, word
)))
747 if
(strcmp
(word
, dst_table
[0].name
) == 0)
750 for
(tp
= time_units_table
; tp
->name
; tp
++)
751 if
(strcmp
(word
, tp
->name
) == 0)
754 /* Strip off any plural and try the units table again. */
755 if
(word
[wordlen
- 1] == 'S')
757 word
[wordlen
- 1] = '\0';
758 for
(tp
= time_units_table
; tp
->name
; tp
++)
759 if
(strcmp
(word
, tp
->name
) == 0)
761 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
764 for
(tp
= relative_time_table
; tp
->name
; tp
++)
765 if
(strcmp
(word
, tp
->name
) == 0)
768 /* Military time zones. */
770 for
(tp
= military_table
; tp
->name
; tp
++)
771 if
(word
[0] == tp
->name
[0])
774 /* Drop out any periods and try the time zone table again. */
775 for
(i
= 0, p
= q
= word
; (*p
= *q
); q
++)
780 if
(i
&& (tp
= lookup_zone
(pc
, word
)))
787 yylex (YYSTYPE *lvalp
, parser_control
*pc
)
794 while
(c
= *pc
->input
, ISSPACE
(c
))
797 if
(ISDIGIT
(c
) || c
== '-' || c
== '+')
802 if
(c
== '-' || c
== '+')
804 sign
= c
== '-' ?
-1 : 1;
807 /* skip the '-' sign */
816 value
= 10 * value
+ c
- '0';
820 lvalp
->textintval.value
= sign
< 0 ?
-value
: value
;
821 lvalp
->textintval.digits
= p
- pc
->input
;
823 return sign ? tSNUMBER
: tUNUMBER
;
834 if
(p
< buff
+ sizeof buff
- 1)
838 while
(ISALPHA
(c
) || c
== '.');
841 tp
= lookup_word
(pc
, buff
);
844 lvalp
->intval
= tp
->value
;
865 /* Do nothing if the parser reports an error. */
867 yyerror (char *s ATTRIBUTE_UNUSED
)
872 /* Parse a date/time string P. Return the corresponding time_t value,
873 or (time_t) -1 if there is an error. P can be an incomplete or
874 relative time specification; if so, use *NOW as the basis for the
877 get_date
(const char *p
, const time_t *now
)
879 time_t Start
= now ?
*now
: time
(0);
880 struct tm
*tmp
= localtime
(&Start
);
889 pc.year.value
= tmp
->tm_year
+ TM_YEAR_BASE
;
891 pc.month
= tmp
->tm_mon
+ 1;
892 pc.day
= tmp
->tm_mday
;
893 pc.hour
= tmp
->tm_hour
;
894 pc.minutes
= tmp
->tm_min
;
895 pc.seconds
= tmp
->tm_sec
;
896 tm.tm_isdst
= tmp
->tm_isdst
;
909 pc.local_zones_seen
= 0;
912 #if HAVE_STRUCT_TM_TM_ZONE
913 pc.local_time_zone_table
[0].name
= tmp
->tm_zone
;
914 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
915 pc.local_time_zone_table
[0].value
= tmp
->tm_isdst
;
916 pc.local_time_zone_table
[1].name
= 0;
918 /* Probe the names used in the next three calendar quarters, looking
919 for a tm_isdst different from the one we already have. */
922 for
(quarter
= 1; quarter
<= 3; quarter
++)
924 time_t probe
= Start
+ quarter
* (90 * 24 * 60 * 60);
925 struct tm
*probe_tm
= localtime
(&probe
);
926 if
(probe_tm
&& probe_tm
->tm_zone
927 && probe_tm
->tm_isdst
!= pc.local_time_zone_table
[0].value
)
930 pc.local_time_zone_table
[1].name
= probe_tm
->tm_zone
;
931 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
932 pc.local_time_zone_table
[1].value
= probe_tm
->tm_isdst
;
933 pc.local_time_zone_table
[2].name
= 0;
943 extern
char *tzname
[];
946 for
(i
= 0; i
< 2; i
++)
948 pc.local_time_zone_table
[i
].name
= tzname
[i
];
949 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
950 pc.local_time_zone_table
[i
].value
= i
;
952 pc.local_time_zone_table
[i
].name
= 0;
955 pc.local_time_zone_table
[0].name
= 0;
959 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
960 && ! strcmp
(pc.local_time_zone_table
[0].name
,
961 pc.local_time_zone_table
[1].name
))
963 /* This locale uses the same abbrevation for standard and
964 daylight times. So if we see that abbreviation, we don't
965 know whether it's daylight time. */
966 pc.local_time_zone_table
[0].value
= -1;
967 pc.local_time_zone_table
[1].name
= 0;
970 if
(yyparse (&pc
) != 0
971 ||
1 < pc.times_seen ||
1 < pc.dates_seen ||
1 < pc.days_seen
972 ||
1 < (pc.local_zones_seen
+ pc.zones_seen
)
973 ||
(pc.local_zones_seen
&& 1 < pc.local_isdst
))
976 tm.tm_year
= to_year
(pc.year
) - TM_YEAR_BASE
+ pc.rel_year
;
977 tm.tm_mon
= pc.month
- 1 + pc.rel_month
;
978 tm.tm_mday
= pc.day
+ pc.rel_day
;
979 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
981 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
984 tm.tm_min
= pc.minutes
;
985 tm.tm_sec
= pc.seconds
;
989 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
992 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
993 or if the relative time stamp mentions days, months, or years. */
994 if
(pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
995 | pc.rel_month | pc.rel_year
)
998 /* But if the input explicitly specifies local time with or without
999 DST, give mktime that information. */
1000 if
(pc.local_zones_seen
)
1001 tm.tm_isdst
= pc.local_isdst
;
1005 Start
= mktime
(&tm
);
1007 if
(Start
== (time_t) -1)
1010 /* Guard against falsely reporting errors near the time_t boundaries
1011 when parsing times in other time zones. For example, if the min
1012 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1013 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1014 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1015 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1016 zone by 24 hours to compensate. This algorithm assumes that
1017 there is no DST transition within a day of the time_t boundaries. */
1021 if
(tm.tm_year
<= EPOCH_YEAR
- TM_YEAR_BASE
)
1024 pc.time_zone
+= 24 * 60;
1029 pc.time_zone
-= 24 * 60;
1031 Start
= mktime
(&tm
);
1034 if
(Start
== (time_t) -1)
1038 if
(pc.days_seen
&& ! pc.dates_seen
)
1040 tm.tm_mday
+= ((pc.day_number
- tm.tm_wday
+ 7) %
7
1041 + 7 * (pc.day_ordinal
- (0 < pc.day_ordinal
)));
1043 Start
= mktime
(&tm
);
1044 if
(Start
== (time_t) -1)
1050 int delta
= pc.time_zone
* 60;
1051 #ifdef HAVE_TM_GMTOFF
1052 delta
-= tm.tm_gmtoff
;
1054 struct tm
*gmt
= gmtime
(&Start
);
1057 delta
-= tm_diff
(&tm
, gmt
);
1059 if
((Start
< Start
- delta
) != (delta
< 0))
1060 return
-1; /* time_t overflow */
1064 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1065 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1066 leap second. Typically this is not what the user wants, but it's
1067 too hard to do it the other way, because the time zone indicator
1068 must be applied before relative times, and if mktime is applied
1069 again the time zone will be lost. */
1072 long d1
= 60 * 60 * (long) pc.rel_hour
;
1073 time_t t1
= t0
+ d1
;
1074 long d2
= 60 * (long) pc.rel_minutes
;
1075 time_t t2
= t1
+ d2
;
1076 int d3
= pc.rel_seconds
;
1077 time_t t3
= t2
+ d3
;
1078 if
((d1
/ (60 * 60) ^ pc.rel_hour
)
1079 |
(d2
/ 60 ^ pc.rel_minutes
)
1080 |
((t0
+ d1
< t0
) ^
(d1
< 0))
1081 |
((t1
+ d2
< t1
) ^
(d2
< 0))
1082 |
((t2
+ d3
< t2
) ^
(d3
< 0)))
1095 main
(int ac
, char **av
)
1100 printf
("Enter date, or blank line to exit.\n\t> ");
1103 buff
[BUFSIZ
- 1] = 0;
1104 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
1106 d
= get_date
(buff
, 0);
1107 if
(d
== (time_t) -1)
1108 printf
("Bad format - couldn't convert.\n");
1110 printf
("%s", ctime
(&d
));
1116 #endif /* defined TEST */