/*
 *	aegis - project change supervisor
 *	Copyright (C) 1991-1995, 1997-1999, 2002, 2003 Peter Miller;
 *	All rights reserved.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * This code is derived from code which is
 * Copyright (C) 1986 Steven M. Bellovin
 *	Steven Bellovin <smb@cs.unc.edu>
 *
 * MANIFEST: functions to parse dates
 */

%token	AGO
%token	COLON
%token	COMMA
%token	DAY
%token	DAYZONE
%token	ID
%token	JUNK
%token	MERIDIAN
%token	MONTH
%token	MUNIT
%token	NUMBER
%token	SLASH
%token	SUNIT
%token	UNIT
%token	ZONE

%{

#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/ctype.h>
#include <ac/string.h>

#include <gettime.h>
#include <now.h>
#include <str.h>
#include <trace.h>

/*
 * This forward declaration needs to be here so that code generated by
 * Sun's yacc doesn't puke.  I'd move the whole yylex function to here,
 * but then the the code generated by Berkeley's yacc will puke.  Sheesh.
 */
static int yylex(void);

#define daysec (24L * 60L * 60L)

#define AM 1
#define PM 2

#define DAYLIGHT 1
#define STANDARD 2
#define MAYBE    3

#define MAX_ID_LENGTH 20

static	int	timeflag;
static	int	zoneflag;
static	int	dateflag;
static	int	dayflag;
static	int	relflag;
static	time_t	relsec;
static	time_t	relmonth;
static	int	hh;
static	int	mm;
static	int	ss;
static	int	merid;
static	int	day_light_flag;
static	int	dayord;
static	int	dayreq;
static	int	month;
static	int	day;
static	int	year;
static	int	ourzone;
static	const char *lptr;
#define YYSTYPE int
extern	YYSTYPE	yylval;
extern	int	yydebug;


static int mdays[12] =
{
    31, 0, 31,  30, 31, 30,  31, 31, 30,  31, 30, 31
};

#define epoch 1970

int yyparse(void); /* forward */


/*
 * NAME
 *	timeconv - convert a time
 *
 * SYNOPSIS
 *	time_t timeconv(int hh, int mm, int ss, int mer);
 *
 * DESCRIPTION
 *	The timeconv function is used to convert a time
 *	specified in hours minutes and seconds, into seconds past midnight.
 *
 * ARGUMENTS
 *	hh	hours, range depends on the meridian
 *	mm	minutes, 0..59
 *	ss	seconds, 0..59
 *	mer	meridian to use: AM, PM or 24
 *
 * RETURNS
 *	time_t; seconds past midnight; -1 on any error.
 */

static time_t
timeconv(int ahh, int amm, int ass, int mer)
{
    time_t          result;

    /*
     * perform sanity checks on input
     */
    trace(("timeconv(ahh = %d, amm = %d, ass = %d, mer = %d)\n{\n",
	ahh, amm, ass, mer));
    result = -1;
    if (amm < 0 || amm > 59 || ass < 0 || ass > 59)
	goto done;

    /*
     * perform range checks depending on the meridian
     */
    switch (mer)
    {
    case AM:
	if (ahh < 1 || ahh > 12)
    	    goto done;
	if (ahh == 12)
    	    ahh = 0;
	break;

    case PM:
	if (ahh < 1 || ahh > 12)
    	    goto done;
	if (ahh == 12)
    	    ahh = 0;
	ahh += 12;
	break;

    case 24:
	if (ahh < 0 || ahh > 23)
    	    goto done;
	break;

    default:
	goto done;
    }
    result = ((ahh * 60L + amm) * 60L + ass);
done:
    trace(("return %ld;\n", (long)result));
    trace(("}\n"));
    return result;
}


static int
is_a_leap_year(int yy)
{
    return (yy % 4 == 0 && (yy % 100 != 0 || yy % 400 == 0));
}


/*
 * NAME
 *	dateconv - convert a date
 *
 * SYNOPSIS
 *	time_t dateconv(int mm, int dd, int year, int h, int m, int s,
 *		int mer, int zone, int dayflag);
 *
 * DESCRIPTION
 *	The dateconv function may be used to convert a date after the
 *	date string has been taken apart by yyparse.
 *
 * ARGUMENTS
 *	mm	month number, in the range 1..12
 *	year	year number,  in several ranges:
 *		0..37 means 2000..2037
 *		70..99 means 1970..1999
 *		1970..2037 mean themselves.
 *	dd	day of month, in the range 1..max, where max varies for
 *		each month, as per the catchy jingle (except February,
 *		which is a monster).
 *	h	hours since midnight or meridian
 *	m	minutes past hour
 *	s	seconds past minute
 *	mer	meridian, AM or PM.
 *	zone	minutes correction for the time zone.
 *	dayflag	whether to use daylight savings: STANDARD, DAYLIGHT or MAYBE.
 *
 * RETURNS
 *	time_t; the time in seconds past Jan 1 0:00:00 1970 GMT, this will
 *	always be positive or zero; -1 is returned for any error.
 *
 * CAVEAT
 *	The date functions only work between 1970 and 2037,
 *	because 0 is Jan 1 00:00:00 1970 GMT
 *	and (2^31-1) is Jan 19 03:14:07 2038 GMT
 *	hence some if the weir magic number below.
 *
 *	Because -1 is used to represent errors, times before noon Jan 1 1970
 *	in places east of GMT can't always be represented.
 */

static time_t
dateconv(int amm, int dd, int ayear, int h, int m, int s, int mer, int zone,
    int adayflag)
{
    time_t          result;
    time_t	    tod;
    time_t	    jdate;
    int	i;

    /*
     * make corrections for the year
     *
     * If it is 0..99, RFC822 says pick closest century.
     */
    trace(("dateconv(amm = %d, dd = %d, ayear = %d, h = %d, m = %d, "
	"s = %d, mer = %d, zone = %d, adayflag = %d)\n{\n",
	amm, dd, ayear, h, m, s, mer, zone, adayflag));
    result = -1;
    if (ayear < 0)
	ayear = -ayear;
    if (ayear < 38)
	ayear += 2000;
    else if (ayear < 100)
	ayear += 1900;

    /*
     * correct February length once we know the year
     */
    mdays[1] =
	28 + is_a_leap_year(ayear);

    /*
     * perform some sanity checks on the input
     */
    if
    (
	ayear < epoch
    ||
	ayear >= 2038
    ||
	amm < 1
    ||
	amm > 12
    ||
	dd < 1
    ||
	dd > mdays[--amm]
    )
	goto done;

    /*
     * Determine the julian day number of the dd-mm-yy given.
     * Turn it into seconds, and add in the time zone correction.
     */
    jdate = dd - 1;
    for (i = 0; i < amm; i++)
	jdate += mdays[i];
    for (i = epoch; i < ayear; i++)
	jdate += 365 + is_a_leap_year(i);
    jdate *= daysec;
    jdate += zone * 60L;

    /*
     * Determine the time of day.
     * that is, seconds from midnight.
     * Add it into the julian date.
     */
    tod = timeconv(h, m, s, mer);
    if (tod < 0)
	goto done;
    jdate += tod;

    /*
     * Perform daylight savings correction if necessary.
     * (This assumes 1 hour daylite savings, which is probably wrong.)
     */
    if
    (
	adayflag == DAYLIGHT
    ||
	(adayflag == MAYBE && localtime(&jdate)->tm_isdst)
    )
	jdate += -1 * 60 * 60;

    /*
     * there you have it.
     */
    result = jdate;
done:
    trace(("return %ld;\n", (long)result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	daylcorr
 *
 * SYNOPSIS
 *	time_t daylcorr(time_t future, time_t relative_to);
 *
 * DESCRIPTION
 *	The daylcorr function is used to determine the difference in seconds
 *	between two times, taking daylight savings into account.
 *
 * ARGUMENTS
 *	future	- a later time
 *	relative_to - an earlier time
 *
 * RETURNS
 *	time_t; the difference in seconds
 *
 * CAVEAT
 *	Assumes daylight savings is alays an integral number of hours.
 *	This is wrong is Saudi Arabia (time zone changes during the day),
 *	and South Australia (half hour DLS).
 */

static time_t
daylcorr(time_t future, time_t relative_to)
{
    int             fdayl;
    int             nowdayl;
    time_t          result;

    trace(("daylcorr(future = %ld, relative_to = %ld)\n{\n", (long)future,
	(long)relative_to));
    nowdayl = (localtime(&relative_to)->tm_hour + 1) % 24;
    fdayl = (localtime(&future)->tm_hour + 1) % 24;
    result = ((future - relative_to) + 60L * 60L * (nowdayl - fdayl));
    trace(("return %ld;\n", (long)result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	dayconv
 *
 * SYNOPSIS
 *	time_t dayconv(int ord, int day, time_t relative_to);
 *
 * DESCRIPTION
 *	The dayconv function is used to convert a day-of-the-week into
 *	a meaningful time.
 *
 * ARGUMENTS
 *	ord	- the ord'th day from relative_to
 *	day	- which day of the week
 *	relative_to - relative to this
 *
 * RETURNS
 *	time_t; time in seconds from epoch
 */

static time_t
dayconv(int ord, int aday, time_t relative_to)
{
    time_t          tod;
    time_t          result;

    trace(("dayconv(ord = %d, aday = %d, relative_to = %ld)\n{\n",
	ord, aday, (long)relative_to));
    tod = relative_to;
    tod += daysec * ((aday - localtime(&tod)->tm_wday + 7) % 7);
    tod += 7 * daysec * (ord <= 0 ? ord : ord - 1);
    result = daylcorr(tod, relative_to);
    trace(("return %ld;\n", (long)result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	monthadd
 *
 * SYNOPSIS
 *	time_t monthadd(time_t sdate, time_t relmonth);
 *
 * DESCRIPTION
 *	The monthadd function is used to add a given number of
 *	months to a specified time.
 *
 * ARGUMENTS
 *	sdate	- add the months to this
 *	relmonth - add this many months
 *
 * RETURNS
 *	time_t; seconds since the epoch
 */

static time_t
monthadd(time_t sdate, time_t arelmonth)
{
    struct tm       *ltime;
    int             amm;
    int             ayear;
    time_t          result;

    trace(("monthadd(sdate = %ld, arelmonth = %ld)\n{\n",
	(long)sdate, (long)arelmonth));
    if (arelmonth == 0)
	result = 0;
    else
    {
	ltime = localtime(&sdate);
	amm = 12 * (ltime->tm_year + 1900) + ltime->tm_mon + arelmonth;
	ayear = amm / 12;
	amm = amm % 12 + 1;
	result =
	    dateconv
	    (
	       	amm,
	       	ltime->tm_mday,
	       	ayear,
	       	ltime->tm_hour,
	       	ltime->tm_min,
	       	ltime->tm_sec,
	       	24,
	       	ourzone,
	       	MAYBE
	    );
	if (result >= 0)
	    result = daylcorr(result, sdate);
    }
    trace(("return %ld;\n", (long)result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	date_scan
 *
 * SYNOPSIS
 *	time_t date_scan(char *s);
 *
 * DESCRIPTION
 *	The date_scan function is used to scan a string and
 *	return a number of seconds since epoch.
 *
 * ARGUMENTS
 *	s	- string to scan
 *
 * RETURNS
 *	time_t; seconds to epoch, -1 on error.
 *
 * CAVEAT
 *	it isn't psychic
 */

time_t
date_scan(const char *p)
{
    time_t          when;
    struct tm       *lt;
    time_t	    result;
    time_t	    tod;

    /*
     * find time zone info, if not given
     */
    trace(("date_scan(p = \"%s\")\n{\n", p));
    lptr = p;

    /*
     * initialize things
     */
    when = now();
    lt = localtime(&when);
    year = lt->tm_year + 1900;
    month = lt->tm_mon + 1;
    day = lt->tm_mday;
    relsec = 0;
    relmonth = 0;
    timeflag = 0;
    zoneflag = 0;
    dateflag = 0;
    dayflag = 0;
    relflag = 0;
    ourzone = 0;
    day_light_flag = MAYBE;
#ifdef HAVE_tm_zone
    ourzone = -lt->tm_gmtoff/60;
#else
#ifdef HAVE_GETTIMEOFDAY
    {
	struct timeval tv;
	struct timezone tz;
	if (0 == gettimeofday(&tv, &tz))
	{
	    ourzone = tz.tz_minuteswest;
	    /* the tz_timezone field isn't any use */
	}
    }
#endif
#endif
    hh = 0;
    mm = 0;
    ss = 0;
    merid = 24;

    /*
     * parse the string
     */
#ifdef DEBUG
    yydebug = trace_pretest_;
#endif
    trace(("yyparse()\n{\n"));
    result = yyparse();
    trace(("}\n"));
    if (result)
    {
	result = -1;
	goto done;
    }

    /*
     * sanity checks
     */
    result = -1;
    if (timeflag > 1 || zoneflag > 1 || dateflag > 1 || dayflag > 1)
	goto done;

    if (dateflag || timeflag || dayflag)
    {
	result =
    	    dateconv
    	    (
       		month,
       		day,
       		year,
       		hh,
       		mm,
       		ss,
       		merid,
       		ourzone,
       		day_light_flag
    	    );
	if (result < 0)
    	    goto done;
    }
    else
    {
	result = when;
	if (!relflag)
	{
    	    result -=
       		(
	  	    (lt->tm_hour * 60L + lt->tm_min * 60)
       		+
	  	    lt->tm_sec
       		);
	}
    }

    result += relsec;
    relsec = monthadd(result, relmonth);
    if (relsec < 0)
    {
	result = -1;
	goto done;
    }
    result += relsec;

    if (dayflag && !dateflag)
    {
	tod = dayconv(dayord, dayreq, result);
	result += tod;
    }

    /*
     * here for all exits
     */
done:
    trace(("return %ld;\n", (long)result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	date_string - build one
 *
 * SYNOPSIS
 *	char *date_string(time_t when);
 *
 * DESCRIPTION
 *	The date_string function may be used to construct a
 *	string from a given time in seconds.
 *
 *	The string will conform to the RFC822 standard,
 *	which states a definite preference for GMT dates.
 *
 * ARGUMENTS
 *	when	the time to be rendered.
 *
 * RETURNS
 *	Pointer to string containing rendered time.
 *	The contents of this string will remain undisturbed
 *	only until the next call to date_string.
 */

const char *
date_string(time_t when)
{
    struct tm	*tm;
    static char	buffer[32];

    static const char *weekday_name[] =
    {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
    };

    static const char *month_name[] =
    {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    };

    /*
     * break the given time down into components
     *	(RFC1036 likes GMT, remember)
     */
    trace(("date_string(when = %ld)\n{\n", (long)when));
    tm = gmtime(&when);

    /*
     * build the date string
     */
    sprintf
    (
	buffer,
	"%s,%3d %s %4.4d %2.2d:%2.2d:%2.2d GMT",
	weekday_name[tm->tm_wday],
	tm->tm_mday,
	month_name[tm->tm_mon],
	tm->tm_year + 1900,
	tm->tm_hour,
	tm->tm_min,
	tm->tm_sec
    );
    trace(("return \"%s\";\n", buffer));
    trace(("}\n"));
    return buffer;
}


/*
 * NAME
 *	yyerror
 *
 * SYNOPSIS
 *	void yyerror(char *);
 *
 * DESCRIPTION
 *	The yyerror function is invoked by yacc to report
 *	errors, but we just throw it away.
 *
 * ARGUMENTS
 *	s	- error to report
 */

static void
yyerror(const char *s)
{
    trace(("yyerror(s = \"%s\")\n{\n", s));
    trace(("}\n"));
}


/*
 * NAME
 *	yytrace - follow parser actions
 *
 * SYNOPSIS
 *	void yytrace(char *, ...);
 *
 * DESCRIPTION
 *	The yytrace function is used to print the various shifts
 *	and reductions, etc, done by the yacc-generated parser.
 *	lines are accumulated and printed whole,
 *	so as to avoid confusing the trace output.
 *
 * ARGUMENTS
 *	as for printf
 *
 * CAVEAT
 *	only available when DEBUG is defined
 */

#ifdef DEBUG
#define YYDEBUG 1

#define printf yytrace

static void
yytrace(const char *s, ...)
{
    va_list		ap;
    static char	line[1024];
    string_ty	*buffer;
    char		*cp;

    va_start(ap, s);
    buffer = str_vformat(s, ap);
    va_end(ap);
    strcat(line, buffer->str_text);
    str_free(buffer);
    cp = line + strlen(line) - 1;
    if (cp > line && *cp == '\n')
    {
	*cp = 0;
	trace_printf("%s\n", line);
	line[0] = 0;
    }
}

#endif /* DEBUG */

%}

%%

timedate
	: /* empty */
	| timedate item
	| error
		{
			/*
			 * Mostly, this production is unnecessary,
			 * however it silences warnings about unused
			 * labels, etc.
			 */
			return -1;
		}
	;

item
	: TimeSpecification
		{ timeflag++; }
	| TimeZone
		{ zoneflag++; }
	| DateSpecification
		{ dateflag++; }
	| DayOfWeekSpecification
		{ dayflag++; }
	| RelativeSpecification
		{ relflag++; }
	| NumberSpecification
	;

NumberSpecification
	: NUMBER
		{
			if (timeflag && dateflag && !relflag)
				year = $1;
			else
			{
				timeflag++;
				hh = $1 / 100;
				mm = $1 % 100;
				ss = 0;
				merid = 24;
			}
		}
	;

TimeSpecification
	: NUMBER MERIDIAN
		{
			hh = $1;
			mm = 0;
			ss = 0;
			merid = $2;
		}
	| NUMBER COLON NUMBER
		{
			hh = $1;
			mm = $3;
			merid = 24;
		}
	| NUMBER COLON NUMBER MERIDIAN
		{
			hh = $1;
			mm = $3;
			merid = $4;
		}
	| NUMBER COLON NUMBER NUMBER
		{
			hh = $1;
			mm = $3;
			merid = 24;
			day_light_flag = STANDARD;
			$4 = -$4;
			ourzone = $4 % 100 + 60 * $4 / 100;
		}
	| NUMBER COLON NUMBER COLON NUMBER
		{
			hh = $1;
			mm = $3;
			ss = $5;
			merid = 24;
		}
	| NUMBER COLON NUMBER COLON NUMBER MERIDIAN
		{
			hh = $1;
			mm = $3;
			ss = $5;
			merid = $6;
		}
	| NUMBER COLON NUMBER COLON NUMBER NUMBER
		{
			hh = $1;
			mm = $3;
			ss = $5;
			merid = 24;
			day_light_flag = STANDARD;
			$6 = -$6;
			ourzone = $6 % 100 + 60 * $6 / 100;
		}
	;

TimeZone
	: ZONE
		{
			ourzone = $1;
			day_light_flag = STANDARD;
		}
	| DAYZONE
		{
			ourzone = $1;
			day_light_flag = DAYLIGHT;
		}
	;

DayOfWeekSpecification
	: DAY
		{
			dayord = 1;
			dayreq = $1;
		}
	| DAY COMMA
		{
			dayord = 1;
			dayreq = $1;
		}
	| NUMBER DAY
		{
			dayord = $1;
			dayreq = $2;
		}
	;

DateSpecification
	: NUMBER SLASH NUMBER
		{
			if ($1 > 12 && $3 <= 12)
			{
				day = $1;
				month = $3;
			}
			else
			{
				month = $1;
				day = $3;
			}
		}
	| NUMBER SLASH NUMBER SLASH NUMBER
		{
			if ($1 > 12 && $3 <= 12)
			{
				/* european and Australian */
				day = $1;
				month = $3;
			}
			else
			{
				month = $1;
				day = $3;
			}
			year = $5;
		}
	| MONTH NUMBER
		{
			month = $1;
			day = $2;
		}
	| MONTH NUMBER COMMA NUMBER
		{
			month = $1;
			day = $2;
			year = $4;
		}
	| NUMBER MONTH
		{
			month = $2;
			day = $1;
		}
	| NUMBER MONTH NUMBER
		{
			month = $2;
			day = $1;
			year = $3;
		}
	;

RelativeSpecification
	: NUMBER UNIT
		{ relsec +=  60L * $1 * $2; }
	| NUMBER MUNIT
		{ relmonth += $1 * $2; }
	| NUMBER SUNIT
		{ relsec += $1; }
	| UNIT
		{ relsec +=  60L * $1; }
	| MUNIT
		{ relmonth += $1; }
	| SUNIT
		{ relsec++; }
	| RelativeSpecification AGO
		{
			relsec = -relsec;
			relmonth = -relmonth;
		}
	;

%%

/* The following needs to be here so Berkeley Yacc doesn't puke.  */


/*
 * NAME
 *	table - list of known names
 *
 * SYNOPSIS
 *	table_t table[];
 *
 * DESCRIPTION
 *	The table is used to hold the list of known names.
 *	This includes time zone names and days of the week, etc.
 *
 * CAVEAT
 *	It is in English.
 *	It is impossible to have a full list of time zones.
 */

typedef struct table_t table_t;
struct table_t
{
    const char      *name;
    int             type;
    int             value;
};

#define HRMIN(a, b) ((a) * 60 + (b))

static table_t table[] =
{
    { "a",          ZONE,           HRMIN(1, 0),    },
    { "a.c.s.s.t.", DAYZONE,        -HRMIN(9, 30),  },
    { "a.c.s.t.",   ZONE,           -HRMIN(9, 30),  },
    { "a.d.t.",     DAYZONE,        HRMIN(4, 0),    },
    { "a.e.s.s.t.", DAYZONE,        -HRMIN(10, 0),  },
    { "a.e.s.t.",   ZONE,           -HRMIN(10, 0),  },
    { "a.m.",       MERIDIAN,       AM,             },
    { "a.s.t.",     ZONE,           HRMIN(4, 0),    },
    { "a.w.s.t.",   ZONE,           -HRMIN(8, 0),   }, /* (no daylight time
                                                          there, I'm told */
    { "acsst",      DAYZONE,        -HRMIN(9, 30),  }, /* Australian Central
                                                          Summer Time */
    { "acst",       ZONE,           -HRMIN(9, 30),  }, /* Australian Central
                                                          Time */
    { "adt",        DAYZONE,        HRMIN(4, 0),    },
    { "aesst",      DAYZONE,        -HRMIN(10, 0),  }, /* Australian Eastern
                                                          Summer Time */
    { "aest",       ZONE,           -HRMIN(10, 0),  }, /* Australian Eastern
                                                          Time */
    { "ago",        AGO,            1,              },
    { "am",         MERIDIAN,       AM,             },
    { "apr",        MONTH,          4,              },
    { "apr.",       MONTH,          4,              },
    { "april",      MONTH,          4,              },
    { "ast",        ZONE,           HRMIN(4, 0),    },  /* Atlantic */
    { "aug",        MONTH,          8,              },
    { "aug.",       MONTH,          8,              },
    { "august",     MONTH,          8,              },
    { "awst",       ZONE,           -HRMIN(8, 0),   }, /* Australian Western
                                                          Time */
    { "b",          ZONE,           HRMIN(2, 0),    },
    { "b.s.t.",     DAYZONE,        HRMIN(0, 0),    },
    { "bst",        DAYZONE,        HRMIN(0, 0),    }, /* British Summer
                                                          Time*/
    { "c",          ZONE,           HRMIN(3, 0),    },
    { "c.d.t.",     DAYZONE,        HRMIN(6, 0),    },
    { "c.s.t.",     ZONE,           HRMIN(6, 0),    },
    { "cdt",        DAYZONE,        HRMIN(6, 0),    },
    { "cst",        ZONE,           HRMIN(6, 0),    }, /* Central */
    { "d",          ZONE,           HRMIN(4, 0),    },
    { "day",        UNIT,           1 * 24 * 60,    },
    { "days",       UNIT,           1 * 24 * 60,    },
    { "dec",        MONTH,          12,             },
    { "dec.",       MONTH,          12,             },
    { "december",   MONTH,          12,             },
    { "e",          ZONE,           HRMIN(5, 0),    },
    { "e.d.t.",     DAYZONE,        HRMIN(5, 0),    },
    { "e.e.s.t.",   DAYZONE,        HRMIN(0, 0),    },
    { "e.e.t.",     ZONE,           HRMIN(0, 0),    },
    { "e.s.t.",     ZONE,           HRMIN(5, 0),    },
    { "edt",        DAYZONE,        HRMIN(5, 0),    },
    { "eest",       DAYZONE,        HRMIN(0, 0),    }, /* European Eastern
                                                          Summer Time */
    { "eet",        ZONE,           HRMIN(0, 0),    }, /* European Eastern
                                                          Time */
    { "eigth",      NUMBER,         8,              },
    { "eleventh",   NUMBER,         11,             },
    { "est",        ZONE,           HRMIN(5, 0),    }, /* Eastern */
    { "f",          ZONE,           HRMIN(6, 0),    },
    { "feb",        MONTH,          2,              },
    { "feb.",       MONTH,          2,              },
    { "february",   MONTH,          2,              },
    { "fifth",      NUMBER,         5,              },
    { "first",      NUMBER,         1,              },
    { "fortnight",  UNIT,           14 * 24 * 60,   },
    { "fortnights", UNIT,           14 * 24 * 60,   },
    { "fourth",     NUMBER,         4,              },
    { "fri",        DAY,            5,              },
    { "fri.",       DAY,            5,              },
    { "friday",     DAY,            5,              },
    { "g",          ZONE,           HRMIN(7, 0),    },
    { "g.m.t.",     ZONE,           HRMIN(0, 0),    },
    { "gmt",        ZONE,           HRMIN(0, 0),    },
    { "h",          ZONE,           HRMIN(8, 0),    },
    { "h.d.t.",     DAYZONE,        HRMIN(10, 0),   },
    { "h.s.t.",     ZONE,           HRMIN(10, 0),   },
    { "hdt",        DAYZONE,        HRMIN(10, 0),   },
    { "hour",       UNIT,           60,             },
    { "hours",      UNIT,           60,             },
    { "hr",         UNIT,           60,             },
    { "hrs",        UNIT,           60,             },
    { "hst",        ZONE,           HRMIN(10, 0),   }, /* Hawaii */
    { "i",          ZONE,           HRMIN(9, 0),    },
    { "j.s.t.",     ZONE,           -HRMIN(9, 0),   }, /* Japan Standard
                                                          Time */
    { "jan",        MONTH,          1,              },
    { "jan.",       MONTH,          1,              },
    { "january",    MONTH,          1,              },
    { "jst",        ZONE,           -HRMIN(9, 0),   }, /* Japan Standard
                                                          Time */
    { "jul",        MONTH,          7,              },
    { "jul.",       MONTH,          7,              },
    { "july",       MONTH,          7,              },
    { "jun",        MONTH,          6,              },
    { "jun.",       MONTH,          6,              },
    { "june",       MONTH,          6,              },
    { "k",          ZONE,           HRMIN(10, 0),   },
    { "l",          ZONE,           HRMIN(11, 0),   },
    { "last",       NUMBER,         -1,             },
    { "m",          ZONE,           HRMIN(12, 0),   },
    { "m.d.t.",     DAYZONE,        HRMIN(7, 0),    },
    { "m.e.s.t.",   DAYZONE,        -HRMIN(1, 0),   },
    { "m.e.t.",     ZONE,           -HRMIN(1, 0),   },
    { "m.s.t.",     ZONE,           HRMIN(7, 0),    },
    { "mar",        MONTH,          3,              },
    { "mar.",       MONTH,          3,              },
    { "march",      MONTH,          3,              },
    { "may",        MONTH,          5,              },
    { "mdt",        DAYZONE,        HRMIN(7, 0),    },
    { "mest",       DAYZONE,        -HRMIN(1, 0),   }, /* Middle European
                                                          Summer Time */
    { "met",        ZONE,           -HRMIN(1, 0),   }, /* Middle European
                                                          Time */
    { "min",        UNIT,           1,              },
    { "mins",       UNIT,           1,              },
    { "minute",     UNIT,           1,              },
    { "minutes",    UNIT,           1,              },
    { "mon",        DAY,            1,              },
    { "mon.",       DAY,            1,              },
    { "monday",     DAY,            1,              },
    { "month",      MUNIT,          1,              },
    { "months",     MUNIT,          1,              },
    { "mst",        ZONE,           HRMIN(7, 0),    }, /* Mountain */
    { "n",          ZONE,           -HRMIN(1, 0),   },
    { "n.s.t.",     ZONE,           HRMIN(3, 30),   },
    { "next",       NUMBER,         2,              },
    { "ninth",      NUMBER,         9,              },
    { "nov",        MONTH,          11,             },
    { "nov.",       MONTH,          11,             },
    { "november",   MONTH,          11,             },
    { "now",        UNIT,           0,              },
    { "nst",        ZONE,           HRMIN(3, 30),   }, /* Newfoundland */
    { "o",          ZONE,           -HRMIN(2, 0),   },
    { "oct",        MONTH,          10,             },
    { "oct.",       MONTH,          10,             },
    { "october",    MONTH,          10,             },
    { "p",          ZONE,           -HRMIN(3, 0),   },
    { "p.d.t.",     DAYZONE,        HRMIN(8, 0),    },
    { "p.m.",       MERIDIAN,       PM,             },
    { "p.s.t.",     ZONE,           HRMIN(8, 0),    },
    { "pdt",        DAYZONE,        HRMIN(8, 0),    },
    { "pm",         MERIDIAN,       PM,             },
    { "pst",        ZONE,           HRMIN(8, 0),    }, /* Pacific */
    { "q",          ZONE,           -HRMIN(4, 0),   },
    { "r",          ZONE,           -HRMIN(5, 0),   },
    { "s",          ZONE,           -HRMIN(6, 0),   },
    { "sat",        DAY,            6,              },
    { "sat.",       DAY,            6,              },
    { "saturday",   DAY,            6,              },
    { "sec",        SUNIT,          1,              },
    { "second",     SUNIT,          1,              },
    { "seconds",    SUNIT,          1,              },
    { "secs",       SUNIT,          1,              },
    { "sep",        MONTH,          9,              },
    { "sep.",       MONTH,          9,              },
    { "sept",       MONTH,          9,              },
    { "sept.",      MONTH,          9,              },
    { "september",  MONTH,          9,              },
    { "seventh",    NUMBER,         7,              },
    { "sixth",      NUMBER,         6,              },
    { "sun",        DAY,            0,              },
    { "sun.",       DAY,            0,              },
    { "sunday",     DAY,            0,              },
    { "t",          ZONE,           -HRMIN(7, 0),   },
    { "tenth",      NUMBER,         10,             },
    { "third",      NUMBER,         3,              },
    { "this",       UNIT,           0,              },
    { "thu",        DAY,            4,              },
    { "thu.",       DAY,            4,              },
    { "thur",       DAY,            4,              },
    { "thur.",      DAY,            4,              },
    { "thurs",      DAY,            4,              },
    { "thurs.",     DAY,            4,              },
    { "thursday",   DAY,            4,              },
    { "today",      UNIT,           0,              },
    { "tomorrow",   UNIT,           1 * 24 * 60,    },
    { "tue",        DAY,            2,              },
    { "tue.",       DAY,            2,              },
    { "tues",       DAY,            2,              },
    { "tues.",      DAY,            2,              },
    { "tuesday",    DAY,            2,              },
    { "twelfth",    NUMBER,         12,             },
    { "u",          ZONE,           -HRMIN(8, 0),   },
    { "u.t.",       ZONE,           HRMIN(0, 0),    },
    { "ut",         ZONE,           HRMIN(0, 0),    },
    { "v",          ZONE,           -HRMIN(9, 0),   },
    { "w",          ZONE,           -HRMIN(10, 0),  },
    { "w.e.s.t.",   DAYZONE,        -HRMIN(2, 0),   },
    { "w.e.t.",     ZONE,           -HRMIN(2, 0),   },
    { "wed",        DAY,            3,              },
    { "wed.",       DAY,            3,              },
    { "wednes",     DAY,            3,              },
    { "wednes.",    DAY,            3,              },
    { "wednesday",  DAY,            3,              },
    { "week",       UNIT,           7 * 24 * 60,    },
    { "weeks",      UNIT,           7 * 24 * 60,    },
    { "west",       DAYZONE,        -HRMIN(2, 0),   }, /* Western European
                                                          Summer Time */
    { "wet",        ZONE,           -HRMIN(2, 0),   }, /* Western European
                                                          Time */
    { "x",          ZONE,           -HRMIN(11, 0),  },
    { "y",          ZONE,           -HRMIN(12, 0),  },
    { "y.d.t.",     DAYZONE,        HRMIN(9, 0),    },
    { "y.s.t.",     ZONE,           HRMIN(9, 0),    },
    { "ydt",        DAYZONE,        HRMIN(9, 0),    },
    { "year",       MUNIT,          12,             },
    { "years",      MUNIT,          12,             },
    { "yesterday",  UNIT,           -1*24*60,       },
    { "yst",        ZONE,           HRMIN(9, 0),    }, /* Yukon */
    { "z",          ZONE,           HRMIN(0, 0),    },
};


/*
 * NAME
 *	lookup - find name
 *
 * SYNOPSIS
 *	int lookup(char *id);
 *
 * DESCRIPTION
 *	The lookup function is used to find a token corresponding to
 *	a given name.
 *
 * ARGUMENTS
 *	id	- name to search for.  Assumes already downcased.
 *
 * RETURNS
 *	int; yacc token, ID if not found.
 */

static int
lookup(char *id)
{
    table_t         *tp;
    int             min;
    int             max;
    int             mid;
    int             cmp;
    int             result;

    /*
     * binary chop the table
     */
    trace(("lookup(id = \"%s\")\n{\n", id));
    result = ID;
    min = 0;
    max = SIZEOF(table) - 1;
    while (min <= max)
    {
	mid = (min + max) / 2;
	tp = table + mid;
	cmp = strcmp(id, tp->name);
	if (!cmp)
	{
	    yylval = tp->value;
	    result = tp->type;
	    break;
	}
	if (cmp < 0)
	    max = mid - 1;
	else
	    min = mid + 1;
    }
    trace(("return %d;\n", result));
    trace(("}\n"));
    return result;
}


/*
 * NAME
 *	yylex - lexical analyser
 *
 * SYNOPSIS
 *	int yylex(void);
 *
 * DESCRIPTION
 *	The yylex function is used to scan the input string
 *	and break it into discrete tokens.
 *
 * RETURNS
 *	int; the yacc token, 0 means the-end.
 */

static int
yylex(void)
{
    int             sign;
    int             c;
    char            *p;
    char            idbuf[MAX_ID_LENGTH];
    int             pcnt;
    int             token;

    trace(("yylex()\n{\n"));
    yylval = 0;
    for (;;)
    {
	/*
	 * get the next input character
	 */
	c = *lptr++;

	/*
	 * action depends on the character
	 */
	switch (c)
	{
	case 0:
	    token = 0;
	    lptr--;
	    break;

	case ' ':
	case '\t':
	    /*
	     * ignore white space
	     */
	    continue;

	case ':':
	    token = COLON;
	    break;

	case ',':
	    token = COMMA;
	    break;

	case '/':
	    token = SLASH;
	    break;

	case '.':
	    /* ignore lonely dots */
	    continue;

	case '-':
	    if (!isdigit((unsigned char)*lptr))
	    {
		/*
		 * ignore lonely '-'s
		 */
		continue;
	    }
	    sign = -1;
	    c = *lptr++;
	    goto number;

	case '+':
	    if (!isdigit((unsigned char)*lptr))
	    {
	       	token = c;
	       	break;
	    }
	    sign = 1;
	    c = *lptr++;
	    goto number;

	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
	    /*
	     * numbers
	     */
	    sign = 1;
	    number:
	    for (;;)
	    {
		yylval = yylval * 10 + c - '0';
		c = *lptr++;
		switch (c)
		{
		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		case '8': case '9':
		    continue;
		}
		break;
	    }
	    yylval *= sign;
	    lptr--;
	    token = NUMBER;
	    break;

	case 'a': case 'b': case 'c': case 'd': case 'e':
	case 'f': case 'g': case 'h': case 'i': case 'j':
	case 'k': case 'l': case 'm': case 'n': case 'o':
	case 'p': case 'q': case 'r': case 's': case 't':
	case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
	case 'A': case 'B': case 'C': case 'D': case 'E':
	case 'F': case 'G': case 'H': case 'I': case 'J':
	case 'K': case 'L': case 'M': case 'N': case 'O':
	case 'P': case 'Q': case 'R': case 'S': case 'T':
	case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
	    /*
	     * name
	     */
	    p = idbuf;
	    for (;;)
	    {
		if (isupper((unsigned char)c))
		    c = tolower(c);
		if (p < idbuf + sizeof(idbuf) - 1)
		    *p++ = c;
		c = *lptr++;
		switch (c)
		{
		case 'a': case 'b': case 'c': case 'd':
		case 'e': case 'f': case 'g': case 'h':
		case 'i': case 'j': case 'k': case 'l':
		case 'm': case 'n': case 'o': case 'p':
		case 'q': case 'r': case 's': case 't':
		case 'u': case 'v': case 'w': case 'x':
		case 'y': case 'z':
		case 'A': case 'B': case 'C': case 'D':
		case 'E': case 'F': case 'G': case 'H':
		case 'I': case 'J': case 'K': case 'L':
		case 'M': case 'N': case 'O': case 'P':
		case 'Q': case 'R': case 'S': case 'T':
		case 'U': case 'V': case 'W': case 'X':
		case 'Y': case 'Z':
		case '.':
		    continue;
		}
		break;
	    }
	    *p = 0;
	    lptr--;
	    token = lookup(idbuf);
	    break;

	case '(':
	    /*
	     * comment
	     */
	    for (pcnt = 1; pcnt > 0; )
	    {
		c = *lptr++;
		switch (c)
		{
		case 0:
		    --lptr;
		    pcnt = 0;
		    break;

		case '(':
		    pcnt++;
		    break;

		case ')':
		    pcnt--;
		    break;
		}
	    }
	    continue;

	default:
	    /*
	     * unrecognosed
	     */
	    token = JUNK;
	    break;
	}
	break;
    }
    trace(("yylval = %d;\n", yylval));
    trace(("return %d;\n", token));
    trace(("}\n"));
    return token;
}
