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 __P((char *s));
102 static int yylex __P((void));
103 static int yyparse __P((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 /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
590 I'm too lazy to try to check for time_t overflow in another way. */
591 if (Year < EPOCH || Year > 2038
592 || Month < 1 || Month > 12
593 /* Lint fluff: "conversion from long may lose accuracy" */
594 || Day < 1 || Day > DaysInMonth[(int)--Month])
597 for (Julian = Day - 1, i = 0; i < Month; i++)
598 Julian += DaysInMonth[i];
599 for (i = EPOCH; i < Year; i++)
600 Julian += 365 + (i % 4 == 0);
601 Julian *= SECSPERDAY;
602 Julian += yyTimezone * 60L;
603 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
607 || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
614 DSTcorrect(Start, Future)
621 StartDay = (localtime(&Start)->tm_hour + 1) % 24;
622 FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
623 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
628 RelativeDate(Start, DayOrdinal, DayNumber)
637 tm = localtime(&now);
638 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
639 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
640 return DSTcorrect(Start, now);
645 RelativeMonth(Start, RelMonth)
655 tm = localtime(&Start);
656 Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
658 Month = Month % 12 + 1;
659 return DSTcorrect(Start,
660 Convert(Month, (time_t)tm->tm_mday, Year,
661 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
676 /* Make it lowercase. */
677 for (p = buff; *p; p++)
678 if (isupper((unsigned char)*p))
679 *p = tolower((unsigned char)*p);
681 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
682 yylval.Meridian = MERam;
685 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
686 yylval.Meridian = MERpm;
690 /* See if we have an abbreviation for a month. */
691 if (strlen(buff) == 3)
693 else if (strlen(buff) == 4 && buff[3] == '.') {
700 for (tp = MonthDayTable; tp->name; tp++) {
702 if (strncmp(buff, tp->name, 3) == 0) {
703 yylval.Number = tp->value;
707 else if (strcmp(buff, tp->name) == 0) {
708 yylval.Number = tp->value;
713 for (tp = TimezoneTable; tp->name; tp++)
714 if (strcmp(buff, tp->name) == 0) {
715 yylval.Number = tp->value;
719 if (strcmp(buff, "dst") == 0)
722 for (tp = UnitsTable; tp->name; tp++)
723 if (strcmp(buff, tp->name) == 0) {
724 yylval.Number = tp->value;
728 /* Strip off any plural and try the units table again. */
729 i = strlen(buff) - 1;
730 if (buff[i] == 's') {
732 for (tp = UnitsTable; tp->name; tp++)
733 if (strcmp(buff, tp->name) == 0) {
734 yylval.Number = tp->value;
737 buff[i] = 's'; /* Put back for "this" in OtherTable. */
740 for (tp = OtherTable; tp->name; tp++)
741 if (strcmp(buff, tp->name) == 0) {
742 yylval.Number = tp->value;
746 /* Military timezones. */
747 if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
748 for (tp = MilitaryTable; tp->name; tp++)
749 if (strcmp(buff, tp->name) == 0) {
750 yylval.Number = tp->value;
755 /* Drop out any periods and try the timezone table again. */
756 for (i = 0, p = q = buff; *q; q++)
763 for (tp = TimezoneTable; tp->name; tp++)
764 if (strcmp(buff, tp->name) == 0) {
765 yylval.Number = tp->value;
783 while (isspace((unsigned char)*yyInput))
786 if (isdigit((unsigned char)(c = *yyInput)) || c == '-' || c == '+') {
787 if (c == '-' || c == '+') {
788 sign = c == '-' ? -1 : 1;
789 if (!isdigit((unsigned char)*++yyInput))
790 /* skip the '-' sign */
795 for (yylval.Number = 0; isdigit((unsigned char)(c = *yyInput++)); )
796 yylval.Number = 10 * yylval.Number + c - '0';
799 yylval.Number = -yylval.Number;
800 return sign ? tSNUMBER : tUNUMBER;
802 if (isalpha((unsigned char)c)) {
803 for (p = buff; isalpha((unsigned char)(c = *yyInput++)) || c == '.'; )
804 if (p < &buff[sizeof buff - 1])
808 return LookupWord(buff);
825 #define TM_YEAR_ORIGIN 1900
827 /* Yield A - B, measured in seconds. */
832 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
833 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
835 /* difference in day of year */
836 a->tm_yday - b->tm_yday
837 /* + intervening leap days */
838 + ((ay >> 2) - (by >> 2))
840 + ((ay/100 >> 2) - (by/100 >> 2))
841 /* + difference in years * 365 */
842 + (long)(ay-by) * 365
844 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
845 + (a->tm_min - b->tm_min))
846 + (a->tm_sec - b->tm_sec));
853 struct tm *tm, *gmt, gmtbuf;
865 /* Make a copy, in case localtime modifies *tm (I think
866 that comment now applies to *gmt, but I am too
867 lazy to dig into how gmtime and locatime allocate the
868 structures they return pointers to). */
873 if (! (tm = localtime (&now)))
877 timezone = difftm (gmt, tm) / 60;
879 /* We are on a system like VMS, where the system clock is
880 in local time and the system has no concept of timezones.
881 Hopefully we can fake this out (for the case in which the
882 user specifies no timezone) by just saying the timezone
889 tm = localtime(&now);
890 yyYear = tm->tm_year + 1900;
891 yyMonth = tm->tm_mon + 1;
893 yyTimezone = timezone;
894 yyDSTmode = DSTmaybe;
908 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
911 if (yyHaveDate || yyHaveTime || yyHaveDay) {
912 Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
913 yyMeridian, yyDSTmode);
920 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
923 Start += yyRelSeconds;
924 Start += RelativeMonth(Start, yyRelMonth);
926 if (yyHaveDay && !yyHaveDate) {
927 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
931 /* Have to do *something* with a legitimate -1 so it's distinguishable
932 * from the error return value. (Alternately could set errno on error.) */
933 return Start == -1 ? 0 : Start;
948 (void)printf("Enter date, or blank line to exit.\n\t> ");
949 (void)fflush(stdout);
950 while (gets(buff) && buff[0]) {
953 (void)printf("Bad format - couldn't convert.\n");
955 (void)printf("%s", ctime(&d));
956 (void)printf("\t> ");
957 (void)fflush(stdout);
962 #endif /* defined(TEST) */