3 ** Originally written by Steven M. Bellovin <smb@research.att.com> while
4 ** at the University of North Carolina at Chapel Hill. Later tweaked by
5 ** a couple of people on Usenet. Completely overhauled by Rich $alz
6 ** <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
8 ** This grammar has 13 shift/reduce conflicts.
10 ** This code is in the public domain and has no copyright.
12 ** Since butchered by Mark Wooding, 1999-03-07.
18 #define ISSPACE(c) (isspace ((unsigned char)c))
19 #define ISALPHA(c) (isalpha ((unsigned char)c))
20 #define ISUPPER(c) (isupper ((unsigned char)c))
21 #define ISDIGIT_LOCALE(c) ((unsigned char)isdigit (c))
23 /* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
24 - Its arg may be any int or unsigned int; it need not be an unsigned char.
25 - It's guaranteed to evaluate its argument exactly once.
26 - It's typically faster.
27 Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
28 only '0' through '9' are digits. Prefer ISDIGIT to ISDIGIT_LOCALE unless
29 it's important to use the locale's definition of `digit' even when the
30 host does not conform to Posix. */
31 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
37 /* Some old versions of bison generate parsers that use bcopy.
38 That loses on systems that don't provide the function, so we have
39 to redefine it here. (Assume everyone has memcpy -- [mdw]) */
41 #define bcopy(from, to, len) memcpy ((to), (from), (len))
43 /* Remap normal yacc parser interface names (yyparse, yylex, yyerror, etc),
44 as well as gratuitiously global symbol names, so we can have multiple
45 yacc generated parsers in the same program. Note that these are only
46 the variables produced by yacc. If other parser generators (bison,
47 byacc, etc) produce additional global names that conflict at link time,
48 then those parser generators need to be fixed instead of adding those
49 names to this list. */
51 #define yymaxdepth gd_maxdepth
52 #define yyparse gd_parse
54 #define yyerror gd_error
55 #define yylval gd_lval
56 #define yychar gd_char
57 #define yydebug gd_debug
58 #define yypact gd_pact
65 #define yyexca gd_exca
66 #define yyerrflag gd_errflag
67 #define yynerrs gd_nerrs
72 #define yystate gd_state
77 #define yylloc gd_lloc
78 #define yyreds gd_reds /* With YYDEBUG defined */
79 #define yytoks gd_toks /* With YYDEBUG defined */
80 #define yylhs gd_yylhs
81 #define yylen gd_yylen
82 #define yydefred gd_yydefred
83 #define yydgoto gd_yydgoto
84 #define yysindex gd_yysindex
85 #define yyrindex gd_yyrindex
86 #define yygindex gd_yygindex
87 #define yytable gd_yytable
88 #define yycheck gd_yycheck
91 static int yyerror ();
94 #define HOUR(x) ((x) * 60)
96 #define MAX_BUFF_LEN 128 /* size of buffer to read the date into */
99 ** An entry in the lexical lookup table.
101 typedef struct _TABLE {
109 ** Meridian: am, pm, or 24-hour style.
111 typedef enum _MERIDIAN {
117 ** Global variables. We could get rid of most of these by using a good
118 ** union as the yacc stack. (This routine was originally written before
119 ** yacc had the %union construct.) Maybe someday; right now we only use
120 ** the %union very rarely.
122 static const char *yyInput;
123 static int yyDayOrdinal;
124 static int yyDayNumber;
125 static int yyHaveDate;
126 static int yyHaveDay;
127 static int yyHaveRel;
128 static int yyHaveTime;
129 static int yyHaveZone;
130 static int yyTimezone;
133 static int yyMinutes;
135 static int yySeconds;
137 static MERIDIAN yyMeridian;
139 static int yyRelHour;
140 static int yyRelMinutes;
141 static int yyRelMonth;
142 static int yyRelSeconds;
143 static int yyRelYear;
149 enum _MERIDIAN Meridian;
152 %token tAGO tDAY tDAY_UNIT tDAYZONE tDST tHOUR_UNIT tID
153 %token tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
154 %token tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
156 %type <Number> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tMINUTE_UNIT
157 %type <Number> tMONTH tMONTH_UNIT
158 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
159 %type <Meridian> tMERIDIAN o_merid
185 time : tUNUMBER tMERIDIAN {
191 | tUNUMBER ':' tUNUMBER o_merid {
197 | tUNUMBER ':' tUNUMBER tSNUMBER {
203 ? -$4 % 100 + (-$4 / 100) * 60
204 : - ($4 % 100 + ($4 / 100) * 60));
206 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
212 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
219 ? -$6 % 100 + (-$6 / 100) * 60
220 : - ($6 % 100 + ($6 / 100) * 60));
228 yyTimezone = $1 - 60;
232 yyTimezone = $1 - 60;
250 date : tUNUMBER '/' tUNUMBER {
254 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
255 /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
256 The goal in recognizing YYYY/MM/DD is solely to support legacy
257 machine-generated dates like those in an RCS log listing. If
258 you want portability, use the ISO 8601 format. */
272 | tUNUMBER tSNUMBER tSNUMBER {
273 /* ISO 8601 format. yyyy-mm-dd. */
278 | tUNUMBER tMONTH tSNUMBER {
279 /* e.g. 17-JUN-1992. */
288 | tMONTH tUNUMBER ',' tUNUMBER {
297 | tUNUMBER tMONTH tUNUMBER {
305 yyRelSeconds = -yyRelSeconds;
306 yyRelMinutes = -yyRelMinutes;
307 yyRelHour = -yyRelHour;
308 yyRelDay = -yyRelDay;
309 yyRelMonth = -yyRelMonth;
310 yyRelYear = -yyRelYear;
315 relunit : tUNUMBER tYEAR_UNIT {
316 yyRelYear += $1 * $2;
318 | tSNUMBER tYEAR_UNIT {
319 yyRelYear += $1 * $2;
324 | tUNUMBER tMONTH_UNIT {
325 yyRelMonth += $1 * $2;
327 | tSNUMBER tMONTH_UNIT {
328 yyRelMonth += $1 * $2;
333 | tUNUMBER tDAY_UNIT {
336 | tSNUMBER tDAY_UNIT {
342 | tUNUMBER tHOUR_UNIT {
343 yyRelHour += $1 * $2;
345 | tSNUMBER tHOUR_UNIT {
346 yyRelHour += $1 * $2;
351 | tUNUMBER tMINUTE_UNIT {
352 yyRelMinutes += $1 * $2;
354 | tSNUMBER tMINUTE_UNIT {
355 yyRelMinutes += $1 * $2;
360 | tUNUMBER tSEC_UNIT {
361 yyRelSeconds += $1 * $2;
363 | tSNUMBER tSEC_UNIT {
364 yyRelSeconds += $1 * $2;
373 if (yyHaveTime && yyHaveDate && !yyHaveRel)
381 yyMonth= ($1/100)%100;
395 yyMinutes = $1 % 100;
416 /* Month and day table. */
417 static TABLE const MonthDayTable[] = {
418 { "january", tMONTH, 1 },
419 { "february", tMONTH, 2 },
420 { "march", tMONTH, 3 },
421 { "april", tMONTH, 4 },
422 { "may", tMONTH, 5 },
423 { "june", tMONTH, 6 },
424 { "july", tMONTH, 7 },
425 { "august", tMONTH, 8 },
426 { "september", tMONTH, 9 },
427 { "sept", tMONTH, 9 },
428 { "october", tMONTH, 10 },
429 { "november", tMONTH, 11 },
430 { "december", tMONTH, 12 },
431 { "sunday", tDAY, 0 },
432 { "monday", tDAY, 1 },
433 { "tuesday", tDAY, 2 },
435 { "wednesday", tDAY, 3 },
436 { "wednes", tDAY, 3 },
437 { "thursday", tDAY, 4 },
439 { "thurs", tDAY, 4 },
440 { "friday", tDAY, 5 },
441 { "saturday", tDAY, 6 },
445 /* Time units table. */
446 static TABLE const UnitsTable[] = {
447 { "year", tYEAR_UNIT, 1 },
448 { "month", tMONTH_UNIT, 1 },
449 { "fortnight", tDAY_UNIT, 14 },
450 { "week", tDAY_UNIT, 7 },
451 { "day", tDAY_UNIT, 1 },
452 { "hour", tHOUR_UNIT, 1 },
453 { "minute", tMINUTE_UNIT, 1 },
454 { "min", tMINUTE_UNIT, 1 },
455 { "second", tSEC_UNIT, 1 },
456 { "sec", tSEC_UNIT, 1 },
460 /* Assorted relative-time words. */
461 static TABLE const OtherTable[] = {
462 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
463 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
464 { "today", tMINUTE_UNIT, 0 },
465 { "now", tMINUTE_UNIT, 0 },
466 { "last", tUNUMBER, -1 },
467 { "this", tMINUTE_UNIT, 0 },
468 { "next", tUNUMBER, 2 },
469 { "first", tUNUMBER, 1 },
470 /* { "second", tUNUMBER, 2 }, */
471 { "third", tUNUMBER, 3 },
472 { "fourth", tUNUMBER, 4 },
473 { "fifth", tUNUMBER, 5 },
474 { "sixth", tUNUMBER, 6 },
475 { "seventh", tUNUMBER, 7 },
476 { "eighth", tUNUMBER, 8 },
477 { "ninth", tUNUMBER, 9 },
478 { "tenth", tUNUMBER, 10 },
479 { "eleventh", tUNUMBER, 11 },
480 { "twelfth", tUNUMBER, 12 },
485 /* The timezone table. */
486 static TABLE const TimezoneTable[] = {
487 { "gmt", tZONE, HOUR ( 0) }, /* Greenwich Mean */
488 { "ut", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
489 { "utc", tZONE, HOUR ( 0) },
490 { "wet", tZONE, HOUR ( 0) }, /* Western European */
491 { "bst", tDAYZONE, HOUR ( 0) }, /* British Summer */
492 { "wat", tZONE, HOUR ( 1) }, /* West Africa */
493 { "at", tZONE, HOUR ( 2) }, /* Azores */
495 /* For completeness. BST is also British Summer, and GST is
496 * also Guam Standard. */
497 { "bst", tZONE, HOUR ( 3) }, /* Brazil Standard */
498 { "gst", tZONE, HOUR ( 3) }, /* Greenland Standard */
501 { "nft", tZONE, HOUR (3.5) }, /* Newfoundland */
502 { "nst", tZONE, HOUR (3.5) }, /* Newfoundland Standard */
503 { "ndt", tDAYZONE, HOUR (3.5) }, /* Newfoundland Daylight */
505 { "ast", tZONE, HOUR ( 4) }, /* Atlantic Standard */
506 { "adt", tDAYZONE, HOUR ( 4) }, /* Atlantic Daylight */
507 { "est", tZONE, HOUR ( 5) }, /* Eastern Standard */
508 { "edt", tDAYZONE, HOUR ( 5) }, /* Eastern Daylight */
509 { "cst", tZONE, HOUR ( 6) }, /* Central Standard */
510 { "cdt", tDAYZONE, HOUR ( 6) }, /* Central Daylight */
511 { "mst", tZONE, HOUR ( 7) }, /* Mountain Standard */
512 { "mdt", tDAYZONE, HOUR ( 7) }, /* Mountain Daylight */
513 { "pst", tZONE, HOUR ( 8) }, /* Pacific Standard */
514 { "pdt", tDAYZONE, HOUR ( 8) }, /* Pacific Daylight */
515 { "yst", tZONE, HOUR ( 9) }, /* Yukon Standard */
516 { "ydt", tDAYZONE, HOUR ( 9) }, /* Yukon Daylight */
517 { "hst", tZONE, HOUR (10) }, /* Hawaii Standard */
518 { "hdt", tDAYZONE, HOUR (10) }, /* Hawaii Daylight */
519 { "cat", tZONE, HOUR (10) }, /* Central Alaska */
520 { "ahst", tZONE, HOUR (10) }, /* Alaska-Hawaii Standard */
521 { "nt", tZONE, HOUR (11) }, /* Nome */
522 { "idlw", tZONE, HOUR (12) }, /* International Date Line West */
523 { "cet", tZONE, -HOUR (1) }, /* Central European */
524 { "met", tZONE, -HOUR (1) }, /* Middle European */
525 { "mewt", tZONE, -HOUR (1) }, /* Middle European Winter */
526 { "mest", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
527 { "mesz", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
528 { "swt", tZONE, -HOUR (1) }, /* Swedish Winter */
529 { "sst", tDAYZONE, -HOUR (1) }, /* Swedish Summer */
530 { "fwt", tZONE, -HOUR (1) }, /* French Winter */
531 { "fst", tDAYZONE, -HOUR (1) }, /* French Summer */
532 { "eet", tZONE, -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
533 { "bt", tZONE, -HOUR (3) }, /* Baghdad, USSR Zone 2 */
535 { "it", tZONE, -HOUR (3.5) },/* Iran */
537 { "zp4", tZONE, -HOUR (4) }, /* USSR Zone 3 */
538 { "zp5", tZONE, -HOUR (5) }, /* USSR Zone 4 */
540 { "ist", tZONE, -HOUR (5.5) },/* Indian Standard */
542 { "zp6", tZONE, -HOUR (6) }, /* USSR Zone 5 */
544 /* For completeness. NST is also Newfoundland Standard, and SST is
545 * also Swedish Summer. */
546 { "nst", tZONE, -HOUR (6.5) },/* North Sumatra */
547 { "sst", tZONE, -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
549 { "wast", tZONE, -HOUR (7) }, /* West Australian Standard */
550 { "wadt", tDAYZONE, -HOUR (7) }, /* West Australian Daylight */
552 { "jt", tZONE, -HOUR (7.5) },/* Java (3pm in Cronusland!) */
554 { "cct", tZONE, -HOUR (8) }, /* China Coast, USSR Zone 7 */
555 { "jst", tZONE, -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
557 { "cast", tZONE, -HOUR (9.5) },/* Central Australian Standard */
558 { "cadt", tDAYZONE, -HOUR (9.5) },/* Central Australian Daylight */
560 { "east", tZONE, -HOUR (10) }, /* Eastern Australian Standard */
561 { "eadt", tDAYZONE, -HOUR (10) }, /* Eastern Australian Daylight */
562 { "gst", tZONE, -HOUR (10) }, /* Guam Standard, USSR Zone 9 */
563 { "nzt", tZONE, -HOUR (12) }, /* New Zealand */
564 { "nzst", tZONE, -HOUR (12) }, /* New Zealand Standard */
565 { "nzdt", tDAYZONE, -HOUR (12) }, /* New Zealand Daylight */
566 { "idle", tZONE, -HOUR (12) }, /* International Date Line East */
570 /* Military timezone table. */
571 static TABLE const MilitaryTable[] = {
572 { "a", tZONE, HOUR ( 1) },
573 { "b", tZONE, HOUR ( 2) },
574 { "c", tZONE, HOUR ( 3) },
575 { "d", tZONE, HOUR ( 4) },
576 { "e", tZONE, HOUR ( 5) },
577 { "f", tZONE, HOUR ( 6) },
578 { "g", tZONE, HOUR ( 7) },
579 { "h", tZONE, HOUR ( 8) },
580 { "i", tZONE, HOUR ( 9) },
581 { "k", tZONE, HOUR ( 10) },
582 { "l", tZONE, HOUR ( 11) },
583 { "m", tZONE, HOUR ( 12) },
584 { "n", tZONE, HOUR (- 1) },
585 { "o", tZONE, HOUR (- 2) },
586 { "p", tZONE, HOUR (- 3) },
587 { "q", tZONE, HOUR (- 4) },
588 { "r", tZONE, HOUR (- 5) },
589 { "s", tZONE, HOUR (- 6) },
590 { "t", tZONE, HOUR (- 7) },
591 { "u", tZONE, HOUR (- 8) },
592 { "v", tZONE, HOUR (- 9) },
593 { "w", tZONE, HOUR (-10) },
594 { "x", tZONE, HOUR (-11) },
595 { "y", tZONE, HOUR (-12) },
596 { "z", tZONE, HOUR ( 0) },
612 ToHour (Hours, Meridian)
619 if (Hours < 0 || Hours > 23)
623 if (Hours < 1 || Hours > 12)
629 if (Hours < 1 || Hours > 12)
647 /* XPG4 suggests that years 00-68 map to 2000-2068, and
648 years 69-99 map to 1969-1999. */
663 register const TABLE *tp;
667 /* Make it lowercase. */
668 for (p = buff; *p; p++)
672 if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
674 yylval.Meridian = MERam;
677 if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
679 yylval.Meridian = MERpm;
683 /* See if we have an abbreviation for a month. */
684 if (strlen (buff) == 3)
686 else if (strlen (buff) == 4 && buff[3] == '.')
694 for (tp = MonthDayTable; tp->name; tp++)
698 if (strncmp (buff, tp->name, 3) == 0)
700 yylval.Number = tp->value;
704 else if (strcmp (buff, tp->name) == 0)
706 yylval.Number = tp->value;
711 for (tp = TimezoneTable; tp->name; tp++)
712 if (strcmp (buff, tp->name) == 0)
714 yylval.Number = tp->value;
718 if (strcmp (buff, "dst") == 0)
721 for (tp = UnitsTable; tp->name; tp++)
722 if (strcmp (buff, tp->name) == 0)
724 yylval.Number = tp->value;
728 /* Strip off any plural and try the units table again. */
729 i = strlen (buff) - 1;
733 for (tp = UnitsTable; tp->name; tp++)
734 if (strcmp (buff, tp->name) == 0)
736 yylval.Number = tp->value;
739 buff[i] = 's'; /* Put back for "this" in OtherTable. */
742 for (tp = OtherTable; tp->name; tp++)
743 if (strcmp (buff, tp->name) == 0)
745 yylval.Number = tp->value;
749 /* Military timezones. */
750 if (buff[1] == '\0' && ISALPHA (*buff))
752 for (tp = MilitaryTable; tp->name; tp++)
753 if (strcmp (buff, tp->name) == 0)
755 yylval.Number = tp->value;
760 /* Drop out any periods and try the timezone table again. */
761 for (i = 0, p = q = buff; *q; q++)
768 for (tp = TimezoneTable; tp->name; tp++)
769 if (strcmp (buff, tp->name) == 0)
771 yylval.Number = tp->value;
789 while (ISSPACE (*yyInput))
792 if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
794 if (c == '-' || c == '+')
796 sign = c == '-' ? -1 : 1;
797 if (!ISDIGIT (*++yyInput))
798 /* skip the '-' sign */
803 for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
804 yylval.Number = 10 * yylval.Number + c - '0';
807 yylval.Number = -yylval.Number;
808 return sign ? tSNUMBER : tUNUMBER;
812 for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
813 if (p < &buff[sizeof buff - 1])
817 return LookupWord (buff);
836 #define TM_YEAR_ORIGIN 1900
838 /* Yield A - B, measured in seconds. */
843 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
844 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
846 /* difference in day of year */
847 a->tm_yday - b->tm_yday
848 /* + intervening leap days */
849 + ((ay >> 2) - (by >> 2))
850 - (ay / 100 - by / 100)
851 + ((ay / 100 >> 2) - (by / 100 >> 2))
852 /* + difference in years * 365 */
853 + (long) (ay - by) * 365
855 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
856 + (a->tm_min - b->tm_min))
857 + (a->tm_sec - b->tm_sec));
865 struct tm tm, tm0, *tmp;
869 Start = now ? *now : time ((time_t *) NULL);
870 tmp = localtime (&Start);
871 yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
872 yyMonth = tmp->tm_mon + 1;
873 yyDay = tmp->tm_mday;
874 yyHour = tmp->tm_hour;
875 yyMinutes = tmp->tm_min;
876 yySeconds = tmp->tm_sec;
891 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
894 tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
895 tm.tm_mon = yyMonth - 1 + yyRelMonth;
896 tm.tm_mday = yyDay + yyRelDay;
897 if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
899 tm.tm_hour = ToHour (yyHour, yyMeridian);
902 tm.tm_min = yyMinutes;
903 tm.tm_sec = yySeconds;
907 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
909 tm.tm_hour += yyRelHour;
910 tm.tm_min += yyRelMinutes;
911 tm.tm_sec += yyRelSeconds;
915 Start = mktime (&tm);
917 if (Start == (time_t) -1)
920 /* Guard against falsely reporting errors near the time_t boundaries
921 when parsing times in other time zones. For example, if the min
922 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
923 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
924 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
925 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
926 zone by 24 hours to compensate. This algorithm assumes that
927 there is no DST transition within a day of the time_t boundaries. */
932 if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
935 yyTimezone -= 24 * 60;
940 yyTimezone += 24 * 60;
942 Start = mktime (&tm);
945 if (Start == (time_t) -1)
949 if (yyHaveDay && !yyHaveDate)
951 tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
952 + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
953 Start = mktime (&tm);
954 if (Start == (time_t) -1)
960 long delta = yyTimezone * 60L + difftm (&tm, gmtime (&Start));
961 if ((Start + delta < Start) != (delta < 0))
962 return -1; /* time_t overflow */
977 char buff[MAX_BUFF_LEN + 1];
980 (void) printf ("Enter date, or blank line to exit.\n\t> ");
981 (void) fflush (stdout);
983 buff[MAX_BUFF_LEN] = 0;
984 while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
986 d = get_date (buff, (time_t *) NULL);
988 (void) printf ("Bad format - couldn't convert.\n");
990 (void) printf ("%s", ctime (&d));
991 (void) printf ("\t> ");
992 (void) fflush (stdout);
997 #endif /* defined (TEST) */