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 */
16 #if defined (emacs) || defined (CONFIG_BROKETS)
23 /* Since the code of getdate.y is not included in the Emacs executable
24 itself, there is no need to #define static in this file. Even if
25 the code were included in the Emacs executable, it probably
26 wouldn't do any harm to #undef it here; this will only cause
27 problems if we try to write to a static variable, which I don't
28 think this code needs to do. */
36 /* The code at the top of get_date which figures out the offset of the
37 current time zone checks various CPP symbols to see if special
38 tricks are need, but defaults to using the gettimeofday system call.
39 Include <sys/time.h> if that will be used. */
48 #include <sys/types.h>
50 #ifdef TIME_WITH_SYS_TIME
54 #ifdef HAVE_SYS_TIME_H
62 #undef timezone /* needed for sgi */
65 #if defined(HAVE_SYS_TIMEB_H)
66 #include <sys/timeb.h>
69 ** We use the obsolete `struct timeb' as part of our interface!
70 ** Since the system doesn't have it, we define it here;
71 ** our callers must do likewise.
74 time_t time; /* Seconds since the epoch */
75 unsigned short millitm; /* Field not used */
76 short timezone; /* Minutes west of GMT */
77 short dstflag; /* Field not used */
79 #endif /* defined(HAVE_SYS_TIMEB_H) */
81 #endif /* defined(vms) */
83 #if defined (STDC_HEADERS) || defined (USG)
87 /* Some old versions of bison generate parsers that use bcopy.
88 That loses on systems that don't provide the function, so we have
89 to redefine it here. */
90 #if !defined (HAVE_BCOPY) && defined (HAVE_MEMCPY) && !defined (bcopy)
91 #define bcopy(from, to, len) memcpy ((to), (from), (len))
94 #if defined (STDC_HEADERS)
98 /* NOTES on rebuilding getdate.c (particularly for inclusion in CVS
101 We don't want to mess with all the portability hassles of alloca.
102 In particular, most (all?) versions of bison will use alloca in
103 their parser. If bison works on your system (e.g. it should work
104 with gcc), then go ahead and use it, but the more general solution
105 is to use byacc instead of bison, which should generate a portable
106 parser. I played with adding "#define alloca dont_use_alloca", to
107 give an error if the parser generator uses alloca (and thus detect
108 unportable getdate.c's), but that seems to cause as many problems
111 extern struct tm *gmtime();
112 extern struct tm *localtime();
114 #define yyparse getdate_yyparse
115 #define yylex getdate_yylex
116 #define yyerror getdate_yyerror
119 static int yyerror ();
122 #define HOUR(x) ((time_t)(x) * 60)
123 #define SECSPERDAY (24L * 60L * 60L)
127 ** An entry in the lexical lookup table.
129 typedef struct _TABLE {
137 ** Daylight-savings mode: on, off, or not yet known.
139 typedef enum _DSTMODE {
140 DSTon, DSToff, DSTmaybe
144 ** Meridian: am, pm, or 24-hour style.
146 typedef enum _MERIDIAN {
152 ** Global variables. We could get rid of most of these by using a good
153 ** union as the yacc stack. (This routine was originally written before
154 ** yacc had the %union construct.) Maybe someday; right now we only use
155 ** the %union very rarely.
157 static char *yyInput;
158 static DSTMODE yyDSTmode;
159 static time_t yyDayOrdinal;
160 static time_t yyDayNumber;
161 static int yyHaveDate;
162 static int yyHaveDay;
163 static int yyHaveRel;
164 static int yyHaveTime;
165 static int yyHaveZone;
166 static time_t yyTimezone;
168 static time_t yyHour;
169 static time_t yyMinutes;
170 static time_t yyMonth;
171 static time_t yySeconds;
172 static time_t yyYear;
173 static MERIDIAN yyMeridian;
174 static time_t yyRelMonth;
175 static time_t yyRelSeconds;
181 enum _MERIDIAN Meridian;
184 %token tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
185 %token tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST
187 %type <Number> tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
188 %type <Number> tSEC_UNIT tSNUMBER tUNUMBER tZONE
189 %type <Meridian> tMERIDIAN o_merid
215 time : tUNUMBER tMERIDIAN {
221 | tUNUMBER ':' tUNUMBER o_merid {
227 | tUNUMBER ':' tUNUMBER tSNUMBER {
232 yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
234 | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
240 | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
246 yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
279 date : tUNUMBER '/' tUNUMBER {
283 | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
288 | tUNUMBER tSNUMBER tSNUMBER {
289 /* ISO 8601 format. yyyy-mm-dd. */
294 | tUNUMBER tMONTH tSNUMBER {
295 /* e.g. 17-JUN-1992. */
304 | tMONTH tUNUMBER ',' tUNUMBER {
313 | tUNUMBER tMONTH tUNUMBER {
321 yyRelSeconds = -yyRelSeconds;
322 yyRelMonth = -yyRelMonth;
327 relunit : tUNUMBER tMINUTE_UNIT {
328 yyRelSeconds += $1 * $2 * 60L;
330 | tSNUMBER tMINUTE_UNIT {
331 yyRelSeconds += $1 * $2 * 60L;
334 yyRelSeconds += $1 * 60L;
336 | tSNUMBER tSEC_UNIT {
339 | tUNUMBER tSEC_UNIT {
345 | tSNUMBER tMONTH_UNIT {
346 yyRelMonth += $1 * $2;
348 | tUNUMBER tMONTH_UNIT {
349 yyRelMonth += $1 * $2;
357 if (yyHaveTime && yyHaveDate && !yyHaveRel)
363 yyMonth= ($1/100)%100;
374 yyMinutes = $1 % 100;
383 o_merid : /* NULL */ {
393 /* Month and day table. */
394 static TABLE const MonthDayTable[] = {
395 { "january", tMONTH, 1 },
396 { "february", tMONTH, 2 },
397 { "march", tMONTH, 3 },
398 { "april", tMONTH, 4 },
399 { "may", tMONTH, 5 },
400 { "june", tMONTH, 6 },
401 { "july", tMONTH, 7 },
402 { "august", tMONTH, 8 },
403 { "september", tMONTH, 9 },
404 { "sept", tMONTH, 9 },
405 { "october", tMONTH, 10 },
406 { "november", tMONTH, 11 },
407 { "december", tMONTH, 12 },
408 { "sunday", tDAY, 0 },
409 { "monday", tDAY, 1 },
410 { "tuesday", tDAY, 2 },
412 { "wednesday", tDAY, 3 },
413 { "wednes", tDAY, 3 },
414 { "thursday", tDAY, 4 },
416 { "thurs", tDAY, 4 },
417 { "friday", tDAY, 5 },
418 { "saturday", tDAY, 6 },
422 /* Time units table. */
423 static TABLE const UnitsTable[] = {
424 { "year", tMONTH_UNIT, 12 },
425 { "month", tMONTH_UNIT, 1 },
426 { "fortnight", tMINUTE_UNIT, 14 * 24 * 60 },
427 { "week", tMINUTE_UNIT, 7 * 24 * 60 },
428 { "day", tMINUTE_UNIT, 1 * 24 * 60 },
429 { "hour", tMINUTE_UNIT, 60 },
430 { "minute", tMINUTE_UNIT, 1 },
431 { "min", tMINUTE_UNIT, 1 },
432 { "second", tSEC_UNIT, 1 },
433 { "sec", tSEC_UNIT, 1 },
437 /* Assorted relative-time words. */
438 static TABLE const OtherTable[] = {
439 { "tomorrow", tMINUTE_UNIT, 1 * 24 * 60 },
440 { "yesterday", tMINUTE_UNIT, -1 * 24 * 60 },
441 { "today", tMINUTE_UNIT, 0 },
442 { "now", tMINUTE_UNIT, 0 },
443 { "last", tUNUMBER, -1 },
444 { "this", tMINUTE_UNIT, 0 },
445 { "next", tUNUMBER, 2 },
446 { "first", tUNUMBER, 1 },
447 /* { "second", tUNUMBER, 2 }, */
448 { "third", tUNUMBER, 3 },
449 { "fourth", tUNUMBER, 4 },
450 { "fifth", tUNUMBER, 5 },
451 { "sixth", tUNUMBER, 6 },
452 { "seventh", tUNUMBER, 7 },
453 { "eighth", tUNUMBER, 8 },
454 { "ninth", tUNUMBER, 9 },
455 { "tenth", tUNUMBER, 10 },
456 { "eleventh", tUNUMBER, 11 },
457 { "twelfth", tUNUMBER, 12 },
462 /* The timezone table. */
463 /* Some of these are commented out because a time_t can't store a float. */
464 static TABLE const TimezoneTable[] = {
465 { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
466 { "ut", tZONE, HOUR( 0) }, /* Universal (Coordinated) */
467 { "utc", tZONE, HOUR( 0) },
468 { "wet", tZONE, HOUR( 0) }, /* Western European */
469 { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
470 { "wat", tZONE, HOUR( 1) }, /* West Africa */
471 { "at", tZONE, HOUR( 2) }, /* Azores */
473 /* For completeness. BST is also British Summer, and GST is
474 * also Guam Standard. */
475 { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
476 { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
479 { "nft", tZONE, HOUR(3.5) }, /* Newfoundland */
480 { "nst", tZONE, HOUR(3.5) }, /* Newfoundland Standard */
481 { "ndt", tDAYZONE, HOUR(3.5) }, /* Newfoundland Daylight */
483 { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
484 { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
485 { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
486 { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
487 { "cst", tZONE, HOUR( 6) }, /* Central Standard */
488 { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
489 { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
490 { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
491 { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
492 { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
493 { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
494 { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
495 { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
496 { "hdt", tDAYZONE, HOUR(10) }, /* Hawaii Daylight */
497 { "cat", tZONE, HOUR(10) }, /* Central Alaska */
498 { "ahst", tZONE, HOUR(10) }, /* Alaska-Hawaii Standard */
499 { "nt", tZONE, HOUR(11) }, /* Nome */
500 { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
501 { "cet", tZONE, -HOUR(1) }, /* Central European */
502 { "met", tZONE, -HOUR(1) }, /* Middle European */
503 { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
504 { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
505 { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
506 { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
507 { "fwt", tZONE, -HOUR(1) }, /* French Winter */
508 { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
509 { "eet", tZONE, -HOUR(2) }, /* Eastern Europe, USSR Zone 1 */
510 { "bt", tZONE, -HOUR(3) }, /* Baghdad, USSR Zone 2 */
512 { "it", tZONE, -HOUR(3.5) },/* Iran */
514 { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
515 { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
517 { "ist", tZONE, -HOUR(5.5) },/* Indian Standard */
519 { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
521 /* For completeness. NST is also Newfoundland Stanard, and SST is
522 * also Swedish Summer. */
523 { "nst", tZONE, -HOUR(6.5) },/* North Sumatra */
524 { "sst", tZONE, -HOUR(7) }, /* South Sumatra, USSR Zone 6 */
526 { "wast", tZONE, -HOUR(7) }, /* West Australian Standard */
527 { "wadt", tDAYZONE, -HOUR(7) }, /* West Australian Daylight */
529 { "jt", tZONE, -HOUR(7.5) },/* Java (3pm in Cronusland!) */
531 { "cct", tZONE, -HOUR(8) }, /* China Coast, USSR Zone 7 */
532 { "jst", tZONE, -HOUR(9) }, /* Japan Standard, USSR Zone 8 */
534 { "cast", tZONE, -HOUR(9.5) },/* Central Australian Standard */
535 { "cadt", tDAYZONE, -HOUR(9.5) },/* Central Australian Daylight */
537 { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
538 { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
539 { "gst", tZONE, -HOUR(10) }, /* Guam Standard, USSR Zone 9 */
540 { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
541 { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
542 { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
543 { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
547 /* Military timezone table. */
548 static TABLE const MilitaryTable[] = {
549 { "a", tZONE, HOUR( 1) },
550 { "b", tZONE, HOUR( 2) },
551 { "c", tZONE, HOUR( 3) },
552 { "d", tZONE, HOUR( 4) },
553 { "e", tZONE, HOUR( 5) },
554 { "f", tZONE, HOUR( 6) },
555 { "g", tZONE, HOUR( 7) },
556 { "h", tZONE, HOUR( 8) },
557 { "i", tZONE, HOUR( 9) },
558 { "k", tZONE, HOUR( 10) },
559 { "l", tZONE, HOUR( 11) },
560 { "m", tZONE, HOUR( 12) },
561 { "n", tZONE, HOUR(- 1) },
562 { "o", tZONE, HOUR(- 2) },
563 { "p", tZONE, HOUR(- 3) },
564 { "q", tZONE, HOUR(- 4) },
565 { "r", tZONE, HOUR(- 5) },
566 { "s", tZONE, HOUR(- 6) },
567 { "t", tZONE, HOUR(- 7) },
568 { "u", tZONE, HOUR(- 8) },
569 { "v", tZONE, HOUR(- 9) },
570 { "w", tZONE, HOUR(-10) },
571 { "x", tZONE, HOUR(-11) },
572 { "y", tZONE, HOUR(-12) },
573 { "z", tZONE, HOUR( 0) },
590 ToSeconds(Hours, Minutes, Seconds, Meridian)
596 if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
600 if (Hours < 0 || Hours > 23)
602 return (Hours * 60L + Minutes) * 60L + Seconds;
604 if (Hours < 1 || Hours > 12)
608 return (Hours * 60L + Minutes) * 60L + Seconds;
610 if (Hours < 1 || Hours > 12)
614 return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
623 Convert(Month, Day, Year, Hours, Minutes, Seconds, Meridian, DSTmode)
633 static int DaysInMonth[12] = {
634 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
644 DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
646 /* Checking for 2038 bogusly assumes that time_t is 32 bits. But
647 I'm too lazy to try to check for time_t overflow in another way. */
648 if (Year < EPOCH || Year > 2038
649 || Month < 1 || Month > 12
650 /* Lint fluff: "conversion from long may lose accuracy" */
651 || Day < 1 || Day > DaysInMonth[(int)--Month])
654 for (Julian = Day - 1, i = 0; i < Month; i++)
655 Julian += DaysInMonth[i];
656 for (i = EPOCH; i < Year; i++)
657 Julian += 365 + (i % 4 == 0);
658 Julian *= SECSPERDAY;
659 Julian += yyTimezone * 60L;
660 if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
664 || (DSTmode == DSTmaybe && localtime(&Julian)->tm_isdst))
671 DSTcorrect(Start, Future)
678 StartDay = (localtime(&Start)->tm_hour + 1) % 24;
679 FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
680 return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
685 RelativeDate(Start, DayOrdinal, DayNumber)
694 tm = localtime(&now);
695 now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
696 now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
697 return DSTcorrect(Start, now);
702 RelativeMonth(Start, RelMonth)
712 tm = localtime(&Start);
713 Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
715 Month = Month % 12 + 1;
716 return DSTcorrect(Start,
717 Convert(Month, (time_t)tm->tm_mday, Year,
718 (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
729 register const TABLE *tp;
733 /* Make it lowercase. */
734 for (p = buff; *p; p++)
738 if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
739 yylval.Meridian = MERam;
742 if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
743 yylval.Meridian = MERpm;
747 /* See if we have an abbreviation for a month. */
748 if (strlen(buff) == 3)
750 else if (strlen(buff) == 4 && buff[3] == '.') {
757 for (tp = MonthDayTable; tp->name; tp++) {
759 if (strncmp(buff, tp->name, 3) == 0) {
760 yylval.Number = tp->value;
764 else if (strcmp(buff, tp->name) == 0) {
765 yylval.Number = tp->value;
770 for (tp = TimezoneTable; tp->name; tp++)
771 if (strcmp(buff, tp->name) == 0) {
772 yylval.Number = tp->value;
776 if (strcmp(buff, "dst") == 0)
779 for (tp = UnitsTable; tp->name; tp++)
780 if (strcmp(buff, tp->name) == 0) {
781 yylval.Number = tp->value;
785 /* Strip off any plural and try the units table again. */
786 i = strlen(buff) - 1;
787 if (buff[i] == 's') {
789 for (tp = UnitsTable; tp->name; tp++)
790 if (strcmp(buff, tp->name) == 0) {
791 yylval.Number = tp->value;
794 buff[i] = 's'; /* Put back for "this" in OtherTable. */
797 for (tp = OtherTable; tp->name; tp++)
798 if (strcmp(buff, tp->name) == 0) {
799 yylval.Number = tp->value;
803 /* Military timezones. */
804 if (buff[1] == '\0' && isalpha(*buff)) {
805 for (tp = MilitaryTable; tp->name; tp++)
806 if (strcmp(buff, tp->name) == 0) {
807 yylval.Number = tp->value;
812 /* Drop out any periods and try the timezone table again. */
813 for (i = 0, p = q = buff; *q; q++)
820 for (tp = TimezoneTable; tp->name; tp++)
821 if (strcmp(buff, tp->name) == 0) {
822 yylval.Number = tp->value;
840 while (isspace(*yyInput))
843 if (isdigit(c = *yyInput) || c == '-' || c == '+') {
844 if (c == '-' || c == '+') {
845 sign = c == '-' ? -1 : 1;
846 if (!isdigit(*++yyInput))
847 /* skip the '-' sign */
852 for (yylval.Number = 0; isdigit(c = *yyInput++); )
853 yylval.Number = 10 * yylval.Number + c - '0';
856 yylval.Number = -yylval.Number;
857 return sign ? tSNUMBER : tUNUMBER;
860 for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
861 if (p < &buff[sizeof buff - 1])
865 return LookupWord(buff);
882 #define TM_YEAR_ORIGIN 1900
884 /* Yield A - B, measured in seconds. */
889 int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
890 int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
892 /* difference in day of year */
893 a->tm_yday - b->tm_yday
894 /* + intervening leap days */
895 + ((ay >> 2) - (by >> 2))
897 + ((ay/100 >> 2) - (by/100 >> 2))
898 /* + difference in years * 365 */
899 + (long)(ay-by) * 365
901 return (60*(60*(24*days + (a->tm_hour - b->tm_hour))
902 + (a->tm_min - b->tm_min))
903 + (a->tm_sec - b->tm_sec));
922 (void)time (&nowtime);
924 gmt_ptr = gmtime (&nowtime);
927 /* Make a copy, in case localtime modifies *tm (I think
928 that comment now applies to *gmt_ptr, but I am too
929 lazy to dig into how gmtime and locatime allocate the
930 structures they return pointers to). */
934 if (! (tm = localtime (&nowtime)))
938 ftz.timezone = difftm (&gmt, tm) / 60;
940 /* We are on a system like VMS, where the system clock is
941 in local time and the system has no concept of timezones.
942 Hopefully we can fake this out (for the case in which the
943 user specifies no timezone) by just saying the timezone
955 tm = localtime(&nowtime);
956 yyYear = tm->tm_year;
957 yyMonth = tm->tm_mon + 1;
959 yyTimezone = now->timezone;
960 yyDSTmode = DSTmaybe;
974 || yyHaveTime > 1 || yyHaveZone > 1 || yyHaveDate > 1 || yyHaveDay > 1)
977 if (yyHaveDate || yyHaveTime || yyHaveDay) {
978 Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
979 yyMeridian, yyDSTmode);
986 Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
989 Start += yyRelSeconds;
990 Start += RelativeMonth(Start, yyRelMonth);
992 if (yyHaveDay && !yyHaveDate) {
993 tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
997 /* Have to do *something* with a legitimate -1 so it's distinguishable
998 * from the error return value. (Alternately could set errno on error.) */
999 return Start == -1 ? 0 : Start;
1014 (void)printf("Enter date, or blank line to exit.\n\t> ");
1015 (void)fflush(stdout);
1016 while (gets(buff) && buff[0]) {
1017 d = get_date(buff, (struct timeb *)NULL);
1019 (void)printf("Bad format - couldn't convert.\n");
1021 (void)printf("%s", ctime(&d));
1022 (void)printf("\t> ");
1023 (void)fflush(stdout);
1028 #endif /* defined(TEST) */