[BACK]Return to date.y CVS log [TXT][DIR] Up to [local] / src / usr.bin / rcs

File: [local] / src / usr.bin / rcs / date.y (download)

Revision 1.1, Wed Apr 26 02:55:13 2006 UTC (18 years, 1 month ago) by joris
Branch: MAIN

fork our code we shared between openrcs/cvs into the openrcs dir.

this was starting to become inhuman to maintain without
ugly ugly hacks in the shared code, and it will be easier
to make specific changes for openrcs without touching the
soon-to-be-replaced opencvs code.

%{
/*	$OpenBSD: date.y,v 1.1 2006/04/26 02:55:13 joris 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 "includes.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;
	int n;

	if (isspace(yyInput[0]) || !isprint(yyInput[0]))
		n = asprintf(&str, "%s: unexpected char 0x%02x in date string",
		    s, yyInput[0]);
	else
		n = asprintf(&str, "%s: unexpected %s in date string",
		    s, yyInput);
	if (n == -1)
		return (0);

#if defined(TEST)
	printf("%s", str);
#else
	warnx("%s", str);
#endif
	free(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)
{
	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);
	}

	/* See if we have an abbreviation for a month. */
	if (strlen(buff) == 3)
		abbrev = 1;
	else if (strlen(buff) == 4 && buff[3] == '.') {
		abbrev = 1;
		buff[3] = '\0';
	} 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. */
	i = strlen(buff) - 1;
	if (buff[i] == 's') {
		buff[i] = '\0';
		for (tp = UnitsTable; tp->name; tp++)
			if (strcmp(buff, tp->name) == 0) {
				yylval.Number = tp->value;
				return (tp->type);
			}
		buff[i] = '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 (buff[1] == '\0' && 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	*tm, gmt;
	struct timeb	ftz, *now;
	time_t		Start, tod, nowtime;

	now = NULL;

	yyInput = p;
	if (now == NULL) {
		struct tm *gmt_ptr;

		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;
	}
	else {
		nowtime = now->time;
	}

	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) */