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) ((time_t)(x) * 60)
95 #define SECSPERDAY (24L * 60L * 60L)
97 #define MAX_BUFF_LEN 128 /* size of buffer to read the date into */
100 ** An entry in the lexical lookup table.
102 typedef struct _TABLE {
110 ** Meridian: am, pm, or 24-hour style.
112 typedef enum _MERIDIAN {
118 ** Global variables. We could get rid of most of these by using a good
119 ** union as the yacc stack. (This routine was originally written before
120 ** yacc had the %union construct.) Maybe someday; right now we only use
121 ** the %union very rarely.
123 static const char *yyInput;
124 static int yyDayOrdinal;
125 static int yyDayNumber;
126 static int yyHaveDate;
127 static int yyHaveDay;
128 static int yyHaveRel;
129 static int yyHaveTime;
130 static int yyHaveZone;
131 static int yyTimezone;
134 static int yyMinutes;
136 static int yySeconds;
138 static MERIDIAN yyMeridian;
140 static int yyRelHour;
141 static int yyRelMinutes;
142 static int yyRelMonth;
143 static int yyRelSeconds;
144 static int yyRelYear;
150 enum _MERIDIAN Meridian;
153 %token tAGO tDAY tDAY_UNIT tDAYZONE tDST tHOUR_UNIT tID
154 %token tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
155 %token tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
157 %type <Number> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tMINUTE_UNIT
158 %type <Number> tMONTH tMONTH_UNIT
159 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tYEAR_UNIT tZONE
160 %type <Meridian> tMERIDIAN o_merid
186 time : tUNUMBER tMERIDIAN {
192 | tUNUMBER ':' tUNUMBER o_merid {
198 | tUNUMBER ':' tUNUMBER tSNUMBER {
204 ? -$4 % 100 + (-$4 / 100) * 60
205 : - ($4 % 100 + ($4 / 100) * 60));
207 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
213 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
220 ? -$6 % 100 + (-$6 / 100) * 60
221 : - ($6 % 100 + ($6 / 100) * 60));
229 yyTimezone = $1 - 60;
233 yyTimezone = $1 - 60;
251 date : tUNUMBER '/' tUNUMBER {
255 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
256 /* Interpret as YYYY/MM/DD if $1 >= 1000, otherwise as MM/DD/YY.
257 The goal in recognizing YYYY/MM/DD is solely to support legacy
258 machine-generated dates like those in an RCS log listing. If
259 you want portability, use the ISO 8601 format. */
273 | tUNUMBER tSNUMBER tSNUMBER {
274 /* ISO 8601 format. yyyy-mm-dd. */
279 | tUNUMBER tMONTH tSNUMBER {
280 /* e.g. 17-JUN-1992. */
289 | tMONTH tUNUMBER ',' tUNUMBER {
298 | tUNUMBER tMONTH tUNUMBER {
306 yyRelSeconds = -yyRelSeconds;
307 yyRelMinutes = -yyRelMinutes;
308 yyRelHour = -yyRelHour;
309 yyRelDay = -yyRelDay;
310 yyRelMonth = -yyRelMonth;
311 yyRelYear = -yyRelYear;
316 relunit : tUNUMBER tYEAR_UNIT {
317 yyRelYear += $1 * $2;
319 | tSNUMBER tYEAR_UNIT {
320 yyRelYear += $1 * $2;
325 | tUNUMBER tMONTH_UNIT {
326 yyRelMonth += $1 * $2;
328 | tSNUMBER tMONTH_UNIT {
329 yyRelMonth += $1 * $2;
334 | tUNUMBER tDAY_UNIT {
337 | tSNUMBER tDAY_UNIT {
343 | tUNUMBER tHOUR_UNIT {
344 yyRelHour += $1 * $2;
346 | tSNUMBER tHOUR_UNIT {
347 yyRelHour += $1 * $2;
352 | tUNUMBER tMINUTE_UNIT {
353 yyRelMinutes += $1 * $2;
355 | tSNUMBER tMINUTE_UNIT {
356 yyRelMinutes += $1 * $2;
361 | tUNUMBER tSEC_UNIT {
362 yyRelSeconds += $1 * $2;
364 | tSNUMBER tSEC_UNIT {
365 yyRelSeconds += $1 * $2;
374 if (yyHaveTime && yyHaveDate && !yyHaveRel)
382 yyMonth= ($1/100)%100;
396 yyMinutes = $1 % 100;
417 /* Month and day table. */
418 static TABLE const MonthDayTable[] = {
419 { "january", tMONTH, 1 },
420 { "february", tMONTH, 2 },
421 { "march", tMONTH, 3 },
422 { "april", tMONTH, 4 },
423 { "may", tMONTH, 5 },
424 { "june", tMONTH, 6 },
425 { "july", tMONTH, 7 },
426 { "august", tMONTH, 8 },
427 { "september", tMONTH, 9 },
428 { "sept", tMONTH, 9 },
429 { "october", tMONTH, 10 },
430 { "november", tMONTH, 11 },
431 { "december", tMONTH, 12 },
432 { "sunday", tDAY, 0 },
433 { "monday", tDAY, 1 },
434 { "tuesday", tDAY, 2 },
436 { "wednesday", tDAY, 3 },
437 { "wednes", tDAY, 3 },
438 { "thursday", tDAY, 4 },
440 { "thurs", tDAY, 4 },
441 { "friday", tDAY, 5 },
442 { "saturday", tDAY, 6 },
446 /* Time units table. */
447 static TABLE const UnitsTable[] = {
448 { "year", tYEAR_UNIT, 1 },
449 { "month", tMONTH_UNIT, 1 },
450 { "fortnight", tDAY_UNIT, 14 },
451 { "week", tDAY_UNIT, 7 },
452 { "day", tDAY_UNIT, 1 },
453 { "hour", tHOUR_UNIT, 1 },
454 { "minute", tMINUTE_UNIT, 1 },
455 { "min", tMINUTE_UNIT, 1 },
456 { "second", tSEC_UNIT, 1 },
457 { "sec", tSEC_UNIT, 1 },
461 /* Assorted relative-time words. */
462 static TABLE const OtherTable[] = {
463 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
464 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
465 { "today", tMINUTE_UNIT, 0 },
466 { "now", tMINUTE_UNIT, 0 },
467 { "last", tUNUMBER, -1 },
468 { "this", tMINUTE_UNIT, 0 },
469 { "next", tUNUMBER, 1 /* Was bogusly 2 [mdw] */ },
470 { "first", tUNUMBER, 1 },
471 /* { "second", tUNUMBER, 2 }, */
472 { "third", tUNUMBER, 3 },
473 { "fourth", tUNUMBER, 4 },
474 { "fifth", tUNUMBER, 5 },
475 { "sixth", tUNUMBER, 6 },
476 { "seventh", tUNUMBER, 7 },
477 { "eighth", tUNUMBER, 8 },
478 { "ninth", tUNUMBER, 9 },
479 { "tenth", tUNUMBER, 10 },
480 { "eleventh", tUNUMBER, 11 },
481 { "twelfth", tUNUMBER, 12 },
486 /* The timezone table. */
487 static TABLE const TimezoneTable[] = {
488 { "gmt", tZONE, HOUR ( 0) }, /* Greenwich Mean */
489 { "ut", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
490 { "utc", tZONE, HOUR ( 0) },
491 { "wet", tZONE, HOUR ( 0) }, /* Western European */
492 { "bst", tDAYZONE, HOUR ( 0) }, /* British Summer */
493 { "wat", tZONE, HOUR ( 1) }, /* West Africa */
494 { "at", tZONE, HOUR ( 2) }, /* Azores */
496 /* For completeness. BST is also British Summer, and GST is
497 * also Guam Standard. */
498 { "bst", tZONE, HOUR ( 3) }, /* Brazil Standard */
499 { "gst", tZONE, HOUR ( 3) }, /* Greenland Standard */
502 { "nft", tZONE, HOUR (3.5) }, /* Newfoundland */
503 { "nst", tZONE, HOUR (3.5) }, /* Newfoundland Standard */
504 { "ndt", tDAYZONE, HOUR (3.5) }, /* Newfoundland Daylight */
506 { "ast", tZONE, HOUR ( 4) }, /* Atlantic Standard */
507 { "adt", tDAYZONE, HOUR ( 4) }, /* Atlantic Daylight */
508 { "est", tZONE, HOUR ( 5) }, /* Eastern Standard */
509 { "edt", tDAYZONE, HOUR ( 5) }, /* Eastern Daylight */
510 { "cst", tZONE, HOUR ( 6) }, /* Central Standard */
511 { "cdt", tDAYZONE, HOUR ( 6) }, /* Central Daylight */
512 { "mst", tZONE, HOUR ( 7) }, /* Mountain Standard */
513 { "mdt", tDAYZONE, HOUR ( 7) }, /* Mountain Daylight */
514 { "pst", tZONE, HOUR ( 8) }, /* Pacific Standard */
515 { "pdt", tDAYZONE, HOUR ( 8) }, /* Pacific Daylight */
516 { "yst", tZONE, HOUR ( 9) }, /* Yukon Standard */
517 { "ydt", tDAYZONE, HOUR ( 9) }, /* Yukon Daylight */
518 { "hst", tZONE, HOUR (10) }, /* Hawaii Standard */
519 { "hdt", tDAYZONE, HOUR (10) }, /* Hawaii Daylight */
520 { "cat", tZONE, HOUR (10) }, /* Central Alaska */
521 { "ahst", tZONE, HOUR (10) }, /* Alaska-Hawaii Standard */
522 { "nt", tZONE, HOUR (11) }, /* Nome */
523 { "idlw", tZONE, HOUR (12) }, /* International Date Line West */
524 { "cet", tZONE, -HOUR (1) }, /* Central European */
525 { "met", tZONE, -HOUR (1) }, /* Middle European */
526 { "mewt", tZONE, -HOUR (1) }, /* Middle European Winter */
527 { "mest", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
528 { "mesz", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
529 { "swt", tZONE, -HOUR (1) }, /* Swedish Winter */
530 { "sst", tDAYZONE, -HOUR (1) }, /* Swedish Summer */
531 { "fwt", tZONE, -HOUR (1) }, /* French Winter */
532 { "fst", tDAYZONE, -HOUR (1) }, /* French Summer */
533 { "eet", tZONE, -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
534 { "bt", tZONE, -HOUR (3) }, /* Baghdad, USSR Zone 2 */
536 { "it", tZONE, -HOUR (3.5) },/* Iran */
538 { "zp4", tZONE, -HOUR (4) }, /* USSR Zone 3 */
539 { "zp5", tZONE, -HOUR (5) }, /* USSR Zone 4 */
541 { "ist", tZONE, -HOUR (5.5) },/* Indian Standard */
543 { "zp6", tZONE, -HOUR (6) }, /* USSR Zone 5 */
545 /* For completeness. NST is also Newfoundland Standard, and SST is
546 * also Swedish Summer. */
547 { "nst", tZONE, -HOUR (6.5) },/* North Sumatra */
548 { "sst", tZONE, -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
550 { "wast", tZONE, -HOUR (7) }, /* West Australian Standard */
551 { "wadt", tDAYZONE, -HOUR (7) }, /* West Australian Daylight */
553 { "jt", tZONE, -HOUR (7.5) },/* Java (3pm in Cronusland!) */
555 { "cct", tZONE, -HOUR (8) }, /* China Coast, USSR Zone 7 */
556 { "jst", tZONE, -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
558 { "cast", tZONE, -HOUR (9.5) },/* Central Australian Standard */
559 { "cadt", tDAYZONE, -HOUR (9.5) },/* Central Australian Daylight */
561 { "east", tZONE, -HOUR (10) }, /* Eastern Australian Standard */
562 { "eadt", tDAYZONE, -HOUR (10) }, /* Eastern Australian Daylight */
563 { "gst", tZONE, -HOUR (10) }, /* Guam Standard, USSR Zone 9 */
564 { "nzt", tZONE, -HOUR (12) }, /* New Zealand */
565 { "nzst", tZONE, -HOUR (12) }, /* New Zealand Standard */
566 { "nzdt", tDAYZONE, -HOUR (12) }, /* New Zealand Daylight */
567 { "idle", tZONE, -HOUR (12) }, /* International Date Line East */
571 /* Military timezone table. */
572 static TABLE const MilitaryTable[] = {
573 { "a", tZONE, HOUR ( 1) },
574 { "b", tZONE, HOUR ( 2) },
575 { "c", tZONE, HOUR ( 3) },
576 { "d", tZONE, HOUR ( 4) },
577 { "e", tZONE, HOUR ( 5) },
578 { "f", tZONE, HOUR ( 6) },
579 { "g", tZONE, HOUR ( 7) },
580 { "h", tZONE, HOUR ( 8) },
581 { "i", tZONE, HOUR ( 9) },
582 { "k", tZONE, HOUR ( 10) },
583 { "l", tZONE, HOUR ( 11) },
584 { "m", tZONE, HOUR ( 12) },
585 { "n", tZONE, HOUR (- 1) },
586 { "o", tZONE, HOUR (- 2) },
587 { "p", tZONE, HOUR (- 3) },
588 { "q", tZONE, HOUR (- 4) },
589 { "r", tZONE, HOUR (- 5) },
590 { "s", tZONE, HOUR (- 6) },
591 { "t", tZONE, HOUR (- 7) },
592 { "u", tZONE, HOUR (- 8) },
593 { "v", tZONE, HOUR (- 9) },
594 { "w", tZONE, HOUR (-10) },
595 { "x", tZONE, HOUR (-11) },
596 { "y", tZONE, HOUR (-12) },
597 { "z", tZONE, HOUR ( 0) },
613 ToHour (Hours, Meridian)
620 if (Hours < 0 || Hours > 23)
624 if (Hours < 1 || Hours > 12)
630 if (Hours < 1 || Hours > 12)
648 /* XPG4 suggests that years 00-68 map to 2000-2068, and
649 years 69-99 map to 1969-1999. */
664 register const TABLE *tp;
668 /* Make it lowercase. */
669 for (p = buff; *p; p++)
673 if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0)
675 yylval.Meridian = MERam;
678 if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0)
680 yylval.Meridian = MERpm;
684 /* See if we have an abbreviation for a month. */
685 if (strlen (buff) == 3)
687 else if (strlen (buff) == 4 && buff[3] == '.')
695 for (tp = MonthDayTable; tp->name; tp++)
699 if (strncmp (buff, tp->name, 3) == 0)
701 yylval.Number = tp->value;
705 else if (strcmp (buff, tp->name) == 0)
707 yylval.Number = tp->value;
712 for (tp = TimezoneTable; tp->name; tp++)
713 if (strcmp (buff, tp->name) == 0)
715 yylval.Number = tp->value;
719 if (strcmp (buff, "dst") == 0)
722 for (tp = UnitsTable; tp->name; tp++)
723 if (strcmp (buff, tp->name) == 0)
725 yylval.Number = tp->value;
729 /* Strip off any plural and try the units table again. */
730 i = strlen (buff) - 1;
734 for (tp = UnitsTable; tp->name; tp++)
735 if (strcmp (buff, tp->name) == 0)
737 yylval.Number = tp->value;
740 buff[i] = 's'; /* Put back for "this" in OtherTable. */
743 for (tp = OtherTable; tp->name; tp++)
744 if (strcmp (buff, tp->name) == 0)
746 yylval.Number = tp->value;
750 /* Military timezones. */
751 if (buff[1] == '\0' && ISALPHA (*buff))
753 for (tp = MilitaryTable; tp->name; tp++)
754 if (strcmp (buff, tp->name) == 0)
756 yylval.Number = tp->value;
761 /* Drop out any periods and try the timezone table again. */
762 for (i = 0, p = q = buff; *q; q++)
769 for (tp = TimezoneTable; tp->name; tp++)
770 if (strcmp (buff, tp->name) == 0)
772 yylval.Number = tp->value;
790 while (ISSPACE (*yyInput))
793 if (ISDIGIT (c = *yyInput) || c == '-' || c == '+')
795 if (c == '-' || c == '+')
797 sign = c == '-' ? -1 : 1;
798 if (!ISDIGIT (*++yyInput))
799 /* skip the '-' sign */
804 for (yylval.Number = 0; ISDIGIT (c = *yyInput++);)
805 yylval.Number = 10 * yylval.Number + c - '0';
808 yylval.Number = -yylval.Number;
809 return sign ? tSNUMBER : tUNUMBER;
813 for (p = buff; (c = *yyInput++, ISALPHA (c)) || c == '.';)
814 if (p < &buff[sizeof buff - 1])
818 return LookupWord (buff);
837 #define TM_YEAR_ORIGIN 1900
839 /* Yield A - B, measured in seconds. */
844 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
845 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
847 /* difference in day of year */
848 a->tm_yday - b->tm_yday
849 /* + intervening leap days */
850 + ((ay >> 2) - (by >> 2))
851 - (ay / 100 - by / 100)
852 + ((ay / 100 >> 2) - (by / 100 >> 2))
853 /* + difference in years * 365 */
854 + (long) (ay - by) * 365
856 return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
857 + (a->tm_min - b->tm_min))
858 + (a->tm_sec - b->tm_sec));
866 struct tm tm, tm0, *tmp;
870 Start = now ? *now : time ((time_t *) NULL);
871 tmp = localtime (&Start);
872 yyYear = tmp->tm_year + TM_YEAR_ORIGIN;
873 yyMonth = tmp->tm_mon + 1;
874 yyDay = tmp->tm_mday;
875 yyHour = tmp->tm_hour;
876 yyMinutes = tmp->tm_min;
877 yySeconds = tmp->tm_sec;
892 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
895 tm.tm_year = ToYear (yyYear) - TM_YEAR_ORIGIN + yyRelYear;
896 tm.tm_mon = yyMonth - 1 + yyRelMonth;
897 tm.tm_mday = yyDay + yyRelDay;
898 if (yyHaveTime || (yyHaveRel && !yyHaveDate && !yyHaveDay))
900 tm.tm_hour = ToHour (yyHour, yyMeridian);
903 tm.tm_min = yyMinutes;
904 tm.tm_sec = yySeconds;
908 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
910 tm.tm_hour += yyRelHour;
911 tm.tm_min += yyRelMinutes;
912 tm.tm_sec += yyRelSeconds;
916 Start = mktime (&tm);
918 if (Start == (time_t) -1)
921 /* Guard against falsely reporting errors near the time_t boundaries
922 when parsing times in other time zones. For example, if the min
923 time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
924 of UTC, then the min localtime value is 1970-01-01 08:00:00; if
925 we apply mktime to 1970-01-01 00:00:00 we will get an error, so
926 we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
927 zone by 24 hours to compensate. This algorithm assumes that
928 there is no DST transition within a day of the time_t boundaries. */
933 if (tm.tm_year <= EPOCH - TM_YEAR_ORIGIN)
936 yyTimezone -= 24 * 60;
941 yyTimezone += 24 * 60;
943 Start = mktime (&tm);
946 if (Start == (time_t) -1)
950 if (yyHaveDay && !yyHaveDate)
952 tm.tm_mday += ((yyDayNumber - tm.tm_wday + 7) % 7
953 + 7 * (yyDayOrdinal - (0 < yyDayOrdinal)));
954 Start = mktime (&tm);
955 if (Start == (time_t) -1)
961 long delta = yyTimezone * 60L + difftm (&tm, gmtime (&Start));
962 if ((Start + delta < Start) != (delta < 0))
963 return -1; /* time_t overflow */
978 char buff[MAX_BUFF_LEN + 1];
981 (void) printf ("Enter date, or blank line to exit.\n\t> ");
982 (void) fflush (stdout);
984 buff[MAX_BUFF_LEN] = 0;
985 while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
987 d = get_date (buff, (time_t *) NULL);
989 (void) printf ("Bad format - couldn't convert.\n");
991 (void) printf ("%s", ctime (&d));
992 (void) printf ("\t> ");
993 (void) fflush (stdout);
998 #endif /* defined (TEST) */