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 */
15 #include <sys/cdefs.h>
19 /* The code at the top of get_date which figures out the offset of the
20 current time zone checks various CPP symbols to see if special
21 tricks are need, but defaults to using the gettimeofday system call.
22 Include <sys/time.h> if that will be used. */
24 # include <sys/types.h>
25 # include <sys/time.h>
27 #if defined (__STDC__) || defined (USG)
31 #if defined (__STDC__)
35 /* NOTES on rebuilding getdate.c (particularly for inclusion in CVS
38 We don't want to mess with all the portability hassles of alloca.
39 In particular, most (all?) versions of bison will use alloca in
40 their parser. If bison works on your system (e.g. it should work
41 with gcc), then go ahead and use it, but the more general solution
42 is to use byacc instead of bison, which should generate a portable
43 parser. I played with adding "#define alloca dont_use_alloca", to
44 give an error if the parser generator uses alloca (and thus detect
45 unportable getdate.c's), but that seems to cause as many problems
50 #define yylex getdate_yylex
51 #define yyerror getdate_yyerror
53 static int yylex(void);
54 static int yyerror(const char *);
56 time_t get_date(char *);
59 #define HOUR(x) ((time_t)(x) * 60)
60 #define SECSPERDAY (24L * 60L * 60L)
64 ** An entry in the lexical lookup table.
66 typedef struct _TABLE {
74 ** Daylight-savings mode: on, off, or not yet known.
76 typedef enum _DSTMODE {
77 DSTon, DSToff, DSTmaybe
81 ** Meridian: am, pm, or 24-hour style.
83 typedef enum _MERIDIAN {
89 ** Global variables. We could get rid of most of these by using a good
90 ** union as the yacc stack. (This routine was originally written before
91 ** yacc had the %union construct.) Maybe someday; right now we only use
92 ** the %union very rarely.
95 static DSTMODE yyDSTmode;
96 static time_t yyDayOrdinal;
97 static time_t yyDayNumber;
98 static int yyHaveDate;
100 static int yyHaveRel;
101 static int yyHaveTime;
102 static int yyHaveZone;
103 static time_t yyTimezone;
105 static time_t yyHour;
106 static time_t yyMinutes;
107 static time_t yyMonth;
108 static time_t yySeconds;
109 static time_t yyYear;
110 static MERIDIAN yyMeridian;
111 static time_t yyRelMonth;
112 static time_t yyRelSeconds;
118 enum _MERIDIAN Meridian;
121 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
122 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
124 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
125 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
126 %type <Meridian> tMERIDIAN o_merid
152 time : tUNUMBER tMERIDIAN {
158 | tUNUMBER ':' tUNUMBER o_merid {
164 | tUNUMBER ':' tUNUMBER tSNUMBER {
169 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
171 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
177 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
183 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
216 date : tUNUMBER '/' tUNUMBER {
220 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
231 | tUNUMBER tSNUMBER tSNUMBER {
232 /* ISO 8601 format. yyyy-mm-dd. */
237 | tUNUMBER tMONTH tSNUMBER {
238 /* e.g. 17-JUN-1992. */
247 | tMONTH tUNUMBER ',' tUNUMBER {
256 | tUNUMBER tMONTH tUNUMBER {
264 yyRelSeconds = -yyRelSeconds;
265 yyRelMonth = -yyRelMonth;
270 relunit : tUNUMBER tMINUTE_UNIT {
271 yyRelSeconds += $1 * $2 * 60L;
273 | tSNUMBER tMINUTE_UNIT {
274 yyRelSeconds += $1 * $2 * 60L;
277 yyRelSeconds += $1 * 60L;
279 | tSNUMBER tSEC_UNIT {
282 | tUNUMBER tSEC_UNIT {
288 | tSNUMBER tMONTH_UNIT {
289 yyRelMonth += $1 * $2;
291 | tUNUMBER tMONTH_UNIT {
292 yyRelMonth += $1 * $2;
300 if (yyHaveTime && yyHaveDate && !yyHaveRel)
306 yyMonth= ($1/100)%100;
317 yyMinutes = $1 % 100;
326 o_merid : /* NULL */ {
336 /* Month and day table. */
337 static TABLE const MonthDayTable[] = {
338 { "january", tMONTH, 1 },
339 { "february", tMONTH, 2 },
340 { "march", tMONTH, 3 },
341 { "april", tMONTH, 4 },
342 { "may", tMONTH, 5 },
343 { "june", tMONTH, 6 },
344 { "july", tMONTH, 7 },
345 { "august", tMONTH, 8 },
346 { "september", tMONTH, 9 },
347 { "sept", tMONTH, 9 },
348 { "october", tMONTH, 10 },
349 { "november", tMONTH, 11 },
350 { "december", tMONTH, 12 },
351 { "sunday", tDAY, 0 },
352 { "monday", tDAY, 1 },
353 { "tuesday", tDAY, 2 },
355 { "wednesday", tDAY, 3 },
356 { "wednes", tDAY, 3 },
357 { "thursday", tDAY, 4 },
359 { "thurs", tDAY, 4 },
360 { "friday", tDAY, 5 },
361 { "saturday", tDAY, 6 },
365 /* Time units table. */
366 static TABLE const UnitsTable[] = {
367 { "year", tMONTH_UNIT, 12 },
368 { "month", tMONTH_UNIT, 1 },
369 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
370 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
371 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
372 { "hour", tMINUTE_UNIT, 60 },
373 { "minute", tMINUTE_UNIT, 1 },
374 { "min", tMINUTE_UNIT, 1 },
375 { "second", tSEC_UNIT, 1 },
376 { "sec", tSEC_UNIT, 1 },
380 /* Assorted relative-time words. */
381 static TABLE const OtherTable[] = {
382 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
383 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
384 { "today", tMINUTE_UNIT, 0 },
385 { "now", tMINUTE_UNIT, 0 },
386 { "last", tUNUMBER, -1 },
387 { "this", tMINUTE_UNIT, 0 },
388 { "next", tUNUMBER, 2 },
389 { "first", tUNUMBER, 1 },
390 /* { "second", tUNUMBER, 2 }, */
391 { "third", tUNUMBER, 3 },
392 { "fourth", tUNUMBER, 4 },
393 { "fifth", tUNUMBER, 5 },
394 { "sixth", tUNUMBER, 6 },
395 { "seventh", tUNUMBER, 7 },
396 { "eighth", tUNUMBER, 8 },
397 { "ninth", tUNUMBER, 9 },
398 { "tenth", tUNUMBER, 10 },
399 { "eleventh", tUNUMBER, 11 },
400 { "twelfth", tUNUMBER, 12 },
405 /* The timezone table. */
406 /* Some of these are commented out because a time_t can't store a float. */
407 static TABLE const TimezoneTable[] = {
408 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
409 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
410 { "utc", tZONE, HOUR( 0) },
411 { "wet", tZONE, HOUR( 0) }, /* Western European */
412 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
413 { "wat", tZONE, HOUR( 1) }, /* West Africa */
414 { "at", tZONE, HOUR( 2) }, /* Azores */
416 /* For completeness. BST is also British Summer, and GST is
417 * also Guam Standard. */
418 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
419 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
422 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
423 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
424 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
426 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
427 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
428 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
429 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
430 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
431 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
432 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
433 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
434 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
435 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
436 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
437 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
438 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
439 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
440 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
441 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
442 { "nt", tZONE, HOUR(11) }, /* Nome */
443 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
444 { "cet", tZONE, -HOUR(1) }, /* Central European */
445 { "met", tZONE, -HOUR(1) }, /* Middle European */
446 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
447 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
448 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
449 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
450 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
451 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
452 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
453 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
455 { "it", tZONE, -HOUR(3.5) },/* Iran */
457 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
458 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
460 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
462 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
464 /* For completeness. NST is also Newfoundland Stanard, and SST is
465 * also Swedish Summer. */
466 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
467 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
469 { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
470 { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
472 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
474 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
475 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
477 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
478 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
480 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
481 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
482 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
483 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
484 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
485 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
486 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
490 /* Military timezone table. */
491 static TABLE const MilitaryTable[] = {
492 { "a", tZONE, HOUR( 1) },
493 { "b", tZONE, HOUR( 2) },
494 { "c", tZONE, HOUR( 3) },
495 { "d", tZONE, HOUR( 4) },
496 { "e", tZONE, HOUR( 5) },
497 { "f", tZONE, HOUR( 6) },
498 { "g", tZONE, HOUR( 7) },
499 { "h", tZONE, HOUR( 8) },
500 { "i", tZONE, HOUR( 9) },
501 { "k", tZONE, HOUR( 10) },
502 { "l", tZONE, HOUR( 11) },
503 { "m", tZONE, HOUR( 12) },
504 { "n", tZONE, HOUR(- 1) },
505 { "o", tZONE, HOUR(- 2) },
506 { "p", tZONE, HOUR(- 3) },
507 { "q", tZONE, HOUR(- 4) },
508 { "r", tZONE, HOUR(- 5) },
509 { "s", tZONE, HOUR(- 6) },
510 { "t", tZONE, HOUR(- 7) },
511 { "u", tZONE, HOUR(- 8) },
512 { "v", tZONE, HOUR(- 9) },
513 { "w", tZONE, HOUR(-10) },
514 { "x", tZONE, HOUR(-11) },
515 { "y", tZONE, HOUR(-12) },
516 { "z", tZONE, HOUR( 0) },
525 yyerror(const char *s __unused)
532 ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
534 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
538 if (Hours < 0 || Hours > 23)
540 return (Hours * 60L + Minutes) * 60L + Seconds;
542 if (Hours < 1 || Hours > 12)
546 return (Hours * 60L + Minutes) * 60L + Seconds;
548 if (Hours < 1 || Hours > 12)
552 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
561 * A negative number, which means to use its absolute value (why?)
562 * A number from 0 to 99, which means a year from 1900 to 1999, or
563 * The actual year (>=100). */
565 Convert(time_t Month, time_t Day, time_t Year,
566 time_t Hours, time_t Minutes, time_t Seconds,
567 MERIDIAN Meridian, DSTMODE DSTmode)
569 static int DaysInMonth[12] = {
570 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
582 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
584 /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
585 I'm too lazy to try to check for time_t overflow in another way. */
586 if (Year < EPOCH || Year > 2038
587 || Month < 1 || Month > 12
588 /* Lint fluff: "conversion from long may lose accuracy" */
589 || Day < 1 || Day > DaysInMonth[(int)--Month])
592 for (Julian = Day - 1, i = 0; i < Month; i++)
593 Julian += DaysInMonth[i];
594 for (i = EPOCH; i < Year; i++)
595 Julian += 365 + (i % 4 == 0);
596 Julian *= SECSPERDAY;
597 Julian += yyTimezone * 60L;
598 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
602 || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
609 DSTcorrect(time_t Start, time_t Future)
614 StartDay = (localtime(&Start)->tm_hour + 1) % 24;
615 FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
616 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
621 RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
627 tm = localtime(&now);
628 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
629 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
630 return DSTcorrect(Start, now);
635 RelativeMonth(time_t Start, time_t RelMonth)
643 tm = localtime(&Start);
644 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
646 Month = Month % 12 + 1;
647 return DSTcorrect(Start,
648 Convert(Month, (time_t)tm->tm_mday, Year,
649 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
655 LookupWord(char *buff)
663 /* Make it lowercase. */
664 for (p = buff; *p; p++)
668 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
669 yylval.Meridian = MERam;
672 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
673 yylval.Meridian = MERpm;
677 /* See if we have an abbreviation for a month. */
678 if (strlen(buff) == 3)
680 else if (strlen(buff) == 4 && buff[3] == '.') {
687 for (tp = MonthDayTable; tp->name; tp++) {
689 if (strncmp(buff, tp->name, 3) == 0) {
690 yylval.Number = tp->value;
694 else if (strcmp(buff, tp->name) == 0) {
695 yylval.Number = tp->value;
700 for (tp = TimezoneTable; tp->name; tp++)
701 if (strcmp(buff, tp->name) == 0) {
702 yylval.Number = tp->value;
706 if (strcmp(buff, "dst") == 0)
709 for (tp = UnitsTable; tp->name; tp++)
710 if (strcmp(buff, tp->name) == 0) {
711 yylval.Number = tp->value;
715 /* Strip off any plural and try the units table again. */
716 i = strlen(buff) - 1;
717 if (buff[i] == 's') {
719 for (tp = UnitsTable; tp->name; tp++)
720 if (strcmp(buff, tp->name) == 0) {
721 yylval.Number = tp->value;
724 buff[i] = 's'; /* Put back for "this" in OtherTable. */
727 for (tp = OtherTable; tp->name; tp++)
728 if (strcmp(buff, tp->name) == 0) {
729 yylval.Number = tp->value;
733 /* Military timezones. */
734 if (buff[1] == '\0' && isalpha(*buff)) {
735 for (tp = MilitaryTable; tp->name; tp++)
736 if (strcmp(buff, tp->name) == 0) {
737 yylval.Number = tp->value;
742 /* Drop out any periods and try the timezone table again. */
743 for (i = 0, p = q = buff; *q; q++)
750 for (tp = TimezoneTable; tp->name; tp++)
751 if (strcmp(buff, tp->name) == 0) {
752 yylval.Number = tp->value;
770 while (isspace(*yyInput))
773 if (isdigit(c = *yyInput) || c == '-' || c == '+') {
774 if (c == '-' || c == '+') {
775 sign = c == '-' ? -1 : 1;
776 if (!isdigit(*++yyInput))
777 /* skip the '-' sign */
782 for (yylval.Number = 0; isdigit(c = *yyInput++); )
783 yylval.Number = 10 * yylval.Number + c - '0';
786 yylval.Number = -yylval.Number;
787 return sign ? tSNUMBER : tUNUMBER;
790 for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
791 if (p < &buff[sizeof buff - 1])
795 return LookupWord(buff);
812 #define TM_YEAR_ORIGIN 1900
814 /* Yield A - B, measured in seconds. */
816 difftm (struct tm *a, struct tm *b)
818 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
819 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
821 /* difference in day of year */
822 a->tm_yday - b->tm_yday
823 /* + intervening leap days */
824 + ((ay >> 2) - (by >> 2))
826 + ((ay/100 >> 2) - (by/100 >> 2))
827 /* + difference in years * 365 */
828 + (long)(ay-by) * 365
830 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
831 + (a->tm_min - b->tm_min))
832 + (a->tm_sec - b->tm_sec));
838 struct tm *tm, *gmt_ptr, gmt;
844 bzero (&gmt, sizeof(struct tm));
847 (void)time (&nowtime);
849 gmt_ptr = gmtime (&nowtime);
852 /* Make a copy, in case localtime modifies *tm (I think
853 that comment now applies to *gmt_ptr, but I am too
854 lazy to dig into how gmtime and locatime allocate the
855 structures they return pointers to). */
859 if (! (tm = localtime (&nowtime)))
863 tzoff = difftm (&gmt, tm) / 60;
865 /* We are on a system like VMS, where the system clock is
866 in local time and the system has no concept of timezones.
867 Hopefully we can fake this out (for the case in which the
868 user specifies no timezone) by just saying the timezone
875 tm = localtime(&nowtime);
876 yyYear = tm->tm_year + 1900;
877 yyMonth = tm->tm_mon + 1;
880 yyDSTmode = DSTmaybe;
894 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
897 if (yyHaveDate || yyHaveTime || yyHaveDay) {
898 Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
899 yyMeridian, yyDSTmode);
906 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
909 Start += yyRelSeconds;
910 Start += RelativeMonth(Start, yyRelMonth);
912 if (yyHaveDay && !yyHaveDate) {
913 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
917 /* Have to do *something* with a legitimate -1 so it's distinguishable
918 * from the error return value. (Alternately could set errno on error.) */
919 return Start == -1 ? 0 : Start;
927 main(int ac, char *av[])
932 (void)printf("Enter date, or blank line to exit.\n\t> ");
933 (void)fflush(stdout);
934 while (gets(buff) && buff[0]) {
937 (void)printf("Bad format - couldn't convert.\n");
939 (void)printf("%s", ctime(&d));
940 (void)printf("\t> ");
941 (void)fflush(stdout);
946 #endif /* defined(TEST) */