4 * Contains yacc grammar for parsing date and time strings.
5 * The output of this file should be the file tclDate.c which
6 * is used directly in the Tcl sources.
8 * Copyright (c) 1992-1995 Karl Lehenbauer and Mark Diekhans.
9 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
21 * This file is generated from a yacc grammar defined in
22 * the file tclGetDate.y. It should not be edited directly.
24 * Copyright (c) 1992-1995 Karl Lehenbauer and Mark Diekhans.
25 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
27 * See the file "license.terms" for information on usage and redistribution
28 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
38 # define START_OF_TIME 1904
39 # define END_OF_TIME 2039
42 # define START_OF_TIME 1902
43 # define END_OF_TIME 2037
47 * The offset of tm_year of struct tm returned by localtime, gmtime, etc.
48 * I don't know how universal this is; K&R II, the NetBSD manpages, and
49 * ../compat/strftime.c all agree that tm_year is the year-1900. However,
50 * some systems may have a different value. This #define should be the
51 * same as in ../compat/strftime.c.
53 #define TM_YEAR_BASE 1900
55 #define HOUR(x) ((int) (60 * x))
56 #define SECSPERDAY (24L * 60L * 60L)
60 * An entry in the lexical lookup table.
62 typedef struct _TABLE {
70 * Daylight-savings mode: on, off, or not yet known.
72 typedef enum _DSTMODE {
73 DSTon, DSToff, DSTmaybe
77 * Meridian: am, pm, or 24-hour style.
79 typedef enum _MERIDIAN {
85 * Global variables. We could get rid of most of these by using a good
86 * union as the yacc stack. (This routine was originally written before
87 * yacc had the %union construct.) Maybe someday; right now we only use
88 * the %union very rarely.
91 static DSTMODE yyDSTmode;
92 static time_t yyDayOrdinal;
93 static time_t yyDayNumber;
94 static int yyHaveDate;
97 static int yyHaveTime;
98 static int yyHaveZone;
99 static time_t yyTimezone;
101 static time_t yyHour;
102 static time_t yyMinutes;
103 static time_t yyMonth;
104 static time_t yySeconds;
105 static time_t yyYear;
106 static MERIDIAN yyMeridian;
107 static time_t yyRelMonth;
108 static time_t yyRelSeconds;
112 * Prototypes of internal functions.
114 static void yyerror _ANSI_ARGS_((char *s));
115 static time_t ToSeconds _ANSI_ARGS_((time_t Hours, time_t Minutes,
116 time_t Seconds, MERIDIAN Meridian));
117 static int Convert _ANSI_ARGS_((time_t Month, time_t Day, time_t Year,
118 time_t Hours, time_t Minutes, time_t Seconds,
119 MERIDIAN Meridia, DSTMODE DSTmode, time_t *TimePtr));
120 static time_t DSTcorrect _ANSI_ARGS_((time_t Start, time_t Future));
121 static time_t RelativeDate _ANSI_ARGS_((time_t Start, time_t DayOrdinal,
123 static int RelativeMonth _ANSI_ARGS_((time_t Start, time_t RelMonth,
125 static int LookupWord _ANSI_ARGS_((char *buff));
126 static int yylex _ANSI_ARGS_((void));
129 yyparse _ANSI_ARGS_((void));
134 enum _MERIDIAN Meridian;
137 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
138 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tEPOCH tDST
140 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT tDST
141 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
142 %type <Meridian> tMERIDIAN o_merid
168 time : tUNUMBER tMERIDIAN {
174 | tUNUMBER ':' tUNUMBER o_merid {
180 | tUNUMBER ':' tUNUMBER tSNUMBER {
185 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
187 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
193 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
199 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
231 date : tUNUMBER '/' tUNUMBER {
235 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
244 | 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;
303 if (yyHaveTime && yyHaveDate && !yyHaveRel) {
312 yyMinutes = $1 % 100;
320 o_merid : /* NULL */ {
331 * Month and day table.
333 static TABLE MonthDayTable[] = {
334 { "january", tMONTH, 1 },
335 { "february", tMONTH, 2 },
336 { "march", tMONTH, 3 },
337 { "april", tMONTH, 4 },
338 { "may", tMONTH, 5 },
339 { "june", tMONTH, 6 },
340 { "july", tMONTH, 7 },
341 { "august", tMONTH, 8 },
342 { "september", tMONTH, 9 },
343 { "sept", tMONTH, 9 },
344 { "october", tMONTH, 10 },
345 { "november", tMONTH, 11 },
346 { "december", tMONTH, 12 },
347 { "sunday", tDAY, 0 },
348 { "monday", tDAY, 1 },
349 { "tuesday", tDAY, 2 },
351 { "wednesday", tDAY, 3 },
352 { "wednes", tDAY, 3 },
353 { "thursday", tDAY, 4 },
355 { "thurs", tDAY, 4 },
356 { "friday", tDAY, 5 },
357 { "saturday", tDAY, 6 },
364 static TABLE UnitsTable[] = {
365 { "year", tMONTH_UNIT, 12 },
366 { "month", tMONTH_UNIT, 1 },
367 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
368 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
369 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
370 { "hour", tMINUTE_UNIT, 60 },
371 { "minute", tMINUTE_UNIT, 1 },
372 { "min", tMINUTE_UNIT, 1 },
373 { "second", tSEC_UNIT, 1 },
374 { "sec", tSEC_UNIT, 1 },
379 * Assorted relative-time words.
381 static TABLE 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 },
390 { "first", tUNUMBER, 1 },
391 /* { "second", tUNUMBER, 2 }, */
392 { "third", tUNUMBER, 3 },
393 { "fourth", tUNUMBER, 4 },
394 { "fifth", tUNUMBER, 5 },
395 { "sixth", tUNUMBER, 6 },
396 { "seventh", tUNUMBER, 7 },
397 { "eighth", tUNUMBER, 8 },
398 { "ninth", tUNUMBER, 9 },
399 { "tenth", tUNUMBER, 10 },
400 { "eleventh", tUNUMBER, 11 },
401 { "twelfth", tUNUMBER, 12 },
404 { "epoch", tEPOCH, 0 },
409 * The timezone table. (Note: This table was modified to not use any floating
410 * point constants to work around an SGI compiler bug).
412 static TABLE TimezoneTable[] = {
413 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
414 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
415 { "utc", tZONE, HOUR( 0) },
416 { "wet", tZONE, HOUR( 0) } , /* Western European */
417 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
418 { "wat", tZONE, HOUR( 1) }, /* West Africa */
419 { "at", tZONE, HOUR( 2) }, /* Azores */
421 /* For completeness. BST is also British Summer, and GST is
422 * also Guam Standard. */
423 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
424 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
426 { "nft", tZONE, HOUR( 7/2) }, /* Newfoundland */
427 { "nst", tZONE, HOUR( 7/2) }, /* Newfoundland Standard */
428 { "ndt", tDAYZONE, HOUR( 7/2) }, /* Newfoundland Daylight */
429 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
430 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
431 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
432 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
433 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
434 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
435 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
436 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
437 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
438 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
439 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
440 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
441 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
442 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
443 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
444 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
445 { "nt", tZONE, HOUR(11) }, /* Nome */
446 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
447 { "cet", tZONE, -HOUR( 1) }, /* Central European */
448 { "met", tZONE, -HOUR( 1) }, /* Middle European */
449 { "mewt", tZONE, -HOUR( 1) }, /* Middle European Winter */
450 { "mest", tDAYZONE, -HOUR( 1) }, /* Middle European Summer */
451 { "swt", tZONE, -HOUR( 1) }, /* Swedish Winter */
452 { "sst", tDAYZONE, -HOUR( 1) }, /* Swedish Summer */
453 { "fwt", tZONE, -HOUR( 1) }, /* French Winter */
454 { "fst", tDAYZONE, -HOUR( 1) }, /* French Summer */
455 { "eet", tZONE, -HOUR( 2) }, /* Eastern Europe, USSR Zone 1 */
456 { "bt", tZONE, -HOUR( 3) }, /* Baghdad, USSR Zone 2 */
457 { "it", tZONE, -HOUR( 7/2) }, /* Iran */
458 { "zp4", tZONE, -HOUR( 4) }, /* USSR Zone 3 */
459 { "zp5", tZONE, -HOUR( 5) }, /* USSR Zone 4 */
460 { "ist", tZONE, -HOUR(11/2) }, /* Indian Standard */
461 { "zp6", tZONE, -HOUR( 6) }, /* USSR Zone 5 */
463 /* For completeness. NST is also Newfoundland Stanard, nad SST is
464 * also Swedish Summer. */
465 { "nst", tZONE, -HOUR(13/2) }, /* North Sumatra */
466 { "sst", tZONE, -HOUR( 7) }, /* South Sumatra, USSR Zone 6 */
468 { "wast", tZONE, -HOUR( 7) }, /* West Australian Standard */
469 { "wadt", tDAYZONE, -HOUR( 7) }, /* West Australian Daylight */
470 { "jt", tZONE, -HOUR(15/2) }, /* Java (3pm in Cronusland!) */
471 { "cct", tZONE, -HOUR( 8) }, /* China Coast, USSR Zone 7 */
472 { "jst", tZONE, -HOUR( 9) }, /* Japan Standard, USSR Zone 8 */
473 { "cast", tZONE, -HOUR(19/2) }, /* Central Australian Standard */
474 { "cadt", tDAYZONE, -HOUR(19/2) }, /* Central Australian Daylight */
475 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
476 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
477 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
478 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
479 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
480 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
481 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
482 /* ADDED BY Marco Nijdam */
483 { "dst", tDST, HOUR( 0) }, /* DST on (hour is ignored) */
489 * Military timezone table.
491 static TABLE 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) },
522 * Dump error messages in the bit bucket.
532 ToSeconds(Hours, Minutes, Seconds, Meridian)
538 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
542 if (Hours < 0 || Hours > 23)
544 return (Hours * 60L + Minutes) * 60L + Seconds;
546 if (Hours < 1 || Hours > 12)
548 return ((Hours % 12) * 60L + Minutes) * 60L + Seconds;
550 if (Hours < 1 || Hours > 12)
552 return (((Hours % 12) + 12) * 60L + Minutes) * 60L + Seconds;
554 return -1; /* Should never be reached */
559 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode, TimePtr)
570 static int DaysInMonth[12] = {
571 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
577 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
579 if (Month < 1 || Month > 12
580 || Year < START_OF_TIME || Year > END_OF_TIME
581 || Day < 1 || Day > DaysInMonth[(int)--Month])
584 for (Julian = Day - 1, i = 0; i < Month; i++)
585 Julian += DaysInMonth[i];
587 for (i = EPOCH; i < Year; i++)
588 Julian += 365 + (i % 4 == 0);
590 for (i = Year; i < EPOCH; i++)
591 Julian -= 365 + (i % 4 == 0);
593 Julian *= SECSPERDAY;
594 Julian += yyTimezone * 60L;
595 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
599 || (DSTmode == DSTmaybe && TclpGetDate(&Julian, 0)->tm_isdst))
607 DSTcorrect(Start, Future)
614 StartDay = (TclpGetDate(&Start, 0)->tm_hour + 1) % 24;
615 FutureDay = (TclpGetDate(&Future, 0)->tm_hour + 1) % 24;
616 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
621 RelativeDate(Start, DayOrdinal, DayNumber)
630 tm = TclpGetDate(&now, 0);
631 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
632 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
633 return DSTcorrect(Start, now);
638 RelativeMonth(Start, RelMonth, TimePtr)
653 tm = TclpGetDate(&Start, 0);
654 Month = 12 * (tm->tm_year + TM_YEAR_BASE) + tm->tm_mon + RelMonth;
656 Month = Month % 12 + 1;
657 result = Convert(Month, (time_t) tm->tm_mday, Year,
658 (time_t) tm->tm_hour, (time_t) tm->tm_min, (time_t) tm->tm_sec,
659 MER24, DSTmaybe, &Julian);
661 * The following iteration takes into account the case were we jump
662 * into a "short month". Far example, "one month from Jan 31" will
663 * fail because there is no Feb 31. The code below will reduce the
664 * day and try converting the date until we succed or the date equals
665 * 28 (which always works unless the date is bad in another way).
668 while ((result != 0) && (tm->tm_mday > 28)) {
670 result = Convert(Month, (time_t) tm->tm_mday, Year,
671 (time_t) tm->tm_hour, (time_t) tm->tm_min, (time_t) tm->tm_sec,
672 MER24, DSTmaybe, &Julian);
677 *TimePtr = DSTcorrect(Start, Julian);
695 for (p = buff; *p; p++) {
696 if (isupper(UCHAR(*p))) {
697 *p = (char) tolower(UCHAR(*p));
701 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
702 yylval.Meridian = MERam;
705 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
706 yylval.Meridian = MERpm;
711 * See if we have an abbreviation for a month.
713 if (strlen(buff) == 3) {
715 } else if (strlen(buff) == 4 && buff[3] == '.') {
722 for (tp = MonthDayTable; tp->name; tp++) {
724 if (strncmp(buff, tp->name, 3) == 0) {
725 yylval.Number = tp->value;
728 } else if (strcmp(buff, tp->name) == 0) {
729 yylval.Number = tp->value;
734 for (tp = TimezoneTable; tp->name; tp++) {
735 if (strcmp(buff, tp->name) == 0) {
736 yylval.Number = tp->value;
741 for (tp = UnitsTable; tp->name; tp++) {
742 if (strcmp(buff, tp->name) == 0) {
743 yylval.Number = tp->value;
749 * Strip off any plural and try the units table again.
751 i = strlen(buff) - 1;
752 if (buff[i] == 's') {
754 for (tp = UnitsTable; tp->name; tp++) {
755 if (strcmp(buff, tp->name) == 0) {
756 yylval.Number = tp->value;
762 for (tp = OtherTable; tp->name; tp++) {
763 if (strcmp(buff, tp->name) == 0) {
764 yylval.Number = tp->value;
770 * Military timezones.
772 if (buff[1] == '\0' && isalpha(UCHAR(*buff))) {
773 for (tp = MilitaryTable; tp->name; tp++) {
774 if (strcmp(buff, tp->name) == 0) {
775 yylval.Number = tp->value;
782 * Drop out any periods and try the timezone table again.
784 for (i = 0, p = q = buff; *q; q++)
792 for (tp = TimezoneTable; tp->name; tp++) {
793 if (strcmp(buff, tp->name) == 0) {
794 yylval.Number = tp->value;
814 while (isspace((unsigned char) (*yyInput))) {
818 if (isdigit(c = *yyInput) || c == '-' || c == '+') {
819 if (c == '-' || c == '+') {
820 sign = c == '-' ? -1 : 1;
821 if (!isdigit(*++yyInput)) {
830 for (yylval.Number = 0; isdigit(c = *yyInput++); ) {
831 yylval.Number = 10 * yylval.Number + c - '0';
835 yylval.Number = -yylval.Number;
837 return sign ? tSNUMBER : tUNUMBER;
839 if (isalpha(UCHAR(c))) {
840 for (p = buff; isalpha(c = *yyInput++) || c == '.'; ) {
841 if (p < &buff[sizeof buff - 1]) {
847 return LookupWord(buff);
857 } else if (c == '(') {
859 } else if (c == ')') {
867 * Specify zone is of -50000 to force GMT. (This allows BST to work).
871 TclGetDate(p, now, zone, timePtr)
875 unsigned long *timePtr;
884 tm = TclpGetDate((time_t *) &now, 0);
885 thisyear = tm->tm_year + TM_YEAR_BASE;
887 yyMonth = tm->tm_mon + 1;
890 if (zone == -50000) {
891 yyDSTmode = DSToff; /* assume GMT */
894 yyDSTmode = DSTmaybe;
908 if (yyparse() || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 ||
913 if (yyHaveDate || yyHaveTime || yyHaveDay) {
914 if (TclDateYear < 0) {
915 TclDateYear = -TclDateYear;
918 * The following line handles years that are specified using
919 * only two digits. The line of code below implements a policy
920 * defined by the X/Open workgroup on the millinium rollover.
921 * Note: some of those dates may not actually be valid on some
922 * platforms. The POSIX standard startes that the dates 70-99
923 * shall refer to 1970-1999 and 00-38 shall refer to 2000-2038.
924 * This later definition should work on all platforms.
927 if (TclDateYear < 100) {
928 if (TclDateYear >= 69) {
934 if (Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
935 yyMeridian, yyDSTmode, &Start) < 0) {
941 Start -= ((tm->tm_hour * 60L) + tm->tm_min * 60L) + tm->tm_sec;
945 Start += yyRelSeconds;
946 if (RelativeMonth(Start, yyRelMonth, &Time) < 0) {
951 if (yyHaveDay && !yyHaveDate) {
952 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);