%{
/*	$OpenBSD: date.y,v 1.6 2007/02/27 07:59:13 xsa Exp $	*/

/*
**  Originally written by Steven M. Bellovin <smb@research.att.com> while
**  at the University of North Carolina at Chapel Hill.  Later tweaked by
**  a couple of people on Usenet.  Completely overhauled by Rich $alz
**  <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990;
**
**  This grammar has 10 shift/reduce conflicts.
**
**  This code is in the public domain and has no copyright.
*/
/* SUPPRESS 287 on yaccpar_sccsid *//* Unused static variable */
/* SUPPRESS 288 on yyerrlab *//* Label unused */

#include <sys/timeb.h>

#include <ctype.h>
#include <err.h>
#include <string.h>

#include "rcsprog.h"

#define YEAR_EPOCH	1970
#define YEAR_TMORIGIN	1900
#define HOUR(x)		((time_t)(x) * 60)
#define SECSPERDAY	(24L * 60L * 60L)


/* An entry in the lexical lookup table */
typedef struct _TABLE {
	char	*name;
	int	type;
	time_t	value;
} TABLE;


/*  Daylight-savings mode:  on, off, or not yet known. */
typedef enum _DSTMODE {
	DSTon, DSToff, DSTmaybe
} DSTMODE;

/*  Meridian:  am, pm, or 24-hour style. */
typedef enum _MERIDIAN {
	MERam, MERpm, MER24
} MERIDIAN;


/*
 *  Global variables.  We could get rid of most of these by using a good
 *  union as the yacc stack.  (This routine was originally written before
 *  yacc had the %union construct.)  Maybe someday; right now we only use
 *  the %union very rarely.
 */
static const char	*yyInput;
static DSTMODE	yyDSTmode;
static time_t	yyDayOrdinal;
static time_t	yyDayNumber;
static int	yyHaveDate;
static int	yyHaveDay;
static int	yyHaveRel;
static int	yyHaveTime;
static int	yyHaveZone;
static time_t	yyTimezone;
static time_t	yyDay;
static time_t	yyHour;
static time_t	yyMinutes;
static time_t	yyMonth;
static time_t	yySeconds;
static time_t	yyYear;
static MERIDIAN	yyMeridian;
static time_t	yyRelMonth;
static time_t	yyRelSeconds;


static int	yyerror(const char *);
static int	yylex(void);
static int	yyparse(void);
static int	lookup(char *);

%}

%union {
	time_t		Number;
	enum _MERIDIAN	Meridian;
}

%token	tAGO tDAY tDAYZONE tID tMERIDIAN tMINUTE_UNIT tMONTH tMONTH_UNIT
%token	tSEC_UNIT tSNUMBER tUNUMBER tZONE tDST

%type	<Number>	tDAY tDAYZONE tMINUTE_UNIT tMONTH tMONTH_UNIT
%type	<Number>	tSEC_UNIT tSNUMBER tUNUMBER tZONE
%type	<Meridian>	tMERIDIAN o_merid

%%

spec	: /* NULL */
	| spec item
	;

item	: time {
		yyHaveTime++;
	}
	| zone {
		yyHaveZone++;
	}
	| date {
		yyHaveDate++;
	}
	| day {
		yyHaveDay++;
	}
	| rel {
		yyHaveRel++;
	}
	| number
	;

time	: tUNUMBER tMERIDIAN {
		yyHour = $1;
		yyMinutes = 0;
		yySeconds = 0;
		yyMeridian = $2;
	}
	| tUNUMBER ':' tUNUMBER o_merid {
		yyHour = $1;
		yyMinutes = $3;
		yySeconds = 0;
		yyMeridian = $4;
	}
	| tUNUMBER ':' tUNUMBER tSNUMBER {
		yyHour = $1;
		yyMinutes = $3;
		yyMeridian = MER24;
		yyDSTmode = DSToff;
		yyTimezone = - ($4 % 100 + ($4 / 100) * 60);
	}
	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
		yyHour = $1;
		yyMinutes = $3;
		yySeconds = $5;
		yyMeridian = $6;
	}
	| tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER {
		yyHour = $1;
		yyMinutes = $3;
		yySeconds = $5;
		yyMeridian = MER24;
		yyDSTmode = DSToff;
		yyTimezone = - ($6 % 100 + ($6 / 100) * 60);
	}
	;

zone	: tZONE {
		yyTimezone = $1;
		yyDSTmode = DSToff;
	}
	| tDAYZONE {
		yyTimezone = $1;
		yyDSTmode = DSTon;
	}
	| tZONE tDST {
		yyTimezone = $1;
		yyDSTmode = DSTon;
	}
	;

day	: tDAY {
		yyDayOrdinal = 1;
		yyDayNumber = $1;
	}
	| tDAY ',' {
		yyDayOrdinal = 1;
		yyDayNumber = $1;
	}
	| tUNUMBER tDAY {
		yyDayOrdinal = $1;
		yyDayNumber = $2;
	}
	;

date	: tUNUMBER '/' tUNUMBER {
		yyMonth = $1;
		yyDay = $3;
	}
	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
		if ($1 >= 100) {
			yyYear = $1;
			yyMonth = $3;
			yyDay = $5;
		} else {
			yyMonth = $1;
			yyDay = $3;
			yyYear = $5;
		}
	}
	| tUNUMBER tSNUMBER tSNUMBER {
		/* ISO 8601 format.  yyyy-mm-dd.  */
		yyYear = $1;
		yyMonth = -$2;
		yyDay = -$3;
	}
	| tUNUMBER tMONTH tSNUMBER {
		/* e.g. 17-JUN-1992.  */
		yyDay = $1;
		yyMonth = $2;
		yyYear = -$3;
	}
	| tMONTH tUNUMBER {
		yyMonth = $1;
		yyDay = $2;
	}
	| tMONTH tUNUMBER ',' tUNUMBER {
		yyMonth = $1;
		yyDay = $2;
		yyYear = $4;
	}
	| tUNUMBER tMONTH {
		yyMonth = $2;
		yyDay = $1;
	}
	| tUNUMBER tMONTH tUNUMBER {
		yyMonth = $2;
		yyDay = $1;
		yyYear = $3;
	}
	;

rel	: relunit tAGO {
		yyRelSeconds = -yyRelSeconds;
		yyRelMonth = -yyRelMonth;
	}
	| relunit
	;

relunit	: tUNUMBER tMINUTE_UNIT {
		yyRelSeconds += $1 * $2 * 60L;
	}
	| tSNUMBER tMINUTE_UNIT {
		yyRelSeconds += $1 * $2 * 60L;
	}
	| tMINUTE_UNIT {
		yyRelSeconds += $1 * 60L;
	}
	| tSNUMBER tSEC_UNIT {
		yyRelSeconds += $1;
	}
	| tUNUMBER tSEC_UNIT {
		yyRelSeconds += $1;
	}
	| tSEC_UNIT {
		yyRelSeconds++;
	}
	| tSNUMBER tMONTH_UNIT {
		yyRelMonth += $1 * $2;
	}
	| tUNUMBER tMONTH_UNIT {
		yyRelMonth += $1 * $2;
	}
	| tMONTH_UNIT {
		yyRelMonth += $1;
	}
	;

number	: tUNUMBER {
		if (yyHaveTime && yyHaveDate && !yyHaveRel)
			yyYear = $1;
		else {
			if ($1 > 10000) {
				yyHaveDate++;
				yyDay= ($1)%100;
				yyMonth= ($1/100)%100;
				yyYear = $1/10000;
			} else {
				yyHaveTime++;
				if ($1 < 100) {
					yyHour = $1;
					yyMinutes = 0;
				} else {
					yyHour = $1 / 100;
					yyMinutes = $1 % 100;
				}
				yySeconds = 0;
				yyMeridian = MER24;
			}
		}
	}
	;

o_merid	: /* NULL */ {
		$$ = MER24;
	}
	| tMERIDIAN {
		$$ = $1;
	}
	;

%%

/* Month and day table. */
static TABLE const MonthDayTable[] = {
	{ "january",	tMONTH,	1 },
	{ "february",	tMONTH,	2 },
	{ "march",	tMONTH,	3 },
	{ "april",	tMONTH,	4 },
	{ "may",	tMONTH,	5 },
	{ "june",	tMONTH,	6 },
	{ "july",	tMONTH,	7 },
	{ "august",	tMONTH,	8 },
	{ "september",	tMONTH,	9 },
	{ "sept",	tMONTH,	9 },
	{ "october",	tMONTH,	10 },
	{ "november",	tMONTH,	11 },
	{ "december",	tMONTH,	12 },
	{ "sunday",	tDAY,	0 },
	{ "monday",	tDAY,	1 },
	{ "tuesday",	tDAY,	2 },
	{ "tues",	tDAY,	2 },
	{ "wednesday",	tDAY,	3 },
	{ "wednes",	tDAY,	3 },
	{ "thursday",	tDAY,	4 },
	{ "thur",	tDAY,	4 },
	{ "thurs",	tDAY,	4 },
	{ "friday",	tDAY,	5 },
	{ "saturday",	tDAY,	6 },
	{ NULL }
};

/* Time units table. */
static TABLE const UnitsTable[] = {
	{ "year",	tMONTH_UNIT,	12 },
	{ "month",	tMONTH_UNIT,	1 },
	{ "fortnight",	tMINUTE_UNIT,	14 * 24 * 60 },
	{ "week",	tMINUTE_UNIT,	7 * 24 * 60 },
	{ "day",	tMINUTE_UNIT,	1 * 24 * 60 },
	{ "hour",	tMINUTE_UNIT,	60 },
	{ "minute",	tMINUTE_UNIT,	1 },
	{ "min",	tMINUTE_UNIT,	1 },
	{ "second",	tSEC_UNIT,	1 },
	{ "sec",	tSEC_UNIT,	1 },
	{ NULL }
};

/* Assorted relative-time words. */
static TABLE const OtherTable[] = {
	{ "tomorrow",	tMINUTE_UNIT,	1 * 24 * 60 },
	{ "yesterday",	tMINUTE_UNIT,	-1 * 24 * 60 },
	{ "today",	tMINUTE_UNIT,	0 },
	{ "now",	tMINUTE_UNIT,	0 },
	{ "last",	tUNUMBER,	-1 },
	{ "this",	tMINUTE_UNIT,	0 },
	{ "next",	tUNUMBER,	2 },
	{ "first",	tUNUMBER,	1 },
/*  { "second",		tUNUMBER,	2 }, */
	{ "third",	tUNUMBER,	3 },
	{ "fourth",	tUNUMBER,	4 },
	{ "fifth",	tUNUMBER,	5 },
	{ "sixth",	tUNUMBER,	6 },
	{ "seventh",	tUNUMBER,	7 },
	{ "eighth",	tUNUMBER,	8 },
	{ "ninth",	tUNUMBER,	9 },
	{ "tenth",	tUNUMBER,	10 },
	{ "eleventh",	tUNUMBER,	11 },
	{ "twelfth",	tUNUMBER,	12 },
	{ "ago",	tAGO,	1 },
	{ NULL }
};

/* The timezone table. */
/* Some of these are commented out because a time_t can't store a float. */
static TABLE const TimezoneTable[] = {
	{ "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
	{ "ut",		tZONE,     HOUR( 0) },	/* Universal (Coordinated) */
	{ "utc",	tZONE,     HOUR( 0) },
	{ "wet",	tZONE,     HOUR( 0) },	/* Western European */
	{ "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
	{ "wat",	tZONE,     HOUR( 1) },	/* West Africa */
	{ "at",		tZONE,     HOUR( 2) },	/* Azores */
#if	0
	/* For completeness.  BST is also British Summer, and GST is
	 * also Guam Standard. */
	{ "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
	{ "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
#endif
#if 0
	{ "nft",	tZONE,     HOUR(3.5) },	/* Newfoundland */
	{ "nst",	tZONE,     HOUR(3.5) },	/* Newfoundland Standard */
	{ "ndt",	tDAYZONE,  HOUR(3.5) },	/* Newfoundland Daylight */
#endif
	{ "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
	{ "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
	{ "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
	{ "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
	{ "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
	{ "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
	{ "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
	{ "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
	{ "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
	{ "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
	{ "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
	{ "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
	{ "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
	{ "hdt",	tDAYZONE,  HOUR(10) },	/* Hawaii Daylight */
	{ "cat",	tZONE,     HOUR(10) },	/* Central Alaska */
	{ "ahst",	tZONE,     HOUR(10) },	/* Alaska-Hawaii Standard */
	{ "nt",		tZONE,     HOUR(11) },	/* Nome */
	{ "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
	{ "cet",	tZONE,     -HOUR(1) },	/* Central European */
	{ "met",	tZONE,     -HOUR(1) },	/* Middle European */
	{ "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
	{ "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
	{ "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
	{ "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
	{ "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
	{ "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
	{ "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe, USSR Zone 1 */
	{ "bt",		tZONE,     -HOUR(3) },	/* Baghdad, USSR Zone 2 */
#if 0
	{ "it",		tZONE,     -HOUR(3.5) },/* Iran */
#endif
	{ "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
	{ "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
#if 0
	{ "ist",	tZONE,     -HOUR(5.5) },/* Indian Standard */
#endif
	{ "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
#if	0
	/* For completeness.  NST is also Newfoundland Stanard, and SST is
	 * also Swedish Summer. */
	{ "nst",	tZONE,     -HOUR(6.5) },/* North Sumatra */
	{ "sst",	tZONE,     -HOUR(7) },	/* South Sumatra, USSR Zone 6 */
#endif	/* 0 */
	{ "wast",	tZONE,     -HOUR(7) },	/* West Australian Standard */
	{ "wadt",	tDAYZONE,  -HOUR(7) },	/* West Australian Daylight */
#if 0
	{ "jt",		tZONE,     -HOUR(7.5) },/* Java (3pm in Cronusland!) */
#endif
	{ "cct",	tZONE,     -HOUR(8) },	/* China Coast, USSR Zone 7 */
	{ "jst",	tZONE,     -HOUR(9) },	/* Japan Standard, USSR Zone 8 */
#if 0
	{ "cast",	tZONE,     -HOUR(9.5) },/* Central Australian Standard */
	{ "cadt",	tDAYZONE,  -HOUR(9.5) },/* Central Australian Daylight */
#endif
	{ "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
	{ "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
	{ "gst",	tZONE,     -HOUR(10) },	/* Guam Standard, USSR Zone 9 */
	{ "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
	{ "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
	{ "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
	{ "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
	{  NULL  }
};

/* Military timezone table. */
static TABLE const MilitaryTable[] = {
	{ "a",	tZONE,	HOUR(  1) },
	{ "b",	tZONE,	HOUR(  2) },
	{ "c",	tZONE,	HOUR(  3) },
	{ "d",	tZONE,	HOUR(  4) },
	{ "e",	tZONE,	HOUR(  5) },
	{ "f",	tZONE,	HOUR(  6) },
	{ "g",	tZONE,	HOUR(  7) },
	{ "h",	tZONE,	HOUR(  8) },
	{ "i",	tZONE,	HOUR(  9) },
	{ "k",	tZONE,	HOUR( 10) },
	{ "l",	tZONE,	HOUR( 11) },
	{ "m",	tZONE,	HOUR( 12) },
	{ "n",	tZONE,	HOUR(- 1) },
	{ "o",	tZONE,	HOUR(- 2) },
	{ "p",	tZONE,	HOUR(- 3) },
	{ "q",	tZONE,	HOUR(- 4) },
	{ "r",	tZONE,	HOUR(- 5) },
	{ "s",	tZONE,	HOUR(- 6) },
	{ "t",	tZONE,	HOUR(- 7) },
	{ "u",	tZONE,	HOUR(- 8) },
	{ "v",	tZONE,	HOUR(- 9) },
	{ "w",	tZONE,	HOUR(-10) },
	{ "x",	tZONE,	HOUR(-11) },
	{ "y",	tZONE,	HOUR(-12) },
	{ "z",	tZONE,	HOUR(  0) },
	{ NULL }
};


static int
yyerror(const char *s)
{
	char *str;

	if (isspace(yyInput[0]) || !isprint(yyInput[0]))
		(void)xasprintf(&str,
		    "%s: unexpected char 0x%02x in date string", s, yyInput[0]);
	else
		(void)xasprintf(&str, "%s: unexpected %s in date string",
		    s, yyInput);

	warnx("%s", str);
	xfree(str);
	return (0);
}


static time_t
ToSeconds(time_t Hours, time_t Minutes, time_t	Seconds, MERIDIAN Meridian)
{
	if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 59)
		return (-1);

	switch (Meridian) {
	case MER24:
		if (Hours < 0 || Hours > 23)
			return (-1);
		return (Hours * 60L + Minutes) * 60L + Seconds;
	case MERam:
		if (Hours < 1 || Hours > 12)
			return (-1);
		if (Hours == 12)
			Hours = 0;
		return (Hours * 60L + Minutes) * 60L + Seconds;
	case MERpm:
		if (Hours < 1 || Hours > 12)
			return (-1);
		if (Hours == 12)
			Hours = 0;
		return ((Hours + 12) * 60L + Minutes) * 60L + Seconds;
	default:
		abort();
	}
	/* NOTREACHED */
}


/* Year is either
 * A negative number, which means to use its absolute value (why?)
 * A number from 0 to 99, which means a year from 1900 to 1999, or
 * The actual year (>=100).
 */
static time_t
Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes,
    time_t Seconds, MERIDIAN Meridian, DSTMODE DSTmode)
{
	static int DaysInMonth[12] = {
		31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
	};
	time_t	tod;
	time_t	julian;
	int	i;

	if (Year < 0)
		Year = -Year;
	if (Year < 69)
		Year += 2000;
	else if (Year < 100) {
		Year += 1900;
		if (Year < YEAR_EPOCH)
			Year += 100;
	}
	DaysInMonth[1] = Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0)
	    ? 29 : 28;
	/* Checking for 2038 bogusly assumes that time_t is 32 bits.  But
	   I'm too lazy to try to check for time_t overflow in another way.  */
	if (Year < YEAR_EPOCH || Year > 2038 || Month < 1 || Month > 12 ||
	    /* Lint fluff:  "conversion from long may lose accuracy" */
	     Day < 1 || Day > DaysInMonth[(int)--Month])
		return (-1);

	for (julian = Day - 1, i = 0; i < Month; i++)
		julian += DaysInMonth[i];

	for (i = YEAR_EPOCH; i < Year; i++)
		julian += 365 + (i % 4 == 0);
	julian *= SECSPERDAY;
	julian += yyTimezone * 60L;

	if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
		return (-1);
	julian += tod;
	if ((DSTmode == DSTon) ||
	    (DSTmode == DSTmaybe && localtime(&julian)->tm_isdst))
	julian -= 60 * 60;
	return (julian);
}


static time_t
DSTcorrect(time_t Start, time_t Future)
{
	time_t	StartDay;
	time_t	FutureDay;

	StartDay = (localtime(&Start)->tm_hour + 1) % 24;
	FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
	return (Future - Start) + (StartDay - FutureDay) * 60L * 60L;
}


static time_t
RelativeDate(time_t Start, time_t DayOrdinal, time_t DayNumber)
{
	struct tm	*tm;
	time_t	now;

	now = Start;
	tm = localtime(&now);
	now += SECSPERDAY * ((DayNumber - tm->tm_wday + 7) % 7);
	now += 7 * SECSPERDAY * (DayOrdinal <= 0 ? DayOrdinal : DayOrdinal - 1);
	return DSTcorrect(Start, now);
}


static time_t
RelativeMonth(time_t Start, time_t RelMonth)
{
	struct tm	*tm;
	time_t	Month;
	time_t	Year;

	if (RelMonth == 0)
		return (0);
	tm = localtime(&Start);
	Month = 12 * (tm->tm_year + 1900) + tm->tm_mon + RelMonth;
	Year = Month / 12;
	Month = Month % 12 + 1;
	return DSTcorrect(Start,
	    Convert(Month, (time_t)tm->tm_mday, Year,
	    (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
	    MER24, DSTmaybe));
}


static int
lookup(char *buff)
{
	size_t		len;
	char		*p, *q;
	int		i, abbrev;
	const TABLE	*tp;

	/* Make it lowercase. */
	for (p = buff; *p; p++)
		if (isupper(*p))
			*p = tolower(*p);

	if (strcmp(buff, "am") == 0 || strcmp(buff, "a.m.") == 0) {
		yylval.Meridian = MERam;
		return (tMERIDIAN);
	}
	if (strcmp(buff, "pm") == 0 || strcmp(buff, "p.m.") == 0) {
		yylval.Meridian = MERpm;
		return (tMERIDIAN);
	}

	len = strlen(buff);
	/* See if we have an abbreviation for a month. */
	if (len == 3)
		abbrev = 1;
	else if (len == 4 && buff[3] == '.') {
		abbrev = 1;
		buff[3] = '\0';
		--len;
	} else
		abbrev = 0;

	for (tp = MonthDayTable; tp->name; tp++) {
		if (abbrev) {
			if (strncmp(buff, tp->name, 3) == 0) {
				yylval.Number = tp->value;
				return (tp->type);
			}
		} else if (strcmp(buff, tp->name) == 0) {
			yylval.Number = tp->value;
			return (tp->type);
		}
	}

	for (tp = TimezoneTable; tp->name; tp++)
		if (strcmp(buff, tp->name) == 0) {
			yylval.Number = tp->value;
			return (tp->type);
		}

	if (strcmp(buff, "dst") == 0)
		return (tDST);

	for (tp = UnitsTable; tp->name; tp++)
		if (strcmp(buff, tp->name) == 0) {
			yylval.Number = tp->value;
			return (tp->type);
		}

	/* Strip off any plural and try the units table again. */
	if (len != 0 && buff[len - 1] == 's') {
		buff[len - 1] = '\0';
		for (tp = UnitsTable; tp->name; tp++)
			if (strcmp(buff, tp->name) == 0) {
				yylval.Number = tp->value;
				return (tp->type);
			}
		buff[len - 1] = 's';	/* Put back for "this" in OtherTable. */
	}

	for (tp = OtherTable; tp->name; tp++)
		if (strcmp(buff, tp->name) == 0) {
			yylval.Number = tp->value;
			return (tp->type);
		}

	/* Military timezones. */
	if (len == 1 && isalpha(*buff)) {
		for (tp = MilitaryTable; tp->name; tp++)
			if (strcmp(buff, tp->name) == 0) {
				yylval.Number = tp->value;
				return (tp->type);
			}
	}

	/* Drop out any periods and try the timezone table again. */
	for (i = 0, p = q = buff; *q; q++)
		if (*q != '.')
			*p++ = *q;
		else
			i++;
	*p = '\0';
	if (i)
		for (tp = TimezoneTable; tp->name; tp++)
			if (strcmp(buff, tp->name) == 0) {
				yylval.Number = tp->value;
				return (tp->type);
			}

	return (tID);
}


static int
yylex(void)
{
	char	c, *p, buff[20];
	int	count, sign;

	for (;;) {
		while (isspace(*yyInput))
			yyInput++;

		if (isdigit(c = *yyInput) || c == '-' || c == '+') {
			if (c == '-' || c == '+') {
				sign = c == '-' ? -1 : 1;
				if (!isdigit(*++yyInput))
					/* skip the '-' sign */
					continue;
			}
			else
				sign = 0;

			for (yylval.Number = 0; isdigit(c = *yyInput++); )
				yylval.Number = 10 * yylval.Number + c - '0';
			yyInput--;
			if (sign < 0)
				yylval.Number = -yylval.Number;
			return sign ? tSNUMBER : tUNUMBER;
		}

		if (isalpha(c)) {
			for (p = buff; isalpha(c = *yyInput++) || c == '.'; )
				if (p < &buff[sizeof buff - 1])
					*p++ = c;
			*p = '\0';
			yyInput--;
			return lookup(buff);
		}
		if (c != '(')
			return *yyInput++;

		count = 0;
		do {
			c = *yyInput++;
			if (c == '\0')
				return (c);
			if (c == '(')
				count++;
			else if (c == ')')
				count--;
		} while (count > 0);
	}
}

/* Yield A - B, measured in seconds.  */
static long
difftm(struct tm *a, struct tm *b)
{
	int ay = a->tm_year + (YEAR_TMORIGIN - 1);
	int by = b->tm_year + (YEAR_TMORIGIN - 1);
	int days = (
	    /* difference in day of year */
	    a->tm_yday - b->tm_yday
	    /* + intervening leap days */
	    +  ((ay >> 2) - (by >> 2))
	    -  (ay/100 - by/100)
	    +  ((ay/100 >> 2) - (by/100 >> 2))
	    /* + difference in years * 365 */
	    +  (long)(ay-by) * 365);
	return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
	    + (a->tm_min - b->tm_min)) + (a->tm_sec - b->tm_sec));
}

/*
 * rcs_date_parse()
 *
 * Returns the number of seconds since the Epoch corresponding to the date.
 */
time_t
rcs_date_parse(const char *p)
{
	struct tm	gmt, *gmt_ptr, *tm;
	struct timeb	ftz, *now;
	time_t		Start, tod, nowtime;

	yyInput = p;

	now = &ftz;
	(void)time(&nowtime);

	gmt_ptr = gmtime(&nowtime);
	if (gmt_ptr != NULL) {
		/* Make a copy, in case localtime modifies *tm (I think
		 * that comment now applies to *gmt_ptr, but I am too
		 * lazy to dig into how gmtime and locatime allocate the
		 * structures they return pointers to).
		 */
		gmt = *gmt_ptr;
	}

	if (!(tm = localtime(&nowtime)))
		return (-1);

	if (gmt_ptr != NULL)
		ftz.timezone = difftm(&gmt, tm) / 60;

	if (tm->tm_isdst)
		ftz.timezone += 60;

	tm = localtime(&nowtime);
	yyYear = tm->tm_year + 1900;
	yyMonth = tm->tm_mon + 1;
	yyDay = tm->tm_mday;
	yyTimezone = now->timezone;
	yyDSTmode = DSTmaybe;
	yyHour = 0;
	yyMinutes = 0;
	yySeconds = 0;
	yyMeridian = MER24;
	yyRelSeconds = 0;
	yyRelMonth = 0;
	yyHaveDate = 0;
	yyHaveDay = 0;
	yyHaveRel = 0;
	yyHaveTime = 0;
	yyHaveZone = 0;

	if (yyparse() || yyHaveTime > 1 || yyHaveZone > 1 ||
	    yyHaveDate > 1 || yyHaveDay > 1)
		return (-1);

	if (yyHaveDate || yyHaveTime || yyHaveDay) {
		Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes,
		    yySeconds, yyMeridian, yyDSTmode);
		if (Start < 0)
			return (-1);
	} else {
		Start = nowtime;
		if (!yyHaveRel)
			Start -= ((tm->tm_hour * 60L + tm->tm_min) * 60L) +
			    tm->tm_sec;
	}

	Start += yyRelSeconds;
	Start += RelativeMonth(Start, yyRelMonth);

	if (yyHaveDay && !yyHaveDate) {
		tod = RelativeDate(Start, yyDayOrdinal, yyDayNumber);
		Start += tod;
	}

	/* Have to do *something* with a legitimate -1 so it's distinguishable
	 * from the error return value.  (Alternately could set errno on error.)
	 */
	return (Start == -1) ? (0) : (Start);
}

#if defined(TEST)
/* ARGSUSED */
int
main(int argc, char **argv)
{
	char	buff[128];
	time_t	d;

	(void)printf("Enter date, or blank line to exit.\n\t> ");
	(void)fflush(stdout);
	while (fgets(buff, sizeof(buff), stdin) && buff[0]) {
		d = rcs_date_parse(buff);
		if (d == -1)
			(void)printf("Bad format - couldn't convert.\n");
		else
			(void)printf("%s", ctime(&d));
		(void)printf("\t> ");
		(void)fflush(stdout);
	}

	return (0);
}
#endif	/* defined(TEST) */