Home | History | Annotate | Line # | Download | only in libutil
parsedate.y revision 1.33
      1 %{
      2 /*
      3 **  Originally written by Steven M. Bellovin <smb (at) 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 (at) bbn.com> and Jim Berets <jberets (at) bbn.com> in August, 1990;
      7 **
      8 **  This grammar has 10 shift/reduce conflicts.
      9 **
     10 **  This code is in the public domain and has no copyright.
     11 */
     12 /* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
     13 /* SUPPRESS 288 on yyerrlab *//* Label unused */
     14 
     15 #include <sys/cdefs.h>
     16 #ifdef __RCSID
     17 __RCSID("$NetBSD: parsedate.y,v 1.33 2020/10/19 15:05:53 kre Exp $");
     18 #endif
     19 
     20 #include <stdio.h>
     21 #include <ctype.h>
     22 #include <errno.h>
     23 #include <string.h>
     24 #include <time.h>
     25 #include <util.h>
     26 #include <stdlib.h>
     27 
     28 /* NOTES on rebuilding parsedate.c (particularly for inclusion in CVS
     29    releases):
     30 
     31    We don't want to mess with all the portability hassles of alloca.
     32    In particular, most (all?) versions of bison will use alloca in
     33    their parser.  If bison works on your system (e.g. it should work
     34    with gcc), then go ahead and use it, but the more general solution
     35    is to use byacc instead of bison, which should generate a portable
     36    parser.  I played with adding "#define alloca dont_use_alloca", to
     37    give an error if the parser generator uses alloca (and thus detect
     38    unportable parsedate.c's), but that seems to cause as many problems
     39    as it solves.  */
     40 
     41 #define EPOCH		1970
     42 #define HOUR(x)		((time_t)((x) * 60))
     43 #define SECSPERDAY	(24L * 60L * 60L)
     44 
     45 #define	MAXREL	16	/* hours mins secs days weeks months years - maybe twice each ...*/
     46 
     47 #define USE_LOCAL_TIME	99999 /* special case for Convert() and yyTimezone */
     48 
     49 /*
     50 **  An entry in the lexical lookup table.
     51 */
     52 typedef struct _TABLE {
     53     const char	*name;
     54     int		type;
     55     time_t	value;
     56 } TABLE;
     57 
     58 
     59 /*
     60 **  Daylight-savings mode:  on, off, or not yet known.
     61 */
     62 typedef enum _DSTMODE {
     63     DSTon, DSToff, DSTmaybe
     64 } DSTMODE;
     65 
     66 /*
     67 **  Meridian:  am, pm, or 24-hour style (plus "noon" and "midnight").
     68 */
     69 typedef enum _MERIDIAN {
     70     MERam, MERpm, MER24, MER_NOON, MER_MN
     71 } MERIDIAN;
     72 
     73 
     74 struct dateinfo {
     75 	DSTMODE	yyDSTmode;	/* DST on/off/maybe */
     76 	time_t	yyDayOrdinal;
     77 	time_t	yyDayNumber;
     78 	int	yyHaveDate;
     79 	int	yyHaveFullYear;	/* if true, year is not abbreviated. */
     80 				/* if false, need to call AdjustYear(). */
     81 	int	yyHaveDay;
     82 	int	yyHaveRel;
     83 	int	yyHaveTime;
     84 	int	yyHaveZone;
     85 	time_t	yyTimezone;	/* Timezone as minutes ahead/east of UTC */
     86 	time_t	yyDay;		/* Day of month [1-31] */
     87 	time_t	yyHour;		/* Hour of day [0-24] or [1-12] */
     88 	time_t	yyMinutes;	/* Minute of hour [0-59] */
     89 	time_t	yyMonth;	/* Month of year [1-12] */
     90 	time_t	yySeconds;	/* Second of minute [0-60] */
     91 	time_t	yyYear;		/* Year, see also yyHaveFullYear */
     92 	MERIDIAN yyMeridian;	/* Interpret yyHour as AM/PM/24 hour clock */
     93 	struct {
     94 		time_t	yyRelVal;
     95 		int	yyRelMonth;
     96 	} yyRel[MAXREL];
     97 };
     98 %}
     99 
    100 %union {
    101     time_t		Number;
    102     enum _MERIDIAN	Meridian;
    103 }
    104 
    105 %token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
    106 %token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST AT_SIGN tTIME
    107 
    108 %type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
    109 %type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE tTIME
    110 %type	<Meridian>	tMERIDIAN
    111 
    112 %type	<Number>	at_number
    113 %type	<Meridian>	o_merid
    114 
    115 %parse-param	{ struct dateinfo *param }
    116 %parse-param 	{ const char **yyInput }
    117 %lex-param	{ const char **yyInput }
    118 %pure-parser
    119 
    120 %%
    121 
    122 spec:
    123 	  /* empty */
    124 	| spec item
    125 ;
    126 
    127 item:
    128 	  time			{ param->yyHaveTime++; }
    129 	| time_numericzone	{ param->yyHaveTime++; param->yyHaveZone++; }
    130 	| zone			{ param->yyHaveZone++; }
    131 	| date			{ param->yyHaveDate++; }
    132 	| day			{ param->yyHaveDay++; }
    133 	| rel			{ param->yyHaveRel++; }
    134 	| cvsstamp		{ param->yyHaveTime++; param->yyHaveDate++;
    135 				  param->yyHaveZone++; }
    136 	| epochdate		{ param->yyHaveTime++; param->yyHaveDate++;
    137 				  param->yyHaveZone++; }
    138 	| number
    139 ;
    140 
    141 cvsstamp:
    142 	tUNUMBER '.' tUNUMBER '.' tUNUMBER '.'
    143 				tUNUMBER '.' tUNUMBER '.' tUNUMBER {
    144 		param->yyYear = $1;
    145 		if (param->yyYear < 100) {
    146 			param->yyYear += 1900;
    147 		}
    148 		param->yyHaveFullYear = 1;
    149 		param->yyMonth = $3;
    150 		param->yyDay = $5;
    151 		param->yyHour = $7;
    152 		param->yyMinutes = $9;
    153 		param->yySeconds = $11;
    154 		param->yyDSTmode = DSToff;
    155 		param->yyTimezone = 0;
    156 	}
    157 ;
    158 
    159 epochdate:
    160 	AT_SIGN at_number {
    161 		time_t	when = $2;
    162 		struct tm tmbuf;
    163 
    164 		if (gmtime_r(&when, &tmbuf) != NULL) {
    165 			param->yyYear = tmbuf.tm_year + 1900;
    166 			param->yyMonth = tmbuf.tm_mon + 1;
    167 			param->yyDay = tmbuf.tm_mday;
    168 
    169 			param->yyHour = tmbuf.tm_hour;
    170 			param->yyMinutes = tmbuf.tm_min;
    171 			param->yySeconds = tmbuf.tm_sec;
    172 		} else {
    173 			param->yyYear = EPOCH;
    174 			param->yyMonth = 1;
    175 			param->yyDay = 1;
    176 
    177 			param->yyHour = 0;
    178 			param->yyMinutes = 0;
    179 			param->yySeconds = 0;
    180 		}
    181 		param->yyHaveFullYear = 1;
    182 		param->yyDSTmode = DSToff;
    183 		param->yyTimezone = 0;
    184 	}
    185 ;
    186 
    187 at_number:
    188 	  tUNUMBER
    189 	| tSNUMBER
    190 ;
    191 
    192 time:
    193 	  tUNUMBER tMERIDIAN {
    194 		param->yyMinutes = 0;
    195 		param->yySeconds = 0;
    196 		if ($2 == MER_NOON || $2 == MER_MN) {
    197 			if ($1 == 12) {
    198 				switch ($2) {
    199 				case MER_NOON: param->yyHour = 12; break;
    200 				case MER_MN  : param->yyHour = 0;  break;
    201 				default:	/* impossible */;  break;
    202 				}
    203 				param->yyMeridian = MER24;
    204 			} else
    205 				YYREJECT;
    206 		} else {
    207 			param->yyHour = $1;
    208 			param->yyMeridian = $2;
    209 		}
    210 	  }
    211 	| tUNUMBER ':' tUNUMBER o_merid {
    212 		param->yyMinutes = $3;
    213 		param->yySeconds = 0;
    214 		if ($4 == MER_NOON || $4 == MER_MN) {
    215 			if ($1 == 12 && $3 == 0) {
    216 				switch ($4) {
    217 				case MER_NOON: param->yyHour = 12; break;
    218 				case MER_MN  : param->yyHour = 0;  break;
    219 				default:	/* impossible */;  break;
    220 				}
    221 				param->yyMeridian = MER24;
    222 			} else
    223 				YYREJECT;
    224 		} else {
    225 			param->yyHour = $1;
    226 			param->yyMeridian = $4;
    227 		}
    228 	  }
    229 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
    230 		param->yyMinutes = $3;
    231 		param->yySeconds = $5;
    232 		if ($6 == MER_NOON || $6 == MER_MN) {
    233 			if ($1 == 12 && $3 == 0 && $5 == 0) {
    234 				switch ($6) {
    235 				case MER_NOON: param->yyHour = 12; break;
    236 				case MER_MN  : param->yyHour = 0;  break;
    237 				default:	/* impossible */;  break;
    238 				}
    239 				param->yyMeridian = MER24;
    240 			} else
    241 				YYREJECT;
    242 		} else {
    243 			param->yyHour = $1;
    244 			param->yyMeridian = $6;
    245 		}
    246 	  }
    247 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER '.' tUNUMBER {
    248 		param->yyHour = $1;
    249 		param->yyMinutes = $3;
    250 		param->yySeconds = $5;
    251 		param->yyMeridian = MER24;
    252 		/* XXX: Do nothing with fractional secs ($7) */
    253 	  }
    254 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER ',' tUNUMBER {
    255 		param->yyHour = $1;
    256 		param->yyMinutes = $3;
    257 		param->yySeconds = $5;
    258 		param->yyMeridian = MER24;
    259 		/* XXX: Do nothing with fractional seconds ($7) */
    260 	  }
    261 	| tTIME {
    262 		param->yyHour = $1;
    263 		param->yyMinutes = 0;
    264 		param->yySeconds = 0;
    265 		param->yyMeridian = MER24;
    266 		/* Tues midnight --> Weds 00:00, midnight Tues -> Tues 00:00 */
    267 		if ($1 == 0 && param->yyHaveDay)
    268 			param->yyDayNumber++;
    269 	  }
    270 	| tUNUMBER tTIME {
    271 		if ($1 == 12 && ($2 == 0 || $2 == 12)) {
    272 			param->yyHour = $2;
    273 			param->yyMinutes = 0;
    274 			param->yySeconds = 0;
    275 			param->yyMeridian = MER24;
    276 		} else
    277 			YYREJECT;
    278 	  }
    279 ;
    280 
    281 time_numericzone:
    282 	  tUNUMBER ':' tUNUMBER tSNUMBER {
    283 		param->yyHour = $1;
    284 		param->yyMinutes = $3;
    285 		param->yyMeridian = MER24;
    286 		param->yyDSTmode = DSToff;
    287 		param->yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
    288 	  }
    289 	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
    290 		param->yyHour = $1;
    291 		param->yyMinutes = $3;
    292 		param->yySeconds = $5;
    293 		param->yyMeridian = MER24;
    294 		param->yyDSTmode = DSToff;
    295 		param->yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
    296 	}
    297 ;
    298 
    299 zone:
    300 	  tZONE		{ param->yyTimezone = $1; param->yyDSTmode = DSToff; }
    301 	| tDAYZONE	{ param->yyTimezone = $1; param->yyDSTmode = DSTon; }
    302 	| tZONE tDST	{ param->yyTimezone = $1; param->yyDSTmode = DSTon; }
    303 ;
    304 
    305 day:
    306 	  tDAY		{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
    307 	| tDAY ','	{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
    308 	| tUNUMBER tDAY	{ param->yyDayOrdinal = $1; param->yyDayNumber = $2; }
    309 ;
    310 
    311 date:
    312 	  tUNUMBER '/' tUNUMBER {
    313 		param->yyMonth = $1;
    314 		param->yyDay = $3;
    315 	  }
    316 	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
    317 		if ($1 >= 100) {
    318 			param->yyYear = $1;
    319 			param->yyMonth = $3;
    320 			param->yyDay = $5;
    321 		} else {
    322 			param->yyMonth = $1;
    323 			param->yyDay = $3;
    324 			param->yyYear = $5;
    325 		}
    326 	  }
    327 	| tUNUMBER tSNUMBER tSNUMBER {
    328 		/* ISO 8601 format.  yyyy-mm-dd.  */
    329 		param->yyYear = $1;
    330 		param->yyHaveFullYear = 1;
    331 		param->yyMonth = -$2;
    332 		param->yyDay = -$3;
    333 	  }
    334 	| tUNUMBER tMONTH tSNUMBER {
    335 		/* e.g. 17-JUN-1992.  */
    336 		param->yyDay = $1;
    337 		param->yyMonth = $2;
    338 		param->yyYear = -$3;
    339 	  }
    340 	| tMONTH tUNUMBER {
    341 		param->yyMonth = $1;
    342 		param->yyDay = $2;
    343 	  }
    344 	| tMONTH tUNUMBER ',' tUNUMBER {
    345 		param->yyMonth = $1;
    346 		param->yyDay = $2;
    347 		param->yyYear = $4;
    348 	  }
    349 	| tUNUMBER tMONTH {
    350 		param->yyMonth = $2;
    351 		param->yyDay = $1;
    352 	  }
    353 	| tUNUMBER tMONTH tUNUMBER {
    354 		param->yyMonth = $2;
    355 		if ($1 < 35) {
    356 			param->yyDay = $1;
    357 			param->yyYear = $3;
    358 		} else {
    359 			param->yyDay = $3;
    360 			param->yyYear = $1;
    361 		}
    362 	  }
    363 ;
    364 
    365 rel:
    366 	  relunit
    367 	| relunit tAGO {
    368 		param->yyRel[param->yyHaveRel].yyRelVal =
    369 		    -param->yyRel[param->yyHaveRel].yyRelVal;
    370 	  }
    371 ;
    372 
    373 relunit:
    374 	  tUNUMBER tMINUTE_UNIT	{ RelVal(param, $1 * $2 * 60L, 0); }
    375 	| tSNUMBER tMINUTE_UNIT	{ RelVal(param, $1 * $2 * 60L, 0); }
    376 	| tMINUTE_UNIT		{ RelVal(param, $1 * 60L, 0); }
    377 	| tSNUMBER tSEC_UNIT	{ RelVal(param, $1, 0); }
    378 	| tUNUMBER tSEC_UNIT	{ RelVal(param, $1, 0); }
    379 	| tSEC_UNIT		{ RelVal(param, 1L, 0);  }
    380 	| tSNUMBER tMONTH_UNIT	{ RelVal(param, $1 * $2, 1); }
    381 	| tUNUMBER tMONTH_UNIT	{ RelVal(param, $1 * $2, 1); }
    382 	| tMONTH_UNIT		{ RelVal(param, $1, 1); }
    383 ;
    384 
    385 number:
    386 	tUNUMBER {
    387 		if (param->yyHaveTime && param->yyHaveDate &&
    388 		    !param->yyHaveRel) {
    389 			param->yyYear = $1;
    390 		} else {
    391 			if ($1 > 10000) {
    392 				param->yyHaveDate++;
    393 				param->yyDay = ($1)%100;
    394 				param->yyMonth = ($1/100)%100;
    395 				param->yyYear = $1/10000;
    396 			}
    397 			else {
    398 				param->yyHaveTime++;
    399 				if ($1 < 100) {
    400 					param->yyHour = $1;
    401 					param->yyMinutes = 0;
    402 				}
    403 				else {
    404 					param->yyHour = $1 / 100;
    405 					param->yyMinutes = $1 % 100;
    406 				}
    407 				param->yySeconds = 0;
    408 				param->yyMeridian = MER24;
    409 			}
    410 		}
    411 	}
    412 ;
    413 
    414 o_merid:
    415 	  /* empty */		{ $$ = MER24; }
    416 	| tMERIDIAN		{ $$ = $1; }
    417 	| tTIME			{ $$ = $1 == 0 ? MER_MN : MER_NOON; }
    418 ;
    419 
    420 %%
    421 
    422 static short DaysInMonth[12] = {
    423     31, 28, 31, 30, 31, 30,
    424     31, 31, 30, 31, 30, 31
    425 };
    426 
    427 /*
    428  * works with tm.tm_year (ie: rel to 1900)
    429  */
    430 #define	isleap(yr)  (((yr) & 3) == 0 && (((yr) % 100) != 0 || \
    431 			((1900+(yr)) % 400) == 0))
    432 
    433 /* Month and day table. */
    434 static const TABLE MonthDayTable[] = {
    435     { "january",	tMONTH,  1 },
    436     { "february",	tMONTH,  2 },
    437     { "march",		tMONTH,  3 },
    438     { "april",		tMONTH,  4 },
    439     { "may",		tMONTH,  5 },
    440     { "june",		tMONTH,  6 },
    441     { "july",		tMONTH,  7 },
    442     { "august",		tMONTH,  8 },
    443     { "september",	tMONTH,  9 },
    444     { "sept",		tMONTH,  9 },
    445     { "october",	tMONTH, 10 },
    446     { "november",	tMONTH, 11 },
    447     { "december",	tMONTH, 12 },
    448     { "sunday",		tDAY, 0 },
    449     { "su",		tDAY, 0 },
    450     { "monday",		tDAY, 1 },
    451     { "mo",		tDAY, 1 },
    452     { "tuesday",	tDAY, 2 },
    453     { "tues",		tDAY, 2 },
    454     { "tu",		tDAY, 2 },
    455     { "wednesday",	tDAY, 3 },
    456     { "wednes",		tDAY, 3 },
    457     { "weds",		tDAY, 3 },
    458     { "we",		tDAY, 3 },
    459     { "thursday",	tDAY, 4 },
    460     { "thurs",		tDAY, 4 },
    461     { "thur",		tDAY, 4 },
    462     { "th",		tDAY, 4 },
    463     { "friday",		tDAY, 5 },
    464     { "fr",		tDAY, 5 },
    465     { "saturday",	tDAY, 6 },
    466     { "sa",		tDAY, 6 },
    467     { NULL,		0,    0 }
    468 };
    469 
    470 /* Time units table. */
    471 static const TABLE UnitsTable[] = {
    472     { "year",		tMONTH_UNIT,	12 },
    473     { "month",		tMONTH_UNIT,	1 },
    474     { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
    475     { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
    476     { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
    477     { "hour",		tMINUTE_UNIT,	60 },
    478     { "minute",		tMINUTE_UNIT,	1 },
    479     { "min",		tMINUTE_UNIT,	1 },
    480     { "second",		tSEC_UNIT,	1 },
    481     { "sec",		tSEC_UNIT,	1 },
    482     { NULL,		0,		0 }
    483 };
    484 
    485 /* Assorted relative-time words. */
    486 static const TABLE OtherTable[] = {
    487     { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
    488     { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
    489     { "today",		tMINUTE_UNIT,	0 },
    490     { "now",		tMINUTE_UNIT,	0 },
    491     { "last",		tUNUMBER,	-1 },
    492     { "this",		tMINUTE_UNIT,	0 },
    493     { "next",		tUNUMBER,	2 },
    494     { "first",		tUNUMBER,	1 },
    495     { "one",		tUNUMBER,	1 },
    496 /*  { "second",		tUNUMBER,	2 }, */
    497     { "two",		tUNUMBER,	2 },
    498     { "third",		tUNUMBER,	3 },
    499     { "three",		tUNUMBER,	3 },
    500     { "fourth",		tUNUMBER,	4 },
    501     { "four",		tUNUMBER,	4 },
    502     { "fifth",		tUNUMBER,	5 },
    503     { "five",		tUNUMBER,	5 },
    504     { "sixth",		tUNUMBER,	6 },
    505     { "six",		tUNUMBER,	6 },
    506     { "seventh",	tUNUMBER,	7 },
    507     { "seven",		tUNUMBER,	7 },
    508     { "eighth",		tUNUMBER,	8 },
    509     { "eight",		tUNUMBER,	8 },
    510     { "ninth",		tUNUMBER,	9 },
    511     { "nine",		tUNUMBER,	9 },
    512     { "tenth",		tUNUMBER,	10 },
    513     { "ten",		tUNUMBER,	10 },
    514     { "eleventh",	tUNUMBER,	11 },
    515     { "eleven",		tUNUMBER,	11 },
    516     { "twelfth",	tUNUMBER,	12 },
    517     { "twelve",		tUNUMBER,	12 },
    518     { "ago",		tAGO,	1 },
    519     { NULL,		0,	0 }
    520 };
    521 
    522 /* The timezone table. */
    523 /* Some of these are commented out because a time_t can't store a float. */
    524 static const TABLE TimezoneTable[] = {
    525     { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
    526     { "ut",	tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
    527     { "utc",	tZONE,     HOUR( 0) },
    528     { "wet",	tZONE,     HOUR( 0) },	/* Western European */
    529     { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
    530     { "wat",	tZONE,     HOUR( 1) },	/* West Africa */
    531     { "at",	tZONE,     HOUR( 2) },	/* Azores */
    532 #if	0
    533     /* For completeness.  BST is also British Summer, and GST is
    534      * also Guam Standard. */
    535     { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
    536     { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
    537 #endif
    538     { "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
    539     { "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
    540     { "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
    541     { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
    542     { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
    543     { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
    544     { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
    545     { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
    546     { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
    547     { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
    548     { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
    549     { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
    550     { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
    551     { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
    552     { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
    553     { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
    554     { "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
    555     { "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
    556     { "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
    557     { "nt",	tZONE,     HOUR(11) },	/* Nome */
    558     { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
    559     { "cet",	tZONE,     -HOUR(1) },	/* Central European */
    560     { "met",	tZONE,     -HOUR(1) },	/* Middle European */
    561     { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
    562     { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
    563     { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
    564     { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
    565     { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
    566     { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
    567     { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
    568     { "bt",	tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
    569     { "it",	tZONE,     -HOUR(3.5) },/* Iran */
    570     { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
    571     { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
    572     { "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
    573     { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
    574 #if	0
    575     /* For completeness.  NST is also Newfoundland Stanard, and SST is
    576      * also Swedish Summer. */
    577     { "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
    578     { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
    579 #endif	/* 0 */
    580     { "ict",	tZONE,     -HOUR(7) },	/* Indo China Time (Thai) */
    581 #if 0	/* this one looks to be bogus */
    582     { "jt",	tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
    583 #endif
    584     { "wast",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    585     { "awst",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    586     { "wadt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
    587     { "awdt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
    588     { "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
    589     { "sgt",	tZONE,     -HOUR(8) },	/* Singapore */
    590     { "hkt",	tZONE,     -HOUR(8) },	/* Hong Kong */
    591     { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
    592     { "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    593     { "acst",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    594     { "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    595     { "acdt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    596     { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    597     { "aest",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    598     { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    599     { "aedt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    600     { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
    601     { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
    602     { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
    603     { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
    604     { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
    605     {  NULL,	0,	    0 }
    606 };
    607 
    608 /* Military timezone table. */
    609 static const TABLE MilitaryTable[] = {
    610     { "a",	tZONE,	HOUR(  1) },
    611     { "b",	tZONE,	HOUR(  2) },
    612     { "c",	tZONE,	HOUR(  3) },
    613     { "d",	tZONE,	HOUR(  4) },
    614     { "e",	tZONE,	HOUR(  5) },
    615     { "f",	tZONE,	HOUR(  6) },
    616     { "g",	tZONE,	HOUR(  7) },
    617     { "h",	tZONE,	HOUR(  8) },
    618     { "i",	tZONE,	HOUR(  9) },
    619     { "k",	tZONE,	HOUR( 10) },
    620     { "l",	tZONE,	HOUR( 11) },
    621     { "m",	tZONE,	HOUR( 12) },
    622     { "n",	tZONE,	HOUR(- 1) },
    623     { "o",	tZONE,	HOUR(- 2) },
    624     { "p",	tZONE,	HOUR(- 3) },
    625     { "q",	tZONE,	HOUR(- 4) },
    626     { "r",	tZONE,	HOUR(- 5) },
    627     { "s",	tZONE,	HOUR(- 6) },
    628     { "t",	tZONE,	HOUR(- 7) },
    629     { "u",	tZONE,	HOUR(- 8) },
    630     { "v",	tZONE,	HOUR(- 9) },
    631     { "w",	tZONE,	HOUR(-10) },
    632     { "x",	tZONE,	HOUR(-11) },
    633     { "y",	tZONE,	HOUR(-12) },
    634     { "z",	tZONE,	HOUR(  0) },
    635     { NULL,	0,	0 }
    636 };
    637 
    638 static const TABLE TimeNames[] = {
    639     { "midnight",	tTIME,		 0 },
    640     { "mn",		tTIME,		 0 },
    641     { "noon",		tTIME,		12 },
    642     { "midday",		tTIME,		12 },
    643     { NULL,		0,		 0 }
    644 };
    645 
    646 
    647 
    649 /* ARGSUSED */
    650 static int
    651 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
    652 {
    653   return 0;
    654 }
    655 
    656 /*
    657  * Save a relative value, if it fits
    658  */
    659 static void
    660 RelVal(struct dateinfo *param, time_t v, int type)
    661 {
    662 	int i;
    663 
    664 	if ((i = param->yyHaveRel) >= MAXREL)
    665 		return;
    666 	param->yyRel[i].yyRelMonth = type;
    667 	param->yyRel[i].yyRelVal = v;
    668 }
    669 
    670 /*
    671  * Adjust year from a value that might be abbreviated, to a full value.
    672  * e.g. convert 70 to 1970.
    673  * Input Year is either:
    674  *  - A negative number, which means to use its absolute value (why?)
    675  *  - A number from 0 to 68, which means a year from 2000 to 2068,
    676  *  - A number from 69 to 99, which means a year from 1969 to 1999, or
    677  *  - The actual year (>=100).
    678  * Returns the full year.
    679  */
    680 static time_t
    681 AdjustYear(time_t Year)
    682 {
    683     /* XXX Y2K */
    684     if (Year < 0)
    685 	Year = -Year;
    686     if (Year < 69)	/* POSIX compliant, 0..68 is 2000's, 69-99 1900's */
    687 	Year += 2000;
    688     else if (Year < 100)
    689 	Year += 1900;
    690     return Year;
    691 }
    692 
    693 static time_t
    694 Convert(
    695     time_t	Month,		/* month of year [1-12] */
    696     time_t	Day,		/* day of month [1-31] */
    697     time_t	Year,		/* year, not abbreviated in any way */
    698     time_t	Hours,		/* Hour of day [0-24] */
    699     time_t	Minutes,	/* Minute of hour [0-59] */
    700     time_t	Seconds,	/* Second of minute [0-60] */
    701     time_t	Timezone,	/* Timezone as minutes east of UTC,
    702 				 * or USE_LOCAL_TIME special case */
    703     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
    704     DSTMODE	DSTmode		/* DST on/off/maybe */
    705 )
    706 {
    707     struct tm tm = {.tm_sec = 0};
    708     struct tm otm;
    709     time_t result;
    710 
    711     tm.tm_sec = Seconds;
    712     tm.tm_min = Minutes;
    713     tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) +
    714 	(Meridian == MERpm ? 12 : 0);
    715 
    716     tm.tm_mday = Day;
    717     tm.tm_mon = Month - 1;
    718     tm.tm_year = Year - 1900;
    719     if (Timezone == USE_LOCAL_TIME) {
    720 	    switch (DSTmode) {
    721 	    case DSTon:  tm.tm_isdst = 1; break;
    722 	    case DSToff: tm.tm_isdst = 0; break;
    723 	    default:     tm.tm_isdst = -1; break;
    724 	    }
    725 	    otm = tm;
    726 	    result = mktime(&tm);
    727     } else {
    728 	    /* We rely on mktime_z(NULL, ...) working in UTC */
    729 	    tm.tm_isdst = 0;	/* hence cannot be summer time */
    730 	    otm = tm;
    731 	    errno = 0;
    732 	    result = mktime_z(NULL, &tm);
    733 	    if (result != -1 || errno == 0) {
    734 		    result += Timezone * 60;
    735 		    if (DSTmode == DSTon)	/* if specified sumer time */
    736 			result -= 3600;		/* UTC is 1 hour earlier XXX */
    737 	    }
    738     }
    739 
    740 #if PARSEDATE_DEBUG
    741     fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
    742 		    " mer=%d DST=%d)",
    743 	__func__,
    744 	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
    745 	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
    746 	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
    747     fprintf(stderr, " -> %jd", (intmax_t)result);
    748     fprintf(stderr, " %s", ctime(&result));
    749 #endif
    750 
    751 #define	TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
    752     if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
    753 	TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
    754 	    /* mktime() "corrected" our tm, so it must have been invalid */
    755 	    result = -1;
    756 	    errno = EAGAIN;
    757     }
    758 #undef	TM_NE
    759 
    760     return result;
    761 }
    762 
    763 
    764 static time_t
    765 DSTcorrect(
    766     time_t	Start,
    767     time_t	Future
    768 )
    769 {
    770     time_t	StartDay;
    771     time_t	FutureDay;
    772     struct tm	tm;
    773 
    774     if (localtime_r(&Start, &tm) == NULL)
    775 	return -1;
    776     StartDay = (tm.tm_hour + 1) % 24;
    777 
    778     if (localtime_r(&Future, &tm) == NULL)
    779 	return -1;
    780     FutureDay = (tm.tm_hour + 1) % 24;
    781 
    782     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
    783 }
    784 
    785 
    786 static time_t
    787 RelativeDate(
    788     time_t	Start,
    789     time_t	DayOrdinal,
    790     time_t	DayNumber
    791 )
    792 {
    793     struct tm	tm;
    794     time_t	now;
    795 
    796     now = Start;
    797     if (localtime_r(&now, &tm) == NULL)
    798 	return -1;
    799     now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
    800     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
    801     return DSTcorrect(Start, now);
    802 }
    803 
    804 
    805 static time_t
    806 RelativeMonth(
    807     time_t	Start,
    808     time_t	RelMonth,
    809     time_t	Timezone
    810 )
    811 {
    812     struct tm	tm;
    813     time_t	Month;
    814     time_t	Then;
    815     int		Day;
    816 
    817     if (RelMonth == 0)
    818 	return 0;
    819     /*
    820      * It doesn't matter what timezone we use to do this computation,
    821      * as long as we use the same one to reassemble the time that we
    822      * used to disassemble it. So always use localtime and mktime. In
    823      * particular, don't use Convert() to reassemble, because it will
    824      * not only reassemble with the wrong timezone but it will also
    825      * fail if we do e.g. three months from March 31 yielding July 1.
    826      */
    827     (void)Timezone;
    828 
    829     if (localtime_r(&Start, &tm) == NULL)
    830 	return -1;
    831 
    832     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
    833     tm.tm_year = (Month / 12) - 1900;
    834     tm.tm_mon = Month % 12;
    835     if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
    836 	((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
    837 	    tm.tm_mday = Day;
    838     errno = 0;
    839     Then = mktime(&tm);
    840     if (Then == -1 && errno != 0)
    841 	return -1;
    842     return DSTcorrect(Start, Then);
    843 }
    844 
    845 
    846 static int
    847 LookupWord(YYSTYPE *yylval, char *buff)
    848 {
    849     register char	*p;
    850     register char	*q;
    851     register const TABLE	*tp;
    852     int			i;
    853     int			abbrev;
    854 
    855     /* Make it lowercase. */
    856     for (p = buff; *p; p++)
    857 	if (isupper((unsigned char)*p))
    858 	    *p = tolower((unsigned char)*p);
    859 
    860     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
    861 	yylval->Meridian = MERam;
    862 	return tMERIDIAN;
    863     }
    864     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
    865 	yylval->Meridian = MERpm;
    866 	return tMERIDIAN;
    867     }
    868 
    869     /* See if we have an abbreviation for a month. */
    870     if (strlen(buff) == 3)
    871 	abbrev = 1;
    872     else if (strlen(buff) == 4 && buff[3] == '.') {
    873 	abbrev = 1;
    874 	buff[3] = '\0';
    875     }
    876     else
    877 	abbrev = 0;
    878 
    879     for (tp = MonthDayTable; tp->name; tp++) {
    880 	if (abbrev) {
    881 	    if (strncmp(buff, tp->name, 3) == 0) {
    882 		yylval->Number = tp->value;
    883 		return tp->type;
    884 	    }
    885 	}
    886 	else if (strcmp(buff, tp->name) == 0) {
    887 	    yylval->Number = tp->value;
    888 	    return tp->type;
    889 	}
    890     }
    891 
    892     for (tp = TimezoneTable; tp->name; tp++)
    893 	if (strcmp(buff, tp->name) == 0) {
    894 	    yylval->Number = tp->value;
    895 	    return tp->type;
    896 	}
    897 
    898     if (strcmp(buff, "dst") == 0)
    899 	return tDST;
    900 
    901     for (tp = TimeNames; tp->name; tp++)
    902 	if (strcmp(buff, tp->name) == 0) {
    903 	    yylval->Number = tp->value;
    904 	    return tp->type;
    905 	}
    906 
    907     for (tp = UnitsTable; tp->name; tp++)
    908 	if (strcmp(buff, tp->name) == 0) {
    909 	    yylval->Number = tp->value;
    910 	    return tp->type;
    911 	}
    912 
    913     /* Strip off any plural and try the units table again. */
    914     i = strlen(buff) - 1;
    915     if (buff[i] == 's') {
    916 	buff[i] = '\0';
    917 	for (tp = UnitsTable; tp->name; tp++)
    918 	    if (strcmp(buff, tp->name) == 0) {
    919 		yylval->Number = tp->value;
    920 		return tp->type;
    921 	    }
    922 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
    923     }
    924 
    925     for (tp = OtherTable; tp->name; tp++)
    926 	if (strcmp(buff, tp->name) == 0) {
    927 	    yylval->Number = tp->value;
    928 	    return tp->type;
    929 	}
    930 
    931     /* Military timezones. */
    932     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
    933 	for (tp = MilitaryTable; tp->name; tp++)
    934 	    if (strcmp(buff, tp->name) == 0) {
    935 		yylval->Number = tp->value;
    936 		return tp->type;
    937 	    }
    938     }
    939 
    940     /* Drop out any periods and try the timezone table again. */
    941     for (i = 0, p = q = buff; *q; q++)
    942 	if (*q != '.')
    943 	    *p++ = *q;
    944 	else
    945 	    i++;
    946     *p = '\0';
    947     if (i)
    948 	for (tp = TimezoneTable; tp->name; tp++)
    949 	    if (strcmp(buff, tp->name) == 0) {
    950 		yylval->Number = tp->value;
    951 		return tp->type;
    952 	    }
    953 
    954     return tID;
    955 }
    956 
    957 
    958 static int
    959 yylex(YYSTYPE *yylval, const char **yyInput)
    960 {
    961     register char	c;
    962     register char	*p;
    963     char		buff[20];
    964     int			Count;
    965     int			sign;
    966     const char		*inp = *yyInput;
    967 
    968     for ( ; ; ) {
    969 	while (isspace((unsigned char)*inp))
    970 	    inp++;
    971 
    972 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
    973 	    if (c == '-' || c == '+') {
    974 		sign = c == '-' ? -1 : 1;
    975 		if (!isdigit((unsigned char)*++inp))
    976 		    /* skip the '-' sign */
    977 		    continue;
    978 	    }
    979 	    else
    980 		sign = 0;
    981 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
    982 		yylval->Number = 10 * yylval->Number + c - '0';
    983 	    if (sign < 0)
    984 		yylval->Number = -yylval->Number;
    985 	    *yyInput = --inp;
    986 	    return sign ? tSNUMBER : tUNUMBER;
    987 	}
    988 	if (isalpha((unsigned char)c)) {
    989 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
    990 		if (p < &buff[sizeof buff - 1])
    991 		    *p++ = c;
    992 	    *p = '\0';
    993 	    *yyInput = --inp;
    994 	    return LookupWord(yylval, buff);
    995 	}
    996 	if (c == '@') {
    997 	    *yyInput = ++inp;
    998 	    return AT_SIGN;
    999 	}
   1000 	if (c != '(') {
   1001 	    *yyInput = ++inp;
   1002 	    return c;
   1003 	}
   1004 	Count = 0;
   1005 	do {
   1006 	    c = *inp++;
   1007 	    if (c == '\0')
   1008 		return c;
   1009 	    if (c == '(')
   1010 		Count++;
   1011 	    else if (c == ')')
   1012 		Count--;
   1013 	} while (Count > 0);
   1014     }
   1015 }
   1016 
   1017 #define TM_YEAR_ORIGIN 1900
   1018 
   1019 time_t
   1020 parsedate(const char *p, const time_t *now, const int *zone)
   1021 {
   1022     struct tm		local, *tm;
   1023     time_t		nowt;
   1024     int			zonet;
   1025     time_t		Start;
   1026     time_t		tod, rm;
   1027     struct dateinfo	param;
   1028     int			saved_errno;
   1029     int			i;
   1030 
   1031     saved_errno = errno;
   1032     errno = 0;
   1033 
   1034     if (now == NULL) {
   1035         now = &nowt;
   1036 	(void)time(&nowt);
   1037     }
   1038     if (zone == NULL) {
   1039 	zone = &zonet;
   1040 	zonet = USE_LOCAL_TIME;
   1041 	if ((tm = localtime_r(now, &local)) == NULL)
   1042 	    return -1;
   1043     } else {
   1044 	/*
   1045 	 * Should use the specified zone, not localtime.
   1046 	 * Fake it using gmtime and arithmetic.
   1047 	 * This is good enough because we use only the year/month/day,
   1048 	 * not other fields of struct tm.
   1049 	 */
   1050 	time_t fake = *now + (*zone * 60);
   1051 	if ((tm = gmtime_r(&fake, &local)) == NULL)
   1052 	    return -1;
   1053     }
   1054     param.yyYear = tm->tm_year + 1900;
   1055     param.yyMonth = tm->tm_mon + 1;
   1056     param.yyDay = tm->tm_mday;
   1057     param.yyTimezone = *zone;
   1058     param.yyDSTmode = DSTmaybe;
   1059     param.yyHour = 0;
   1060     param.yyMinutes = 0;
   1061     param.yySeconds = 0;
   1062     param.yyMeridian = MER24;
   1063     param.yyHaveDate = 0;
   1064     param.yyHaveFullYear = 0;
   1065     param.yyHaveDay = 0;
   1066     param.yyHaveRel = 0;
   1067     param.yyHaveTime = 0;
   1068     param.yyHaveZone = 0;
   1069 
   1070     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
   1071 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
   1072 	errno = EINVAL;
   1073 	return -1;
   1074     }
   1075 
   1076     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
   1077 	if (! param.yyHaveFullYear) {
   1078 		param.yyYear = AdjustYear(param.yyYear);
   1079 		param.yyHaveFullYear = 1;
   1080 	}
   1081 	errno = 0;
   1082 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
   1083 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
   1084 	    param.yyMeridian, param.yyDSTmode);
   1085 	if (Start == -1 && errno != 0)
   1086 	    return -1;
   1087     }
   1088     else {
   1089 	Start = *now;
   1090 	if (!param.yyHaveRel)
   1091 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
   1092     }
   1093 
   1094     if (param.yyHaveRel > MAXREL) {
   1095 	errno = EINVAL;
   1096 	return -1;
   1097     }
   1098     for (i = 0; i < param.yyHaveRel; i++) {
   1099 	if (param.yyRel[i].yyRelMonth) {
   1100 	    errno = 0;
   1101 	    rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
   1102 	    if (rm == -1 && errno != 0)
   1103 		return -1;
   1104 	    Start += rm;
   1105 	} else
   1106 	    Start += param.yyRel[i].yyRelVal;
   1107     }
   1108 
   1109     if (param.yyHaveDay && !param.yyHaveDate) {
   1110 	errno = 0;
   1111 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
   1112 	if (tod == -1 && errno != 0)
   1113 	    return -1;
   1114 	Start += tod;
   1115     }
   1116 
   1117     errno = saved_errno;
   1118     return Start;
   1119 }
   1120 
   1121 
   1122 #if	defined(TEST)
   1123 
   1124 /* ARGSUSED */
   1125 int
   1126 main(int ac, char *av[])
   1127 {
   1128     char	buff[128];
   1129     time_t	d;
   1130 
   1131     (void)printf("Enter date, or blank line to exit.\n\t> ");
   1132     (void)fflush(stdout);
   1133     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
   1134 	errno = 0;
   1135 	d = parsedate(buff, NULL, NULL);
   1136 	if (d == -1 && errno != 0)
   1137 	    (void)printf("Bad format - couldn't convert: %s\n",
   1138 	        strerror(errno));
   1139 	else
   1140 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
   1141 	(void)printf("\t> ");
   1142 	(void)fflush(stdout);
   1143     }
   1144     exit(0);
   1145     /* NOTREACHED */
   1146 }
   1147 #endif	/* defined(TEST) */
   1148