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;
7 ** send any email to Rich.
9 ** This grammar has 10 shift/reduce conflicts.
11 ** This code is in the public domain and has no copyright.
13 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
14 /* SUPPRESS 288 on yyerrlab *//* Label unused */
20 /* Since the code of getdate.y is not included in the Emacs executable
21 itself, there is no need to #define static in this file. Even if
22 the code were included in the Emacs executable, it probably
23 wouldn't do any harm to #undef it here; this will only cause
24 problems if we try to write to a static variable, which I don't
25 think this code needs to do. */
33 /* The code at the top of get_date which figures out the offset of the
34 current time zone checks various CPP symbols to see if special
35 tricks are need, but defaults to using the gettimeofday system call.
36 Include <sys/time.h> if that will be used. */
45 #include <sys/types.h>
47 #ifdef TIME_WITH_SYS_TIME
51 #ifdef HAVE_SYS_TIME_H
59 #undef timezone /* needed for sgi */
62 #if defined (HAVE_SYS_TIMEB_H)
63 #include <sys/timeb.h>
66 ** We use the obsolete `struct timeb' as part of our interface!
67 ** Since the system doesn't have it, we define it here;
68 ** our callers must do likewise.
71 time_t time; /* Seconds since the epoch */
72 unsigned short millitm; /* Field not used */
73 short timezone; /* Minutes west of GMT */
74 short dstflag; /* Field not used */
76 #endif /* defined (HAVE_SYS_TIMEB_H) */
78 #endif /* defined (vms) */
80 #if defined (STDC_HEADERS) || defined (USG)
84 /* Some old versions of bison generate parsers that use bcopy.
85 That loses on systems that don't provide the function, so we have
86 to redefine it here. */
87 #if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
88 #define bcopy(from, to, len) memcpy ((to), (from), (len))
91 extern struct tm *gmtime ();
92 extern struct tm *localtime ();
94 #define yyparse getdate_yyparse
95 #define yylex getdate_yylex
96 #define yyerror getdate_yyerror
99 static int yyerror ();
102 #define HOUR(x) ((time_t)(x) * 60)
103 #define SECSPERDAY (24L * 60L * 60L)
105 #define MAX_BUFF_LEN 128 /* size of buffer to read the date into */
108 ** An entry in the lexical lookup table.
110 typedef struct _TABLE {
118 ** Daylight-savings mode: on, off, or not yet known.
120 typedef enum _DSTMODE {
121 DSTon, DSToff, DSTmaybe
125 ** Meridian: am, pm, or 24-hour style.
127 typedef enum _MERIDIAN {
133 ** Global variables. We could get rid of most of these by using a good
134 ** union as the yacc stack. (This routine was originally written before
135 ** yacc had the %union construct.) Maybe someday; right now we only use
136 ** the %union very rarely.
138 static char *yyInput;
139 static DSTMODE yyDSTmode;
140 static time_t yyDayOrdinal;
141 static time_t yyDayNumber;
142 static int yyHaveDate;
143 static int yyHaveDay;
144 static int yyHaveRel;
145 static int yyHaveTime;
146 static int yyHaveZone;
147 static time_t yyTimezone;
149 static time_t yyHour;
150 static time_t yyMinutes;
151 static time_t yyMonth;
152 static time_t yySeconds;
153 static time_t yyYear;
154 static MERIDIAN yyMeridian;
155 static time_t yyRelMonth;
156 static time_t yyRelSeconds;
162 enum _MERIDIAN Meridian;
165 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
166 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
168 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
169 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
170 %type <Meridian> tMERIDIAN o_merid
196 time : tUNUMBER tMERIDIAN {
202 | tUNUMBER ':' tUNUMBER o_merid {
208 | tUNUMBER ':' tUNUMBER tSNUMBER {
213 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
215 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
221 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
227 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
260 date : tUNUMBER '/' tUNUMBER {
264 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
269 | tUNUMBER tSNUMBER tSNUMBER {
270 /* ISO 8601 format. yyyy-mm-dd. */
275 | tUNUMBER tMONTH tSNUMBER {
276 /* e.g. 17-JUN-1992. */
285 | tMONTH tUNUMBER ',' tUNUMBER {
294 | tUNUMBER tMONTH tUNUMBER {
302 yyRelSeconds = -yyRelSeconds;
303 yyRelMonth = -yyRelMonth;
308 relunit : tUNUMBER tMINUTE_UNIT {
309 yyRelSeconds += $1 * $2 * 60L;
311 | tSNUMBER tMINUTE_UNIT {
312 yyRelSeconds += $1 * $2 * 60L;
315 yyRelSeconds += $1 * 60L;
317 | tSNUMBER tSEC_UNIT {
320 | tUNUMBER tSEC_UNIT {
326 | tSNUMBER tMONTH_UNIT {
327 yyRelMonth += $1 * $2;
329 | tUNUMBER tMONTH_UNIT {
330 yyRelMonth += $1 * $2;
338 if (yyHaveTime && yyHaveDate && !yyHaveRel)
344 yyMonth= ($1/100)%100;
355 yyMinutes = $1 % 100;
364 o_merid : /* NULL */ {
374 /* Month and day table. */
375 static TABLE const MonthDayTable[] = {
376 { "january", tMONTH, 1 },
377 { "february", tMONTH, 2 },
378 { "march", tMONTH, 3 },
379 { "april", tMONTH, 4 },
380 { "may", tMONTH, 5 },
381 { "june", tMONTH, 6 },
382 { "july", tMONTH, 7 },
383 { "august", tMONTH, 8 },
384 { "september", tMONTH, 9 },
385 { "sept", tMONTH, 9 },
386 { "october", tMONTH, 10 },
387 { "november", tMONTH, 11 },
388 { "december", tMONTH, 12 },
389 { "sunday", tDAY, 0 },
390 { "monday", tDAY, 1 },
391 { "tuesday", tDAY, 2 },
393 { "wednesday", tDAY, 3 },
394 { "wednes", tDAY, 3 },
395 { "thursday", tDAY, 4 },
397 { "thurs", tDAY, 4 },
398 { "friday", tDAY, 5 },
399 { "saturday", tDAY, 6 },
403 /* Time units table. */
404 static TABLE const UnitsTable[] = {
405 { "year", tMONTH_UNIT, 12 },
406 { "month", tMONTH_UNIT, 1 },
407 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
408 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
409 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
410 { "hour", tMINUTE_UNIT, 60 },
411 { "minute", tMINUTE_UNIT, 1 },
412 { "min", tMINUTE_UNIT, 1 },
413 { "second", tSEC_UNIT, 1 },
414 { "sec", tSEC_UNIT, 1 },
418 /* Assorted relative-time words. */
419 static TABLE const OtherTable[] = {
420 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
421 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
422 { "today", tMINUTE_UNIT, 0 },
423 { "now", tMINUTE_UNIT, 0 },
424 { "last", tUNUMBER, -1 },
425 { "this", tMINUTE_UNIT, 0 },
426 { "next", tUNUMBER, 2 },
427 { "first", tUNUMBER, 1 },
428 /* { "second", tUNUMBER, 2 }, */
429 { "third", tUNUMBER, 3 },
430 { "fourth", tUNUMBER, 4 },
431 { "fifth", tUNUMBER, 5 },
432 { "sixth", tUNUMBER, 6 },
433 { "seventh", tUNUMBER, 7 },
434 { "eighth", tUNUMBER, 8 },
435 { "ninth", tUNUMBER, 9 },
436 { "tenth", tUNUMBER, 10 },
437 { "eleventh", tUNUMBER, 11 },
438 { "twelfth", tUNUMBER, 12 },
443 /* The timezone table. */
444 /* Some of these are commented out because a time_t can't store a float. */
445 static TABLE const TimezoneTable[] = {
446 { "gmt", tZONE, HOUR ( 0) }, /* Greenwich Mean */
447 { "ut", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
448 { "utc", tZONE, HOUR ( 0) },
449 { "wet", tZONE, HOUR ( 0) }, /* Western European */
450 { "bst", tDAYZONE, HOUR ( 0) }, /* British Summer */
451 { "wat", tZONE, HOUR ( 1) }, /* West Africa */
452 { "at", tZONE, HOUR ( 2) }, /* Azores */
454 /* For completeness. BST is also British Summer, and GST is
455 * also Guam Standard. */
456 { "bst", tZONE, HOUR ( 3) }, /* Brazil Standard */
457 { "gst", tZONE, HOUR ( 3) }, /* Greenland Standard */
460 { "nft", tZONE, HOUR (3.5) }, /* Newfoundland */
461 { "nst", tZONE, HOUR (3.5) }, /* Newfoundland Standard */
462 { "ndt", tDAYZONE, HOUR (3.5) }, /* Newfoundland Daylight */
464 { "ast", tZONE, HOUR ( 4) }, /* Atlantic Standard */
465 { "adt", tDAYZONE, HOUR ( 4) }, /* Atlantic Daylight */
466 { "est", tZONE, HOUR ( 5) }, /* Eastern Standard */
467 { "edt", tDAYZONE, HOUR ( 5) }, /* Eastern Daylight */
468 { "cst", tZONE, HOUR ( 6) }, /* Central Standard */
469 { "cdt", tDAYZONE, HOUR ( 6) }, /* Central Daylight */
470 { "mst", tZONE, HOUR ( 7) }, /* Mountain Standard */
471 { "mdt", tDAYZONE, HOUR ( 7) }, /* Mountain Daylight */
472 { "pst", tZONE, HOUR ( 8) }, /* Pacific Standard */
473 { "pdt", tDAYZONE, HOUR ( 8) }, /* Pacific Daylight */
474 { "yst", tZONE, HOUR ( 9) }, /* Yukon Standard */
475 { "ydt", tDAYZONE, HOUR ( 9) }, /* Yukon Daylight */
476 { "hst", tZONE, HOUR (10) }, /* Hawaii Standard */
477 { "hdt", tDAYZONE, HOUR (10) }, /* Hawaii Daylight */
478 { "cat", tZONE, HOUR (10) }, /* Central Alaska */
479 { "ahst", tZONE, HOUR (10) }, /* Alaska-Hawaii Standard */
480 { "nt", tZONE, HOUR (11) }, /* Nome */
481 { "idlw", tZONE, HOUR (12) }, /* International Date Line West */
482 { "cet", tZONE, -HOUR (1) }, /* Central European */
483 { "met", tZONE, -HOUR (1) }, /* Middle European */
484 { "mewt", tZONE, -HOUR (1) }, /* Middle European Winter */
485 { "mest", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
486 { "mesz", tDAYZONE, -HOUR (1) }, /* Middle European Summer */
487 { "swt", tZONE, -HOUR (1) }, /* Swedish Winter */
488 { "sst", tDAYZONE, -HOUR (1) }, /* Swedish Summer */
489 { "fwt", tZONE, -HOUR (1) }, /* French Winter */
490 { "fst", tDAYZONE, -HOUR (1) }, /* French Summer */
491 { "eet", tZONE, -HOUR (2) }, /* Eastern Europe, USSR Zone 1 */
492 { "bt", tZONE, -HOUR (3) }, /* Baghdad, USSR Zone 2 */
494 { "it", tZONE, -HOUR (3.5) },/* Iran */
496 { "zp4", tZONE, -HOUR (4) }, /* USSR Zone 3 */
497 { "zp5", tZONE, -HOUR (5) }, /* USSR Zone 4 */
499 { "ist", tZONE, -HOUR (5.5) },/* Indian Standard */
501 { "zp6", tZONE, -HOUR (6) }, /* USSR Zone 5 */
503 /* For completeness. NST is also Newfoundland Standard, and SST is
504 * also Swedish Summer. */
505 { "nst", tZONE, -HOUR (6.5) },/* North Sumatra */
506 { "sst", tZONE, -HOUR (7) }, /* South Sumatra, USSR Zone 6 */
508 { "wast", tZONE, -HOUR (7) }, /* West Australian Standard */
509 { "wadt", tDAYZONE, -HOUR (7) }, /* West Australian Daylight */
511 { "jt", tZONE, -HOUR (7.5) },/* Java (3pm in Cronusland!) */
513 { "cct", tZONE, -HOUR (8) }, /* China Coast, USSR Zone 7 */
514 { "jst", tZONE, -HOUR (9) }, /* Japan Standard, USSR Zone 8 */
516 { "cast", tZONE, -HOUR (9.5) },/* Central Australian Standard */
517 { "cadt", tDAYZONE, -HOUR (9.5) },/* Central Australian Daylight */
519 { "east", tZONE, -HOUR (10) }, /* Eastern Australian Standard */
520 { "eadt", tDAYZONE, -HOUR (10) }, /* Eastern Australian Daylight */
521 { "gst", tZONE, -HOUR (10) }, /* Guam Standard, USSR Zone 9 */
522 { "nzt", tZONE, -HOUR (12) }, /* New Zealand */
523 { "nzst", tZONE, -HOUR (12) }, /* New Zealand Standard */
524 { "nzdt", tDAYZONE, -HOUR (12) }, /* New Zealand Daylight */
525 { "idle", tZONE, -HOUR (12) }, /* International Date Line East */
529 /* Military timezone table. */
530 static TABLE const MilitaryTable[] = {
531 { "a", tZONE, HOUR ( 1) },
532 { "b", tZONE, HOUR ( 2) },
533 { "c", tZONE, HOUR ( 3) },
534 { "d", tZONE, HOUR ( 4) },
535 { "e", tZONE, HOUR ( 5) },
536 { "f", tZONE, HOUR ( 6) },
537 { "g", tZONE, HOUR ( 7) },
538 { "h", tZONE, HOUR ( 8) },
539 { "i", tZONE, HOUR ( 9) },
540 { "k", tZONE, HOUR ( 10) },
541 { "l", tZONE, HOUR ( 11) },
542 { "m", tZONE, HOUR ( 12) },
543 { "n", tZONE, HOUR (- 1) },
544 { "o", tZONE, HOUR (- 2) },
545 { "p", tZONE, HOUR (- 3) },
546 { "q", tZONE, HOUR (- 4) },
547 { "r", tZONE, HOUR (- 5) },
548 { "s", tZONE, HOUR (- 6) },
549 { "t", tZONE, HOUR (- 7) },
550 { "u", tZONE, HOUR (- 8) },
551 { "v", tZONE, HOUR (- 9) },
552 { "w", tZONE, HOUR (-10) },
553 { "x", tZONE, HOUR (-11) },
554 { "y", tZONE, HOUR (-12) },
555 { "z", tZONE, HOUR ( 0) },
572 ToSeconds (Hours, Minutes, Seconds, Meridian)
578 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
582 if (Hours < 0 || Hours > 23)
584 return (Hours * 60L + Minutes) * 60L + Seconds;
586 if (Hours < 1 || Hours > 12)
588 return (Hours * 60L + Minutes) * 60L + Seconds;
590 if (Hours < 1 || Hours > 12)
592 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
601 Convert (Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
611 static int DaysInMonth[12] = {
612 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
622 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
624 if (Year < EPOCH || Year > 1999
625 || Month < 1 || Month > 12
626 /* Lint fluff: "conversion from long may lose accuracy" */
627 || Day < 1 || Day > DaysInMonth[(int)--Month])
630 for (Julian = Day - 1, i = 0; i < Month; i++)
631 Julian += DaysInMonth[i];
632 for (i = EPOCH; i < Year; i++)
633 Julian += 365 + (i % 4 == 0);
634 Julian *= SECSPERDAY;
635 Julian += yyTimezone * 60L;
636 if ((tod = ToSeconds (Hours, Minutes, Seconds, Meridian)) < 0)
640 || (DSTmode == DSTmaybe && localtime (&Julian)->tm_isdst))
647 DSTcorrect (Start, Future)
654 StartDay = (localtime (&Start)->tm_hour + 1) % 24;
655 FutureDay = (localtime (&Future)->tm_hour + 1) % 24;
656 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
661 RelativeDate (Start, DayOrdinal, DayNumber)
670 tm = localtime (&now);
671 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
672 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
673 return DSTcorrect (Start, now);
678 RelativeMonth (Start, RelMonth)
688 tm = localtime (&Start);
689 Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
691 Month = Month % 12 + 1;
692 return DSTcorrect (Start,
693 Convert (Month, (time_t)tm->tm_mday, Year,
694 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
705 register const TABLE *tp;
709 /* Make it lowercase. */
710 for (p = buff; *p; p++)
714 if (strcmp (buff, "am") == 0 || strcmp (buff, "a.m.") == 0) {
715 yylval.Meridian = MERam;
718 if (strcmp (buff, "pm") == 0 || strcmp (buff, "p.m.") == 0) {
719 yylval.Meridian = MERpm;
723 /* See if we have an abbreviation for a month. */
724 if (strlen (buff) == 3)
726 else if (strlen (buff) == 4 && buff[3] == '.') {
733 for (tp = MonthDayTable; tp->name; tp++) {
735 if (strncmp (buff, tp->name, 3) == 0) {
736 yylval.Number = tp->value;
740 else if (strcmp (buff, tp->name) == 0) {
741 yylval.Number = tp->value;
746 for (tp = TimezoneTable; tp->name; tp++)
747 if (strcmp (buff, tp->name) == 0) {
748 yylval.Number = tp->value;
752 if (strcmp (buff, "dst") == 0)
755 for (tp = UnitsTable; tp->name; tp++)
756 if (strcmp (buff, tp->name) == 0) {
757 yylval.Number = tp->value;
761 /* Strip off any plural and try the units table again. */
762 i = strlen (buff) - 1;
763 if (buff[i] == 's') {
765 for (tp = UnitsTable; tp->name; tp++)
766 if (strcmp (buff, tp->name) == 0) {
767 yylval.Number = tp->value;
770 buff[i] = 's'; /* Put back for "this" in OtherTable. */
773 for (tp = OtherTable; tp->name; tp++)
774 if (strcmp (buff, tp->name) == 0) {
775 yylval.Number = tp->value;
779 /* Military timezones. */
780 if (buff[1] == '\0' && isalpha (*buff)) {
781 for (tp = MilitaryTable; tp->name; tp++)
782 if (strcmp (buff, tp->name) == 0) {
783 yylval.Number = tp->value;
788 /* Drop out any periods and try the timezone table again. */
789 for (i = 0, p = q = buff; *q; q++)
796 for (tp = TimezoneTable; tp->name; tp++)
797 if (strcmp (buff, tp->name) == 0) {
798 yylval.Number = tp->value;
816 while (isspace (*yyInput))
819 if (isdigit (c = *yyInput) || c == '-' || c == '+') {
820 if (c == '-' || c == '+') {
821 sign = c == '-' ? -1 : 1;
822 if (!isdigit (*++yyInput))
823 /* skip the '-' sign */
828 for (yylval.Number = 0; isdigit (c = *yyInput++); )
829 yylval.Number = 10 * yylval.Number + c - '0';
832 yylval.Number = -yylval.Number;
833 return sign ? tSNUMBER : tUNUMBER;
836 for (p = buff; isalpha (c = *yyInput++) || c == '.'; )
837 if (p < &buff[sizeof buff - 1])
841 return LookupWord (buff);
858 #define TM_YEAR_ORIGIN 1900
860 /* Yield A - B, measured in seconds. */
865 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
866 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
868 /* difference in day of year */
869 a->tm_yday - b->tm_yday
870 /* + intervening leap days */
871 + ((ay >> 2) - (by >> 2))
873 + ((ay/100 >> 2) - (by/100 >> 2))
874 /* + difference in years * 365 */
875 + (long)(ay-by) * 365
877 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
878 + (a->tm_min - b->tm_min))
879 + (a->tm_sec - b->tm_sec));
895 (void)time (&ftz.time);
897 if (! (tm = gmtime (&ftz.time)))
899 gmt = *tm; /* Make a copy, in case localtime modifies *tm. */
901 if (! (tm = localtime (&ftz.time)))
904 ftz.timezone = difftm (&gmt, tm) / 60;
909 tm = localtime (&now->time);
910 yyYear = tm->tm_year;
911 yyMonth = tm->tm_mon + 1;
913 yyTimezone = now->timezone;
914 yyDSTmode = DSTmaybe;
928 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
931 if (yyHaveDate || yyHaveTime || yyHaveDay) {
932 Start = Convert (yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
933 yyMeridian, yyDSTmode);
940 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
943 Start += yyRelSeconds;
944 Start += RelativeMonth (Start, yyRelMonth);
946 if (yyHaveDay && !yyHaveDate) {
947 tod = RelativeDate (Start, yyDayOrdinal, yyDayNumber);
951 /* Have to do *something* with a legitimate -1 so it's distinguishable
952 * from the error return value. (Alternately could set errno on error.) */
953 return Start == -1 ? 0 : Start;
968 (void)printf ("Enter date, or blank line to exit.\n\t> ");
969 (void)fflush (stdout);
970 while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0]) {
971 d = get_date (buff, (struct timeb *)NULL);
973 (void)printf ("Bad format - couldn't convert.\n");
975 (void)printf ("%s", ctime (&d));
976 (void)printf ("\t> ");
977 (void)fflush (stdout);
982 #endif /* defined (TEST) */