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*/
51typedef 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*/
61typedef enum _DSTMODE {
62    DSTon, DSToff, DSTmaybe
63} DSTMODE;
64
65/*
66**  Meridian:  am, pm, or 24-hour style (plus "noon" and "midnight").
67*/
68typedef enum _MERIDIAN {
69    MERam, MERpm, MER24, MER_NOON, MER_MN
70} MERIDIAN;
71
72
73struct 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
98static 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
130spec:
131	  /* empty */
132	| spec item
133;
134
135item:
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
149cvsstamp:
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
167epochdate:
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
195at_number:
196	  tUNUMBER
197	| tSNUMBER
198;
199
200time:
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
299time_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
325zone:
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
339day:
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
345date:
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
421rel:
422	  relunit
423	| relunit tAGO {
424		param->yyRel[param->yyHaveRel].yyRelVal =
425		    -param->yyRel[param->yyHaveRel].yyRelVal;
426	  }
427;
428
429relunit:
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
441number:
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
470o_merid:
471	  /* empty */		{ $$ = MER24; }
472	| tMERIDIAN		{ $$ = $1; }
473	| tTIME			{ $$ = $1 == 0 ? MER_MN : MER_NOON; }
474;
475
476%%
477
478static 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. */
490static 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. */
527static 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. */
542static 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. */
580static 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. */
665static 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
694static 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 */
705static int
706yyerror(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 */
714static int
715RelVal(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 */
766static time_t
767AdjustYear(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
779static time_t
780Convert(
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
854static time_t
855DSTcorrect(
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
876static time_t
877RelativeDate(
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
912static time_t
913RelativeMonth(
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
962static int
963LookupWord(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
1074static int
1075yylex(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
1143time_t
1144parsedate(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 */
1331int
1332main(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