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>
16 __FBSDID("$FreeBSD$");
21 /* The code at the top of get_date which figures out the offset of the
22 current time zone checks various CPP symbols to see if special
23 tricks are need, but defaults to using the gettimeofday system call.
24 Include <sys/time.h> if that will be used. */
26 # include <sys/types.h>
27 # include <sys/time.h>
29 #if defined (__STDC__) || defined (USG)
33 #if defined (__STDC__)
37 /* NOTES on rebuilding getdate.c (particularly for inclusion in CVS
40 We don't want to mess with all the portability hassles of alloca.
41 In particular, most (all?) versions of bison will use alloca in
42 their parser. If bison works on your system (e.g. it should work
43 with gcc), then go ahead and use it, but the more general solution
44 is to use byacc instead of bison, which should generate a portable
45 parser. I played with adding "#define alloca dont_use_alloca", to
46 give an error if the parser generator uses alloca (and thus detect
47 unportable getdate.c's), but that seems to cause as many problems
52 #define yylex getdate_yylex
53 #define yyerror getdate_yyerror
55 static int yylex(void);
56 static int yyerror(const char *);
58 time_t get_date(char *);
61 #define HOUR(x) ((time_t)(x) * 60)
62 #define SECSPERDAY (24L * 60L * 60L)
66 ** An entry in the lexical lookup table.
68 typedef struct _TABLE {
76 ** Daylight-savings mode: on, off, or not yet known.
78 typedef enum _DSTMODE {
79 DSTon, DSToff, DSTmaybe
83 ** Meridian: am, pm, or 24-hour style.
85 typedef enum _MERIDIAN {
91 ** Global variables. We could get rid of most of these by using a good
92 ** union as the yacc stack. (This routine was originally written before
93 ** yacc had the %union construct.) Maybe someday; right now we only use
94 ** the %union very rarely.
97 static DSTMODE yyDSTmode;
98 static time_t yyDayOrdinal;
99 static time_t yyDayNumber;
100 static int yyHaveDate;
101 static int yyHaveDay;
102 static int yyHaveRel;
103 static int yyHaveTime;
104 static int yyHaveZone;
105 static time_t yyTimezone;
107 static time_t yyHour;
108 static time_t yyMinutes;
109 static time_t yyMonth;
110 static time_t yySeconds;
111 static time_t yyYear;
112 static MERIDIAN yyMeridian;
113 static time_t yyRelMonth;
114 static time_t yyRelSeconds;
120 enum _MERIDIAN Meridian;
123 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
124 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
126 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
127 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
128 %type <Meridian> tMERIDIAN o_merid
154 time : tUNUMBER tMERIDIAN {
160 | tUNUMBER ':' tUNUMBER o_merid {
166 | tUNUMBER ':' tUNUMBER tSNUMBER {
171 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
173 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
179 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
185 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
218 date : tUNUMBER '/' tUNUMBER {
222 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
233 | tUNUMBER tSNUMBER tSNUMBER {
234 /* ISO 8601 format. yyyy-mm-dd. */
239 | tUNUMBER tMONTH tSNUMBER {
240 /* e.g. 17-JUN-1992. */
249 | tMONTH tUNUMBER ',' tUNUMBER {
258 | tUNUMBER tMONTH tUNUMBER {
266 yyRelSeconds = -yyRelSeconds;
267 yyRelMonth = -yyRelMonth;
272 relunit : tUNUMBER tMINUTE_UNIT {
273 yyRelSeconds += $1 * $2 * 60L;
275 | tSNUMBER tMINUTE_UNIT {
276 yyRelSeconds += $1 * $2 * 60L;
279 yyRelSeconds += $1 * 60L;
281 | tSNUMBER tSEC_UNIT {
284 | tUNUMBER tSEC_UNIT {
290 | tSNUMBER tMONTH_UNIT {
291 yyRelMonth += $1 * $2;
293 | tUNUMBER tMONTH_UNIT {
294 yyRelMonth += $1 * $2;
302 if (yyHaveTime && yyHaveDate && !yyHaveRel)
308 yyMonth= ($1/100)%100;
319 yyMinutes = $1 % 100;
328 o_merid : /* NULL */ {
338 /* Month and day table. */
339 static TABLE const MonthDayTable[] = {
340 { "january", tMONTH, 1 },
341 { "february", tMONTH, 2 },
342 { "march", tMONTH, 3 },
343 { "april", tMONTH, 4 },
344 { "may", tMONTH, 5 },
345 { "june", tMONTH, 6 },
346 { "july", tMONTH, 7 },
347 { "august", tMONTH, 8 },
348 { "september", tMONTH, 9 },
349 { "sept", tMONTH, 9 },
350 { "october", tMONTH, 10 },
351 { "november", tMONTH, 11 },
352 { "december", tMONTH, 12 },
353 { "sunday", tDAY, 0 },
354 { "monday", tDAY, 1 },
355 { "tuesday", tDAY, 2 },
357 { "wednesday", tDAY, 3 },
358 { "wednes", tDAY, 3 },
359 { "thursday", tDAY, 4 },
361 { "thurs", tDAY, 4 },
362 { "friday", tDAY, 5 },
363 { "saturday", tDAY, 6 },
367 /* Time units table. */
368 static TABLE const UnitsTable[] = {
369 { "year", tMONTH_UNIT, 12 },
370 { "month", tMONTH_UNIT, 1 },
371 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
372 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
373 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
374 { "hour", tMINUTE_UNIT, 60 },
375 { "minute", tMINUTE_UNIT, 1 },
376 { "min", tMINUTE_UNIT, 1 },
377 { "second", tSEC_UNIT, 1 },
378 { "sec", tSEC_UNIT, 1 },
382 /* Assorted relative-time words. */
383 static TABLE const OtherTable[] = {
384 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
385 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
386 { "today", tMINUTE_UNIT, 0 },
387 { "now", tMINUTE_UNIT, 0 },
388 { "last", tUNUMBER, -1 },
389 { "this", tMINUTE_UNIT, 0 },
390 { "next", tUNUMBER, 2 },
391 { "first", tUNUMBER, 1 },
392 /* { "second", tUNUMBER, 2 }, */
393 { "third", tUNUMBER, 3 },
394 { "fourth", tUNUMBER, 4 },
395 { "fifth", tUNUMBER, 5 },
396 { "sixth", tUNUMBER, 6 },
397 { "seventh", tUNUMBER, 7 },
398 { "eighth", tUNUMBER, 8 },
399 { "ninth", tUNUMBER, 9 },
400 { "tenth", tUNUMBER, 10 },
401 { "eleventh", tUNUMBER, 11 },
402 { "twelfth", tUNUMBER, 12 },
407 /* The timezone table. */
408 /* Some of these are commented out because a time_t can't store a float. */
409 static TABLE const TimezoneTable[] = {
410 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
411 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
412 { "utc", tZONE, HOUR( 0) },
413 { "wet", tZONE, HOUR( 0) }, /* Western European */
414 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
415 { "wat", tZONE, HOUR( 1) }, /* West Africa */
416 { "at", tZONE, HOUR( 2) }, /* Azores */
418 /* For completeness. BST is also British Summer, and GST is
419 * also Guam Standard. */
420 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
421 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
424 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
425 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
426 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
428 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
429 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
430 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
431 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
432 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
433 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
434 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
435 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
436 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
437 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
438 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
439 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
440 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
441 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
442 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
443 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
444 { "nt", tZONE, HOUR(11) }, /* Nome */
445 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
446 { "cet", tZONE, -HOUR(1) }, /* Central European */
447 { "met", tZONE, -HOUR(1) }, /* Middle European */
448 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
449 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
450 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
451 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
452 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
453 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
454 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
455 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
457 { "it", tZONE, -HOUR(3.5) },/* Iran */
459 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
460 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
462 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
464 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
466 /* For completeness. NST is also Newfoundland Stanard, and SST is
467 * also Swedish Summer. */
468 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
469 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
471 { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
472 { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
474 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
476 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
477 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
479 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
480 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
482 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
483 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
484 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
485 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
486 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
487 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
488 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
492 /* Military timezone table. */
493 static TABLE const MilitaryTable[] = {
494 { "a", tZONE, HOUR( 1) },
495 { "b", tZONE, HOUR( 2) },
496 { "c", tZONE, HOUR( 3) },
497 { "d", tZONE, HOUR( 4) },
498 { "e", tZONE, HOUR( 5) },
499 { "f", tZONE, HOUR( 6) },
500 { "g", tZONE, HOUR( 7) },
501 { "h", tZONE, HOUR( 8) },
502 { "i", tZONE, HOUR( 9) },
503 { "k", tZONE, HOUR( 10) },
504 { "l", tZONE, HOUR( 11) },
505 { "m", tZONE, HOUR( 12) },
506 { "n", tZONE, HOUR(- 1) },
507 { "o", tZONE, HOUR(- 2) },
508 { "p", tZONE, HOUR(- 3) },
509 { "q", tZONE, HOUR(- 4) },
510 { "r", tZONE, HOUR(- 5) },
511 { "s", tZONE, HOUR(- 6) },
512 { "t", tZONE, HOUR(- 7) },
513 { "u", tZONE, HOUR(- 8) },
514 { "v", tZONE, HOUR(- 9) },
515 { "w", tZONE, HOUR(-10) },
516 { "x", tZONE, HOUR(-11) },
517 { "y", tZONE, HOUR(-12) },
518 { "z", tZONE, HOUR( 0) },
527 yyerror(const char *s __unused)
534 ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
536 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
540 if (Hours < 0 || Hours > 23)
542 return (Hours * 60L + Minutes) * 60L + Seconds;
544 if (Hours < 1 || Hours > 12)
548 return (Hours * 60L + Minutes) * 60L + Seconds;
550 if (Hours < 1 || Hours > 12)
554 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
563 * A negative number, which means to use its absolute value (why?)
564 * A number from 0 to 99, which means a year from 1900 to 1999, or
565 * The actual year (>=100). */
567 Convert(time_t Month, time_t Day, time_t Year,
568 time_t Hours, time_t Minutes, time_t Seconds,
569 MERIDIAN Meridian, DSTMODE DSTmode)
571 static int DaysInMonth[12] = {
572 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
584 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
586 /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
587 I'm too lazy to try to check for time_t overflow in another way. */
588 if (Year < EPOCH || Year > 2038
589 || Month < 1 || Month > 12
590 /* Lint fluff: "conversion from long may lose accuracy" */
591 || Day < 1 || Day > DaysInMonth[(int)--Month])
594 for (Julian = Day - 1, i = 0; i < Month; i++)
595 Julian += DaysInMonth[i];
596 for (i = EPOCH; i < Year; i++)
597 Julian += 365 + (i % 4 == 0);
598 Julian *= SECSPERDAY;
599 Julian += yyTimezone * 60L;
600 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
604 || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
611 DSTcorrect(time_t Start, time_t Future)
616 StartDay = (localtime(&Start)->tm_hour + 1) % 24;
617 FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
618 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
623 RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
629 tm = localtime(&now);
630 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
631 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
632 return DSTcorrect(Start, now);
637 RelativeMonth(time_t Start, time_t RelMonth)
645 tm = localtime(&Start);
646 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
648 Month = Month % 12 + 1;
649 return DSTcorrect(Start,
650 Convert(Month, (time_t)tm->tm_mday, Year,
651 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
657 LookupWord(char *buff)
665 /* Make it lowercase. */
666 for (p = buff; *p; p++)
670 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
671 yylval.Meridian = MERam;
674 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
675 yylval.Meridian = MERpm;
679 /* See if we have an abbreviation for a month. */
680 if (strlen(buff) == 3)
682 else if (strlen(buff) == 4 && buff[3] == '.') {
689 for (tp = MonthDayTable; tp->name; tp++) {
691 if (strncmp(buff, tp->name, 3) == 0) {
692 yylval.Number = tp->value;
696 else if (strcmp(buff, tp->name) == 0) {
697 yylval.Number = tp->value;
702 for (tp = TimezoneTable; tp->name; tp++)
703 if (strcmp(buff, tp->name) == 0) {
704 yylval.Number = tp->value;
708 if (strcmp(buff, "dst") == 0)
711 for (tp = UnitsTable; tp->name; tp++)
712 if (strcmp(buff, tp->name) == 0) {
713 yylval.Number = tp->value;
717 /* Strip off any plural and try the units table again. */
718 i = strlen(buff) - 1;
719 if (buff[i] == 's') {
721 for (tp = UnitsTable; tp->name; tp++)
722 if (strcmp(buff, tp->name) == 0) {
723 yylval.Number = tp->value;
726 buff[i] = 's'; /* Put back for "this" in OtherTable. */
729 for (tp = OtherTable; tp->name; tp++)
730 if (strcmp(buff, tp->name) == 0) {
731 yylval.Number = tp->value;
735 /* Military timezones. */
736 if (buff[1] == '\0' && isalpha(*buff)) {
737 for (tp = MilitaryTable; tp->name; tp++)
738 if (strcmp(buff, tp->name) == 0) {
739 yylval.Number = tp->value;
744 /* Drop out any periods and try the timezone table again. */
745 for (i = 0, p = q = buff; *q; q++)
752 for (tp = TimezoneTable; tp->name; tp++)
753 if (strcmp(buff, tp->name) == 0) {
754 yylval.Number = tp->value;
772 while (isspace(*yyInput))
775 if (isdigit(c = *yyInput) || c == '-' || c == '+') {
776 if (c == '-' || c == '+') {
777 sign = c == '-' ? -1 : 1;
778 if (!isdigit(*++yyInput))
779 /* skip the '-' sign */
784 for (yylval.Number = 0; isdigit(c = *yyInput++); )
785 yylval.Number = 10 * yylval.Number + c - '0';
788 yylval.Number = -yylval.Number;
789 return sign ? tSNUMBER : tUNUMBER;
792 for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
793 if (p < &buff[sizeof buff - 1])
797 return LookupWord(buff);
814 #define TM_YEAR_ORIGIN 1900
816 /* Yield A - B, measured in seconds. */
818 difftm (struct tm *a, struct tm *b)
820 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
821 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
823 /* difference in day of year */
824 a->tm_yday - b->tm_yday
825 /* + intervening leap days */
826 + ((ay >> 2) - (by >> 2))
828 + ((ay/100 >> 2) - (by/100 >> 2))
829 /* + difference in years * 365 */
830 + (long)(ay-by) * 365
832 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
833 + (a->tm_min - b->tm_min))
834 + (a->tm_sec - b->tm_sec));
840 struct tm *tm, *gmt_ptr, gmt;
846 bzero (&gmt, sizeof(struct tm));
849 (void)time (&nowtime);
851 gmt_ptr = gmtime (&nowtime);
854 /* Make a copy, in case localtime modifies *tm (I think
855 that comment now applies to *gmt_ptr, but I am too
856 lazy to dig into how gmtime and locatime allocate the
857 structures they return pointers to). */
861 if (! (tm = localtime (&nowtime)))
865 tzoff = difftm (&gmt, tm) / 60;
867 /* We are on a system like VMS, where the system clock is
868 in local time and the system has no concept of timezones.
869 Hopefully we can fake this out (for the case in which the
870 user specifies no timezone) by just saying the timezone
877 tm = localtime(&nowtime);
878 yyYear = tm->tm_year + 1900;
879 yyMonth = tm->tm_mon + 1;
882 yyDSTmode = DSTmaybe;
896 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
899 if (yyHaveDate || yyHaveTime || yyHaveDay) {
900 Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
901 yyMeridian, yyDSTmode);
908 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
911 Start += yyRelSeconds;
912 Start += RelativeMonth(Start, yyRelMonth);
914 if (yyHaveDay && !yyHaveDate) {
915 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
919 /* Have to do *something* with a legitimate -1 so it's distinguishable
920 * from the error return value. (Alternately could set errno on error.) */
921 return Start == -1 ? 0 : Start;
929 main(int ac, char *av[])
934 (void)printf("Enter date, or blank line to exit.\n\t> ");
935 (void)fflush(stdout);
936 while (gets(buff) && buff[0]) {
939 (void)printf("Bad format - couldn't convert.\n");
941 (void)printf("%s", ctime(&d));
942 (void)printf("\t> ");
943 (void)fflush(stdout);
948 #endif /* defined(TEST) */