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 */
17 #include <sys/types.h>
27 #endif /* STDC_HEADERS */
29 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
33 #endif /* HAVE_STRING_H */
36 #endif /* HAVE_STRINGS_H */
37 #if TIME_WITH_SYS_TIME
46 #define HOUR(x) ((time_t)(x) * 60)
47 #define SECSPERDAY (24L * 60L * 60L)
51 ** An entry in the lexical lookup table.
53 typedef struct _TABLE {
61 ** Daylight-savings mode: on, off, or not yet known.
63 typedef enum _DSTMODE {
64 DSTon, DSToff, DSTmaybe
68 ** Meridian: am, pm, or 24-hour style.
70 typedef enum _MERIDIAN {
76 ** Global variables. We could get rid of most of these by using a good
77 ** union as the yacc stack. (This routine was originally written before
78 ** yacc had the %union construct.) Maybe someday; right now we only use
79 ** the %union very rarely.
82 static DSTMODE yyDSTmode;
83 static time_t yyDayOrdinal;
84 static time_t yyDayNumber;
85 static int yyHaveDate;
88 static int yyHaveTime;
89 static int yyHaveZone;
90 static time_t yyTimezone;
93 static time_t yyMinutes;
94 static time_t yyMonth;
95 static time_t yySeconds;
97 static MERIDIAN yyMeridian;
98 static time_t yyRelMonth;
99 static time_t yyRelSeconds;
101 static int yyerror(char *s);
102 static int yylex(void);
109 enum _MERIDIAN Meridian;
112 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
113 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
115 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
116 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
117 %type <Meridian> tMERIDIAN o_merid
143 time : tUNUMBER tMERIDIAN {
149 | tUNUMBER ':' tUNUMBER o_merid {
155 | tUNUMBER ':' tUNUMBER tSNUMBER {
160 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
162 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
168 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
174 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
207 date : tUNUMBER '/' tUNUMBER {
211 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
222 | tUNUMBER tSNUMBER tSNUMBER {
223 /* ISO 8601 format. yyyy-mm-dd. */
228 | tUNUMBER tMONTH tSNUMBER {
229 /* e.g. 17-JUN-1992. */
238 | tMONTH tUNUMBER ',' tUNUMBER {
247 | tUNUMBER tMONTH tUNUMBER {
255 yyRelSeconds = -yyRelSeconds;
256 yyRelMonth = -yyRelMonth;
261 relunit : tUNUMBER tMINUTE_UNIT {
262 yyRelSeconds += $1 * $2 * 60L;
264 | tSNUMBER tMINUTE_UNIT {
265 yyRelSeconds += $1 * $2 * 60L;
268 yyRelSeconds += $1 * 60L;
270 | tSNUMBER tSEC_UNIT {
273 | tUNUMBER tSEC_UNIT {
279 | tSNUMBER tMONTH_UNIT {
280 yyRelMonth += $1 * $2;
282 | tUNUMBER tMONTH_UNIT {
283 yyRelMonth += $1 * $2;
291 if (yyHaveTime && yyHaveDate && !yyHaveRel)
297 yyMonth= ($1/100)%100;
308 yyMinutes = $1 % 100;
317 o_merid : /* NULL */ {
327 /* Month and day table. */
328 static TABLE const MonthDayTable[] = {
329 { "january", tMONTH, 1 },
330 { "february", tMONTH, 2 },
331 { "march", tMONTH, 3 },
332 { "april", tMONTH, 4 },
333 { "may", tMONTH, 5 },
334 { "june", tMONTH, 6 },
335 { "july", tMONTH, 7 },
336 { "august", tMONTH, 8 },
337 { "september", tMONTH, 9 },
338 { "sept", tMONTH, 9 },
339 { "october", tMONTH, 10 },
340 { "november", tMONTH, 11 },
341 { "december", tMONTH, 12 },
342 { "sunday", tDAY, 0 },
343 { "monday", tDAY, 1 },
344 { "tuesday", tDAY, 2 },
346 { "wednesday", tDAY, 3 },
347 { "wednes", tDAY, 3 },
348 { "thursday", tDAY, 4 },
350 { "thurs", tDAY, 4 },
351 { "friday", tDAY, 5 },
352 { "saturday", tDAY, 6 },
356 /* Time units table. */
357 static TABLE const UnitsTable[] = {
358 { "year", tMONTH_UNIT, 12 },
359 { "month", tMONTH_UNIT, 1 },
360 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
361 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
362 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
363 { "hour", tMINUTE_UNIT, 60 },
364 { "minute", tMINUTE_UNIT, 1 },
365 { "min", tMINUTE_UNIT, 1 },
366 { "second", tSEC_UNIT, 1 },
367 { "sec", tSEC_UNIT, 1 },
371 /* Assorted relative-time words. */
372 static TABLE const OtherTable[] = {
373 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
374 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
375 { "today", tMINUTE_UNIT, 0 },
376 { "now", tMINUTE_UNIT, 0 },
377 { "last", tUNUMBER, -1 },
378 { "this", tMINUTE_UNIT, 0 },
379 { "next", tUNUMBER, 2 },
380 { "first", tUNUMBER, 1 },
381 /* { "second", tUNUMBER, 2 }, */
382 { "third", tUNUMBER, 3 },
383 { "fourth", tUNUMBER, 4 },
384 { "fifth", tUNUMBER, 5 },
385 { "sixth", tUNUMBER, 6 },
386 { "seventh", tUNUMBER, 7 },
387 { "eighth", tUNUMBER, 8 },
388 { "ninth", tUNUMBER, 9 },
389 { "tenth", tUNUMBER, 10 },
390 { "eleventh", tUNUMBER, 11 },
391 { "twelfth", tUNUMBER, 12 },
396 /* The timezone table. */
397 /* Some of these are commented out because a time_t can't store a float. */
398 static TABLE const TimezoneTable[] = {
399 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
400 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
401 { "utc", tZONE, HOUR( 0) },
402 { "wet", tZONE, HOUR( 0) }, /* Western European */
403 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
404 { "wat", tZONE, HOUR( 1) }, /* West Africa */
405 { "at", tZONE, HOUR( 2) }, /* Azores */
407 /* For completeness. BST is also British Summer, and GST is
408 * also Guam Standard. */
409 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
410 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
413 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
414 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
415 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
417 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
418 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
419 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
420 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
421 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
422 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
423 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
424 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
425 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
426 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
427 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
428 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
429 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
430 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
431 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
432 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
433 { "nt", tZONE, HOUR(11) }, /* Nome */
434 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
435 { "cet", tZONE, -HOUR(1) }, /* Central European */
436 { "met", tZONE, -HOUR(1) }, /* Middle European */
437 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
438 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
439 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
440 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
441 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
442 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
443 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
444 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
446 { "it", tZONE, -HOUR(3.5) },/* Iran */
448 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
449 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
451 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
453 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
455 /* For completeness. NST is also Newfoundland Stanard, and SST is
456 * also Swedish Summer. */
457 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
458 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
460 { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
461 { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
463 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
465 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
466 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
468 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
469 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
471 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
472 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
473 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
474 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
475 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
476 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
477 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
481 /* Military timezone table. */
482 static TABLE const MilitaryTable[] = {
483 { "a", tZONE, HOUR( 1) },
484 { "b", tZONE, HOUR( 2) },
485 { "c", tZONE, HOUR( 3) },
486 { "d", tZONE, HOUR( 4) },
487 { "e", tZONE, HOUR( 5) },
488 { "f", tZONE, HOUR( 6) },
489 { "g", tZONE, HOUR( 7) },
490 { "h", tZONE, HOUR( 8) },
491 { "i", tZONE, HOUR( 9) },
492 { "k", tZONE, HOUR( 10) },
493 { "l", tZONE, HOUR( 11) },
494 { "m", tZONE, HOUR( 12) },
495 { "n", tZONE, HOUR(- 1) },
496 { "o", tZONE, HOUR(- 2) },
497 { "p", tZONE, HOUR(- 3) },
498 { "q", tZONE, HOUR(- 4) },
499 { "r", tZONE, HOUR(- 5) },
500 { "s", tZONE, HOUR(- 6) },
501 { "t", tZONE, HOUR(- 7) },
502 { "u", tZONE, HOUR(- 8) },
503 { "v", tZONE, HOUR(- 9) },
504 { "w", tZONE, HOUR(-10) },
505 { "x", tZONE, HOUR(-11) },
506 { "y", tZONE, HOUR(-12) },
507 { "z", tZONE, HOUR( 0) },
524 ToSeconds(Hours, Minutes, Seconds, Meridian)
530 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
534 if (Hours < 0 || Hours > 23)
536 return (Hours * 60L + Minutes) * 60L + Seconds;
538 if (Hours < 1 || Hours > 12)
542 return (Hours * 60L + Minutes) * 60L + Seconds;
544 if (Hours < 1 || Hours > 12)
548 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
557 * A negative number, which means to use its absolute value (why?)
558 * A number from 0 to 99, which means a year from 1900 to 1999, or
559 * The actual year (>=100). */
561 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
571 static int DaysInMonth[12] = {
572 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
582 else if (Year < 100) {
587 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
589 /* 32-bit time_t cannot represent years past 2038 */
590 if (Year < EPOCH || (sizeof(time_t) == sizeof(int) && Year > 2038)
591 || Month < 1 || Month > 12
592 /* Lint fluff: "conversion from long may lose accuracy" */
593 || Day < 1 || Day > DaysInMonth[(int)--Month])
596 for (Julian = Day - 1, i = 0; i < Month; i++)
597 Julian += DaysInMonth[i];
598 for (i = EPOCH; i < Year; i++)
599 Julian += 365 + (i % 4 == 0);
600 Julian *= SECSPERDAY;
601 Julian += yyTimezone * 60L;
602 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
606 || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
613 DSTcorrect(Start, Future)
620 StartDay = (localtime(&Start)->tm_hour + 1) % 24;
621 FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
622 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
627 RelativeDate(Start, DayOrdinal, DayNumber)
636 tm = localtime(&now);
637 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
638 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
639 return DSTcorrect(Start, now);
644 RelativeMonth(Start, RelMonth)
654 tm = localtime(&Start);
655 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
657 Month = Month % 12 + 1;
658 return DSTcorrect(Start,
659 Convert(Month, (time_t)tm->tm_mday, Year,
660 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
675 /* Make it lowercase. */
676 for (p = buff; *p; p++)
677 if (isupper((unsigned char)*p))
678 *p = tolower((unsigned char)*p);
680 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
681 yylval.Meridian = MERam;
684 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
685 yylval.Meridian = MERpm;
689 /* See if we have an abbreviation for a month. */
690 if (strlen(buff) == 3)
692 else if (strlen(buff) == 4 && buff[3] == '.') {
699 for (tp = MonthDayTable; tp->name; tp++) {
701 if (strncmp(buff, tp->name, 3) == 0) {
702 yylval.Number = tp->value;
706 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) {
714 yylval.Number = tp->value;
718 if (strcmp(buff, "dst") == 0)
721 for (tp = UnitsTable; tp->name; tp++)
722 if (strcmp(buff, tp->name) == 0) {
723 yylval.Number = tp->value;
727 /* Strip off any plural and try the units table again. */
728 i = strlen(buff) - 1;
729 if (buff[i] == 's') {
731 for (tp = UnitsTable; tp->name; tp++)
732 if (strcmp(buff, tp->name) == 0) {
733 yylval.Number = tp->value;
736 buff[i] = 's'; /* Put back for "this" in OtherTable. */
739 for (tp = OtherTable; tp->name; tp++)
740 if (strcmp(buff, tp->name) == 0) {
741 yylval.Number = tp->value;
745 /* Military timezones. */
746 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
747 for (tp = MilitaryTable; tp->name; tp++)
748 if (strcmp(buff, tp->name) == 0) {
749 yylval.Number = tp->value;
754 /* Drop out any periods and try the timezone table again. */
755 for (i = 0, p = q = buff; *q; q++)
762 for (tp = TimezoneTable; tp->name; tp++)
763 if (strcmp(buff, tp->name) == 0) {
764 yylval.Number = tp->value;
782 while (isspace((unsigned char)*yyInput))
785 if (isdigit((unsigned char)(c = *yyInput)) || c == '-' || c == '+') {
786 if (c == '-' || c == '+') {
787 sign = c == '-' ? -1 : 1;
788 if (!isdigit((unsigned char)*++yyInput))
789 /* skip the '-' sign */
794 for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); )
795 yylval.Number = 10 * yylval.Number + c - '0';
798 yylval.Number = -yylval.Number;
799 return sign ? tSNUMBER : tUNUMBER;
801 if (isalpha((unsigned char)c)) {
802 for (p = buff; isalpha((unsigned char)(c = *yyInput++)) || c == '.'; )
803 if (p < &buff[sizeof buff - 1])
807 return LookupWord(buff);
824 #define TM_YEAR_ORIGIN 1900
826 /* Yield A - B, measured in seconds. */
831 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
832 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
834 /* difference in day of year */
835 a->tm_yday - b->tm_yday
836 /* + intervening leap days */
837 + ((ay >> 2) - (by >> 2))
839 + ((ay/100 >> 2) - (by/100 >> 2))
840 /* + difference in years * 365 */
841 + (long)(ay-by) * 365
843 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
844 + (a->tm_min - b->tm_min))
845 + (a->tm_sec - b->tm_sec));
852 struct tm *tm, *gmt, gmtbuf;
864 /* Make a copy, in case localtime modifies *tm (I think
865 that comment now applies to *gmt, but I am too
866 lazy to dig into how gmtime and locatime allocate the
867 structures they return pointers to). */
872 if (! (tm = localtime (&now)))
876 timezone = difftm (gmt, tm) / 60;
878 /* We are on a system like VMS, where the system clock is
879 in local time and the system has no concept of timezones.
880 Hopefully we can fake this out (for the case in which the
881 user specifies no timezone) by just saying the timezone
888 tm = localtime(&now);
889 yyYear = tm->tm_year + 1900;
890 yyMonth = tm->tm_mon + 1;
892 yyTimezone = timezone;
893 yyDSTmode = DSTmaybe;
907 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
910 if (yyHaveDate || yyHaveTime || yyHaveDay) {
911 Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
912 yyMeridian, yyDSTmode);
919 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
922 Start += yyRelSeconds;
923 Start += RelativeMonth(Start, yyRelMonth);
925 if (yyHaveDay && !yyHaveDate) {
926 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
930 /* Have to do *something* with a legitimate -1 so it's distinguishable
931 * from the error return value. (Alternately could set errno on error.) */
932 return Start == -1 ? 0 : Start;
947 (void)printf("Enter date, or blank line to exit.\n\t> ");
948 (void)fflush(stdout);
949 while (gets(buff) && buff[0]) {
952 (void)printf("Bad format - couldn't convert.\n");
954 (void)printf("%s", ctime(&d));
955 (void)printf("\t> ");
956 (void)fflush(stdout);
961 #endif /* defined(TEST) */