4 ** Originally written by Steven M. Bellovin <smb@research.att.com> while
5 ** at the University of North Carolina at Chapel Hill. Later tweaked by
6 ** a couple of people on Usenet. Completely overhauled by Rich $alz
7 ** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
8 ** Further revised (removed obsolete constructs and cleaned up timezone
9 ** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
10 ** helped in September, 1992. Art Cancro <ajc@uncensored.citadel.org> cleaned
11 ** it up for ANSI C in December, 1999.
13 ** This grammar has six shift/reduce conflicts.
15 ** This code is in the public domain and has no copyright.
17 /* SUPPRESS 530 *//* Empty body for statement */
18 /* SUPPRESS 593 on yyerrlab *//* Label was not used */
19 /* SUPPRESS 593 on yynewstate *//* Label was not used */
20 /* SUPPRESS 595 on yypvt *//* Automatic variable may be used before set */
26 #include <sys/types.h>
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
34 # include <sys/time.h>
41 # if !STDC_HEADERS && HAVE_MEMORY_H
50 #include "parsedate.h"
54 #define yyparse date_parse
55 #define yylex date_lex
56 #define yyerror date_error
59 /* See the LeapYears table in Convert. */
61 #define END_OF_TIME 2038
62 /* Constants for general time calculations. */
64 #define SECSPERDAY (24L * 60L * 60L)
65 /* Readability for TABLE stuff. */
66 #define HOUR(x) (x * 60)
70 #define IS7BIT(x) ((unsigned int)(x) < 0200)
72 #define SIZEOF(array) ((int)(sizeof array / sizeof array[0]))
73 #define ENDOF(array) (&array[SIZEOF(array)])
77 ** An entry in the lexical lookup table.
79 typedef
struct _TABLE
{
86 ** Daylight-savings mode: on, off, or not yet known.
88 typedef
enum _DSTMODE
{
89 DSTon
, DSToff
, DSTmaybe
93 ** Meridian: am, pm, or 24-hour style.
95 typedef
enum _MERIDIAN
{
101 ** Global variables. We could get rid of most of them by using a yacc
102 ** union, but this is more efficient. (This routine predates the
103 ** yacc %union construct.)
105 static char *yyInput
;
106 static DSTMODE yyDSTmode
;
107 static int yyHaveDate
;
108 static int yyHaveRel
;
109 static int yyHaveTime
;
110 static time_t yyTimezone
;
112 static time_t yyHour
;
113 static time_t yyMinutes
;
114 static time_t yyMonth
;
115 static time_t yySeconds
;
116 static time_t yyYear
;
117 static MERIDIAN yyMeridian
;
118 static time_t yyRelMonth
;
119 static time_t yyRelSeconds
;
122 static void date_error
(char *);
127 enum _MERIDIAN Meridian
;
130 %token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
131 %token tUNUMBER tZONE
133 %type
<Number
> tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
134 %type
<Number
> tSNUMBER tUNUMBER tZONE numzone zone
135 %type
<Meridian
> tMERIDIAN o_merid
146 /* I am compulsive about lint natterings... */
147 if
(yyHaveTime
== -1) {
164 time
: tUNUMBER o_merid
{
171 yyMinutes
= $1 %
100;
176 | tUNUMBER
':' tUNUMBER o_merid
{
182 | tUNUMBER
':' tUNUMBER numzone
{
189 | tUNUMBER
':' tUNUMBER
':' tUNUMBER o_merid
{
195 | tUNUMBER
':' tUNUMBER
':' tUNUMBER numzone
{
214 /* Only allow "GMT+300" and "GMT-0800" */
230 /* Unix and GMT and numeric timezones -- a little confusing. */
232 /* Don't work with negative modulus. */
234 if
($1 > 9999 ||
(i
= $1 %
100) >= 60) {
237 $$
= ($1 / 100) * 60 + i
;
240 if
($1 > 9999 ||
(i
= $1 %
100) >= 60) {
243 $$
= -(($1 / 100) * 60 + i
);
248 date
: tUNUMBER
'/' tUNUMBER
{
252 | tUNUMBER
'/' tUNUMBER
'/' tUNUMBER
{
268 | tMONTH tUNUMBER
',' tUNUMBER
{
277 | tUNUMBER tMONTH tUNUMBER
{
282 | tDAY
',' tUNUMBER tMONTH tUNUMBER
{
289 rel
: tSNUMBER tSEC_UNIT
{
290 yyRelSeconds
+= $1 * $2;
292 | tUNUMBER tSEC_UNIT
{
293 yyRelSeconds
+= $1 * $2;
295 | tSNUMBER tMONTH_UNIT
{
296 yyRelMonth
+= $1 * $2;
298 | tUNUMBER tMONTH_UNIT
{
299 yyRelMonth
+= $1 * $2;
303 o_merid
: /* NULL */ {
313 /* Month and day table. */
314 static TABLE MonthDayTable
[] = {
315 { "january", tMONTH
, 1 },
316 { "february", tMONTH
, 2 },
317 { "march", tMONTH
, 3 },
318 { "april", tMONTH
, 4 },
319 { "may", tMONTH
, 5 },
320 { "june", tMONTH
, 6 },
321 { "july", tMONTH
, 7 },
322 { "august", tMONTH
, 8 },
323 { "september", tMONTH
, 9 },
324 { "october", tMONTH
, 10 },
325 { "november", tMONTH
, 11 },
326 { "december", tMONTH
, 12 },
327 /* The value of the day isn't used... */
328 { "sunday", tDAY
, 0 },
329 { "monday", tDAY
, 0 },
330 { "tuesday", tDAY
, 0 },
331 { "wednesday", tDAY
, 0 },
332 { "thursday", tDAY
, 0 },
333 { "friday", tDAY
, 0 },
334 { "saturday", tDAY
, 0 },
337 /* Time units table. */
338 static TABLE UnitsTable
[] = {
339 { "year", tMONTH_UNIT
, 12 },
340 { "month", tMONTH_UNIT
, 1 },
341 { "week", tSEC_UNIT
, 7L * 24 * 60 * 60 },
342 { "day", tSEC_UNIT
, 1L * 24 * 60 * 60 },
343 { "hour", tSEC_UNIT
, 60 * 60 },
344 { "minute", tSEC_UNIT
, 60 },
345 { "min", tSEC_UNIT
, 60 },
346 { "second", tSEC_UNIT
, 1 },
347 { "sec", tSEC_UNIT
, 1 },
350 /* Timezone table. */
351 static TABLE TimezoneTable
[] = {
352 { "gmt", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
353 { "ut", tZONE
, HOUR
( 0) }, /* Universal */
354 { "utc", tZONE
, HOUR
( 0) }, /* Universal Coordinated */
355 { "cut", tZONE
, HOUR
( 0) }, /* Coordinated Universal */
356 { "z", tZONE
, HOUR
( 0) }, /* Greenwich Mean */
357 { "wet", tZONE
, HOUR
( 0) }, /* Western European */
358 { "bst", tDAYZONE
, HOUR
( 0) }, /* British Summer */
359 { "nst", tZONE
, HOUR
(3)+30 }, /* Newfoundland Standard */
360 { "ndt", tDAYZONE
, HOUR
(3)+30 }, /* Newfoundland Daylight */
361 { "ast", tZONE
, HOUR
( 4) }, /* Atlantic Standard */
362 { "adt", tDAYZONE
, HOUR
( 4) }, /* Atlantic Daylight */
363 { "est", tZONE
, HOUR
( 5) }, /* Eastern Standard */
364 { "edt", tDAYZONE
, HOUR
( 5) }, /* Eastern Daylight */
365 { "cst", tZONE
, HOUR
( 6) }, /* Central Standard */
366 { "cdt", tDAYZONE
, HOUR
( 6) }, /* Central Daylight */
367 { "mst", tZONE
, HOUR
( 7) }, /* Mountain Standard */
368 { "mdt", tDAYZONE
, HOUR
( 7) }, /* Mountain Daylight */
369 { "pst", tZONE
, HOUR
( 8) }, /* Pacific Standard */
370 { "pdt", tDAYZONE
, HOUR
( 8) }, /* Pacific Daylight */
371 { "yst", tZONE
, HOUR
( 9) }, /* Yukon Standard */
372 { "ydt", tDAYZONE
, HOUR
( 9) }, /* Yukon Daylight */
373 { "akst", tZONE
, HOUR
( 9) }, /* Alaska Standard */
374 { "akdt", tDAYZONE
, HOUR
( 9) }, /* Alaska Daylight */
375 { "hst", tZONE
, HOUR
(10) }, /* Hawaii Standard */
376 { "hast", tZONE
, HOUR
(10) }, /* Hawaii-Aleutian Standard */
377 { "hadt", tDAYZONE
, HOUR
(10) }, /* Hawaii-Aleutian Daylight */
378 { "ces", tDAYZONE
, -HOUR
(1) }, /* Central European Summer */
379 { "cest", tDAYZONE
, -HOUR
(1) }, /* Central European Summer */
380 { "mez", tZONE
, -HOUR
(1) }, /* Middle European */
381 { "mezt", tDAYZONE
, -HOUR
(1) }, /* Middle European Summer */
382 { "cet", tZONE
, -HOUR
(1) }, /* Central European */
383 { "met", tZONE
, -HOUR
(1) }, /* Middle European */
384 { "eet", tZONE
, -HOUR
(2) }, /* Eastern Europe */
385 { "msk", tZONE
, -HOUR
(3) }, /* Moscow Winter */
386 { "msd", tDAYZONE
, -HOUR
(3) }, /* Moscow Summer */
387 { "wast", tZONE
, -HOUR
(8) }, /* West Australian Standard */
388 { "wadt", tDAYZONE
, -HOUR
(8) }, /* West Australian Daylight */
389 { "hkt", tZONE
, -HOUR
(8) }, /* Hong Kong */
390 { "cct", tZONE
, -HOUR
(8) }, /* China Coast */
391 { "jst", tZONE
, -HOUR
(9) }, /* Japan Standard */
392 { "kst", tZONE
, -HOUR
(9) }, /* Korean Standard */
393 { "kdt", tZONE
, -HOUR
(9) }, /* Korean Daylight */
394 { "cast", tZONE
, -(HOUR
(9)+30) }, /* Central Australian Standard */
395 { "cadt", tDAYZONE
, -(HOUR
(9)+30) }, /* Central Australian Daylight */
396 { "east", tZONE
, -HOUR
(10) }, /* Eastern Australian Standard */
397 { "eadt", tDAYZONE
, -HOUR
(10) }, /* Eastern Australian Daylight */
398 { "nzst", tZONE
, -HOUR
(12) }, /* New Zealand Standard */
399 { "nzdt", tDAYZONE
, -HOUR
(12) }, /* New Zealand Daylight */
401 /* For completeness we include the following entries. */
404 /* Duplicate names. Either they conflict with a zone listed above
405 * (which is either more likely to be seen or just been in circulation
406 * longer), or they conflict with another zone in this section and
407 * we could not reasonably choose one over the other. */
408 { "fst", tZONE
, HOUR
( 2) }, /* Fernando De Noronha Standard */
409 { "fdt", tDAYZONE
, HOUR
( 2) }, /* Fernando De Noronha Daylight */
410 { "bst", tZONE
, HOUR
( 3) }, /* Brazil Standard */
411 { "est", tZONE
, HOUR
( 3) }, /* Eastern Standard (Brazil) */
412 { "edt", tDAYZONE
, HOUR
( 3) }, /* Eastern Daylight (Brazil) */
413 { "wst", tZONE
, HOUR
( 4) }, /* Western Standard (Brazil) */
414 { "wdt", tDAYZONE
, HOUR
( 4) }, /* Western Daylight (Brazil) */
415 { "cst", tZONE
, HOUR
( 5) }, /* Chile Standard */
416 { "cdt", tDAYZONE
, HOUR
( 5) }, /* Chile Daylight */
417 { "ast", tZONE
, HOUR
( 5) }, /* Acre Standard */
418 { "adt", tDAYZONE
, HOUR
( 5) }, /* Acre Daylight */
419 { "cst", tZONE
, HOUR
( 5) }, /* Cuba Standard */
420 { "cdt", tDAYZONE
, HOUR
( 5) }, /* Cuba Daylight */
421 { "est", tZONE
, HOUR
( 6) }, /* Easter Island Standard */
422 { "edt", tDAYZONE
, HOUR
( 6) }, /* Easter Island Daylight */
423 { "sst", tZONE
, HOUR
(11) }, /* Samoa Standard */
424 { "ist", tZONE
, -HOUR
(2) }, /* Israel Standard */
425 { "idt", tDAYZONE
, -HOUR
(2) }, /* Israel Daylight */
426 { "idt", tDAYZONE
, -(HOUR
(3)+30) }, /* Iran Daylight */
427 { "ist", tZONE
, -(HOUR
(3)+30) }, /* Iran Standard */
428 { "cst", tZONE
, -HOUR
(8) }, /* China Standard */
429 { "cdt", tDAYZONE
, -HOUR
(8) }, /* China Daylight */
430 { "sst", tZONE
, -HOUR
(8) }, /* Singapore Standard */
432 /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
433 { "gst", tZONE
, HOUR
( 3) }, /* Greenland Standard */
434 { "wat", tZONE
, -HOUR
(1) }, /* West Africa */
435 { "at", tZONE
, HOUR
( 2) }, /* Azores */
436 { "gst", tZONE
, -HOUR
(10) }, /* Guam Standard */
437 { "nft", tZONE
, HOUR
(3)+30 }, /* Newfoundland */
438 { "idlw", tZONE
, HOUR
(12) }, /* International Date Line West */
439 { "mewt", tZONE
, -HOUR
(1) }, /* Middle European Winter */
440 { "mest", tDAYZONE
, -HOUR
(1) }, /* Middle European Summer */
441 { "swt", tZONE
, -HOUR
(1) }, /* Swedish Winter */
442 { "sst", tDAYZONE
, -HOUR
(1) }, /* Swedish Summer */
443 { "fwt", tZONE
, -HOUR
(1) }, /* French Winter */
444 { "fst", tDAYZONE
, -HOUR
(1) }, /* French Summer */
445 { "bt", tZONE
, -HOUR
(3) }, /* Baghdad */
446 { "it", tZONE
, -(HOUR
(3)+30) }, /* Iran */
447 { "zp4", tZONE
, -HOUR
(4) }, /* USSR Zone 3 */
448 { "zp5", tZONE
, -HOUR
(5) }, /* USSR Zone 4 */
449 { "ist", tZONE
, -(HOUR
(5)+30) }, /* Indian Standard */
450 { "zp6", tZONE
, -HOUR
(6) }, /* USSR Zone 5 */
451 { "nst", tZONE
, -HOUR
(7) }, /* North Sumatra */
452 { "sst", tZONE
, -HOUR
(7) }, /* South Sumatra */
453 { "jt", tZONE
, -(HOUR
(7)+30) }, /* Java (3pm in Cronusland!) */
454 { "nzt", tZONE
, -HOUR
(12) }, /* New Zealand */
455 { "idle", tZONE
, -HOUR
(12) }, /* International Date Line East */
456 { "cat", tZONE
, HOUR
(10) }, /* -- expired 1967 */
457 { "nt", tZONE
, HOUR
(11) }, /* -- expired 1967 */
458 { "ahst", tZONE
, HOUR
(10) }, /* -- expired 1983 */
459 { "hdt", tDAYZONE
, HOUR
(10) }, /* -- expired 1986 */
473 ToSeconds
(time_t Hours
, time_t Minutes
, time_t Seconds
, MERIDIAN Meridian
)
475 if
(Minutes
< 0 || Minutes
> 59 || Seconds
< 0 || Seconds
> 61)
477 if
(Meridian
== MER24
) {
478 if
(Hours
< 0 || Hours
> 23)
482 if
(Hours
< 1 || Hours
> 12)
486 if
(Meridian
== MERpm
)
489 return
(Hours
* 60L + Minutes
) * 60L + Seconds
;
494 Convert
(time_t Month
, time_t Day
, time_t Year
,
495 time_t Hours
, time_t Minutes
, time_t Seconds
,
496 MERIDIAN Meridian
, DSTMODE dst
)
498 static int DaysNormal
[13] = {
499 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
501 static int DaysLeap
[13] = {
502 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
504 static int LeapYears
[] = {
505 1972, 1976, 1980, 1984, 1988, 1992, 1996,
506 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
510 register
time_t Julian
;
520 for
(mp
= DaysNormal
, yp
= LeapYears
; yp
< ENDOF
(LeapYears
); yp
++)
525 if
(Year
< EPOCH || Year
> END_OF_TIME
526 || Month
< 1 || Month
> 12
527 /* NOSTRICT *//* conversion from long may lose accuracy */
528 || Day
< 1 || Day
> mp
[(int)Month
])
531 Julian
= Day
- 1 + (Year
- EPOCH
) * 365;
532 for
(yp
= LeapYears
; yp
< ENDOF
(LeapYears
); yp
++, Julian
++)
535 for
(i
= 1; i
< Month
; i
++)
537 Julian
*= SECSPERDAY
;
538 Julian
+= yyTimezone
* 60L;
539 if
((tod
= ToSeconds
(Hours
, Minutes
, Seconds
, Meridian
)) < 0)
543 if
(dst
== DSTon ||
(dst
== DSTmaybe
&& localtime
(&tod
)->tm_isdst
))
544 Julian
-= DST_OFFSET
* 60L * 60L;
550 DSTcorrect
(time_t Start
, time_t Future
)
555 StartDay
= (localtime
(&Start
)->tm_hour
+ 1) %
24;
556 FutureDay
= (localtime
(&Future
)->tm_hour
+ 1) %
24;
557 return
(Future
- Start
) + (StartDay
- FutureDay
) * DST_OFFSET
* 60L * 60L;
562 RelativeMonth
(time_t Start
, time_t RelMonth
)
568 tm
= localtime
(&Start
);
569 Month
= 12 * tm
->tm_year
+ tm
->tm_mon
+ RelMonth
;
571 Month
= Month %
12 + 1;
572 return DSTcorrect
(Start
,
573 Convert
(Month
, (time_t)tm
->tm_mday
, Year
,
574 (time_t)tm
->tm_hour
, (time_t)tm
->tm_min
, (time_t)tm
->tm_sec
,
580 LookupWord
(char *buff
, register
int length
)
590 /* See if we have an abbreviation for a month. */
591 if
(length
== 3 ||
(length
== 4 && p
[3] == '.'))
592 for
(tp
= MonthDayTable
; tp
< ENDOF
(MonthDayTable
); tp
++) {
594 if
(c
== q
[0] && p
[1] == q
[1] && p
[2] == q
[2]) {
595 yylval.Number
= tp
->value
;
600 for
(tp
= MonthDayTable
; tp
< ENDOF
(MonthDayTable
); tp
++)
601 if
(c
== tp
->name
[0] && strcmp
(p
, tp
->name
) == 0) {
602 yylval.Number
= tp
->value
;
606 /* Try for a timezone. */
607 for
(tp
= TimezoneTable
; tp
< ENDOF
(TimezoneTable
); tp
++)
608 if
(c
== tp
->name
[0] && p
[1] == tp
->name
[1]
609 && strcmp
(p
, tp
->name
) == 0) {
610 yylval.Number
= tp
->value
;
614 /* Try the units table. */
615 for
(tp
= UnitsTable
; tp
< ENDOF
(UnitsTable
); tp
++)
616 if
(c
== tp
->name
[0] && strcmp
(p
, tp
->name
) == 0) {
617 yylval.Number
= tp
->value
;
621 /* Strip off any plural and try the units table again. */
622 if
(--length
> 0 && p
[length
] == 's') {
624 for
(tp
= UnitsTable
; tp
< ENDOF
(UnitsTable
); tp
++)
625 if
(c
== tp
->name
[0] && strcmp
(p
, tp
->name
) == 0) {
627 yylval.Number
= tp
->value
;
634 /* Drop out any periods. */
635 for
(p
= buff
, q
= (char*)buff
; *q
; q
++)
640 /* Try the meridians. */
641 if
(buff
[1] == 'm' && buff
[2] == '\0') {
642 if
(buff
[0] == 'a') {
643 yylval.Meridian
= MERam
;
646 if
(buff
[0] == 'p') {
647 yylval.Meridian
= MERpm
;
652 /* If we saw any periods, try the timezones again. */
653 if
(p
- buff
!= length
) {
655 for
(p
= buff
, tp
= TimezoneTable
; tp
< ENDOF
(TimezoneTable
); tp
++)
656 if
(c
== tp
->name
[0] && p
[1] == tp
->name
[1]
657 && strcmp
(p
, tp
->name
) == 0) {
658 yylval.Number
= tp
->value
;
663 /* Unknown word -- assume GMT timezone. */
677 register
int nesting
;
680 /* Get first character after the whitespace. */
682 while
(isspace
(*yyInput
))
686 /* Ignore RFC 822 comments, typically time zone names. */
689 for
(nesting
= 1; (c
= *++yyInput
) != RPAREN ||
--nesting
; )
692 else if
(!IS7BIT
(c
) || c
== '\0' || c
== '\r'
693 ||
(c
== '\\' && ((c
= *++yyInput
) == '\0' ||
!IS7BIT
(c
))))
694 /* Lexical error: bad comment. */
700 if
(isdigit
(c
) || c
== '-' || c
== '+') {
701 if
(c
== '-' || c
== '+') {
702 sign
= c
== '-' ?
-1 : 1;
704 if
(!isdigit
(*yyInput
))
705 /* Skip the plus or minus sign. */
710 for
(i
= 0; (c
= *yyInput
++) != '\0' && isdigit
(c
); )
711 i
= 10 * i
+ c
- '0';
713 yylval.Number
= sign
< 0 ?
-i
: i
;
714 return sign ? tSNUMBER
: tUNUMBER
;
719 for
(p
= buff
; (c
= *yyInput
++) == '.' || isalpha
(c
); )
720 if
(p
< &buff
[sizeof buff
- 1])
721 *p
++ = isupper
(c
) ? tolower
(c
) : c
;
724 return LookupWord
(buff
, p
- buff
);
735 extern
int date_parse
(void);
744 yyDSTmode
= DSTmaybe
;
755 if
(date_parse
() || yyHaveTime
> 1 || yyHaveDate
> 1)
758 if
(yyHaveDate || yyHaveTime
) {
759 Start
= Convert
(yyMonth
, yyDay
, yyYear
, yyHour
, yyMinutes
, yySeconds
,
760 yyMeridian
, yyDSTmode
);
767 Start
+= yyRelSeconds
;
769 Start
+= RelativeMonth
(Start
, yyRelMonth
);
771 /* Have to do *something* with a legitimate -1 so it's distinguishable
772 * from the error return value. (Alternately could set errno on error.) */
773 return Start
== -1 ?
0 : Start
;
785 main
(int ac
, char *av
[])
794 (void)printf
("Enter date, or blank line to exit.\n\t> ");
796 (void)printf
("\t> ");
797 (void)fflush
(stdout
);
798 if
(fgets
(buff
, sizeof buff
, stdin
) == NULL || buff
[0] == '\n')
801 if
(strcmp
(buff
, "yydebug") == 0) {
803 printf
("yydebug = %s\n", yydebug ?
"on" : "off");
807 d
= parsedate
(buff
, (TIMEINFO
*)NULL
);
809 (void)printf
("Bad format - couldn't convert.\n");
811 (void)printf
("%s", ctime
(&d
));