/*
PostgreSQL Data Base Management System (formerly known as Postgres, then
as Postgres95).

Copyright (c) 1994-7 Regents of the University of California

Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without a written agreement
is hereby granted, provided that the above copyright notice and this
paragraph and the following two paragraphs appear in all copies.

IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/

/*-------------------------------------------------------------------------
 *
 * dt.c--
 *	  Functions for the built-in type "dt".
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 *-------------------------------------------------------------------------
 */
#include <time.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <sys/timeb.h>

#include "parsedt.h"

static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
static int	DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
static int DecodeNumber(int flen, char *field,
			 int fmask, int *tmask, struct tm * tm, double *fsec);
static int DecodeNumberField(int len, char *str,
				  int fmask, int *tmask, struct tm * tm, double *fsec);
static int	DecodeSpecial(int field, char *lowtoken, int *val);
static int DecodeTime(char *str, int fmask, int *tmask,
		   struct tm * tm, double *fsec);
static int	DecodeTimezone(char *str, int *tzp);

#define USE_DATE_CACHE 1
#define ROUND_ALL 0

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

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

static const char * const days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", NULL};

/* those three vars are useless, and not even initialized, so
 * I'd rather remove them all (EPP)
 */
int	DateStyle;
bool	EuroDates;
int	CTimeZone;

#define UTIME_MINYEAR (1901)
#define UTIME_MINMONTH (12)
#define UTIME_MINDAY (14)
#define UTIME_MAXYEAR (2038)
#define UTIME_MAXMONTH (01)
#define UTIME_MAXDAY (18)

/* Assumes month in 1..12. Note tm_mon is 0..11 */
#define IS_VALID_UTIME(y,m,d) (((y > UTIME_MINYEAR) \
 || ((y == UTIME_MINYEAR) && ((m > UTIME_MINMONTH) \
  || ((m == UTIME_MINMONTH) && (d >= UTIME_MINDAY))))) \
 && ((y < UTIME_MAXYEAR) \
 || ((y == UTIME_MAXYEAR) && ((m < UTIME_MAXMONTH) \
  || ((m == UTIME_MAXMONTH) && (d <= UTIME_MAXDAY))))))




/*****************************************************************************
 *	 PRIVATE ROUTINES														 *
 *****************************************************************************/

/* definitions for squeezing values into "value" */
#define ABS_SIGNBIT		(char) 0200
#define VALMASK			(char) 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))

/*
 * 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 NULL-terminated.
 */
static datetkn datetktbl[] = {
/*		text			token	lexval */
	{EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
	{"acsst", DTZ, 63},			/* Cent. Australia */
	{"acst", TZ, 57},			/* Cent. Australia */
	{DA_D, ADBC, AD},			/* "ad" for years >= 0 */
	{"abstime", IGNOREFIELD, 0},		/* "abstime" for pre-v6.1 "Invalid
								 * Abstime" */
	{"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 */
	{"allballs", RESERV, DTK_ZULU},		/* 00:00:00 */
	{"am", AMPM, AM},
	{"apr", MONTH, 4},
	{"april", MONTH, 4},
	{"ast", TZ, NEG(24)},		/* Atlantic Std Time (Canada) */
	{"at", IGNOREFIELD, 0},			/* "at" (throwaway) */
	{"aug", MONTH, 8},
	{"august", MONTH, 8},
	{"awsst", DTZ, 54},			/* W. Australia */
	{"awst", TZ, 48},			/* W. Australia */
	{DB_C, ADBC, BC},			/* "bc" for years < 0 */
	{"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 */
	{DCURRENT, RESERV, DTK_CURRENT},	/* "current" is always now */
	{"dec", MONTH, 12},
	{"december", MONTH, 12},
	{"dnt", TZ, 6},				/* Dansk Normal Tid */
	{"dow", RESERV, DTK_DOW},	/* day of week */
	{"doy", RESERV, DTK_DOY},	/* day of year */
	{"dst", DTZMOD, 6},
	{"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 */
	{EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
#if USE_AUSTRALIAN_RULES
	{"est", TZ, 60},			/* Australia Eastern Std Time */
#else
	{"est", TZ, NEG(30)},		/* Eastern Standard Time */
#endif
	{"feb", MONTH, 2},
	{"february", MONTH, 2},
	{"fri", DOW, 5},
	{"friday", DOW, 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,,	est */
	{LATE, RESERV, DTK_LATE},	/* "infinity" reserved for "late time" */
	{INVALID, RESERV, DTK_INVALID},		/* "invalid" reserved for invalid
										 * time */
	{"ist", TZ, 12},			/* Israel */
	{"it", TZ, 22},				/* Iran Time */
	{"jan", MONTH, 1},
	{"january", 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", DOW, 1},
	{"monday", DOW, 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},
	{"november", MONTH, 11},
	{NOW, RESERV, DTK_NOW},		/* current transaction time */
	{"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},
	{"october", MONTH, 10},
	{"on", IGNOREFIELD, 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", DOW, 6},
	{"saturday", DOW, 6},
	{"sep", MONTH, 9},
	{"sept", MONTH, 9},
	{"september", MONTH, 9},
	{"set", TZ, NEG(6)},		/* Seychelles Time ?? */
	{"sst", DTZ, 12},			/* Swedish Summer Time */
	{"sun", DOW, 0},
	{"sunday", DOW, 0},
	{"swt", TZ, 6},				/* Swedish Winter Time	*/
	{"thu", DOW, 4},
	{"thur", DOW, 4},
	{"thurs", DOW, 4},
	{"thursday", DOW, 4},
	{TODAY, RESERV, DTK_TODAY}, /* midnight */
	{TOMORROW, RESERV, DTK_TOMORROW},	/* tomorrow midnight */
	{"tue", DOW, 2},
	{"tues", DOW, 2},
	{"tuesday", DOW, 2},
	{"undefined", RESERV, DTK_INVALID}, /* "undefined" pre-v6.1 invalid
										 * time */
	{"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", DOW, 3},
	{"wednesday", DOW, 3},
	{"weds", DOW, 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 */
	{YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */
	{"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. */
	{"z", RESERV, DTK_ZULU},	/* 00:00:00 */
	{ZULU, RESERV, DTK_ZULU},	/* 00:00:00 */
};

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



#if USE_DATE_CACHE
datetkn    *datecache[MAXDATEFIELDS] = {NULL};

datetkn    *deltacache[MAXDATEFIELDS] = {NULL};

#endif


/*
 * Calendar time to Julian date conversions.
 * Julian date is commonly used in astronomical applications,
 *	since it is numerically accurate and computationally simple.
 * The algorithms here will accurately convert between Julian day
 *	and calendar date for all non-negative Julian days
 *	(i.e. from Nov 23, -4713 on).
 *
 * Ref: Explanatory Supplement to the Astronomical Almanac, 1992.
 *	University Science Books, 20 Edgehill Rd. Mill Valley CA 94941.
 *
 * Use the algorithm by Henry Fliegel, a former NASA/JPL colleague
 *	now at Aerospace Corp. (hi, Henry!)
 *
 * These routines will be used by other date/time packages - tgl 97/02/25
 */

/* Set the minimum year to one greater than the year of the first valid day
 *	to avoid having to check year and day both. - tgl 97/05/08
 */

#define JULIAN_MINYEAR (-4713)
#define JULIAN_MINMONTH (11)
#define JULIAN_MINDAY (23)

#define IS_VALID_JULIAN(y,m,d) ((y > JULIAN_MINYEAR) \
 || ((y == JULIAN_MINYEAR) && ((m > JULIAN_MINMONTH) \
  || ((m == JULIAN_MINMONTH) && (d >= JULIAN_MINDAY)))))

int
date2j(int y, int m, int d)
{
	int			m12 = (m - 14) / 12;

	return ((1461 * (y + 4800 + m12)) / 4 + (367 * (m - 2 - 12 * (m12))) / 12
			- (3 * ((y + 4900 + m12) / 100)) / 4 + d - 32075);
}	/* date2j() */

void
j2date(int jd, int *year, int *month, int *day)
{
	int			j,
				y,
				m,
				d;

	int			i,
				l,
				n;

	l = jd + 68569;
	n = (4 * l) / 146097;
	l -= (146097 * n + 3) / 4;
	i = (4000 * (l + 1)) / 1461001;
	l += 31 - (1461 * i) / 4;
	j = (80 * l) / 2447;
	d = l - (2447 * j) / 80;
	l = j / 11;
	m = (j + 2) - (12 * l);
	y = 100 * (n - 49) + i + l;

	*year = y;
	*month = m;
	*day = d;
	return;
}	/* j2date() */




/*
 * parse and convert date in timestr (the normal interface)
 *
 * Returns the number of seconds since epoch (J2000)
 */

/* ParseDateTime()
 * Break string into tokens based on a date/time context.
 */
int
ParseDateTime(char *timestr, char *lowstr,
			  char **field, int *ftype, int maxfields, int *numfields)
{
	int			nf = 0;
	char	   *cp = timestr;
	char	   *lp = lowstr;

#ifdef DATEDEBUG
	printf("ParseDateTime- input string is %s\n", timestr);
#endif
	/* outer loop through fields */
	while (*cp != '\0')
	{
		field[nf] = lp;

		/* leading digit? then date or time */
		if (isdigit(*cp) || (*cp == '.'))
		{
			*lp++ = *cp++;
			while (isdigit(*cp))
				*lp++ = *cp++;
			/* time field? */
			if (*cp == ':')
			{
				ftype[nf] = DTK_TIME;
				while (isdigit(*cp) || (*cp == ':') || (*cp == '.'))
					*lp++ = *cp++;

			}
			/* date field? allow embedded text month */
			else if ((*cp == '-') || (*cp == '/') || (*cp == '.'))
			{
				ftype[nf] = DTK_DATE;
				while (isalnum(*cp) || (*cp == '-') || (*cp == '/') || (*cp == '.'))
					*lp++ = tolower(*cp++);

			}

			/*
			 * otherwise, number only and will determine year, month, or
			 * day later
			 */
			else
				ftype[nf] = DTK_NUMBER;

		}

		/*
		 * text? then date string, month, day of week, special, or
		 * timezone
		 */
		else if (isalpha(*cp))
		{
			ftype[nf] = DTK_STRING;
			*lp++ = tolower(*cp++);
			while (isalpha(*cp))
				*lp++ = tolower(*cp++);

			/* full date string with leading text month? */
			if ((*cp == '-') || (*cp == '/') || (*cp == '.'))
			{
				ftype[nf] = DTK_DATE;
				while (isdigit(*cp) || (*cp == '-') || (*cp == '/') || (*cp == '.'))
					*lp++ = tolower(*cp++);
			}

			/* skip leading spaces */
		}
		else if (isspace(*cp))
		{
			cp++;
			continue;

			/* sign? then special or numeric timezone */
		}
		else if ((*cp == '+') || (*cp == '-'))
		{
			*lp++ = *cp++;
			/* soak up leading whitespace */
			while (isspace(*cp))
				cp++;
			/* numeric timezone? */
			if (isdigit(*cp))
			{
				ftype[nf] = DTK_TZ;
				*lp++ = *cp++;
				while (isdigit(*cp) || (*cp == ':'))
					*lp++ = *cp++;

				/* special? */
			}
			else if (isalpha(*cp))
			{
				ftype[nf] = DTK_SPECIAL;
				*lp++ = tolower(*cp++);
				while (isalpha(*cp))
					*lp++ = tolower(*cp++);

				/* otherwise something wrong... */
			}
			else
				return -1;

			/* ignore punctuation but use as delimiter */
		}
		else if (ispunct(*cp))
		{
			cp++;
			continue;

		}
		else
			return -1;

		/* force in a delimiter */
		*lp++ = '\0';
		nf++;
		if (nf > MAXDATEFIELDS)
			return -1;
#ifdef DATEDEBUG
		printf("ParseDateTime- set field[%d] to %s type %d\n", (nf - 1), field[nf - 1], ftype[nf - 1]);
#endif
	}

	*numfields = nf;

	return 0;
}	/* ParseDateTime() */


/* DecodeDateTime()
 * Interpret previously parsed fields for general date and time.
 * Return 0 if full date, 1 if only time, and -1 if problems.
 *		External format(s):
 *				"<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
 *				"Fri Feb-7-1997 15:23:27"
 *				"Feb-7-1997 15:23:27"
 *				"2-7-1997 15:23:27"
 *				"1997-2-7 15:23:27"
 *				"1997.038 15:23:27"		(day of year 1-366)
 *		Also supports input in compact time:
 *				"970207 152327"
 *				"97038 152327"
 *
 * Use the system-provided functions to get the current time zone
 *	if not specified in the input string.
 * If the date is outside the time_t system-supported time range,
 *	then assume GMT time zone. - tgl 97/05/27
 */
int
DecodeDateTime(char **field, int *ftype, int nf,
			   int *dtype, struct tm * tm, double *fsec, int *tzp)
{
	int			fmask = 0,
				tmask,
				type;
	int			i;
	int			flen,
				val;
	int			mer = HR24;
	int			bc = FALSE;

	*dtype = DTK_DATE;
	tm->tm_hour = 0;
	tm->tm_min = 0;
	tm->tm_sec = 0;
	*fsec = 0;
	tm->tm_isdst = -1;			/* don't know daylight savings time status
								 * apriori */
	if (tzp != NULL)
		*tzp = 0;

	for (i = 0; i < nf; i++)
	{
#ifdef DATEDEBUG
		printf("DecodeDateTime- field[%d] is %s (type %d)\n", i, field[i], ftype[i]);
#endif
		switch (ftype[i])
		{
			case DTK_DATE:
				if (DecodeDate(field[i], fmask, &tmask, tm) != 0)
					return -1;
				break;

			case DTK_TIME:
				if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
					return -1;

				/*
				 * check upper limit on hours; other limits checked in
				 * DecodeTime()
				 */
				if (tm->tm_hour > 23)
					return -1;
				break;

			case DTK_TZ:
				if (tzp == NULL)
					return -1;
				if (DecodeTimezone(field[i], tzp) != 0)
					return -1;
				tmask = DTK_M(TZ);
				break;

			case DTK_NUMBER:
				flen = strlen(field[i]);

				if (flen > 4)
				{
					if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec) != 0)
						return -1;

				}
				else
				{
					if (DecodeNumber(flen, field[i], fmask, &tmask, tm, fsec) != 0)
						return -1;
				}
				break;

			case DTK_STRING:
			case DTK_SPECIAL:
				type = DecodeSpecial(i, field[i], &val);
#ifdef DATEDEBUG
				printf("DecodeDateTime- special field[%d] %s type=%d value=%d\n", i, field[i], type, val);
#endif
				if (type == IGNOREFIELD)
					continue;

				tmask = DTK_M(type);
				switch (type)
				{
					case RESERV:
#ifdef DATEDEBUG
						printf("DecodeDateTime- RESERV field %s value is %d\n", field[i], val);
#endif
						switch (val)
						{

							default:
								*dtype = val;
						}

						break;

					case MONTH:
#ifdef DATEDEBUG
						printf("DecodeDateTime- month field %s value is %d\n", field[i], val);
#endif
                        /* tm_mon is 0->11, so need to subtract one from value in table */
						tm->tm_mon = val-1;
						break;

						/*
						 * daylight savings time modifier (solves "MET
						 * DST" syntax)
						 */
					case DTZMOD:
						tmask |= DTK_M(DTZ);
						tm->tm_isdst = 1;
						if (tzp == NULL)
							return -1;
						*tzp += val * 60;
						break;

					case DTZ:

						/*
						 * set mask for TZ here _or_ check for DTZ later
						 * when getting default timezone
						 */
						tmask |= DTK_M(TZ);
						tm->tm_isdst = 1;
						if (tzp == NULL)
							return -1;
						*tzp = val * 60;
						break;

					case TZ:
						tm->tm_isdst = 0;
						if (tzp == NULL)
							return -1;
						*tzp = val * 60;
						break;

					case IGNOREFIELD:
						break;

					case AMPM:
						mer = val;
						break;

					case ADBC:
						bc = (val == BC);
						break;

					case DOW:
						tm->tm_wday = val;
						break;

					default:
						return -1;
				}
				break;

			default:
				return -1;
		}

#ifdef DATEDEBUG
		printf("DecodeDateTime- field[%d] %s (%08x/%08x) value is %d\n",
			   i, field[i], fmask, tmask, val);
#endif

		if (tmask & fmask)
			return -1;
		fmask |= tmask;
	}

	/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
	if (bc)
		tm->tm_year = -(tm->tm_year - 1);

	if ((mer != HR24) && (tm->tm_hour > 12))
		return -1;
	if ((mer == AM) && (tm->tm_hour == 12))
		tm->tm_hour = 0;
	else if ((mer == PM) && (tm->tm_hour != 12))
		tm->tm_hour += 12;

    /* If parsing a time string into a date, all date parts are unset.
       Win2k defaults these to 30 dec, 1899 so:                        */
    if (tm->tm_year == 0 && tm->tm_mon == 0 && tm->tm_mday == 0 && fmask == DTK_TIME_M) {
        tm->tm_year  = 1899;
        tm->tm_mon   = 11; /* December, as tm_mon is 0..11 */
        tm->tm_mday  = 30;
    }


#ifdef DATEDEBUG
	printf("DecodeDateTime- mask %08x (%08x)", fmask, DTK_DATE_M);
	printf(" set y%04d m%02d d%02d", tm->tm_year, (tm->tm_mon+1), tm->tm_mday);
	printf(" %02d:%02d:%02d\n", tm->tm_hour, tm->tm_min, tm->tm_sec);
#endif

	if ((*dtype == DTK_DATE) && ((fmask & DTK_DATE_M) != DTK_DATE_M))
		return ((fmask & DTK_TIME_M) == DTK_TIME_M) ? 1 : -1;

	/* timezone not specified? then find local timezone if possible */
	if ((*dtype == DTK_DATE) && ((fmask & DTK_DATE_M) == DTK_DATE_M)
		&& (tzp != NULL) && (!(fmask & DTK_M(TZ))))
	{

		/*
		 * daylight savings time modifier but no standard timezone? then
		 * error
		 */
		if (fmask & DTK_M(DTZMOD))
			return -1;

		if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon+1, tm->tm_mday))
		{
			/* FIXME: The code below is not correct */
#if 0 /* defined(USE_POSIX_TIME) */
			tm->tm_year -= 1900;
			tm->tm_mon -= 1;
			tm->tm_isdst = -1;
			mktime(tm);
			tm->tm_year += 1900;
			tm->tm_mon += 1;

#if 0 /* defined(HAVE_INT_TIMEZONE) */
			*tzp = ((tm->tm_isdst > 0) ? (timezone - 3600) : timezone);

#else							/* !HAVE_INT_TIMEZONE */
			*tzp = -(tm->tm_gmtoff);	/* tm_gmtoff is Sun/DEC-ism */
#endif

#else							/* !USE_POSIX_TIME */
			*tzp = CTimeZone;
#endif
		}
		else
		{
			tm->tm_isdst = 0;
			*tzp = 0;
		}
	}

	return 0;
}	/* DecodeDateTime() */


/* DecodeTimeOnly()
 * Interpret parsed string as time fields only.
 */
int
DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct tm * tm, double *fsec)
{
	int			fmask,
				tmask,
				type;
	int			i;
	int			flen,
				val;
	int			mer = HR24;

	*dtype = DTK_TIME;
	tm->tm_hour = 0;
	tm->tm_min = 0;
	tm->tm_sec = 0;
	tm->tm_isdst = -1;			/* don't know daylight savings time status
								 * apriori */
	*fsec = 0;

	fmask = DTK_DATE_M;

	for (i = 0; i < nf; i++)
	{
#ifdef DATEDEBUG
		printf("DecodeTimeOnly- field[%d] is %s (type %d)\n", i, field[i], ftype[i]);
#endif
		switch (ftype[i])
		{
			case DTK_TIME:
				if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
					return -1;
				break;

			case DTK_NUMBER:
				flen = strlen(field[i]);

				if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec) != 0)
					return -1;
				break;

			case DTK_STRING:
			case DTK_SPECIAL:
				type = DecodeSpecial(i, field[i], &val);
#ifdef DATEDEBUG
				printf("DecodeTimeOnly- special field[%d] %s type=%d value=%d\n", i, field[i], type, val);
#endif
				if (type == IGNOREFIELD)
					continue;

				tmask = DTK_M(type);
				switch (type)
				{
					case RESERV:
#ifdef DATEDEBUG
						printf("DecodeTimeOnly- RESERV field %s value is %d\n", field[i], val);
#endif
						switch (val)
						{

							default:
								return -1;
						}

						break;

					case IGNOREFIELD:
						break;

					case AMPM:
						mer = val;
						break;

					default:
						return -1;
				}
				break;

			default:
				return -1;
		}

		if (tmask & fmask)
			return -1;
		fmask |= tmask;

#ifdef DATEDEBUG
		printf("DecodeTimeOnly- field[%d] %s value is %d\n", i, field[i], val);
#endif
	}

#ifdef DATEDEBUG
	printf("DecodeTimeOnly- mask %08x (%08x)", fmask, DTK_TIME_M);
	printf(" %02d:%02d:%02d (%f)\n", tm->tm_hour, tm->tm_min, tm->tm_sec, *fsec);
#endif

	if ((mer != HR24) && (tm->tm_hour > 12))
		return -1;
	if ((mer == AM) && (tm->tm_hour == 12))
		tm->tm_hour = 0;
	else if ((mer == PM) && (tm->tm_hour != 12))
		tm->tm_hour += 12;

	if ((fmask & DTK_TIME_M) != DTK_TIME_M)
		return -1;

	return 0;
}	/* DecodeTimeOnly() */


/* DecodeDate()
 * Decode date string which includes delimiters.
 * Insist on a complete set of fields.
 */
static int
DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
{
	double		fsec;

	int			nf = 0;
	int			i,
				len;
	int			type,
				val,
				dmask = 0;
	char	   *field[MAXDATEFIELDS];

	/* parse this string... */
	while ((*str != '\0') && (nf < MAXDATEFIELDS))
	{
		/* skip field separators */
		while (!isalnum(*str))
			str++;

		field[nf] = str;
		if (isdigit(*str))
		{
			while (isdigit(*str))
				str++;
		}
		else if (isalpha(*str))
		{
			while (isalpha(*str))
				str++;
		}

		if (*str != '\0')
			*str++ = '\0';
		nf++;
	}

	/* don't allow too many fields */
	if (nf > 3)
		return -1;

	*tmask = 0;

	/* look first for text fields, since that will be unambiguous month */
	for (i = 0; i < nf; i++)
	{
		if (isalpha(*field[i]))
		{
			type = DecodeSpecial(i, field[i], &val);
			if (type == IGNOREFIELD)
				continue;

			dmask = DTK_M(type);
			switch (type)
			{
				case MONTH:
#ifdef DATEDEBUG
					printf("DecodeDate- month field %s value is %d\n", field[i], val);
#endif
                    /* tm_mon is 0->11, so need to subtract one from value in table */
					tm->tm_mon = val-1;
					break;

				default:
#ifdef DATEDEBUG
					printf("DecodeDate- illegal field %s value is %d\n", field[i], val);
#endif
					return -1;
			}
			if (fmask & dmask)
				return -1;

			fmask |= dmask;
			*tmask |= dmask;

			/* mark this field as being completed */
			field[i] = NULL;
		}
	}

	/* now pick up remaining numeric fields */
	for (i = 0; i < nf; i++)
	{
		if (field[i] == NULL)
			continue;

		if ((len = strlen(field[i])) <= 0)
			return -1;

		if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec) != 0)
			return -1;

		if (fmask & dmask)
			return -1;

		fmask |= dmask;
		*tmask |= dmask;
	}

	return 0;
}	/* DecodeDate() */


/* DecodeTime()
 * Decode time string which includes delimiters.
 * Only check the lower limit on hours, since this same code
 *	can be used to represent time spans.
 */
static int
DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
{
	char	   *cp;

	*tmask = DTK_TIME_M;

	tm->tm_hour = strtol(str, &cp, 10);
	if (*cp != ':')
		return -1;
	str = cp + 1;
	tm->tm_min = strtol(str, &cp, 10);
	if (*cp == '\0')
	{
		tm->tm_sec = 0;
		*fsec = 0;

	}
	else if (*cp != ':')
	{
		return -1;

	}
	else
	{
		str = cp + 1;
		tm->tm_sec = strtol(str, &cp, 10);
		if (*cp == '\0')
			*fsec = 0;
		else if (*cp == '.')
		{
			str = cp;
			*fsec = strtod(str, &cp);
			if (cp == str)
				return -1;
		}
		else
			return -1;
	}

	/* do a sanity check */
	if ((tm->tm_hour < 0)
		|| (tm->tm_min < 0) || (tm->tm_min > 59)
		|| (tm->tm_sec < 0) || (tm->tm_sec > 59))
		return -1;

	return 0;
}	/* DecodeTime() */


/* DecodeNumber()
 * Interpret numeric field as a date value in context.
 */
static int
DecodeNumber(int flen, char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
{
	int			val;
	char	   *cp;

	*tmask = 0;

	val = strtol(str, &cp, 10);
	if (cp == str)
		return -1;
	if (*cp == '.')
	{
		*fsec = strtod(cp, &cp);
		if (*cp != '\0')
			return -1;
	}

#ifdef DATEDEBUG
	printf("DecodeNumber- %s is %d fmask=%08x tmask=%08x\n", str, val, fmask, *tmask);
#endif

	/* enough digits to be unequivocal year? */
	if (flen == 4)
	{
#ifdef DATEDEBUG
		printf("DecodeNumber- match %d (%s) as year\n", val, str);
#endif
		*tmask = DTK_M(YEAR);

		/* already have a year? then see if we can substitute... */
		if (fmask & DTK_M(YEAR))
		{
			if ((!(fmask & DTK_M(DAY)))
				&& ((tm->tm_year >= 1) && (tm->tm_year <= 31)))
			{
#ifdef DATEDEBUG
				printf("DecodeNumber- misidentified year previously; swap with day %d\n", tm->tm_mday);
#endif
				tm->tm_mday = tm->tm_year;
				*tmask = DTK_M(DAY);
			}
		}

		tm->tm_year = val;

		/* special case day of year? */
	}
	else if ((flen == 3) && (fmask & DTK_M(YEAR))
			 && ((val >= 1) && (val <= 366)))
	{
		*tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
		tm->tm_yday = val;
		j2date((date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1),
			   &tm->tm_year, &tm->tm_mon, &tm->tm_mday);

		/* already have year? then could be month */
	}
	else if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(MONTH)))
			 && ((val >= 1) && (val <= 12)))
	{
#ifdef DATEDEBUG
		printf("DecodeNumber- match %d (%s) as month\n", val, str);
#endif
		*tmask = DTK_M(MONTH);
        /* tm_mon is 0..11 */
		tm->tm_mon = val-1;

		/* no year and EuroDates enabled? then could be day */
	}
	else if ((EuroDates || (fmask & DTK_M(MONTH)))
			 && (!(fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY)))
			 && ((val >= 1) && (val <= 31)))
	{
#ifdef DATEDEBUG
		printf("DecodeNumber- match %d (%s) as day\n", val, str);
#endif
		*tmask = DTK_M(DAY);
		tm->tm_mday = val;

	}
	else if ((!(fmask & DTK_M(MONTH)))
			 && ((val >= 1) && (val <= 12)))
	{
#ifdef DATEDEBUG
		printf("DecodeNumber- (2) match %d (%s) as month\n", val, str);
#endif
		*tmask = DTK_M(MONTH);
        /* tm_mon is 0..11 */
		tm->tm_mon = val-1;

	}
	else if ((!(fmask & DTK_M(DAY)))
			 && ((val >= 1) && (val <= 31)))
	{
#ifdef DATEDEBUG
		printf("DecodeNumber- (2) match %d (%s) as day\n", val, str);
#endif
		*tmask = DTK_M(DAY);
		tm->tm_mday = val;

	}
	else if (!(fmask & DTK_M(YEAR)))
	{
#ifdef DATEDEBUG
		printf("DecodeNumber- (2) match %d (%s) as year\n", val, str);
#endif
		*tmask = DTK_M(YEAR);
		tm->tm_year = val;
		if (tm->tm_year < 70)
			tm->tm_year += 2000;
		else if (tm->tm_year < 100)
			tm->tm_year += 1900;

	}
	else
		return -1;

	return 0;
}	/* DecodeNumber() */


/* DecodeNumberField()
 * Interpret numeric string as a concatenated date field.
 */
static int
DecodeNumberField(int len, char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
{
	char	   *cp;

	/* yyyymmdd? */
	if (len == 8)
	{
#ifdef DATEDEBUG
		printf("DecodeNumberField- %s is 8 character date fmask=%08x tmask=%08x\n", str, fmask, *tmask);
#endif

		*tmask = DTK_DATE_M;

		tm->tm_mday = atoi(str + 6);
		*(str + 6) = '\0';
		tm->tm_mon = atoi(str + 4) - 1;  /* tm_mon is 0..11 */
		*(str + 4) = '\0';
		tm->tm_year = atoi(str + 0);

		/* yymmdd or hhmmss? */
	}
	else if (len == 6)
	{
#ifdef DATEDEBUG
		printf("DecodeNumberField- %s is 6 characters fmask=%08x tmask=%08x\n", str, fmask, *tmask);
#endif
		if (fmask & DTK_DATE_M)
		{
#ifdef DATEDEBUG
			printf("DecodeNumberField- %s is time field fmask=%08x tmask=%08x\n", str, fmask, *tmask);
#endif
			*tmask = DTK_TIME_M;
			tm->tm_sec = atoi(str + 4);
			*(str + 4) = '\0';
			tm->tm_min = atoi(str + 2);
			*(str + 2) = '\0';
			tm->tm_hour = atoi(str + 0);

		}
		else
		{
#ifdef DATEDEBUG
			printf("DecodeNumberField- %s is date field fmask=%08x tmask=%08x\n", str, fmask, *tmask);
#endif
			*tmask = DTK_DATE_M;
			tm->tm_mday = atoi(str + 4);
			*(str + 4) = '\0';
			tm->tm_mon = atoi(str + 2) - 1; /* tm_mon is 0..11 */
			*(str + 2) = '\0';
			tm->tm_year = atoi(str + 0);
		}

	}
	else if (strchr(str, '.') != NULL)
	{
#ifdef DATEDEBUG
		printf("DecodeNumberField- %s is time field fmask=%08x tmask=%08x\n", str, fmask, *tmask);
#endif
		*tmask = DTK_TIME_M;
		tm->tm_sec = strtod((str + 4), &cp);
		if (cp == (str + 4))
			return -1;
		if (*cp == '.')
			*fsec = strtod(cp, NULL);
		*(str + 4) = '\0';
		tm->tm_min = strtod((str + 2), &cp);
		*(str + 2) = '\0';
		tm->tm_hour = strtod((str + 0), &cp);

	}
	else
		return -1;

	return 0;
}	/* DecodeNumberField() */


/* DecodeTimezone()
 * Interpret string as a numeric timezone.
 */
static int
DecodeTimezone(char *str, int *tzp)
{
	int			tz;
	int			hr,
				min;
	char	   *cp;
	int			len;

	/* assume leading character is "+" or "-" */
	hr = strtol((str + 1), &cp, 10);

	/* explicit delimiter? */
	if (*cp == ':')
	{
		min = strtol((cp + 1), &cp, 10);

		/* otherwise, might have run things together... */
	}
	else if ((*cp == '\0') && ((len = strlen(str)) > 3))
	{
		min = strtol((str + len - 2), &cp, 10);
		*(str + len - 2) = '\0';
		hr = strtol((str + 1), &cp, 10);

	}
	else
		min = 0;

	tz = (hr * 60 + min) * 60;
	if (*str == '-')
		tz = -tz;

	*tzp = -tz;
	return *cp != '\0';
}	/* DecodeTimezone() */


/* DecodeSpecial()
 * Decode text string using lookup table.
 * Implement a cache lookup since it is likely that dates
 *	will be related in format.
 */
static int
DecodeSpecial(int field, char *lowtoken, int *val)
{
	int			type;
	datetkn    *tp;

#if USE_DATE_CACHE
	if ((datecache[field] != NULL)
		&& (strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0))
		tp = datecache[field];
	else
	{
#endif
		tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
#if USE_DATE_CACHE
	}
	datecache[field] = tp;
#endif
	if (tp == NULL)
	{
		type = IGNOREFIELD;
		*val = 0;
	}
	else
	{
		type = tp->type;
		switch (type)
		{
			case TZ:
			case DTZ:
			case DTZMOD:
				*val = FROMVAL(tp);
				break;

			default:
				*val = tp->value;
				break;
		}
	}

	return type;
}	/* DecodeSpecial() */



/* datebsearch()
 * Binary search -- from Knuth (6.2.1) Algorithm B.  Special case like this
 * is WAY faster than the generic bsearch().
 */
static datetkn *
datebsearch(char *key, datetkn *base, unsigned int nel)
{
	datetkn    *last = base + nel - 1,
			   *position;
	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 NULL;
}
