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 #ifndef HAVE___ATTRIBUTE__
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. */
108 struct parser_control
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 abbreviations, terminated by a null entry. */
153 table local_time_zone_table
[3];
158 %lex
-param
{struct parser_control
*pc
}
159 %parse
-param
{struct parser_control
*pc
}
161 /* We want a reentrant parser. */
164 /* This grammar has 13 shift/reduce conflicts. */
175 static int yyerror(struct parser_control
*, const char *);
176 static int yylex(YYSTYPE *, struct parser_control
*);
182 %token
<intval
> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
183 %token
<intval
> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
185 %token
<textintval
> tSNUMBER tUNUMBER
187 %type
<intval
> o_merid
198 { pc
->times_seen
++; }
200 { pc
->local_zones_seen
++; }
202 { pc
->zones_seen
++; }
204 { pc
->dates_seen
++; }
220 | tUNUMBER
':' tUNUMBER o_merid
223 pc
->minutes
= $3.value
;
227 | tUNUMBER
':' tUNUMBER tSNUMBER
230 pc
->minutes
= $3.value
;
231 pc
->meridian
= MER24
;
233 pc
->time_zone
= $4.value %
100 + ($4.value
/ 100) * 60;
235 | tUNUMBER
':' tUNUMBER
':' tUNUMBER o_merid
238 pc
->minutes
= $3.value
;
239 pc
->seconds
= $5.value
;
242 | tUNUMBER
':' tUNUMBER
':' tUNUMBER tSNUMBER
245 pc
->minutes
= $3.value
;
246 pc
->seconds
= $5.value
;
247 pc
->meridian
= MER24
;
249 pc
->time_zone
= $6.value %
100 + ($6.value
/ 100) * 60;
255 { pc
->local_isdst
= $1; }
257 { pc
->local_isdst
= $1 < 0 ?
1 : $1 + 1; }
262 { pc
->time_zone
= $1; }
264 { pc
->time_zone
= $1 + 60; }
266 { pc
->time_zone
= $1 + 60; }
282 pc
->day_ordinal
= $1.value
;
288 tUNUMBER
'/' tUNUMBER
290 pc
->month
= $1.value
;
293 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
295 /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
296 otherwise as MM/DD/YY.
297 The goal in recognizing YYYY/MM/DD is solely to support legacy
298 machine-generated dates like those in an RCS log listing. If
299 you want portability, use the ISO 8601 format. */
303 pc
->month
= $3.value
;
308 pc
->month
= $1.value
;
313 | tUNUMBER tSNUMBER tSNUMBER
315 /* ISO 8601 format. YYYY-MM-DD. */
317 pc
->month
= -$2.value
;
320 | tUNUMBER tMONTH tSNUMBER
322 /* e.g. 17-JUN-1992. */
325 pc
->year.value
= -$3.value
;
326 pc
->year.digits
= $3.digits
;
333 | tMONTH tUNUMBER
',' tUNUMBER
344 | tUNUMBER tMONTH tUNUMBER
355 pc
->rel_seconds
= -pc
->rel_seconds
;
356 pc
->rel_minutes
= -pc
->rel_minutes
;
357 pc
->rel_hour
= -pc
->rel_hour
;
358 pc
->rel_day
= -pc
->rel_day
;
359 pc
->rel_month
= -pc
->rel_month
;
360 pc
->rel_year
= -pc
->rel_year
;
367 { pc
->rel_year
+= $1.value
* $2; }
368 | tSNUMBER tYEAR_UNIT
369 { pc
->rel_year
+= $1.value
* $2; }
371 { pc
->rel_year
+= $1; }
372 | tUNUMBER tMONTH_UNIT
373 { pc
->rel_month
+= $1.value
* $2; }
374 | tSNUMBER tMONTH_UNIT
375 { pc
->rel_month
+= $1.value
* $2; }
377 { pc
->rel_month
+= $1; }
379 { pc
->rel_day
+= $1.value
* $2; }
381 { pc
->rel_day
+= $1.value
* $2; }
383 { pc
->rel_day
+= $1; }
384 | tUNUMBER tHOUR_UNIT
385 { pc
->rel_hour
+= $1.value
* $2; }
386 | tSNUMBER tHOUR_UNIT
387 { pc
->rel_hour
+= $1.value
* $2; }
389 { pc
->rel_hour
+= $1; }
390 | tUNUMBER tMINUTE_UNIT
391 { pc
->rel_minutes
+= $1.value
* $2; }
392 | tSNUMBER tMINUTE_UNIT
393 { pc
->rel_minutes
+= $1.value
* $2; }
395 { pc
->rel_minutes
+= $1; }
397 { pc
->rel_seconds
+= $1.value
* $2; }
399 { pc
->rel_seconds
+= $1.value
* $2; }
401 { pc
->rel_seconds
+= $1; }
408 && ! pc
->rels_seen
&& (pc
->times_seen ||
2 < $1.digits
))
415 pc
->day
= $1.value %
100;
416 pc
->month
= ($1.value
/ 100) %
100;
417 pc
->year.value
= $1.value
/ 10000;
418 pc
->year.digits
= $1.digits
- 4;
430 pc
->hour
= $1.value
/ 100;
431 pc
->minutes
= $1.value %
100;
434 pc
->meridian
= MER24
;
449 /* Include this file down here because bison inserts code above which
450 may define-away `const'. We want the prototype for get_date to have
451 the same signature as the function definition. */
452 #include "modules/getdate.h"
455 struct tm
*gmtime
(const time_t *);
458 struct tm
*localtime
(const time_t *);
461 time_t mktime
(struct tm
*);
464 static table
const meridian_table
[] =
466 { "AM", tMERIDIAN
, MERam
},
467 { "A.M.", tMERIDIAN
, MERam
},
468 { "PM", tMERIDIAN
, MERpm
},
469 { "P.M.", tMERIDIAN
, MERpm
},
473 static table
const dst_table
[] =
478 static table
const month_and_day_table
[] =
480 { "JANUARY", tMONTH
, 1 },
481 { "FEBRUARY", tMONTH
, 2 },
482 { "MARCH", tMONTH
, 3 },
483 { "APRIL", tMONTH
, 4 },
484 { "MAY", tMONTH
, 5 },
485 { "JUNE", tMONTH
, 6 },
486 { "JULY", tMONTH
, 7 },
487 { "AUGUST", tMONTH
, 8 },
488 { "SEPTEMBER",tMONTH
, 9 },
489 { "SEPT", tMONTH
, 9 },
490 { "OCTOBER", tMONTH
, 10 },
491 { "NOVEMBER", tMONTH
, 11 },
492 { "DECEMBER", tMONTH
, 12 },
493 { "SUNDAY", tDAY
, 0 },
494 { "MONDAY", tDAY
, 1 },
495 { "TUESDAY", tDAY
, 2 },
497 { "WEDNESDAY",tDAY
, 3 },
498 { "WEDNES", tDAY
, 3 },
499 { "THURSDAY", tDAY
, 4 },
501 { "THURS", tDAY
, 4 },
502 { "FRIDAY", tDAY
, 5 },
503 { "SATURDAY", tDAY
, 6 },
507 static table
const time_units_table
[] =
509 { "YEAR", tYEAR_UNIT
, 1 },
510 { "MONTH", tMONTH_UNIT
, 1 },
511 { "FORTNIGHT",tDAY_UNIT
, 14 },
512 { "WEEK", tDAY_UNIT
, 7 },
513 { "DAY", tDAY_UNIT
, 1 },
514 { "HOUR", tHOUR_UNIT
, 1 },
515 { "MINUTE", tMINUTE_UNIT
, 1 },
516 { "MIN", tMINUTE_UNIT
, 1 },
517 { "SECOND", tSEC_UNIT
, 1 },
518 { "SEC", tSEC_UNIT
, 1 },
522 /* Assorted relative-time words. */
523 static table
const relative_time_table
[] =
525 { "TOMORROW", tMINUTE_UNIT
, 24 * 60 },
526 { "YESTERDAY",tMINUTE_UNIT
, - (24 * 60) },
527 { "TODAY", tMINUTE_UNIT
, 0 },
528 { "NOW", tMINUTE_UNIT
, 0 },
529 { "LAST", tUNUMBER
, -1 },
530 { "THIS", tUNUMBER
, 0 },
531 { "NEXT", tUNUMBER
, 1 },
532 { "FIRST", tUNUMBER
, 1 },
533 /*{ "SECOND", tUNUMBER, 2 }, */
534 { "THIRD", tUNUMBER
, 3 },
535 { "FOURTH", tUNUMBER
, 4 },
536 { "FIFTH", tUNUMBER
, 5 },
537 { "SIXTH", tUNUMBER
, 6 },
538 { "SEVENTH", tUNUMBER
, 7 },
539 { "EIGHTH", tUNUMBER
, 8 },
540 { "NINTH", tUNUMBER
, 9 },
541 { "TENTH", tUNUMBER
, 10 },
542 { "ELEVENTH", tUNUMBER
, 11 },
543 { "TWELFTH", tUNUMBER
, 12 },
548 /* The time zone table. This table is necessarily incomplete, as time
549 zone abbreviations are ambiguous; e.g. Australians interpret "EST"
550 as Eastern time in Australia, not as US Eastern Standard Time.
551 You cannot rely on getdate to handle arbitrary time zone
552 abbreviations; use numeric abbreviations like `-0500' instead. */
553 static table
const time_zone_table
[] =
555 { "GMT", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
556 { "UT", tZONE
, HOUR
( 0) }, /* Universal (Coordinated) */
557 { "UTC", tZONE
, HOUR
( 0) },
558 { "WET", tZONE
, HOUR
( 0) }, /* Western European */
559 { "WEST", tDAYZONE
, HOUR
( 0) }, /* Western European Summer */
560 { "BST", tDAYZONE
, HOUR
( 0) }, /* British Summer */
561 { "ART", tZONE
, -HOUR
( 3) }, /* Argentina */
562 { "BRT", tZONE
, -HOUR
( 3) }, /* Brazil */
563 { "BRST", tDAYZONE
, -HOUR
( 3) }, /* Brazil Summer */
564 { "NST", tZONE
, -(HOUR
( 3) + 30) }, /* Newfoundland Standard */
565 { "NDT", tDAYZONE
,-(HOUR
( 3) + 30) }, /* Newfoundland Daylight */
566 { "AST", tZONE
, -HOUR
( 4) }, /* Atlantic Standard */
567 { "ADT", tDAYZONE
, -HOUR
( 4) }, /* Atlantic Daylight */
568 { "CLT", tZONE
, -HOUR
( 4) }, /* Chile */
569 { "CLST", tDAYZONE
, -HOUR
( 4) }, /* Chile Summer */
570 { "EST", tZONE
, -HOUR
( 5) }, /* Eastern Standard */
571 { "EDT", tDAYZONE
, -HOUR
( 5) }, /* Eastern Daylight */
572 { "CST", tZONE
, -HOUR
( 6) }, /* Central Standard */
573 { "CDT", tDAYZONE
, -HOUR
( 6) }, /* Central Daylight */
574 { "MST", tZONE
, -HOUR
( 7) }, /* Mountain Standard */
575 { "MDT", tDAYZONE
, -HOUR
( 7) }, /* Mountain Daylight */
576 { "PST", tZONE
, -HOUR
( 8) }, /* Pacific Standard */
577 { "PDT", tDAYZONE
, -HOUR
( 8) }, /* Pacific Daylight */
578 { "AKST", tZONE
, -HOUR
( 9) }, /* Alaska Standard */
579 { "AKDT", tDAYZONE
, -HOUR
( 9) }, /* Alaska Daylight */
580 { "HST", tZONE
, -HOUR
(10) }, /* Hawaii Standard */
581 { "HAST", tZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Standard */
582 { "HADT", tDAYZONE
, -HOUR
(10) }, /* Hawaii-Aleutian Daylight */
583 { "SST", tZONE
, -HOUR
(12) }, /* Samoa Standard */
584 { "WAT", tZONE
, HOUR
( 1) }, /* West Africa */
585 { "CET", tZONE
, HOUR
( 1) }, /* Central European */
586 { "CEST", tDAYZONE
, HOUR
( 1) }, /* Central European Summer */
587 { "MET", tZONE
, HOUR
( 1) }, /* Middle European */
588 { "MEZ", tZONE
, HOUR
( 1) }, /* Middle European */
589 { "MEST", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
590 { "MESZ", tDAYZONE
, HOUR
( 1) }, /* Middle European Summer */
591 { "EET", tZONE
, HOUR
( 2) }, /* Eastern European */
592 { "EEST", tDAYZONE
, HOUR
( 2) }, /* Eastern European Summer */
593 { "CAT", tZONE
, HOUR
( 2) }, /* Central Africa */
594 { "SAST", tZONE
, HOUR
( 2) }, /* South Africa Standard */
595 { "EAT", tZONE
, HOUR
( 3) }, /* East Africa */
596 { "MSK", tZONE
, HOUR
( 3) }, /* Moscow */
597 { "MSD", tDAYZONE
, HOUR
( 3) }, /* Moscow Daylight */
598 { "IST", tZONE
, (HOUR
( 5) + 30) }, /* India Standard */
599 { "SGT", tZONE
, HOUR
( 8) }, /* Singapore */
600 { "KST", tZONE
, HOUR
( 9) }, /* Korea Standard */
601 { "JST", tZONE
, HOUR
( 9) }, /* Japan Standard */
602 { "GST", tZONE
, HOUR
(10) }, /* Guam Standard */
603 { "NZST", tZONE
, HOUR
(12) }, /* New Zealand Standard */
604 { "NZDT", tDAYZONE
, HOUR
(12) }, /* New Zealand Daylight */
608 /* Military time zone table. */
609 static table
const military_table
[] =
611 { "A", tZONE
, -HOUR
( 1) },
612 { "B", tZONE
, -HOUR
( 2) },
613 { "C", tZONE
, -HOUR
( 3) },
614 { "D", tZONE
, -HOUR
( 4) },
615 { "E", tZONE
, -HOUR
( 5) },
616 { "F", tZONE
, -HOUR
( 6) },
617 { "G", tZONE
, -HOUR
( 7) },
618 { "H", tZONE
, -HOUR
( 8) },
619 { "I", tZONE
, -HOUR
( 9) },
620 { "K", tZONE
, -HOUR
(10) },
621 { "L", tZONE
, -HOUR
(11) },
622 { "M", tZONE
, -HOUR
(12) },
623 { "N", tZONE
, HOUR
( 1) },
624 { "O", tZONE
, HOUR
( 2) },
625 { "P", tZONE
, HOUR
( 3) },
626 { "Q", tZONE
, HOUR
( 4) },
627 { "R", tZONE
, HOUR
( 5) },
628 { "S", tZONE
, HOUR
( 6) },
629 { "T", tZONE
, HOUR
( 7) },
630 { "U", tZONE
, HOUR
( 8) },
631 { "V", tZONE
, HOUR
( 9) },
632 { "W", tZONE
, HOUR
(10) },
633 { "X", tZONE
, HOUR
(11) },
634 { "Y", tZONE
, HOUR
(12) },
635 { "Z", tZONE
, HOUR
( 0) },
642 to_hour
(int hours
, int meridian
)
647 return
0 <= hours
&& hours
< 24 ? hours
: -1;
649 return
0 < hours
&& hours
< 12 ? hours
: hours
== 12 ?
0 : -1;
651 return
0 < hours
&& hours
< 12 ? hours
+ 12 : hours
== 12 ?
12 : -1;
660 to_year
(textint textyear
)
662 int year
= textyear.value
;
667 /* XPG4 suggests that years 00-68 map to 2000-2068, and
668 years 69-99 map to 1969-1999. */
669 if
(textyear.digits
== 2)
670 year
+= year
< 69 ?
2000 : 1900;
676 lookup_zone
(struct parser_control
const *pc
, char const *name
)
680 /* Try local zone abbreviations first; they're more likely to be right. */
681 for
(tp
= pc
->local_time_zone_table
; tp
->name
; tp
++)
682 if
(strcmp
(name
, tp
->name
) == 0)
685 for
(tp
= time_zone_table
; tp
->name
; tp
++)
686 if
(strcmp
(name
, tp
->name
) == 0)
693 /* Yield the difference between *A and *B,
694 measured in seconds, ignoring leap seconds.
695 The body of this function is taken directly from the GNU C Library;
696 see src/strftime.c. */
698 tm_diff
(struct tm
const *a
, struct tm
const *b
)
700 /* Compute intervening leap days correctly even if year is negative.
701 Take care to avoid int overflow in leap day calculations,
702 but it's OK to assume that A and B are close to each other. */
703 int a4
= (a
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (a
->tm_year
& 3);
704 int b4
= (b
->tm_year
>> 2) + (TM_YEAR_BASE
>> 2) - ! (b
->tm_year
& 3);
705 int a100
= a4
/ 25 - (a4 %
25 < 0);
706 int b100
= b4
/ 25 - (b4 %
25 < 0);
707 int a400
= a100
>> 2;
708 int b400
= b100
>> 2;
709 int intervening_leap_days
= (a4
- b4
) - (a100
- b100
) + (a400
- b400
);
710 int years
= a
->tm_year
- b
->tm_year
;
711 int days
= (365 * years
+ intervening_leap_days
712 + (a
->tm_yday
- b
->tm_yday
));
713 return
(60 * (60 * (24 * days
+ (a
->tm_hour
- b
->tm_hour
))
714 + (a
->tm_min
- b
->tm_min
))
715 + (a
->tm_sec
- b
->tm_sec
));
717 #endif /* ! HAVE_TM_GMTOFF */
720 lookup_word
(struct parser_control
const *pc
, char *word
)
729 /* Make it uppercase. */
730 for
(p
= word
; *p
; p
++)
731 if
(ISLOWER
((unsigned char) *p
))
732 *p
= toupper
((unsigned char) *p
);
734 for
(tp
= meridian_table
; tp
->name
; tp
++)
735 if
(strcmp
(word
, tp
->name
) == 0)
738 /* See if we have an abbreviation for a month. */
739 wordlen
= strlen
(word
);
740 abbrev
= wordlen
== 3 ||
(wordlen
== 4 && word
[3] == '.');
742 for
(tp
= month_and_day_table
; tp
->name
; tp
++)
743 if
((abbrev ? strncmp
(word
, tp
->name
, 3) : strcmp
(word
, tp
->name
)) == 0)
746 if
((tp
= lookup_zone
(pc
, word
)))
749 if
(strcmp
(word
, dst_table
[0].name
) == 0)
752 for
(tp
= time_units_table
; tp
->name
; tp
++)
753 if
(strcmp
(word
, tp
->name
) == 0)
756 /* Strip off any plural and try the units table again. */
757 if
(word
[wordlen
- 1] == 'S')
759 word
[wordlen
- 1] = '\0';
760 for
(tp
= time_units_table
; tp
->name
; tp
++)
761 if
(strcmp
(word
, tp
->name
) == 0)
763 word
[wordlen
- 1] = 'S'; /* For "this" in relative_time_table. */
766 for
(tp
= relative_time_table
; tp
->name
; tp
++)
767 if
(strcmp
(word
, tp
->name
) == 0)
770 /* Military time zones. */
772 for
(tp
= military_table
; tp
->name
; tp
++)
773 if
(word
[0] == tp
->name
[0])
776 /* Drop out any periods and try the time zone table again. */
777 for
(i
= 0, p
= q
= word
; (*p
= *q
); q
++)
782 if
(i
&& (tp
= lookup_zone
(pc
, word
)))
789 yylex (YYSTYPE *lvalp
, struct parser_control
*pc
)
796 while
(c
= *pc
->input
, ISSPACE
(c
))
799 if
(ISDIGIT
(c
) || c
== '-' || c
== '+')
804 if
(c
== '-' || c
== '+')
806 sign
= c
== '-' ?
-1 : 1;
809 /* skip the '-' sign */
818 value
= 10 * value
+ c
- '0';
822 lvalp
->textintval.value
= sign
< 0 ?
-value
: value
;
823 lvalp
->textintval.digits
= p
- pc
->input
;
825 return sign ? tSNUMBER
: tUNUMBER
;
840 while
(ISALPHA
(c
) || c
== '.');
843 tp
= lookup_word
(pc
, buff
);
846 lvalp
->intval
= tp
->value
;
867 /* Do nothing if the parser reports an error. */
869 yyerror (struct parser_control
*pc ATTRIBUTE_UNUSED
, const char *s ATTRIBUTE_UNUSED
)
874 /* Parse a date/time string P. Return the corresponding time_t value,
875 or (time_t) -1 if there is an error. P can be an incomplete or
876 relative time specification; if so, use *NOW as the basis for the
879 get_date
(const char *p
, const time_t *now
)
881 time_t Start
= now ?
*now
: time
(0);
882 struct tm
*tmp
= localtime
(&Start
);
885 struct parser_control pc
;
891 pc.year.value
= tmp
->tm_year
+ TM_YEAR_BASE
;
893 pc.month
= tmp
->tm_mon
+ 1;
894 pc.day
= tmp
->tm_mday
;
895 pc.hour
= tmp
->tm_hour
;
896 pc.minutes
= tmp
->tm_min
;
897 pc.seconds
= tmp
->tm_sec
;
898 tm.tm_isdst
= tmp
->tm_isdst
;
911 pc.local_zones_seen
= 0;
914 #ifdef HAVE_STRUCT_TM_TM_ZONE
915 pc.local_time_zone_table
[0].name
= tmp
->tm_zone
;
916 pc.local_time_zone_table
[0].type
= tLOCAL_ZONE
;
917 pc.local_time_zone_table
[0].value
= tmp
->tm_isdst
;
918 pc.local_time_zone_table
[1].name
= 0;
920 /* Probe the names used in the next three calendar quarters, looking
921 for a tm_isdst different from the one we already have. */
924 for
(quarter
= 1; quarter
<= 3; quarter
++)
926 time_t probe
= Start
+ quarter
* (90 * 24 * 60 * 60);
927 struct tm
*probe_tm
= localtime
(&probe
);
928 if
(probe_tm
&& probe_tm
->tm_zone
929 && probe_tm
->tm_isdst
!= pc.local_time_zone_table
[0].value
)
932 pc.local_time_zone_table
[1].name
= probe_tm
->tm_zone
;
933 pc.local_time_zone_table
[1].type
= tLOCAL_ZONE
;
934 pc.local_time_zone_table
[1].value
= probe_tm
->tm_isdst
;
935 pc.local_time_zone_table
[2].name
= 0;
945 extern
char *tzname
[];
948 for
(i
= 0; i
< 2; i
++)
950 pc.local_time_zone_table
[i
].name
= tzname
[i
];
951 pc.local_time_zone_table
[i
].type
= tLOCAL_ZONE
;
952 pc.local_time_zone_table
[i
].value
= i
;
954 pc.local_time_zone_table
[i
].name
= 0;
957 pc.local_time_zone_table
[0].name
= 0;
961 if
(pc.local_time_zone_table
[0].name
&& pc.local_time_zone_table
[1].name
962 && ! strcmp
(pc.local_time_zone_table
[0].name
,
963 pc.local_time_zone_table
[1].name
))
965 /* This locale uses the same abbreviation for standard and
966 daylight times. So if we see that abbreviation, we don't
967 know whether it's daylight time. */
968 pc.local_time_zone_table
[0].value
= -1;
969 pc.local_time_zone_table
[1].name
= 0;
972 if
(yyparse (&pc
) != 0
973 ||
1 < pc.times_seen ||
1 < pc.dates_seen ||
1 < pc.days_seen
974 ||
1 < (pc.local_zones_seen
+ pc.zones_seen
)
975 ||
(pc.local_zones_seen
&& 1 < pc.local_isdst
))
978 tm.tm_year
= to_year
(pc.year
) - TM_YEAR_BASE
+ pc.rel_year
;
979 tm.tm_mon
= pc.month
- 1 + pc.rel_month
;
980 tm.tm_mday
= pc.day
+ pc.rel_day
;
981 if
(pc.times_seen ||
(pc.rels_seen
&& ! pc.dates_seen
&& ! pc.days_seen
))
983 tm.tm_hour
= to_hour
(pc.hour
, pc.meridian
);
986 tm.tm_min
= pc.minutes
;
987 tm.tm_sec
= pc.seconds
;
991 tm.tm_hour
= tm.tm_min
= tm.tm_sec
= 0;
994 /* Let mktime deduce tm_isdst if we have an absolute time stamp,
995 or if the relative time stamp mentions days, months, or years. */
996 if
(pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
997 | pc.rel_month | pc.rel_year
)
1000 /* But if the input explicitly specifies local time with or without
1001 DST, give mktime that information. */
1002 if
(pc.local_zones_seen
)
1003 tm.tm_isdst
= pc.local_isdst
;
1007 Start
= mktime
(&tm
);
1009 if
(Start
== (time_t) -1)
1012 /* Guard against falsely reporting errors near the time_t boundaries
1013 when parsing times in other time zones. For example, if the min
1014 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
1015 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
1016 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
1017 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
1018 zone by 24 hours to compensate. This algorithm assumes that
1019 there is no DST transition within a day of the time_t boundaries. */
1023 if
(tm.tm_year
<= EPOCH_YEAR
- TM_YEAR_BASE
)
1026 pc.time_zone
+= 24 * 60;
1031 pc.time_zone
-= 24 * 60;
1033 Start
= mktime
(&tm
);
1036 if
(Start
== (time_t) -1)
1040 if
(pc.days_seen
&& ! pc.dates_seen
)
1042 tm.tm_mday
+= ((pc.day_number
- tm.tm_wday
+ 7) %
7
1043 + 7 * (pc.day_ordinal
- (0 < pc.day_ordinal
)));
1045 Start
= mktime
(&tm
);
1046 if
(Start
== (time_t) -1)
1052 int delta
= pc.time_zone
* 60;
1053 #ifdef HAVE_TM_GMTOFF
1054 delta
-= tm.tm_gmtoff
;
1056 struct tm
*gmt
= gmtime
(&Start
);
1059 delta
-= tm_diff
(&tm
, gmt
);
1061 if
((Start
< Start
- delta
) != (delta
< 0))
1062 return
-1; /* time_t overflow */
1066 /* Add relative hours, minutes, and seconds. Ignore leap seconds;
1067 i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
1068 leap second. Typically this is not what the user wants, but it's
1069 too hard to do it the other way, because the time zone indicator
1070 must be applied before relative times, and if mktime is applied
1071 again the time zone will be lost. */
1074 long d1
= 60 * 60 * (long) pc.rel_hour
;
1075 time_t t1
= t0
+ d1
;
1076 long d2
= 60 * (long) pc.rel_minutes
;
1077 time_t t2
= t1
+ d2
;
1078 int d3
= pc.rel_seconds
;
1079 time_t t3
= t2
+ d3
;
1080 if
((d1
/ (60 * 60) ^ pc.rel_hour
)
1081 |
(d2
/ 60 ^ pc.rel_minutes
)
1082 |
((t0
+ d1
< t0
) ^
(d1
< 0))
1083 |
((t1
+ d2
< t1
) ^
(d2
< 0))
1084 |
((t2
+ d3
< t2
) ^
(d3
< 0)))
1097 main
(int ac
, char **av
)
1102 printf
("Enter date, or blank line to exit.\n\t> ");
1105 buff
[BUFSIZ
- 1] = 0;
1106 while
(fgets
(buff
, BUFSIZ
- 1, stdin
) && buff
[0])
1108 d
= get_date
(buff
, 0);
1109 if
(d
== (time_t) -1)
1110 printf
("Bad format - couldn't convert.\n");
1112 printf
("%s", ctime
(&d
));
1118 #endif /* defined TEST */