/*
 * getabsdate - parse almost any absolute date getdate(3) can (& some it can't)
 *
 * NOTE:  In porting this to postgres I condensed 6 files to 3.  So the abstime
 *	  adt is made up of the files nabstime.c dateconv.c and datetok.c
 *	  -mer 6 Feb 1992
 *
 * $Header: /private/postgres/src/utils/adt/RCS/nabstime.c,v 1.4 1992/02/28 05:34:42 mao Exp $
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/timeb.h>
#include "tmp/postgres.h"
#include "utils/nabstime.h"

#define MAXDATEFIELDS 25

#define ISSPACE(c) ((c) == ' ' || (c) == '\n' || (c) == '\t')

/* this is fast but dirty.  note the return's in the middle. */
#define GOBBLE_NUM(cp, c, x, ip) \
	(c) = *(cp)++; \
	if ((c) < '0' || (c) > '9') \
		return -1;		/* missing digit */ \
	(x) = (c) - '0'; \
	(c) = *(cp)++; \
	if ((c) >= '0' && (c) <= '9') { \
		(x) = 10*(x) + (c) - '0'; \
		(c) = *(cp)++; \
	} \
	if ((c) != ':' && (c) != '\0' && !ISSPACE(c)) \
		return -1;		/* missing colon */ \
	*(ip) = (x)			/* N.B.: no semi-colon here */

#define EPOCH 1970
#define DAYS_PER_400YRS	(time_t)146097
#define DAYS_PER_4YRS	(time_t)1461
#define SECS_PER_DAY	86400
#define SECS_PER_HOUR	3600
#define DIVBY4(n) ((n) >> 2)
#define YRNUM(c, y) (DIVBY4(DAYS_PER_400YRS*(c)) + DIVBY4(DAYS_PER_4YRS*(y)))
#define DAYNUM(c,y,mon,d)	(YRNUM((c), (y)) + mdays[mon] + (d))
#define EPOCH_DAYNUM	DAYNUM(19, 69, 10, 1)	/* really January 1, 1970 */


/* definitions for squeezing values into "value" */
#define ABS_SIGNBIT 0200
#define VALMASK 0177
#define NEG(n)		((n)|ABS_SIGNBIT)
#define SIGNEDCHAR(c)	((c)&ABS_SIGNBIT? -((c)&VALMASK): (c))
#define FROMVAL(tp)	(-SIGNEDCHAR((tp)->value) * 10)	/* uncompress */
#define TOVAL(tp, v)	((tp)->value = ((v) < 0? NEG((-(v))/10): (v)/10))
#define IsLeapYear(yr) ((yr%4) == 0)

char nmdays[] = {
	0, 31, 28, 31,  30, 31, 30,  31, 31, 30,  31, 30, 31
};
/* days since start of year. mdays[0] is March, mdays[11] is February */
static short mdays[] = {
	0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337
};

/* exports */
extern int dtok_numparsed;

/*
 * parse and convert absolute date in timestr (the normal interface)
 *
 * Returns the number of seconds since epoch (January 1 1970 GMT)
 */
AbsoluteTime
nabstimein(timestr)
char *timestr;
{
    int tz = 0;
    struct tm date;

    if (IsEpochStr(timestr))
	return EPOCH_ABSTIME;
    if (IsNowStr(timestr))
	return GetCurrentTransactionStartTime();

    return (prsabsdate(timestr, NULL, &date, &tz) < 0) ?
		INVALID_ABSTIME :
		dateconv(&date, tz);
}

int
IsNowStr(tstr)
    char *tstr;
{
    while (ISSPACE(*tstr))
	tstr++;

    return ((tstr[0] == 'n' || tstr[0] == 'N') &&
	    (tstr[1] == 'o' || tstr[1] == 'O') &&
	    (tstr[2] == 'w' || tstr[2] == 'W')); 
}


int
IsEpochStr(tstr)
    char *tstr;
{
    while (ISSPACE(*tstr))
	tstr++;
    return ((tstr[0] == 'e' || tstr[0] == 'E') &&
	    (tstr[1] == 'p' || tstr[1] == 'P') &&
	    (tstr[2] == 'o' || tstr[2] == 'O') &&
	    (tstr[3] == 'c' || tstr[3] == 'C') &&
	    (tstr[4] == 'h' || tstr[4] == 'H'));
}

/*
 * just parse the absolute date in timestr and get back a broken-out date.
 */
int
prsabsdate(timestr, now, tm, tzp)
char *timestr;
struct timeb *now;
register struct tm *tm;
int *tzp;
{
	register int nf;
	char *fields[MAXDATEFIELDS];
	static char delims[] = "- \t\n/,";

	nf = split(timestr, fields, MAXDATEFIELDS, delims+1);
	if (nf > MAXDATEFIELDS)
		return -1;
	if (tryabsdate(fields, nf, now, tm, tzp) < 0) {
		register char *p = timestr;

		/*
		 * could be a DEC-date; glue it all back together, split it
		 * with dash as a delimiter and try again.  Yes, this is a
		 * hack, but so are DEC-dates.
		 */
		while (--nf > 0) {
			while (*p++ != '\0')
				;
			p[-1] = ' ';
		}
		nf = split(timestr, fields, MAXDATEFIELDS, delims);
		if (nf > MAXDATEFIELDS)
			return -1;
		if (tryabsdate(fields, nf, now, tm, tzp) < 0)
			return -1;
	}
	return 0;
}

/*
 * try to parse pre-split timestr as an absolute date
 */
int
tryabsdate(fields, nf, now, tm, tzp)
char *fields[];
int nf;
struct timeb *now;
register struct tm *tm;
int *tzp;
{
	register int i;
	register datetkn *tp;
	register long flg = 0, ty;
	int mer = HR24, bigval = -1;
	struct timeb ftz;

	if (now == NULL) {		/* default to local time (zone) */
		now = &ftz;
		(void) ftime(now);
	}
	*tzp = now->timezone;

	tm->tm_mday = tm->tm_mon = tm->tm_year = -1;	/* mandatory */
	tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
	tm->tm_isdst = 0;
	dtok_numparsed = 0;

	for (i = 0; i < nf; i++) {
		if (fields[i][0] == '\0')
			continue;
		tp = datetoktype(fields[i], &bigval);
		ty = (1L << tp->type) & ~(1L << IGNORE);
		if (flg&ty)
			return -1;		/* repeated type */
		flg |= ty;
		switch (tp->type) {
		case YEAR:
			tm->tm_year = bigval;
			break;
		case DAY:
			tm->tm_mday = bigval;
			break;
		case MONTH:
			tm->tm_mon = tp->value;
			break;
		case TIME:
			if (parsetime(fields[i], tm) < 0)
				return -1;
			break;
		case DTZ:
#if 0
			tm->tm_isdst++;
#endif
			/* FALLTHROUGH */
		case TZ:
			*tzp = FROMVAL(tp);
			break;
		case IGNORE:
			break;
		case AMPM:
			mer = tp->value;
			break;
		default:
			return -1;	/* bad token type: CANTHAPPEN */
		}
	}
	if (tm->tm_year == -1 || tm->tm_mon == -1 || tm->tm_mday == -1)
		return -1;		/* missing component */
	if (mer == PM)
		tm->tm_hour += 12;
	return 0;
}


/* return -1 on failure */
int
parsetime(time, tm)
register char *time;
register struct tm *tm;
{
	register char c;
	register int x;

	tm->tm_sec = 0;
	GOBBLE_NUM(time, c, x, &tm->tm_hour);
	if (c != ':')
		return -1;		/* only hour; too short */
	GOBBLE_NUM(time, c, x, &tm->tm_min);
	if (c != ':')
		return 0;		/* no seconds; okay */
	GOBBLE_NUM(time, c, x, &tm->tm_sec);
	/* this may be considered too strict.  garbage at end of time? */
	return (c == '\0' || ISSPACE(c)? 0: -1);
}


/*
 * split - divide a string into fields, like awk split()
 */
int				/* number of fields, including overflow */
split(string, fields, nfields, sep)
char *string;
char *fields[];			/* list is not NULL-terminated */
int nfields;			/* number of entries available in fields[] */
char *sep;			/* "" white, "c" single char, "ab" [ab]+ */
{
	register char *p = string;
	register char c;			/* latest character */
	register char sepc = sep[0];
	register char sepc2;
	register int fn;
	register char **fp = fields;
	register char *sepp;
	register int trimtrail;

	/* white space */
	if (sepc == '\0') {
		while ((c = *p++) == ' ' || c == '\t')
			continue;
		p--;
		trimtrail = 1;
		sep = " \t";	/* note, code below knows this is 2 long */
		sepc = ' ';
	} else
		trimtrail = 0;
	sepc2 = sep[1];		/* now we can safely pick this up */

	/* catch empties */
	if (*p == '\0')
		return(0);

	/* single separator */
	if (sepc2 == '\0') {
		fn = nfields;
		for (;;) {
			*fp++ = p;
			fn--;
			if (fn == 0)
				break;
			while ((c = *p++) != sepc)
				if (c == '\0')
					return(nfields - fn);
			*(p-1) = '\0';
		}
		/* we have overflowed the fields vector -- just count them */
		fn = nfields;
		for (;;) {
			while ((c = *p++) != sepc)
				if (c == '\0')
					return(fn);
			fn++;
		}
		/* not reached */
	}

	/* two separators */
	if (sep[2] == '\0') {
		fn = nfields;
		for (;;) {
			*fp++ = p;
			fn--;
			while ((c = *p++) != sepc && c != sepc2)
				if (c == '\0') {
					if (trimtrail && **(fp-1) == '\0')
						fn++;
					return(nfields - fn);
				}
			if (fn == 0)
				break;
			*(p-1) = '\0';
			while ((c = *p++) == sepc || c == sepc2)
				continue;
			p--;
		}
		/* we have overflowed the fields vector -- just count them */
		fn = nfields;
		while (c != '\0') {
			while ((c = *p++) == sepc || c == sepc2)
				continue;
			p--;
			fn++;
			while ((c = *p++) != '\0' && c != sepc && c != sepc2)
				continue;
		}
		/* might have to trim trailing white space */
		if (trimtrail) {
			p--;
			while ((c = *--p) == sepc || c == sepc2)
				continue;
			p++;
			if (*p != '\0') {
				if (fn == nfields+1)
					*p = '\0';
				fn--;
			}
		}
		return(fn);
	}

	/* n separators */
	fn = 0;
	for (;;) {
		if (fn < nfields)
			*fp++ = p;
		fn++;
		for (;;) {
			c = *p++;
			if (c == '\0')
				return(fn);
			sepp = sep;
			while ((sepc = *sepp++) != '\0' && sepc != c)
				continue;
			if (sepc != '\0')	/* it was a separator */
				break;
		}
		if (fn < nfields)
			*(p-1) = '\0';
		for (;;) {
			c = *p++;
			sepp = sep;
			while ((sepc = *sepp++) != '\0' && sepc != c)
				continue;
			if (sepc == '\0')	/* it wasn't a separator */
				break;
		}
		p--;
	}

	/* not reached */
}

/*
 * Given an AbsoluteTime return the English text version of the date
 *
 * e.g. January 1 1970
 */
char *
nabstimeout(time)
    AbsoluteTime time;
{
    char *outStr;
    char *tzoneStr;
    struct tm timeVals;
    struct tm *timeValp;
    char month[16];
    char weekday[16];

    outStr = (char *)palloc(64);

    if (time == EPOCH_ABSTIME)
    {
	strcpy(outStr, "epoch");
	return outStr;
    }
    if (time == INVALID_ABSTIME)
    {
	strcpy(outStr, "Invalid Abstime");
	return outStr;
    }

    timeValp = localtime((time_t *)&time);
    MonthNumToStr(timeValp->tm_mon, month);
    WeekdayToStr(timeValp->tm_wday, weekday);

    /*
     *  Dynix 3.0.17.10 and Ultrix WS 2 don't provide tm_zone.
     */

    /*
     * Linux has timezones in an external tzname[], not struct tm. (kai)
     */
#ifdef linux
    tzoneStr = tzname[timeValp->tm_isdst];
#else
#ifdef sequent
    tzoneStr = "";
#else
#ifdef OLD_DEC
    tzoneStr = "";
#else
    tzoneStr = timeValp->tm_zone;
#endif
#endif
#endif

    sprintf(outStr,
	    "%s %s %d %2.2d:%2.2d:%2.2d %d %s",
	    weekday,
	    month,
	    timeValp->tm_mday,
	    timeValp->tm_hour,
	    timeValp->tm_min,
	    timeValp->tm_sec,
	    (timeValp->tm_year >= 69) ? timeValp->tm_year+1900 :
					timeValp->tm_year+2000,
	    tzoneStr);
    return(outStr);
}

int
MonthNumToStr(mnum, mstr)
    int mnum;
    char *mstr;
{
    switch(mnum)
    {
	case 0:
	    strcpy(mstr, "Jan");
	    break;
	case 1:
	    strcpy(mstr, "Feb");
	    break;
	case 2:
	    strcpy(mstr, "Mar");
	    break;
	case 3:
	    strcpy(mstr, "Apr");
	    break;
	case 4:
	    strcpy(mstr, "May");
	    break;
	case 5:
	    strcpy(mstr, "Jun");
	    break;
	case 6:
	    strcpy(mstr, "Jul");
	    break;
	case 7:
	    strcpy(mstr, "Aug");
	    break;
	case 8:
	    strcpy(mstr, "Sep");
	    break;
	case 9:
	    strcpy(mstr, "Oct");
	    break;
	case 10:
	    strcpy(mstr, "Nov");
	    break;
	case 11:
	    strcpy(mstr, "Dec");
	    break;
	default:
	    strcpy(mstr, "Bad Month");
	    break;
    }
    return 1;
}

int
WeekdayToStr(wday, wstr)
    int wday;
    char *wstr;
{
    switch(wday)
    {
	case 0:
	    strcpy(wstr, "Sun");
	    break;
	case 1:
	    strcpy(wstr, "Mon");
	    break;
	case 2:
	    strcpy(wstr, "Tues");
	    break;
	case 3:
	    strcpy(wstr, "Wed");
	    break;
	case 4:
	    strcpy(wstr, "Thurs");
	    break;
	case 5:
	    strcpy(wstr, "Fri");
	    break;
	case 6:
	    strcpy(wstr, "Sat");
	    break;
	default:
	    strcpy(wstr, "Bad Weekday");
	    break;
    }
    return 1;
}

/* turn a (struct tm) and a few variables into a time_t, with range checking */
AbsoluteTime
dateconv(tm, zone)
register struct tm *tm;
int zone;
{
	tm->tm_wday = tm->tm_yday = 0;

	/* validate, before going out of range on some members */
	if (tm->tm_year < 0 || tm->tm_mon < 1 || tm->tm_mon > 12 ||
	    tm->tm_mday < 1 || tm->tm_hour < 0 || tm->tm_hour >= 24 ||
	    tm->tm_min < 0 || tm->tm_min > 59 ||
	    tm->tm_sec < 0 || tm->tm_sec > 59)
		return -1;

	/*
	 * zone should really be -zone, and tz should be set to tp->value, not
	 * -tp->value.  Or the table could be fixed.
	 */
	tm->tm_min += zone;		/* mktime lets it be out of range */

	/* convert to seconds */
	return qmktime(tm);
}


/*
 * near-ANSI qmktime suitable for use by dateconv; not necessarily as paranoid
 * as ANSI requires, and it may not canonicalise the struct tm.  Ignores tm_wday
 * and tm_yday.
 */
time_t
qmktime(tp)
register struct tm *tp;
{
	register int mon = tp->tm_mon;
	register int day = tp->tm_mday, year = tp->tm_year;
	register time_t daynum;
	register int century;
	time_t nrdaynum;

	/* If it was a 2 digit year */
	if (year < 100)
		year += 1900;
	if (year < EPOCH)
		return -1;		/* can't represent early date */

	/*
	 * validate day against days-per-month table, with leap-year
	 * correction
	 */
	if (day > nmdays[mon])
		if (mon != 2 || year % 4 == 0 &&
		    (year % 100 != 0 || year % 400 == 0) && day > 29)
			return -1;	/* day too large for month */

	/* split year into century and year-of-century */
	century = year / 100;
	year %= 100;
	/*
	 * We calculate the day number exactly, assuming the calendar has
	 * always had the current leap year rules.  (The leap year rules are
	 * to compensate for the fact that the Earth's revolution around the
	 * Sun takes 365.2425 days).  We first need to rotate months so March
	 * is 0, since we want the last month to have the reduced number of
	 * days.
	 */
	if (mon > 2)
		mon -= 3;
	else {
		mon += 9;
		if (year == 0) {
			century--;
			year = 99;
		} else
			--year;
	}
	daynum = -EPOCH_DAYNUM + DAYNUM(century, year, mon, day);

	/* convert to seconds */
	nrdaynum = daynum =
		tp->tm_sec + (tp->tm_min +(daynum*24 + tp->tm_hour)*60)*60;

	/* daylight correction */
	if (tp->tm_isdst < 0)		/* unknown; find out */
		tp->tm_isdst = localtime(&nrdaynum)->tm_isdst;
	if (tp->tm_isdst > 0)
		daynum -= 60*60;

	return daynum < 0? -1: daynum;
}

/* Globals */
static int dtok_numparsed;

/* forwards */
extern datetkn datetktbl[];
extern unsigned szdatetktbl;

datetkn *
datetoktype(s, bigvalp)
char *s;
int *bigvalp;
{
	register char *cp = s;
	register char c = *cp;
	static datetkn t;
	register datetkn *tp = &t;

	if (isascii(c) && isdigit(c)) {
		register int len = strlen(cp);

		if (len > 3 && (cp[1] == ':' || cp[2] == ':'))
			tp->type = TIME;
		else {
			if (bigvalp != NULL)
				/* won't fit in tp->value */
				*bigvalp = atoi(cp);
			if (len == 4)
				tp->type = YEAR;
			else if (++dtok_numparsed == 1)
				tp->type = DAY;
			else
				tp->type = YEAR;
		}
	} else if (c == '-' || c == '+') {
		register int val = atoi(cp + 1);
		register int hr =  val / 100;
		register int min = val % 100;

		val = hr*60 + min;
		if (c == '-')
			val = -val;
		tp->type = TZ;
		TOVAL(tp, val);
	} else {
		char lowtoken[TOKMAXLEN+1];
		register char *ltp = lowtoken, *endltp = lowtoken+TOKMAXLEN;

		/* copy to lowtoken to avoid modifying s */
		while ((c = *cp++) != '\0' && ltp < endltp)
			*ltp++ = (isascii(c) && isupper(c)? tolower(c): c);
		*ltp = '\0';
		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
		if (tp == NULL) {
			tp = &t;
			tp->type = IGNORE;
		}
	}
	return tp;
}

/*
 * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
 * is WAY faster than the generic bsearch().
 */
datetkn *
datebsearch(key, base, nel)
register char *key;
register datetkn *base;
unsigned int nel;
{
	register datetkn *last = base + nel - 1, *position;
	register int result;

	while (last >= base) {
		position = base + ((last - base) >> 1);
		result = key[0] - position->token[0];
		if (result == 0) {
			result = strncmp(key, position->token, TOKMAXLEN);
			if (result == 0)
				return position;
		}
		if (result < 0)
			last = position - 1;
		else
			base = position + 1;
	}
	return 0;
}


/*
 * to keep this table reasonably small, we divide the lexval for TZ and DTZ
 * entries by 10 and truncate the text field at MAXTOKLEN characters.
 * the text field is not guaranteed to be NUL-terminated.
 */
static datetkn datetktbl[] = {
/*	text		token	lexval */
	"acsst",	DTZ,	63,		/* Cent. Australia */
	"acst",		TZ,	57,		/* Cent. Australia */
	"adt",		DTZ,	NEG(18),	/* Atlantic Daylight Time */
	"aesst",	DTZ,	66,		/* E. Australia */
	"aest",		TZ,	60,		/* Australia Eastern Std Time */
	"ahst",		TZ,	60,		/* Alaska-Hawaii Std Time */
	"am",		AMPM,	AM,
	"apr",		MONTH,	4,
	"april",	MONTH,	4,
	"ast",		TZ,	NEG(24),	/* Atlantic Std Time (Canada) */
	"at",		IGNORE,	0,		/* "at" (throwaway) */
	"aug",		MONTH,	8,
	"august",	MONTH,	8,
	"awsst",	DTZ,	54,		/* W. Australia */
	"awst",		TZ,	48,		/* W. Australia */
	"bst",		TZ,	6,		/* British Summer Time */
	"bt",		TZ,	18,		/* Baghdad Time */
	"cadt",		DTZ,	63,		/* Central Australian DST */
	"cast",		TZ,	57,		/* Central Australian ST */
	"cat",		TZ,	NEG(60),	/* Central Alaska Time */
	"cct",		TZ,	48,		/* China Coast */
	"cdt",		DTZ,	NEG(30),	/* Central Daylight Time */
	"cet",		TZ,	6,		/* Central European Time */
	"cetdst",	DTZ,	12,		/* Central European Dayl.Time */
	"cst",		TZ,	NEG(36),	/* Central Standard Time */
	"dec",		MONTH,	12,
	"decemb",	MONTH,	12,
	"dnt",		TZ,	6,		/* Dansk Normal Tid */
	"dst",		IGNORE,	0,
	"east",		TZ,	NEG(60),	/* East Australian Std Time */
	"edt",		DTZ,	NEG(24),	/* Eastern Daylight Time */
	"eet",		TZ,	12,		/* East. Europe, USSR Zone 1 */
	"eetdst",	DTZ,	18,		/* Eastern Europe */
	"est",		TZ,	NEG(30),	/* Eastern Standard Time */
	"feb",		MONTH,	2,
	"februa",	MONTH,	2,
	"fri",		IGNORE,	5,
	"friday",	IGNORE,	5,
	"fst",		TZ,	6,		/* French Summer Time */
	"fwt",		DTZ,	12,		/* French Winter Time  */
	"gmt",		TZ,	0,		/* Greenwish Mean Time */
	"gst",		TZ,	60,		/* Guam Std Time, USSR Zone 9 */
	"hdt",		DTZ,	NEG(54),	/* Hawaii/Alaska */
	"hmt",		DTZ,	18,		/* Hellas ? ? */
	"hst",		TZ,	NEG(60),	/* Hawaii Std Time */
	"idle",		TZ,	72,		/* Intl. Date Line, East */
	"idlw",		TZ,	NEG(72),	/* Intl. Date Line, West */
	"ist",		TZ,	12,		/* Israel */
	"it",		TZ,	22,		/* Iran Time */
	"jan",		MONTH,	1,
	"januar",	MONTH,	1,
	"jst",		TZ,	54,		/* Japan Std Time,USSR Zone 8 */
	"jt",		TZ,	45,		/* Java Time */
	"jul",		MONTH,	7,
	"july",		MONTH,	7,
	"jun",		MONTH,	6,
	"june",		MONTH,	6,
	"kst",		TZ,	54,		/* Korea Standard Time */
	"ligt",		TZ,	60,		/* From Melbourne, Australia */
	"mar",		MONTH,	3,
	"march",	MONTH,	3,
	"may",		MONTH,	5,
	"mdt",		DTZ,	NEG(36),	/* Mountain Daylight Time */
	"mest",		DTZ,	12,		/* Middle Europe Summer Time */
	"met",		TZ,	6,		/* Middle Europe Time */
	"metdst",	DTZ,	12,		/* Middle Europe Daylight Time*/
	"mewt",		TZ,	6,		/* Middle Europe Winter Time */
	"mez",		TZ,	6,		/* Middle Europe Zone */
	"mon",		IGNORE,	1,
	"monday",	IGNORE,	1,
	"mst",		TZ,	NEG(42),	/* Mountain Standard Time */
	"mt",		TZ,	51,		/* Moluccas Time */
	"ndt",		DTZ,	NEG(15),	/* Nfld. Daylight Time */
	"nft",		TZ,	NEG(21),	/* Newfoundland Standard Time */
	"nor",		TZ,	6,		/* Norway Standard Time */
	"nov",		MONTH,	11,
	"novemb",	MONTH,	11,
	"nst",		TZ,	NEG(21),	/* Nfld. Standard Time */
	"nt",		TZ,	NEG(66),	/* Nome Time */
	"nzdt",		DTZ,	78,		/* New Zealand Daylight Time */
	"nzst",		TZ,	72,		/* New Zealand Standard Time */
	"nzt",		TZ,	72,		/* New Zealand Time */
	"oct",		MONTH,	10,
	"octobe",	MONTH,	10,
	"on",		IGNORE,	0,		/* "on" (throwaway) */
	"pdt",		DTZ,	NEG(42),	/* Pacific Daylight Time */
	"pm",		AMPM,	PM,
	"pst",		TZ,	NEG(48),	/* Pacific Standard Time */
	"sadt",		DTZ,	63,		/* S. Australian Dayl. Time */
	"sast",		TZ,	57,		/* South Australian Std Time */
	"sat",		IGNORE,	6,
	"saturd",	IGNORE,	6,
	"sep",		MONTH,	9,
	"sept",		MONTH,	9,
	"septem",	MONTH,	9,
	"set",		TZ,	NEG(6),		/* Seychelles Time ?? */
	"sst",		DTZ,	12,		/* Swedish Summer Time */
	"sun",		IGNORE,	0,
	"sunday",	IGNORE,	0,
	"swt",		TZ,	6,		/* Swedish Winter Time  */
	"thu",		IGNORE,	4,
	"thur",		IGNORE,	4,
	"thurs",	IGNORE,	4,
	"thursd",	IGNORE,	4,
	"tue",		IGNORE,	2,
	"tues",		IGNORE,	2,
	"tuesda",	IGNORE,	2,
	"ut",		TZ,	0,
	"utc",		TZ,	0,
	"wadt",		DTZ,	48,		/* West Australian DST */
	"wast",		TZ,	42,		/* West Australian Std Time */
	"wat",		TZ,	NEG(6),		/* West Africa Time */
	"wdt",		DTZ,	54,		/* West Australian DST */
	"wed",		IGNORE,	3,
	"wednes",	IGNORE,	3,
	"weds",		IGNORE,	3,
	"wet",		TZ,	0,		/* Western Europe */
	"wetdst",	DTZ,	6,		/* Western Europe */
	"wst",		TZ,	48,		/* West Australian Std Time */
	"ydt",		DTZ,	NEG(48),	/* Yukon Daylight Time */
	"yst",		TZ,	NEG(54),	/* Yukon Standard Time */
	"zp4",		TZ,	NEG(24),	/* GMT +4  hours. */
	"zp5",		TZ,	NEG(30),	/* GMT +5  hours. */
	"zp6",		TZ,	NEG(36),	/* GMT +6  hours. */
};

#if	0
/*
 * these time zones are orphans, i.e. the name is also used by a more
 * likely-to-appear time zone
 */
	"at",		TZ,	NEG(12),	/* Azores Time */
	"bst",		TZ,	NEG(18),	/* Brazil Std Time */
	"bt",		TZ,	NEG(66),	/* Bering Time */
	"edt",		TZ,	66,		/* Australian Eastern DaylTime*/
	"est",		TZ,	60,		/* Australian Eastern Std Time*/
	"ist",		TZ,	33,		/* Indian Standard Time */
	"nst",		TZ,	51,		/* North Sumatra Time */
	"sst",		TZ,	42,		/* South Sumatra, USSR Zone 6 */
	"sst",		TZ,	48,		/* Singapore Std Time */
	"wet",		TZ,	6,		/* Western European Time */
/* military timezones are deprecated by RFC 1123 section 5.2.14 */
	"a",		TZ,	6,		/* UTC+1h */
	"b",		TZ,	12,		/* UTC+2h */
	"c",		TZ,	18,		/* UTC+3h */
	"d",		TZ,	24,		/* UTC+4h */
	"e",		TZ,	30,		/* UTC+5h */
	"f",		TZ,	36,		/* UTC+6h */
	"g",		TZ,	42,		/* UTC+7h */
	"h",		TZ,	48,		/* UTC+8h */
	"i",		TZ,	54,		/* UTC+9h */
	"k",		TZ,	60,		/* UTC+10h */
	"l",		TZ,	66,		/* UTC+11h */
	"m",		TZ,	72,		/* UTC+12h */
	"n",		TZ,	NEG(6),		/* UTC-1h */
	"o",		TZ,	NEG(12),	/* UTC-2h */
	"p",		TZ,	NEG(18),	/* UTC-3h */
	"q",		TZ,	NEG(24),	/* UTC-4h */
	"r",		TZ,	NEG(30),	/* UTC-5h */
	"s",		TZ,	NEG(36),	/* UTC-6h */
	"t",		TZ,	NEG(42),	/* UTC-7h */
	"u",		TZ,	NEG(48),	/* UTC-8h */
	"v",		TZ,	NEG(54),	/* UTC-9h */
	"w",		TZ,	NEG(60),	/* UTC-10h */
	"x",		TZ,	NEG(66),	/* UTC-11h */
	"y",		TZ,	NEG(72),	/* UTC-12h */
	"z",		TZ,	0,		/* UTC */
#endif

static unsigned int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
 

/*
 *  AbsoluteTimeIsBefore -- true iff time1 is before time2.
 *
 *	Since we store AbsoluteTimes as unsigned quantities, the comparison
 *	below would fail for times before the Unix epoch.  We cast them to
 *	signed quantities for the sake of this comparison, even though it
 *	violates the type-hiding abstraction.
 *
 *	The above comment isn't really valid anymore since the change
 *	to absolute time storage. -mer 15 Feb. 1992
 */

bool
AbsoluteTimeIsBefore(time1, time2)
	AbsoluteTime	time1;
	AbsoluteTime	time2;
{
    Assert(AbsoluteTimeIsValid(time1));
    Assert(AbsoluteTimeIsValid(time2));

    if ((time1 == time2) || (time2 == EPOCH_ABSTIME))
	return false;
    if (time1 == EPOCH_ABSTIME)
	return true;

    return ((bool)((int32) time1 < (int32) time2));
}

bool
AbsoluteTimeIsAfter(time1, time2)
	AbsoluteTime	time1;
	AbsoluteTime	time2;
{
    Assert(AbsoluteTimeIsValid(time1));
    Assert(AbsoluteTimeIsValid(time2));

    if ((time1 == time2) || (time1 == EPOCH_ABSTIME))
	return false;
    if (time2 == EPOCH_ABSTIME)
	return true;

    return ((bool) ((int32) time1 > (int32) time2));
}


/*
 *	abstimeeq	- returns 1, iff arguments are equal
 *	abstimene	- returns 1, iff arguments are not equal
 *	abstimelt	- returns 1, iff t1 less than t2
 *	abstimegt	- returns 1, iff t1 greater than t2
 *	abstimele	- returns 1, iff t1 less than or equal to t2
 *	abstimege	- returns 1, iff t1 greater than or equal to t2
 *
 */
int32
abstimeeq(t1,t2)
	AbsoluteTime	t1,t2;
{ return(t1 == t2); }

int32
abstimene(t1, t2)
	AbsoluteTime	t1,t2;
{ return(t1 != t2); }

int32
abstimelt(t1, t2)
	int32 /* AbsoluteTime */	t1,t2;
{ return(t1 < t2); }

int32
abstimegt(t1, t2)
	int32 /* AbsoluteTime */	t1,t2;
{ return(t1 > t2); }

int32
abstimele(t1, t2)
	int32 /* AbsoluteTime */	t1,t2;
{ return(t1 <= t2); }

int32
abstimege(t1, t2)
	int32 /* AbsoluteTime */	t1,t2;
{ return(t1 >= t2); }


/*
 *	isabstime	- returns 1, iff datestring is of type abstime
 *				(and returns the convertion in brokentime)
 *			  returns 0 for any syntax error
 *			  returns 2 for time 'now'
 *
 */
int
isabstime(datestring, brokentime)
	char	*datestring;
	struct tm	*brokentime;
{
    int tz = 0;
    struct tm dummy;

    if (brokentime == NULL)
	brokentime = &dummy;

    if (IsNowStr(datestring))
	return 2;
    if (prsabsdate(datestring, NULL, brokentime, &tz) >= 0)
	return 1;
    return 0;
}
