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