Home | History | Annotate | Line # | Download | only in libutil
parsedate.y revision 1.34
      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.34 2020/10/19 15:08:17 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 	| tSNUMBER	{
    304 			  if (param->yyHaveDate == 0 && param->yyHaveTime == 0)
    305 				YYREJECT;
    306 			  param->yyTimezone = - ($1 % 100 + ($1 / 100) * 60);
    307 			  param->yyDSTmode = DSTmaybe;
    308 			}
    309 ;
    310 
    311 day:
    312 	  tDAY		{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
    313 	| tDAY ','	{ param->yyDayOrdinal = 1; param->yyDayNumber = $1; }
    314 	| tUNUMBER tDAY	{ param->yyDayOrdinal = $1; param->yyDayNumber = $2; }
    315 ;
    316 
    317 date:
    318 	  tUNUMBER '/' tUNUMBER {
    319 		param->yyMonth = $1;
    320 		param->yyDay = $3;
    321 	  }
    322 	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
    323 		if ($1 >= 100) {
    324 			param->yyYear = $1;
    325 			param->yyMonth = $3;
    326 			param->yyDay = $5;
    327 		} else {
    328 			param->yyMonth = $1;
    329 			param->yyDay = $3;
    330 			param->yyYear = $5;
    331 		}
    332 	  }
    333 	| tUNUMBER tSNUMBER tSNUMBER {
    334 		/* ISO 8601 format.  yyyy-mm-dd.  */
    335 		param->yyYear = $1;
    336 		param->yyHaveFullYear = 1;
    337 		param->yyMonth = -$2;
    338 		param->yyDay = -$3;
    339 	  }
    340 	| tUNUMBER tMONTH tSNUMBER {
    341 		/* e.g. 17-JUN-1992.  */
    342 		param->yyDay = $1;
    343 		param->yyMonth = $2;
    344 		param->yyYear = -$3;
    345 	  }
    346 	| tMONTH tUNUMBER {
    347 		param->yyMonth = $1;
    348 		param->yyDay = $2;
    349 	  }
    350 	| tMONTH tUNUMBER ',' tUNUMBER {
    351 		param->yyMonth = $1;
    352 		param->yyDay = $2;
    353 		param->yyYear = $4;
    354 	  }
    355 	| tUNUMBER tMONTH {
    356 		param->yyMonth = $2;
    357 		param->yyDay = $1;
    358 	  }
    359 	| tUNUMBER tMONTH tUNUMBER {
    360 		param->yyMonth = $2;
    361 		if ($1 < 35) {
    362 			param->yyDay = $1;
    363 			param->yyYear = $3;
    364 		} else {
    365 			param->yyDay = $3;
    366 			param->yyYear = $1;
    367 		}
    368 	  }
    369 ;
    370 
    371 rel:
    372 	  relunit
    373 	| relunit tAGO {
    374 		param->yyRel[param->yyHaveRel].yyRelVal =
    375 		    -param->yyRel[param->yyHaveRel].yyRelVal;
    376 	  }
    377 ;
    378 
    379 relunit:
    380 	  tUNUMBER tMINUTE_UNIT	{ RelVal(param, $1 * $2 * 60L, 0); }
    381 	| tSNUMBER tMINUTE_UNIT	{ RelVal(param, $1 * $2 * 60L, 0); }
    382 	| tMINUTE_UNIT		{ RelVal(param, $1 * 60L, 0); }
    383 	| tSNUMBER tSEC_UNIT	{ RelVal(param, $1, 0); }
    384 	| tUNUMBER tSEC_UNIT	{ RelVal(param, $1, 0); }
    385 	| tSEC_UNIT		{ RelVal(param, 1L, 0);  }
    386 	| tSNUMBER tMONTH_UNIT	{ RelVal(param, $1 * $2, 1); }
    387 	| tUNUMBER tMONTH_UNIT	{ RelVal(param, $1 * $2, 1); }
    388 	| tMONTH_UNIT		{ RelVal(param, $1, 1); }
    389 ;
    390 
    391 number:
    392 	tUNUMBER {
    393 		if (param->yyHaveTime && param->yyHaveDate &&
    394 		    !param->yyHaveRel) {
    395 			param->yyYear = $1;
    396 		} else {
    397 			if ($1 > 10000) {
    398 				param->yyHaveDate++;
    399 				param->yyDay = ($1)%100;
    400 				param->yyMonth = ($1/100)%100;
    401 				param->yyYear = $1/10000;
    402 			}
    403 			else {
    404 				param->yyHaveTime++;
    405 				if ($1 < 100) {
    406 					param->yyHour = $1;
    407 					param->yyMinutes = 0;
    408 				}
    409 				else {
    410 					param->yyHour = $1 / 100;
    411 					param->yyMinutes = $1 % 100;
    412 				}
    413 				param->yySeconds = 0;
    414 				param->yyMeridian = MER24;
    415 			}
    416 		}
    417 	}
    418 ;
    419 
    420 o_merid:
    421 	  /* empty */		{ $$ = MER24; }
    422 	| tMERIDIAN		{ $$ = $1; }
    423 	| tTIME			{ $$ = $1 == 0 ? MER_MN : MER_NOON; }
    424 ;
    425 
    426 %%
    427 
    428 static short DaysInMonth[12] = {
    429     31, 28, 31, 30, 31, 30,
    430     31, 31, 30, 31, 30, 31
    431 };
    432 
    433 /*
    434  * works with tm.tm_year (ie: rel to 1900)
    435  */
    436 #define	isleap(yr)  (((yr) & 3) == 0 && (((yr) % 100) != 0 || \
    437 			((1900+(yr)) % 400) == 0))
    438 
    439 /* Month and day table. */
    440 static const TABLE MonthDayTable[] = {
    441     { "january",	tMONTH,  1 },
    442     { "february",	tMONTH,  2 },
    443     { "march",		tMONTH,  3 },
    444     { "april",		tMONTH,  4 },
    445     { "may",		tMONTH,  5 },
    446     { "june",		tMONTH,  6 },
    447     { "july",		tMONTH,  7 },
    448     { "august",		tMONTH,  8 },
    449     { "september",	tMONTH,  9 },
    450     { "sept",		tMONTH,  9 },
    451     { "october",	tMONTH, 10 },
    452     { "november",	tMONTH, 11 },
    453     { "december",	tMONTH, 12 },
    454     { "sunday",		tDAY, 0 },
    455     { "su",		tDAY, 0 },
    456     { "monday",		tDAY, 1 },
    457     { "mo",		tDAY, 1 },
    458     { "tuesday",	tDAY, 2 },
    459     { "tues",		tDAY, 2 },
    460     { "tu",		tDAY, 2 },
    461     { "wednesday",	tDAY, 3 },
    462     { "wednes",		tDAY, 3 },
    463     { "weds",		tDAY, 3 },
    464     { "we",		tDAY, 3 },
    465     { "thursday",	tDAY, 4 },
    466     { "thurs",		tDAY, 4 },
    467     { "thur",		tDAY, 4 },
    468     { "th",		tDAY, 4 },
    469     { "friday",		tDAY, 5 },
    470     { "fr",		tDAY, 5 },
    471     { "saturday",	tDAY, 6 },
    472     { "sa",		tDAY, 6 },
    473     { NULL,		0,    0 }
    474 };
    475 
    476 /* Time units table. */
    477 static const TABLE UnitsTable[] = {
    478     { "year",		tMONTH_UNIT,	12 },
    479     { "month",		tMONTH_UNIT,	1 },
    480     { "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
    481     { "week",		tMINUTE_UNIT,	7 * 24 * 60 },
    482     { "day",		tMINUTE_UNIT,	1 * 24 * 60 },
    483     { "hour",		tMINUTE_UNIT,	60 },
    484     { "minute",		tMINUTE_UNIT,	1 },
    485     { "min",		tMINUTE_UNIT,	1 },
    486     { "second",		tSEC_UNIT,	1 },
    487     { "sec",		tSEC_UNIT,	1 },
    488     { NULL,		0,		0 }
    489 };
    490 
    491 /* Assorted relative-time words. */
    492 static const TABLE OtherTable[] = {
    493     { "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
    494     { "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
    495     { "today",		tMINUTE_UNIT,	0 },
    496     { "now",		tMINUTE_UNIT,	0 },
    497     { "last",		tUNUMBER,	-1 },
    498     { "this",		tMINUTE_UNIT,	0 },
    499     { "next",		tUNUMBER,	2 },
    500     { "first",		tUNUMBER,	1 },
    501     { "one",		tUNUMBER,	1 },
    502 /*  { "second",		tUNUMBER,	2 }, */
    503     { "two",		tUNUMBER,	2 },
    504     { "third",		tUNUMBER,	3 },
    505     { "three",		tUNUMBER,	3 },
    506     { "fourth",		tUNUMBER,	4 },
    507     { "four",		tUNUMBER,	4 },
    508     { "fifth",		tUNUMBER,	5 },
    509     { "five",		tUNUMBER,	5 },
    510     { "sixth",		tUNUMBER,	6 },
    511     { "six",		tUNUMBER,	6 },
    512     { "seventh",	tUNUMBER,	7 },
    513     { "seven",		tUNUMBER,	7 },
    514     { "eighth",		tUNUMBER,	8 },
    515     { "eight",		tUNUMBER,	8 },
    516     { "ninth",		tUNUMBER,	9 },
    517     { "nine",		tUNUMBER,	9 },
    518     { "tenth",		tUNUMBER,	10 },
    519     { "ten",		tUNUMBER,	10 },
    520     { "eleventh",	tUNUMBER,	11 },
    521     { "eleven",		tUNUMBER,	11 },
    522     { "twelfth",	tUNUMBER,	12 },
    523     { "twelve",		tUNUMBER,	12 },
    524     { "ago",		tAGO,	1 },
    525     { NULL,		0,	0 }
    526 };
    527 
    528 /* The timezone table. */
    529 /* Some of these are commented out because a time_t can't store a float. */
    530 static const TABLE TimezoneTable[] = {
    531     { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
    532     { "ut",	tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
    533     { "utc",	tZONE,     HOUR( 0) },
    534     { "wet",	tZONE,     HOUR( 0) },	/* Western European */
    535     { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
    536     { "wat",	tZONE,     HOUR( 1) },	/* West Africa */
    537     { "at",	tZONE,     HOUR( 2) },	/* Azores */
    538 #if	0
    539     /* For completeness.  BST is also British Summer, and GST is
    540      * also Guam Standard. */
    541     { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
    542     { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
    543 #endif
    544     { "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
    545     { "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
    546     { "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
    547     { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
    548     { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
    549     { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
    550     { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
    551     { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
    552     { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
    553     { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
    554     { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
    555     { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
    556     { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
    557     { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
    558     { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
    559     { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
    560     { "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
    561     { "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
    562     { "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
    563     { "nt",	tZONE,     HOUR(11) },	/* Nome */
    564     { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
    565     { "cet",	tZONE,     -HOUR(1) },	/* Central European */
    566     { "met",	tZONE,     -HOUR(1) },	/* Middle European */
    567     { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
    568     { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
    569     { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
    570     { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
    571     { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
    572     { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
    573     { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
    574     { "bt",	tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
    575     { "it",	tZONE,     -HOUR(3.5) },/* Iran */
    576     { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
    577     { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
    578     { "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
    579     { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
    580 #if	0
    581     /* For completeness.  NST is also Newfoundland Stanard, and SST is
    582      * also Swedish Summer. */
    583     { "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
    584     { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
    585 #endif	/* 0 */
    586     { "ict",	tZONE,     -HOUR(7) },	/* Indo China Time (Thai) */
    587 #if 0	/* this one looks to be bogus */
    588     { "jt",	tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
    589 #endif
    590     { "wast",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    591     { "awst",	tZONE,     -HOUR(8) },	/* West Australian Standard */
    592     { "wadt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
    593     { "awdt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
    594     { "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
    595     { "sgt",	tZONE,     -HOUR(8) },	/* Singapore */
    596     { "hkt",	tZONE,     -HOUR(8) },	/* Hong Kong */
    597     { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
    598     { "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    599     { "acst",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
    600     { "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    601     { "acdt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
    602     { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    603     { "aest",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
    604     { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    605     { "aedt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
    606     { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
    607     { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
    608     { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
    609     { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
    610     { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
    611     {  NULL,	0,	    0 }
    612 };
    613 
    614 /* Military timezone table. */
    615 static const TABLE MilitaryTable[] = {
    616     { "a",	tZONE,	HOUR(  1) },
    617     { "b",	tZONE,	HOUR(  2) },
    618     { "c",	tZONE,	HOUR(  3) },
    619     { "d",	tZONE,	HOUR(  4) },
    620     { "e",	tZONE,	HOUR(  5) },
    621     { "f",	tZONE,	HOUR(  6) },
    622     { "g",	tZONE,	HOUR(  7) },
    623     { "h",	tZONE,	HOUR(  8) },
    624     { "i",	tZONE,	HOUR(  9) },
    625     { "k",	tZONE,	HOUR( 10) },
    626     { "l",	tZONE,	HOUR( 11) },
    627     { "m",	tZONE,	HOUR( 12) },
    628     { "n",	tZONE,	HOUR(- 1) },
    629     { "o",	tZONE,	HOUR(- 2) },
    630     { "p",	tZONE,	HOUR(- 3) },
    631     { "q",	tZONE,	HOUR(- 4) },
    632     { "r",	tZONE,	HOUR(- 5) },
    633     { "s",	tZONE,	HOUR(- 6) },
    634     { "t",	tZONE,	HOUR(- 7) },
    635     { "u",	tZONE,	HOUR(- 8) },
    636     { "v",	tZONE,	HOUR(- 9) },
    637     { "w",	tZONE,	HOUR(-10) },
    638     { "x",	tZONE,	HOUR(-11) },
    639     { "y",	tZONE,	HOUR(-12) },
    640     { "z",	tZONE,	HOUR(  0) },
    641     { NULL,	0,	0 }
    642 };
    643 
    644 static const TABLE TimeNames[] = {
    645     { "midnight",	tTIME,		 0 },
    646     { "mn",		tTIME,		 0 },
    647     { "noon",		tTIME,		12 },
    648     { "midday",		tTIME,		12 },
    649     { NULL,		0,		 0 }
    650 };
    651 
    652 
    653 
    655 /* ARGSUSED */
    656 static int
    657 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
    658 {
    659   return 0;
    660 }
    661 
    662 /*
    663  * Save a relative value, if it fits
    664  */
    665 static void
    666 RelVal(struct dateinfo *param, time_t v, int type)
    667 {
    668 	int i;
    669 
    670 	if ((i = param->yyHaveRel) >= MAXREL)
    671 		return;
    672 	param->yyRel[i].yyRelMonth = type;
    673 	param->yyRel[i].yyRelVal = v;
    674 }
    675 
    676 /*
    677  * Adjust year from a value that might be abbreviated, to a full value.
    678  * e.g. convert 70 to 1970.
    679  * Input Year is either:
    680  *  - A negative number, which means to use its absolute value (why?)
    681  *  - A number from 0 to 68, which means a year from 2000 to 2068,
    682  *  - A number from 69 to 99, which means a year from 1969 to 1999, or
    683  *  - The actual year (>=100).
    684  * Returns the full year.
    685  */
    686 static time_t
    687 AdjustYear(time_t Year)
    688 {
    689     /* XXX Y2K */
    690     if (Year < 0)
    691 	Year = -Year;
    692     if (Year < 69)	/* POSIX compliant, 0..68 is 2000's, 69-99 1900's */
    693 	Year += 2000;
    694     else if (Year < 100)
    695 	Year += 1900;
    696     return Year;
    697 }
    698 
    699 static time_t
    700 Convert(
    701     time_t	Month,		/* month of year [1-12] */
    702     time_t	Day,		/* day of month [1-31] */
    703     time_t	Year,		/* year, not abbreviated in any way */
    704     time_t	Hours,		/* Hour of day [0-24] */
    705     time_t	Minutes,	/* Minute of hour [0-59] */
    706     time_t	Seconds,	/* Second of minute [0-60] */
    707     time_t	Timezone,	/* Timezone as minutes east of UTC,
    708 				 * or USE_LOCAL_TIME special case */
    709     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
    710     DSTMODE	DSTmode		/* DST on/off/maybe */
    711 )
    712 {
    713     struct tm tm = {.tm_sec = 0};
    714     struct tm otm;
    715     time_t result;
    716 
    717     tm.tm_sec = Seconds;
    718     tm.tm_min = Minutes;
    719     tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) +
    720 	(Meridian == MERpm ? 12 : 0);
    721 
    722     tm.tm_mday = Day;
    723     tm.tm_mon = Month - 1;
    724     tm.tm_year = Year - 1900;
    725     if (Timezone == USE_LOCAL_TIME) {
    726 	    switch (DSTmode) {
    727 	    case DSTon:  tm.tm_isdst = 1; break;
    728 	    case DSToff: tm.tm_isdst = 0; break;
    729 	    default:     tm.tm_isdst = -1; break;
    730 	    }
    731 	    otm = tm;
    732 	    result = mktime(&tm);
    733     } else {
    734 	    /* We rely on mktime_z(NULL, ...) working in UTC */
    735 	    tm.tm_isdst = 0;	/* hence cannot be summer time */
    736 	    otm = tm;
    737 	    errno = 0;
    738 	    result = mktime_z(NULL, &tm);
    739 	    if (result != -1 || errno == 0) {
    740 		    result += Timezone * 60;
    741 		    if (DSTmode == DSTon)	/* if specified sumer time */
    742 			result -= 3600;		/* UTC is 1 hour earlier XXX */
    743 	    }
    744     }
    745 
    746 #if PARSEDATE_DEBUG
    747     fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
    748 		    " mer=%d DST=%d)",
    749 	__func__,
    750 	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
    751 	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
    752 	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
    753     fprintf(stderr, " -> %jd", (intmax_t)result);
    754     fprintf(stderr, " %s", ctime(&result));
    755 #endif
    756 
    757 #define	TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
    758     if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
    759 	TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
    760 	    /* mktime() "corrected" our tm, so it must have been invalid */
    761 	    result = -1;
    762 	    errno = EAGAIN;
    763     }
    764 #undef	TM_NE
    765 
    766     return result;
    767 }
    768 
    769 
    770 static time_t
    771 DSTcorrect(
    772     time_t	Start,
    773     time_t	Future
    774 )
    775 {
    776     time_t	StartDay;
    777     time_t	FutureDay;
    778     struct tm	tm;
    779 
    780     if (localtime_r(&Start, &tm) == NULL)
    781 	return -1;
    782     StartDay = (tm.tm_hour + 1) % 24;
    783 
    784     if (localtime_r(&Future, &tm) == NULL)
    785 	return -1;
    786     FutureDay = (tm.tm_hour + 1) % 24;
    787 
    788     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
    789 }
    790 
    791 
    792 static time_t
    793 RelativeDate(
    794     time_t	Start,
    795     time_t	DayOrdinal,
    796     time_t	DayNumber
    797 )
    798 {
    799     struct tm	tm;
    800     time_t	now;
    801 
    802     now = Start;
    803     if (localtime_r(&now, &tm) == NULL)
    804 	return -1;
    805     now += SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
    806     now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
    807     return DSTcorrect(Start, now);
    808 }
    809 
    810 
    811 static time_t
    812 RelativeMonth(
    813     time_t	Start,
    814     time_t	RelMonth,
    815     time_t	Timezone
    816 )
    817 {
    818     struct tm	tm;
    819     time_t	Month;
    820     time_t	Then;
    821     int		Day;
    822 
    823     if (RelMonth == 0)
    824 	return 0;
    825     /*
    826      * It doesn't matter what timezone we use to do this computation,
    827      * as long as we use the same one to reassemble the time that we
    828      * used to disassemble it. So always use localtime and mktime. In
    829      * particular, don't use Convert() to reassemble, because it will
    830      * not only reassemble with the wrong timezone but it will also
    831      * fail if we do e.g. three months from March 31 yielding July 1.
    832      */
    833     (void)Timezone;
    834 
    835     if (localtime_r(&Start, &tm) == NULL)
    836 	return -1;
    837 
    838     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
    839     tm.tm_year = (Month / 12) - 1900;
    840     tm.tm_mon = Month % 12;
    841     if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
    842 	((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
    843 	    tm.tm_mday = Day;
    844     errno = 0;
    845     Then = mktime(&tm);
    846     if (Then == -1 && errno != 0)
    847 	return -1;
    848     return DSTcorrect(Start, Then);
    849 }
    850 
    851 
    852 static int
    853 LookupWord(YYSTYPE *yylval, char *buff)
    854 {
    855     register char	*p;
    856     register char	*q;
    857     register const TABLE	*tp;
    858     int			i;
    859     int			abbrev;
    860 
    861     /* Make it lowercase. */
    862     for (p = buff; *p; p++)
    863 	if (isupper((unsigned char)*p))
    864 	    *p = tolower((unsigned char)*p);
    865 
    866     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
    867 	yylval->Meridian = MERam;
    868 	return tMERIDIAN;
    869     }
    870     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
    871 	yylval->Meridian = MERpm;
    872 	return tMERIDIAN;
    873     }
    874 
    875     /* See if we have an abbreviation for a month. */
    876     if (strlen(buff) == 3)
    877 	abbrev = 1;
    878     else if (strlen(buff) == 4 && buff[3] == '.') {
    879 	abbrev = 1;
    880 	buff[3] = '\0';
    881     }
    882     else
    883 	abbrev = 0;
    884 
    885     for (tp = MonthDayTable; tp->name; tp++) {
    886 	if (abbrev) {
    887 	    if (strncmp(buff, tp->name, 3) == 0) {
    888 		yylval->Number = tp->value;
    889 		return tp->type;
    890 	    }
    891 	}
    892 	else if (strcmp(buff, tp->name) == 0) {
    893 	    yylval->Number = tp->value;
    894 	    return tp->type;
    895 	}
    896     }
    897 
    898     for (tp = TimezoneTable; tp->name; tp++)
    899 	if (strcmp(buff, tp->name) == 0) {
    900 	    yylval->Number = tp->value;
    901 	    return tp->type;
    902 	}
    903 
    904     if (strcmp(buff, "dst") == 0)
    905 	return tDST;
    906 
    907     for (tp = TimeNames; tp->name; tp++)
    908 	if (strcmp(buff, tp->name) == 0) {
    909 	    yylval->Number = tp->value;
    910 	    return tp->type;
    911 	}
    912 
    913     for (tp = UnitsTable; tp->name; tp++)
    914 	if (strcmp(buff, tp->name) == 0) {
    915 	    yylval->Number = tp->value;
    916 	    return tp->type;
    917 	}
    918 
    919     /* Strip off any plural and try the units table again. */
    920     i = strlen(buff) - 1;
    921     if (buff[i] == 's') {
    922 	buff[i] = '\0';
    923 	for (tp = UnitsTable; tp->name; tp++)
    924 	    if (strcmp(buff, tp->name) == 0) {
    925 		yylval->Number = tp->value;
    926 		return tp->type;
    927 	    }
    928 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
    929     }
    930 
    931     for (tp = OtherTable; tp->name; tp++)
    932 	if (strcmp(buff, tp->name) == 0) {
    933 	    yylval->Number = tp->value;
    934 	    return tp->type;
    935 	}
    936 
    937     /* Military timezones. */
    938     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
    939 	for (tp = MilitaryTable; tp->name; tp++)
    940 	    if (strcmp(buff, tp->name) == 0) {
    941 		yylval->Number = tp->value;
    942 		return tp->type;
    943 	    }
    944     }
    945 
    946     /* Drop out any periods and try the timezone table again. */
    947     for (i = 0, p = q = buff; *q; q++)
    948 	if (*q != '.')
    949 	    *p++ = *q;
    950 	else
    951 	    i++;
    952     *p = '\0';
    953     if (i)
    954 	for (tp = TimezoneTable; tp->name; tp++)
    955 	    if (strcmp(buff, tp->name) == 0) {
    956 		yylval->Number = tp->value;
    957 		return tp->type;
    958 	    }
    959 
    960     return tID;
    961 }
    962 
    963 
    964 static int
    965 yylex(YYSTYPE *yylval, const char **yyInput)
    966 {
    967     register char	c;
    968     register char	*p;
    969     char		buff[20];
    970     int			Count;
    971     int			sign;
    972     const char		*inp = *yyInput;
    973 
    974     for ( ; ; ) {
    975 	while (isspace((unsigned char)*inp))
    976 	    inp++;
    977 
    978 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
    979 	    if (c == '-' || c == '+') {
    980 		sign = c == '-' ? -1 : 1;
    981 		if (!isdigit((unsigned char)*++inp))
    982 		    /* skip the '-' sign */
    983 		    continue;
    984 	    }
    985 	    else
    986 		sign = 0;
    987 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); )
    988 		yylval->Number = 10 * yylval->Number + c - '0';
    989 	    if (sign < 0)
    990 		yylval->Number = -yylval->Number;
    991 	    *yyInput = --inp;
    992 	    return sign ? tSNUMBER : tUNUMBER;
    993 	}
    994 	if (isalpha((unsigned char)c)) {
    995 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
    996 		if (p < &buff[sizeof buff - 1])
    997 		    *p++ = c;
    998 	    *p = '\0';
    999 	    *yyInput = --inp;
   1000 	    return LookupWord(yylval, buff);
   1001 	}
   1002 	if (c == '@') {
   1003 	    *yyInput = ++inp;
   1004 	    return AT_SIGN;
   1005 	}
   1006 	if (c != '(') {
   1007 	    *yyInput = ++inp;
   1008 	    return c;
   1009 	}
   1010 	Count = 0;
   1011 	do {
   1012 	    c = *inp++;
   1013 	    if (c == '\0')
   1014 		return c;
   1015 	    if (c == '(')
   1016 		Count++;
   1017 	    else if (c == ')')
   1018 		Count--;
   1019 	} while (Count > 0);
   1020     }
   1021 }
   1022 
   1023 #define TM_YEAR_ORIGIN 1900
   1024 
   1025 time_t
   1026 parsedate(const char *p, const time_t *now, const int *zone)
   1027 {
   1028     struct tm		local, *tm;
   1029     time_t		nowt;
   1030     int			zonet;
   1031     time_t		Start;
   1032     time_t		tod, rm;
   1033     struct dateinfo	param;
   1034     int			saved_errno;
   1035     int			i;
   1036 
   1037     saved_errno = errno;
   1038     errno = 0;
   1039 
   1040     if (now == NULL) {
   1041         now = &nowt;
   1042 	(void)time(&nowt);
   1043     }
   1044     if (zone == NULL) {
   1045 	zone = &zonet;
   1046 	zonet = USE_LOCAL_TIME;
   1047 	if ((tm = localtime_r(now, &local)) == NULL)
   1048 	    return -1;
   1049     } else {
   1050 	/*
   1051 	 * Should use the specified zone, not localtime.
   1052 	 * Fake it using gmtime and arithmetic.
   1053 	 * This is good enough because we use only the year/month/day,
   1054 	 * not other fields of struct tm.
   1055 	 */
   1056 	time_t fake = *now + (*zone * 60);
   1057 	if ((tm = gmtime_r(&fake, &local)) == NULL)
   1058 	    return -1;
   1059     }
   1060     param.yyYear = tm->tm_year + 1900;
   1061     param.yyMonth = tm->tm_mon + 1;
   1062     param.yyDay = tm->tm_mday;
   1063     param.yyTimezone = *zone;
   1064     param.yyDSTmode = DSTmaybe;
   1065     param.yyHour = 0;
   1066     param.yyMinutes = 0;
   1067     param.yySeconds = 0;
   1068     param.yyMeridian = MER24;
   1069     param.yyHaveDate = 0;
   1070     param.yyHaveFullYear = 0;
   1071     param.yyHaveDay = 0;
   1072     param.yyHaveRel = 0;
   1073     param.yyHaveTime = 0;
   1074     param.yyHaveZone = 0;
   1075 
   1076     /*
   1077      * This one is too hard to parse using a grammar (the lexer would
   1078      * confuse the 'T' with the Mil format timezone designator)
   1079      * so handle it as a special case.
   1080      */
   1081     do {
   1082 	const unsigned char *pp = (const unsigned char *)p;
   1083 	char *ep;	/* starts as "expected, becomes "end ptr" */
   1084 	static char format[] = "-dd-ddTdd:dd:dd";
   1085 
   1086 	while (isdigit(*pp))
   1087 		pp++;
   1088 
   1089 	if (pp == (const unsigned char *)p)
   1090 		break;
   1091 
   1092 	for (ep = format; *ep; ep++, pp++) {
   1093 		switch (*ep) {
   1094 		case 'd':
   1095 			if (isdigit(*pp))
   1096 				continue;
   1097 			break;
   1098 		case 'T':
   1099 			if (*pp == 'T' || *pp == 't' || *pp == ' ')
   1100 				continue;
   1101 			break;
   1102 		default:
   1103 			if (*pp == *ep)
   1104 				continue;
   1105 			break;
   1106 		}
   1107 		break;
   1108 	}
   1109 	if (*ep != '\0')
   1110 		break;
   1111 	if (*pp == '.' || *pp == ',') {
   1112 		if (!isdigit(pp[1]))
   1113 			break;
   1114 		while (isdigit(*++pp))
   1115 			continue;
   1116 	}
   1117 	if (*pp == 'Z' || *pp == 'z')
   1118 		pp++;
   1119 	else if (isdigit(*pp))
   1120 		break;
   1121 
   1122 	if (*pp != '\0' && !isspace(*pp))
   1123 		break;
   1124 
   1125 	/*
   1126 	 * This is good enough to commit to there being an ISO format
   1127 	 * timestamp leading the input string.   We permit standard
   1128 	 * parsedate() modifiers to follow but not precede this string.
   1129 	 */
   1130 	param.yyHaveTime = 1;
   1131 	param.yyHaveDate = 1;
   1132 	param.yyHaveFullYear = 1;
   1133 
   1134 	if (pp[-1] == 'Z' || pp[-1] == 'z') {
   1135 		param.yyTimezone = 0;
   1136 		param.yyHaveZone = 1;
   1137 	}
   1138 
   1139 	errno = 0;
   1140 	param.yyYear = (time_t)strtol(p, &ep, 10);
   1141 	if (errno != 0)			/* out of range (can be big number) */
   1142 		break;			/* the ones below are all 2 digits */
   1143 	param.yyMonth = (time_t)strtol(ep + 1, &ep, 10);
   1144 	param.yyDay = (time_t)strtol(ep + 1, &ep, 10);
   1145 	param.yyHour = (time_t)strtol(ep + 1, &ep, 10);
   1146 	param.yyMinutes = (time_t)strtol(ep + 1, &ep, 10);
   1147 	param.yySeconds = (time_t)strtol(ep + 1, &ep, 10);
   1148 	/* ignore any fractional seconds, no way to return them in a time_t */
   1149 
   1150 	param.yyMeridian = MER24;
   1151 
   1152 	p = (const char *)pp;
   1153     } while (0);
   1154 
   1155     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
   1156 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
   1157 	errno = EINVAL;
   1158 	return -1;
   1159     }
   1160 
   1161     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
   1162 	if (! param.yyHaveFullYear) {
   1163 		param.yyYear = AdjustYear(param.yyYear);
   1164 		param.yyHaveFullYear = 1;
   1165 	}
   1166 	errno = 0;
   1167 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
   1168 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
   1169 	    param.yyMeridian, param.yyDSTmode);
   1170 	if (Start == -1 && errno != 0)
   1171 	    return -1;
   1172     }
   1173     else {
   1174 	Start = *now;
   1175 	if (!param.yyHaveRel)
   1176 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
   1177     }
   1178 
   1179     if (param.yyHaveRel > MAXREL) {
   1180 	errno = EINVAL;
   1181 	return -1;
   1182     }
   1183     for (i = 0; i < param.yyHaveRel; i++) {
   1184 	if (param.yyRel[i].yyRelMonth) {
   1185 	    errno = 0;
   1186 	    rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
   1187 	    if (rm == -1 && errno != 0)
   1188 		return -1;
   1189 	    Start += rm;
   1190 	} else
   1191 	    Start += param.yyRel[i].yyRelVal;
   1192     }
   1193 
   1194     if (param.yyHaveDay && !param.yyHaveDate) {
   1195 	errno = 0;
   1196 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
   1197 	if (tod == -1 && errno != 0)
   1198 	    return -1;
   1199 	Start += tod;
   1200     }
   1201 
   1202     errno = saved_errno;
   1203     return Start;
   1204 }
   1205 
   1206 
   1207 #if	defined(TEST)
   1208 
   1209 /* ARGSUSED */
   1210 int
   1211 main(int ac, char *av[])
   1212 {
   1213     char	buff[128];
   1214     time_t	d;
   1215 
   1216     (void)printf("Enter date, or blank line to exit.\n\t> ");
   1217     (void)fflush(stdout);
   1218     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
   1219 	errno = 0;
   1220 	d = parsedate(buff, NULL, NULL);
   1221 	if (d == -1 && errno != 0)
   1222 	    (void)printf("Bad format - couldn't convert: %s\n",
   1223 	        strerror(errno));
   1224 	else
   1225 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
   1226 	(void)printf("\t> ");
   1227 	(void)fflush(stdout);
   1228     }
   1229     exit(0);
   1230     /* NOTREACHED */
   1231 }
   1232 #endif	/* defined(TEST) */
   1233