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