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 10 shift/reduce conflicts.
10 ** This code is in the public domain and has no copyright.
12 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
13 /* SUPPRESS 288 on yyerrlab *//* Label unused */
18 #if defined (emacs) || defined (CONFIG_BROKETS)
25 /* Since the code of getdate.y is not included in the Emacs executable
26 itself, there is no need to #define static in this file. Even if
27 the code were included in the Emacs executable, it probably
28 wouldn't do any harm to #undef it here; this will only cause
29 problems if we try to write to a static variable, which I don't
30 think this code needs to do. */
38 /* The code at the top of get_date which figures out the offset of the
39 current time zone checks various CPP symbols to see if special
40 tricks are need, but defaults to using the gettimeofday system call.
41 Include <sys/time.h> if that will be used. */
45 #else /* defined(vms) */
46 # include <sys/types.h>
47 /*# include "xtime.h"*/
48 # include <sys/time.h>
49 # include <sys/timeb.h>
50 #endif /* !defined(vms) */
52 #if defined (STDC_HEADERS) || defined (USG)
56 /* Some old versions of bison generate parsers that use bcopy.
57 That loses on systems that don't provide the function, so we have
58 to redefine it here. */
59 #if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
60 #define bcopy(from, to, len) memcpy ((to), (from), (len))
63 #if defined (STDC_HEADERS)
67 /* NOTES on rebuilding getdate.c (particularly for inclusion in CVS
70 We don't want to mess with all the portability hassles of alloca.
71 In particular, most (all?) versions of bison will use alloca in
72 their parser. If bison works on your system (e.g. it should work
73 with gcc), then go ahead and use it, but the more general solution
74 is to use byacc instead of bison, which should generate a portable
75 parser. I played with adding "#define alloca dont_use_alloca", to
76 give an error if the parser generator uses alloca (and thus detect
77 unportable getdate.c's), but that seems to cause as many problems
80 extern struct tm *gmtime();
81 extern struct tm *localtime();
83 #define yyparse getdate_yyparse
84 #define yylex getdate_yylex
85 #define yyerror getdate_yyerror
87 static int yyparse ();
89 static int yyerror ();
92 #define HOUR(x) ((time_t)(x) * 60)
93 #define SECSPERDAY (24L * 60L * 60L)
97 ** An entry in the lexical lookup table.
99 typedef struct _TABLE {
107 ** Daylight-savings mode: on, off, or not yet known.
109 typedef enum _DSTMODE {
110 DSTon, DSToff, DSTmaybe
114 ** Meridian: am, pm, or 24-hour style.
116 typedef enum _MERIDIAN {
122 ** Global variables. We could get rid of most of these by using a good
123 ** union as the yacc stack. (This routine was originally written before
124 ** yacc had the %union construct.) Maybe someday; right now we only use
125 ** the %union very rarely.
127 static char *yyInput;
128 static DSTMODE yyDSTmode;
129 static time_t yyDayOrdinal;
130 static time_t yyDayNumber;
131 static int yyHaveDate;
132 static int yyHaveDay;
133 static int yyHaveRel;
134 static int yyHaveTime;
135 static int yyHaveZone;
136 static time_t yyTimezone;
138 static time_t yyHour;
139 static time_t yyMinutes;
140 static time_t yyMonth;
141 static time_t yySeconds;
142 static time_t yyYear;
143 static MERIDIAN yyMeridian;
144 static time_t yyRelMonth;
145 static time_t yyRelSeconds;
151 enum _MERIDIAN Meridian;
154 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
155 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
157 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
158 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
159 %type <Meridian> tMERIDIAN o_merid
185 time : tUNUMBER tMERIDIAN {
191 | tUNUMBER ':' tUNUMBER o_merid {
197 | tUNUMBER ':' tUNUMBER tSNUMBER {
202 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
204 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
210 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
216 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
249 date : tUNUMBER '/' tUNUMBER {
253 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
264 | tUNUMBER tSNUMBER tSNUMBER {
265 /* ISO 8601 format. yyyy-mm-dd. */
270 | tUNUMBER tMONTH tSNUMBER {
271 /* e.g. 17-JUN-1992. */
280 | tMONTH tUNUMBER ',' tUNUMBER {
289 | tUNUMBER tMONTH tUNUMBER {
297 yyRelSeconds = -yyRelSeconds;
298 yyRelMonth = -yyRelMonth;
303 relunit : tUNUMBER tMINUTE_UNIT {
304 yyRelSeconds += $1 * $2 * 60L;
306 | tSNUMBER tMINUTE_UNIT {
307 yyRelSeconds += $1 * $2 * 60L;
310 yyRelSeconds += $1 * 60L;
312 | tSNUMBER tSEC_UNIT {
315 | tUNUMBER tSEC_UNIT {
321 | tSNUMBER tMONTH_UNIT {
322 yyRelMonth += $1 * $2;
324 | tUNUMBER tMONTH_UNIT {
325 yyRelMonth += $1 * $2;
333 if (yyHaveTime && yyHaveDate && !yyHaveRel)
339 yyMonth= ($1/100)%100;
350 yyMinutes = $1 % 100;
359 o_merid : /* NULL */ {
369 /* Month and day table. */
370 static TABLE const MonthDayTable[] = {
371 { "january", tMONTH, 1 },
372 { "february", tMONTH, 2 },
373 { "march", tMONTH, 3 },
374 { "april", tMONTH, 4 },
375 { "may", tMONTH, 5 },
376 { "june", tMONTH, 6 },
377 { "july", tMONTH, 7 },
378 { "august", tMONTH, 8 },
379 { "september", tMONTH, 9 },
380 { "sept", tMONTH, 9 },
381 { "october", tMONTH, 10 },
382 { "november", tMONTH, 11 },
383 { "december", tMONTH, 12 },
384 { "sunday", tDAY, 0 },
385 { "monday", tDAY, 1 },
386 { "tuesday", tDAY, 2 },
388 { "wednesday", tDAY, 3 },
389 { "wednes", tDAY, 3 },
390 { "thursday", tDAY, 4 },
392 { "thurs", tDAY, 4 },
393 { "friday", tDAY, 5 },
394 { "saturday", tDAY, 6 },
398 /* Time units table. */
399 static TABLE const UnitsTable[] = {
400 { "year", tMONTH_UNIT, 12 },
401 { "month", tMONTH_UNIT, 1 },
402 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
403 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
404 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
405 { "hour", tMINUTE_UNIT, 60 },
406 { "minute", tMINUTE_UNIT, 1 },
407 { "min", tMINUTE_UNIT, 1 },
408 { "second", tSEC_UNIT, 1 },
409 { "sec", tSEC_UNIT, 1 },
413 /* Assorted relative-time words. */
414 static TABLE const OtherTable[] = {
415 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
416 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
417 { "today", tMINUTE_UNIT, 0 },
418 { "now", tMINUTE_UNIT, 0 },
419 { "last", tUNUMBER, -1 },
420 { "this", tMINUTE_UNIT, 0 },
421 { "next", tUNUMBER, 2 },
422 { "first", tUNUMBER, 1 },
423 /* { "second", tUNUMBER, 2 }, */
424 { "third", tUNUMBER, 3 },
425 { "fourth", tUNUMBER, 4 },
426 { "fifth", tUNUMBER, 5 },
427 { "sixth", tUNUMBER, 6 },
428 { "seventh", tUNUMBER, 7 },
429 { "eighth", tUNUMBER, 8 },
430 { "ninth", tUNUMBER, 9 },
431 { "tenth", tUNUMBER, 10 },
432 { "eleventh", tUNUMBER, 11 },
433 { "twelfth", tUNUMBER, 12 },
438 /* The timezone table. */
439 /* Some of these are commented out because a time_t can't store a float. */
440 static TABLE const TimezoneTable[] = {
441 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
442 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
443 { "utc", tZONE, HOUR( 0) },
444 { "wet", tZONE, HOUR( 0) }, /* Western European */
445 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
446 { "wat", tZONE, HOUR( 1) }, /* West Africa */
447 { "at", tZONE, HOUR( 2) }, /* Azores */
449 /* For completeness. BST is also British Summer, and GST is
450 * also Guam Standard. */
451 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
452 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
455 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
456 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
457 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
459 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
460 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
461 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
462 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
463 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
464 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
465 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
466 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
467 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
468 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
469 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
470 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
471 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
472 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
473 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
474 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
475 { "nt", tZONE, HOUR(11) }, /* Nome */
476 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
477 { "cet", tZONE, -HOUR(1) }, /* Central European */
478 { "met", tZONE, -HOUR(1) }, /* Middle European */
479 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
480 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
481 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
482 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
483 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
484 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
485 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
486 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
488 { "it", tZONE, -HOUR(3.5) },/* Iran */
490 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
491 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
493 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
495 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
497 /* For completeness. NST is also Newfoundland Stanard, and SST is
498 * also Swedish Summer. */
499 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
500 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
502 { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
503 { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
505 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
507 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
508 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
510 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
511 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
513 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
514 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
515 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
516 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
517 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
518 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
519 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
523 /* Military timezone table. */
524 static TABLE const MilitaryTable[] = {
525 { "a", tZONE, HOUR( 1) },
526 { "b", tZONE, HOUR( 2) },
527 { "c", tZONE, HOUR( 3) },
528 { "d", tZONE, HOUR( 4) },
529 { "e", tZONE, HOUR( 5) },
530 { "f", tZONE, HOUR( 6) },
531 { "g", tZONE, HOUR( 7) },
532 { "h", tZONE, HOUR( 8) },
533 { "i", tZONE, HOUR( 9) },
534 { "k", tZONE, HOUR( 10) },
535 { "l", tZONE, HOUR( 11) },
536 { "m", tZONE, HOUR( 12) },
537 { "n", tZONE, HOUR(- 1) },
538 { "o", tZONE, HOUR(- 2) },
539 { "p", tZONE, HOUR(- 3) },
540 { "q", tZONE, HOUR(- 4) },
541 { "r", tZONE, HOUR(- 5) },
542 { "s", tZONE, HOUR(- 6) },
543 { "t", tZONE, HOUR(- 7) },
544 { "u", tZONE, HOUR(- 8) },
545 { "v", tZONE, HOUR(- 9) },
546 { "w", tZONE, HOUR(-10) },
547 { "x", tZONE, HOUR(-11) },
548 { "y", tZONE, HOUR(-12) },
549 { "z", tZONE, HOUR( 0) },
566 ToSeconds(Hours, Minutes, Seconds, Meridian)
572 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
576 if (Hours < 0 || Hours > 23)
578 return (Hours * 60L + Minutes) * 60L + Seconds;
580 if (Hours < 1 || Hours > 12)
584 return (Hours * 60L + Minutes) * 60L + Seconds;
586 if (Hours < 1 || Hours > 12)
590 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
599 * A negative number, which means to use its absolute value (why?)
600 * A number from 0 to 99, which means a year from 1900 to 1999, or
601 * The actual year (>=100). */
603 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
613 static int DaysInMonth[12] = {
614 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
626 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
628 /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
629 I'm too lazy to try to check for time_t overflow in another way. */
630 if (Year < EPOCH || Year > 2038
631 || Month < 1 || Month > 12
632 /* Lint fluff: "conversion from long may lose accuracy" */
633 || Day < 1 || Day > DaysInMonth[(int)--Month])
636 for (Julian = Day - 1, i = 0; i < Month; i++)
637 Julian += DaysInMonth[i];
638 for (i = EPOCH; i < Year; i++)
639 Julian += 365 + (i % 4 == 0);
640 Julian *= SECSPERDAY;
641 Julian += yyTimezone * 60L;
642 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
646 || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
653 DSTcorrect(Start, Future)
660 StartDay = (localtime(&Start)->tm_hour + 1) % 24;
661 FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
662 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
667 RelativeDate(Start, DayOrdinal, DayNumber)
676 tm = localtime(&now);
677 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
678 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
679 return DSTcorrect(Start, now);
684 RelativeMonth(Start, RelMonth)
694 tm = localtime(&Start);
695 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
697 Month = Month % 12 + 1;
698 return DSTcorrect(Start,
699 Convert(Month, (time_t)tm->tm_mday, Year,
700 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
711 register const TABLE *tp;
715 /* Make it lowercase. */
716 for (p = buff; *p; p++)
720 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
721 yylval.Meridian = MERam;
724 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
725 yylval.Meridian = MERpm;
729 /* See if we have an abbreviation for a month. */
730 if (strlen(buff) == 3)
732 else if (strlen(buff) == 4 && buff[3] == '.') {
739 for (tp = MonthDayTable; tp->name; tp++) {
741 if (strncmp(buff, tp->name, 3) == 0) {
742 yylval.Number = tp->value;
746 else if (strcmp(buff, tp->name) == 0) {
747 yylval.Number = tp->value;
752 for (tp = TimezoneTable; tp->name; tp++)
753 if (strcmp(buff, tp->name) == 0) {
754 yylval.Number = tp->value;
758 if (strcmp(buff, "dst") == 0)
761 for (tp = UnitsTable; tp->name; tp++)
762 if (strcmp(buff, tp->name) == 0) {
763 yylval.Number = tp->value;
767 /* Strip off any plural and try the units table again. */
768 i = strlen(buff) - 1;
769 if (buff[i] == 's') {
771 for (tp = UnitsTable; tp->name; tp++)
772 if (strcmp(buff, tp->name) == 0) {
773 yylval.Number = tp->value;
776 buff[i] = 's'; /* Put back for "this" in OtherTable. */
779 for (tp = OtherTable; tp->name; tp++)
780 if (strcmp(buff, tp->name) == 0) {
781 yylval.Number = tp->value;
785 /* Military timezones. */
786 if (buff[1] == '\0' && isalpha(*buff)) {
787 for (tp = MilitaryTable; tp->name; tp++)
788 if (strcmp(buff, tp->name) == 0) {
789 yylval.Number = tp->value;
794 /* Drop out any periods and try the timezone table again. */
795 for (i = 0, p = q = buff; *q; q++)
802 for (tp = TimezoneTable; tp->name; tp++)
803 if (strcmp(buff, tp->name) == 0) {
804 yylval.Number = tp->value;
822 while (isspace(*yyInput))
825 if (isdigit(c = *yyInput) || c == '-' || c == '+') {
826 if (c == '-' || c == '+') {
827 sign = c == '-' ? -1 : 1;
828 if (!isdigit(*++yyInput))
829 /* skip the '-' sign */
834 for (yylval.Number = 0; isdigit(c = *yyInput++); )
835 yylval.Number = 10 * yylval.Number + c - '0';
838 yylval.Number = -yylval.Number;
839 return sign ? tSNUMBER : tUNUMBER;
842 for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
843 if (p < &buff[sizeof buff - 1])
847 return LookupWord(buff);
864 #define TM_YEAR_ORIGIN 1900
866 /* Yield A - B, measured in seconds. */
871 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
872 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
874 /* difference in day of year */
875 a->tm_yday - b->tm_yday
876 /* + intervening leap days */
877 + ((ay >> 2) - (by >> 2))
879 + ((ay/100 >> 2) - (by/100 >> 2))
880 /* + difference in years * 365 */
881 + (long)(ay-by) * 365
883 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
884 + (a->tm_min - b->tm_min))
885 + (a->tm_sec - b->tm_sec));
904 (void)time (&nowtime);
906 gmt_ptr = gmtime (&nowtime);
909 /* Make a copy, in case localtime modifies *tm (I think
910 that comment now applies to *gmt_ptr, but I am too
911 lazy to dig into how gmtime and locatime allocate the
912 structures they return pointers to). */
916 if (! (tm = localtime (&nowtime)))
920 ftz.timezone = difftm (&gmt, tm) / 60;
922 /* We are on a system like VMS, where the system clock is
923 in local time and the system has no concept of timezones.
924 Hopefully we can fake this out (for the case in which the
925 user specifies no timezone) by just saying the timezone
937 tm = localtime(&nowtime);
938 yyYear = tm->tm_year + 1900;
939 yyMonth = tm->tm_mon + 1;
941 yyTimezone = now->timezone;
942 yyDSTmode = DSTmaybe;
956 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
959 if (yyHaveDate || yyHaveTime || yyHaveDay) {
960 Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
961 yyMeridian, yyDSTmode);
968 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
971 Start += yyRelSeconds;
972 Start += RelativeMonth(Start, yyRelMonth);
974 if (yyHaveDay && !yyHaveDate) {
975 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
979 /* Have to do *something* with a legitimate -1 so it's distinguishable
980 * from the error return value. (Alternately could set errno on error.) */
981 return Start == -1 ? 0 : Start;
996 (void)printf("Enter date, or blank line to exit.\n\t> ");
997 (void)fflush(stdout);
998 while (gets(buff) && buff[0]) {
999 d = get_date(buff, (struct timeb *)NULL);
1001 (void)printf("Bad format - couldn't convert.\n");
1003 (void)printf("%s", ctime(&d));
1004 (void)printf("\t> ");
1005 (void)fflush(stdout);
1010 #endif /* defined(TEST) */