xref: /netbsd-src/lib/libutil/parsedate.y (revision 81769fc7f98636e878f49fdcdb8ee66fce0ce864)
1 %{
2 /*
3 **  Originally written by Steven M. Bellovin <smb@research.att.com> while
4 **  at the University of North Carolina at Chapel Hill.  Later tweaked by
5 **  a couple of people on Usenet.  Completely overhauled by Rich $alz
6 **  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
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 
704 /* ARGSUSED */
705 static int
706 yyerror(struct dateinfo *param, const char **inp, const char *s __unused)
707 {
708   return 0;
709 }
710 
711 /*
712  * Save a relative value, if it fits
713  */
714 static int
715 RelVal(struct dateinfo *param, time_t num, time_t unit, int scale, int type)
716 {
717 	int i;
718 	time_t v;
719 	uintmax_t m;
720 	int sign = 1;
721 
722 	if ((i = param->yyHaveRel) >= MAXREL)
723 		return 0;
724 
725 	if (num < 0) {
726 		sign = -sign;
727 		num = -num;
728 	}
729 	if (unit < 0) {
730 		sign = -sign;
731 		unit = -unit;
732 	}
733 	/* scale is always positive */
734 
735 	m = LLONG_MAX;		/* TIME_T_MAX */
736 	if (scale > 1)
737 		m /= scale;
738 	if (unit > 1)
739 		m /= unit;
740 	if ((uintmax_t)num > m)
741 		return 0;
742 
743 	m = num * unit * scale;
744 	v = (time_t) m;
745 	if (v < 0 || (uintmax_t)v != m)
746 		return 0;
747 	if (sign < 0)
748 		v = -v;
749 
750 	param->yyRel[i].yyRelMonth = type;
751 	param->yyRel[i].yyRelVal = v;
752 
753 	return 1;
754 }
755 
756 /*
757  * Adjust year from a value that might be abbreviated, to a full value.
758  * e.g. convert 70 to 1970.
759  * Input Year is either:
760  *  - A negative number, which means to use its absolute value (why?)
761  *  - A number from 0 to 68, which means a year from 2000 to 2068,
762  *  - A number from 69 to 99, which means a year from 1969 to 1999, or
763  *  - The actual year (>=100).
764  * Returns the full year.
765  */
766 static time_t
767 AdjustYear(time_t Year)
768 {
769     /* XXX Y2K */
770     if (Year < 0)
771 	Year = -Year;
772     if (Year < 69)	/* POSIX compliant, 0..68 is 2000's, 69-99 1900's */
773 	Year += 2000;
774     else if (Year < 100)
775 	Year += 1900;
776     return Year;
777 }
778 
779 static time_t
780 Convert(
781     time_t	Month,		/* month of year [1-12] */
782     time_t	Day,		/* day of month [1-31] */
783     time_t	Year,		/* year, not abbreviated in any way */
784     time_t	Hours,		/* Hour of day [0-24] */
785     time_t	Minutes,	/* Minute of hour [0-59] */
786     time_t	Seconds,	/* Second of minute [0-60] */
787     time_t	Timezone,	/* Timezone as minutes east of UTC,
788 				 * or USE_LOCAL_TIME special case */
789     MERIDIAN	Meridian,	/* Hours are am/pm/24 hour clock */
790     DSTMODE	DSTmode		/* DST on/off/maybe */
791 )
792 {
793     struct tm tm = {.tm_sec = 0};
794     struct tm otm;
795     time_t result;
796 
797     tm.tm_sec = Seconds;
798     tm.tm_min = Minutes;
799     tm.tm_hour = ((Hours == 12 && Meridian != MER24) ? 0 : Hours) +
800 	(Meridian == MERpm ? 12 : 0);
801 
802     tm.tm_mday = Day;
803     tm.tm_mon = Month - 1;
804     tm.tm_year = Year - 1900;
805     if ((time_t)tm.tm_year + 1900 != Year) {
806 	errno = EOVERFLOW;
807 	return -1;
808     }
809     if (Timezone == USE_LOCAL_TIME) {
810 	    switch (DSTmode) {
811 	    case DSTon:  tm.tm_isdst = 1; break;
812 	    case DSToff: tm.tm_isdst = 0; break;
813 	    default:     tm.tm_isdst = -1; break;
814 	    }
815 	    otm = tm;
816 	    result = mktime(&tm);
817     } else {
818 	    /* We rely on mktime_z(NULL, ...) working in UTC */
819 	    tm.tm_isdst = 0;	/* hence cannot be summer time */
820 	    otm = tm;
821 	    errno = 0;
822 	    result = mktime_z(NULL, &tm);
823 	    if (result != -1 || errno == 0) {
824 		    result += Timezone * 60;
825 		    if (DSTmode == DSTon)	/* if specified sumer time */
826 			result -= 3600;		/* UTC is 1 hour earlier XXX */
827 	    }
828     }
829 
830 #if PARSEDATE_DEBUG
831     fprintf(stderr, "%s(M=%jd D=%jd Y=%jd H=%jd M=%jd S=%jd Z=%jd"
832 		    " mer=%d DST=%d)",
833 	__func__,
834 	(intmax_t)Month, (intmax_t)Day, (intmax_t)Year,
835 	(intmax_t)Hours, (intmax_t)Minutes, (intmax_t)Seconds,
836 	(intmax_t)Timezone, (int)Meridian, (int)DSTmode);
837     fprintf(stderr, " -> %jd", (intmax_t)result);
838     fprintf(stderr, " %s", ctime(&result));
839 #endif
840 
841 #define	TM_NE(fld) (otm.tm_ ## fld != tm.tm_ ## fld)
842     if (TM_NE(year) || TM_NE(mon) || TM_NE(mday) ||
843 	TM_NE(hour) || TM_NE(min) || TM_NE(sec)) {
844 	    /* mktime() "corrected" our tm, so it must have been invalid */
845 	    result = -1;
846 	    errno = EAGAIN;
847     }
848 #undef	TM_NE
849 
850     return result;
851 }
852 
853 
854 static time_t
855 DSTcorrect(
856     time_t	Start,
857     time_t	Future
858 )
859 {
860     time_t	StartDay;
861     time_t	FutureDay;
862     struct tm	tm;
863 
864     if (localtime_r(&Start, &tm) == NULL)
865 	return -1;
866     StartDay = (tm.tm_hour + 1) % 24;
867 
868     if (localtime_r(&Future, &tm) == NULL)
869 	return -1;
870     FutureDay = (tm.tm_hour + 1) % 24;
871 
872     return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
873 }
874 
875 
876 static time_t
877 RelativeDate(
878     time_t	Start,
879     time_t	DayOrdinal,
880     time_t	DayNumber
881 )
882 {
883     struct tm	tm;
884     time_t	now;
885     time_t	change;
886 
887     now = Start;
888     if (localtime_r(&now, &tm) == NULL)
889 	return -1;
890 
891     /* should be using TIME_T_MAX but there is no such thing, so just "know" */
892     if (llabs(DayOrdinal) >= LLONG_MAX / (7 * SECSPERDAY)) {
893 	errno = EOVERFLOW;
894 	return -1;
895     }
896 
897     change = SECSPERDAY * ((DayNumber - tm.tm_wday + 7) % 7);
898     change += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
899 
900     /* same here for _MAX and _MIN */
901     if ((change > 0 && LLONG_MAX - change < now) ||
902 	(change < 0 && LLONG_MIN - change > now)) {
903 	    errno = EOVERFLOW;
904 	    return -1;
905     }
906 
907     now += change;
908     return DSTcorrect(Start, now);
909 }
910 
911 
912 static time_t
913 RelativeMonth(
914     time_t	Start,
915     time_t	RelMonth,
916     time_t	Timezone
917 )
918 {
919     struct tm	tm;
920     time_t	Month;
921     time_t	Then;
922     int		Day;
923 
924     if (RelMonth == 0)
925 	return 0;
926     /*
927      * It doesn't matter what timezone we use to do this computation,
928      * as long as we use the same one to reassemble the time that we
929      * used to disassemble it. So always use localtime and mktime. In
930      * particular, don't use Convert() to reassemble, because it will
931      * not only reassemble with the wrong timezone but it will also
932      * fail if we do e.g. three months from March 31 yielding July 1.
933      */
934     (void)Timezone;
935 
936     if (localtime_r(&Start, &tm) == NULL)
937 	return -1;
938 
939     if (RelMonth >= LLONG_MAX - 12*((time_t)tm.tm_year + 1900) - tm.tm_mon) {
940 	errno = EOVERFLOW;
941 	return -1;
942     }
943     Month = 12 * (tm.tm_year + 1900) + tm.tm_mon + RelMonth;
944     tm.tm_year = (Month / 12) - 1900;
945     /* check for tm_year (an int) overflow */
946     if (((time_t)tm.tm_year + 1900) != Month/12) {
947 	errno = EOVERFLOW;
948 	return -1;
949     }
950     tm.tm_mon = Month % 12;
951     if (tm.tm_mday > (Day = DaysInMonth[tm.tm_mon] +
952 	((tm.tm_mon==1) ? isleap(tm.tm_year) : 0)))
953 	    tm.tm_mday = Day;
954     errno = 0;
955     Then = mktime(&tm);
956     if (Then == -1 && errno != 0)
957 	return -1;
958     return DSTcorrect(Start, Then);
959 }
960 
961 
962 static int
963 LookupWord(YYSTYPE *yylval, char *buff)
964 {
965     register char	*p;
966     register char	*q;
967     register const TABLE	*tp;
968     int			i;
969     int			abbrev;
970 
971     /* Make it lowercase. */
972     for (p = buff; *p; p++)
973 	if (isupper((unsigned char)*p))
974 	    *p = tolower((unsigned char)*p);
975 
976     if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
977 	yylval->Meridian = MERam;
978 	return tMERIDIAN;
979     }
980     if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
981 	yylval->Meridian = MERpm;
982 	return tMERIDIAN;
983     }
984 
985     /* See if we have an abbreviation for a month. */
986     if (strlen(buff) == 3)
987 	abbrev = 1;
988     else if (strlen(buff) == 4 && buff[3] == '.') {
989 	abbrev = 1;
990 	buff[3] = '\0';
991     }
992     else
993 	abbrev = 0;
994 
995     for (tp = MonthDayTable; tp->name; tp++) {
996 	if (abbrev) {
997 	    if (strncmp(buff, tp->name, 3) == 0) {
998 		yylval->Number = tp->value;
999 		return tp->type;
1000 	    }
1001 	}
1002 	else if (strcmp(buff, tp->name) == 0) {
1003 	    yylval->Number = tp->value;
1004 	    return tp->type;
1005 	}
1006     }
1007 
1008     for (tp = TimezoneTable; tp->name; tp++)
1009 	if (strcmp(buff, tp->name) == 0) {
1010 	    yylval->Number = tp->value;
1011 	    return tp->type;
1012 	}
1013 
1014     if (strcmp(buff, "dst") == 0)
1015 	return tDST;
1016 
1017     for (tp = TimeNames; tp->name; tp++)
1018 	if (strcmp(buff, tp->name) == 0) {
1019 	    yylval->Number = tp->value;
1020 	    return tp->type;
1021 	}
1022 
1023     for (tp = UnitsTable; tp->name; tp++)
1024 	if (strcmp(buff, tp->name) == 0) {
1025 	    yylval->Number = tp->value;
1026 	    return tp->type;
1027 	}
1028 
1029     /* Strip off any plural and try the units table again. */
1030     i = strlen(buff) - 1;
1031     if (buff[i] == 's') {
1032 	buff[i] = '\0';
1033 	for (tp = UnitsTable; tp->name; tp++)
1034 	    if (strcmp(buff, tp->name) == 0) {
1035 		yylval->Number = tp->value;
1036 		return tp->type;
1037 	    }
1038 	buff[i] = 's';		/* Put back for "this" in OtherTable. */
1039     }
1040 
1041     for (tp = OtherTable; tp->name; tp++)
1042 	if (strcmp(buff, tp->name) == 0) {
1043 	    yylval->Number = tp->value;
1044 	    return tp->type;
1045 	}
1046 
1047     /* Military timezones. */
1048     if (buff[1] == '\0' && isalpha((unsigned char)*buff)) {
1049 	for (tp = MilitaryTable; tp->name; tp++)
1050 	    if (strcmp(buff, tp->name) == 0) {
1051 		yylval->Number = tp->value;
1052 		return tp->type;
1053 	    }
1054     }
1055 
1056     /* Drop out any periods and try the timezone table again. */
1057     for (i = 0, p = q = buff; *q; q++)
1058 	if (*q != '.')
1059 	    *p++ = *q;
1060 	else
1061 	    i++;
1062     *p = '\0';
1063     if (i)
1064 	for (tp = TimezoneTable; tp->name; tp++)
1065 	    if (strcmp(buff, tp->name) == 0) {
1066 		yylval->Number = tp->value;
1067 		return tp->type;
1068 	    }
1069 
1070     return tID;
1071 }
1072 
1073 
1074 static int
1075 yylex(YYSTYPE *yylval, const char **yyInput)
1076 {
1077     register char	c;
1078     register char	*p;
1079     char		buff[20];
1080     int			Count;
1081     int			sign;
1082     const char		*inp = *yyInput;
1083 
1084     for ( ; ; ) {
1085 	while (isspace((unsigned char)*inp))
1086 	    inp++;
1087 
1088 	if (isdigit((unsigned char)(c = *inp)) || c == '-' || c == '+') {
1089 	    if (c == '-' || c == '+') {
1090 		sign = c == '-' ? -1 : 1;
1091 		if (!isdigit((unsigned char)*++inp))
1092 		    /* skip the '-' sign */
1093 		    continue;
1094 	    }
1095 	    else
1096 		sign = 0;
1097 	    for (yylval->Number = 0; isdigit((unsigned char)(c = *inp++)); ) {
1098 	        time_t	v;
1099 
1100 		v = yylval->Number;
1101 		if (v > LLONG_MAX/10 ||
1102 		    (v == LLONG_MAX/10 && (v * 10 > LLONG_MAX - (c - '0'))))
1103 			yylval->Number = LLONG_MAX;
1104 		else
1105 			yylval->Number = 10 * yylval->Number + c - '0';
1106 	    }
1107 	    if (sign < 0)
1108 		yylval->Number = -yylval->Number;
1109 	    *yyInput = --inp;
1110 	    return sign ? tSNUMBER : tUNUMBER;
1111 	}
1112 	if (isalpha((unsigned char)c)) {
1113 	    for (p = buff; isalpha((unsigned char)(c = *inp++)) || c == '.'; )
1114 		if (p < &buff[sizeof buff - 1])
1115 		    *p++ = c;
1116 	    *p = '\0';
1117 	    *yyInput = --inp;
1118 	    return LookupWord(yylval, buff);
1119 	}
1120 	if (c == '@') {
1121 	    *yyInput = ++inp;
1122 	    return AT_SIGN;
1123 	}
1124 	if (c != '(') {
1125 	    *yyInput = ++inp;
1126 	    return c;
1127 	}
1128 	Count = 0;
1129 	do {
1130 	    c = *inp++;
1131 	    if (c == '\0')
1132 		return c;
1133 	    if (c == '(')
1134 		Count++;
1135 	    else if (c == ')')
1136 		Count--;
1137 	} while (Count > 0);
1138     }
1139 }
1140 
1141 #define TM_YEAR_ORIGIN 1900
1142 
1143 time_t
1144 parsedate(const char *p, const time_t *now, const int *zone)
1145 {
1146     struct tm		local, *tm;
1147     time_t		nowt;
1148     int			zonet;
1149     time_t		Start;
1150     time_t		tod, rm;
1151     struct dateinfo	param;
1152     int			saved_errno;
1153     int			i;
1154 
1155     saved_errno = errno;
1156     errno = 0;
1157 
1158     if (now == NULL) {
1159         now = &nowt;
1160 	(void)time(&nowt);
1161     }
1162     if (zone == NULL) {
1163 	zone = &zonet;
1164 	zonet = USE_LOCAL_TIME;
1165 	if ((tm = localtime_r(now, &local)) == NULL)
1166 	    return -1;
1167     } else {
1168 	/*
1169 	 * Should use the specified zone, not localtime.
1170 	 * Fake it using gmtime and arithmetic.
1171 	 * This is good enough because we use only the year/month/day,
1172 	 * not other fields of struct tm.
1173 	 */
1174 	time_t fake = *now + (*zone * 60);
1175 	if ((tm = gmtime_r(&fake, &local)) == NULL)
1176 	    return -1;
1177     }
1178     param.yyYear = tm->tm_year + 1900;
1179     param.yyMonth = tm->tm_mon + 1;
1180     param.yyDay = tm->tm_mday;
1181     param.yyTimezone = *zone;
1182     param.yyDSTmode = DSTmaybe;
1183     param.yyHour = 0;
1184     param.yyMinutes = 0;
1185     param.yySeconds = 0;
1186     param.yyMeridian = MER24;
1187     param.yyHaveDate = 0;
1188     param.yyHaveFullYear = 0;
1189     param.yyHaveDay = 0;
1190     param.yyHaveRel = 0;
1191     param.yyHaveTime = 0;
1192     param.yyHaveZone = 0;
1193 
1194     /*
1195      * This one is too hard to parse using a grammar (the lexer would
1196      * confuse the 'T' with the Mil format timezone designator)
1197      * so handle it as a special case.
1198      */
1199     do {
1200 	const unsigned char *pp = (const unsigned char *)p;
1201 	char *ep;	/* starts as "expected, becomes "end ptr" */
1202 	static char format[] = "-dd-ddTdd:dd:dd";
1203 	time_t yr;
1204 
1205 	while (isdigit(*pp))
1206 		pp++;
1207 
1208 	if (pp == (const unsigned char *)p)
1209 		break;
1210 
1211 	for (ep = format; *ep; ep++, pp++) {
1212 		switch (*ep) {
1213 		case 'd':
1214 			if (isdigit(*pp))
1215 				continue;
1216 			break;
1217 		case 'T':
1218 			if (*pp == 'T' || *pp == 't' || *pp == ' ')
1219 				continue;
1220 			break;
1221 		default:
1222 			if (*pp == *ep)
1223 				continue;
1224 			break;
1225 		}
1226 		break;
1227 	}
1228 	if (*ep != '\0')
1229 		break;
1230 	if (*pp == '.' || *pp == ',') {
1231 		if (!isdigit(pp[1]))
1232 			break;
1233 		while (isdigit(*++pp))
1234 			continue;
1235 	}
1236 	if (*pp == 'Z' || *pp == 'z')
1237 		pp++;
1238 	else if (isdigit(*pp))
1239 		break;
1240 
1241 	if (*pp != '\0' && !isspace(*pp))
1242 		break;
1243 
1244 	errno = 0;
1245 	yr = (time_t)strtol(p, &ep, 10);
1246 	if (errno != 0)			/* out of range (can be big number) */
1247 		break;			/* the ones below are all 2 digits */
1248 
1249 	/*
1250 	 * This is good enough to commit to there being an ISO format
1251 	 * timestamp leading the input string.   We permit standard
1252 	 * parsedate() modifiers to follow but not precede this string.
1253 	 */
1254 	param.yyHaveTime = 1;
1255 	param.yyHaveDate = 1;
1256 	param.yyHaveFullYear = 1;
1257 
1258 	if (pp[-1] == 'Z' || pp[-1] == 'z') {
1259 		param.yyTimezone = 0;
1260 		param.yyHaveZone = 1;
1261 	}
1262 
1263 	param.yyYear = yr;
1264 	param.yyMonth = (time_t)strtol(ep + 1, &ep, 10);
1265 	param.yyDay = (time_t)strtol(ep + 1, &ep, 10);
1266 	param.yyHour = (time_t)strtol(ep + 1, &ep, 10);
1267 	param.yyMinutes = (time_t)strtol(ep + 1, &ep, 10);
1268 	param.yySeconds = (time_t)strtol(ep + 1, &ep, 10);
1269 	/* ignore any fractional seconds, no way to return them in a time_t */
1270 
1271 	param.yyMeridian = MER24;
1272 
1273 	p = (const char *)pp;
1274     } while (0);
1275 
1276     if (yyparse(&param, &p) || param.yyHaveTime > 1 || param.yyHaveZone > 1 ||
1277 	param.yyHaveDate > 1 || param.yyHaveDay > 1) {
1278 	errno = EINVAL;
1279 	return -1;
1280     }
1281 
1282     if (param.yyHaveDate || param.yyHaveTime || param.yyHaveDay) {
1283 	if (! param.yyHaveFullYear) {
1284 		param.yyYear = AdjustYear(param.yyYear);
1285 		param.yyHaveFullYear = 1;
1286 	}
1287 	errno = 0;
1288 	Start = Convert(param.yyMonth, param.yyDay, param.yyYear, param.yyHour,
1289 	    param.yyMinutes, param.yySeconds, param.yyTimezone,
1290 	    param.yyMeridian, param.yyDSTmode);
1291 	if (Start == -1 && errno != 0)
1292 	    return -1;
1293     }
1294     else {
1295 	Start = *now;
1296 	if (!param.yyHaveRel)
1297 	    Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) + tm->tm_sec;
1298     }
1299 
1300     if (param.yyHaveRel > MAXREL) {
1301 	errno = EINVAL;
1302 	return -1;
1303     }
1304     for (i = 0; i < param.yyHaveRel; i++) {
1305 	if (param.yyRel[i].yyRelMonth) {
1306 	    errno = 0;
1307 	    rm = RelativeMonth(Start, param.yyRel[i].yyRelVal, param.yyTimezone);
1308 	    if (rm == -1 && errno != 0)
1309 		return -1;
1310 	    Start += rm;
1311 	} else
1312 	    Start += param.yyRel[i].yyRelVal;
1313     }
1314 
1315     if (param.yyHaveDay && !param.yyHaveDate) {
1316 	errno = 0;
1317 	tod = RelativeDate(Start, param.yyDayOrdinal, param.yyDayNumber);
1318 	if (tod == -1 && errno != 0)
1319 	    return -1;
1320 	Start += tod;
1321     }
1322 
1323     errno = saved_errno;
1324     return Start;
1325 }
1326 
1327 
1328 #if	defined(TEST)
1329 
1330 /* ARGSUSED */
1331 int
1332 main(int ac, char *av[])
1333 {
1334     char	buff[128];
1335     time_t	d;
1336 
1337     (void)printf("Enter date, or blank line to exit.\n\t> ");
1338     (void)fflush(stdout);
1339     while (fgets(buff, sizeof(buff), stdin) && buff[0] != '\n') {
1340 	errno = 0;
1341 	d = parsedate(buff, NULL, NULL);
1342 	if (d == -1 && errno != 0)
1343 	    (void)printf("Bad format - couldn't convert: %s\n",
1344 	        strerror(errno));
1345 	else
1346 	    (void)printf("%jd\t%s", (intmax_t)d, ctime(&d));
1347 	(void)printf("\t> ");
1348 	(void)fflush(stdout);
1349     }
1350     exit(0);
1351     /* NOTREACHED */
1352 }
1353 #endif	/* defined(TEST) */
1354