/* getdate.c - turn textual date into a time_t
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif				/* HAVE_CONFIG_H */
#ifdef TEST
#include <stdio.h>
#endif				/* TEST */
#ifdef STDC_HEADERS
#include <stdlib.h>
#include <string.h>
#else				/* !STDC_HEADERS */
extern char *malloc();
extern void *memcpy();
#endif				/* STDC_HEADERS */
#include <ctype.h>
#include <time.h>

typedef enum {				 /* token types */
	GD_MONTH,			 /* month name */
	GD_DAY,				 /* day name */
	GDU_YEAR,			 /* unit: years */
	GDU_MONTH,			 /* unit: months */
	GDU_DAY,			 /* unit: days */
	GDU_HOUR,			 /* unit: hours */
	GDU_MIN,			 /* unit: minutes */
	GDU_SEC,			 /* unit: seconds */
	GDR_DAY,			 /* relative: days */
	GDR_AGO,			 /* relative: ago */
	GD_TZ,				 /* time zone */
	GD_TZD,				 /* time zone, daylight saving */
	GD_AMPM,			 /* am or pm modifier */
	GD_HHMM,			 /* time of day: HHMM */
	GD_HHMMSS,			 /* time of day: HHMMSS */
	GD_YY,				 /* year: 'YY */
	GD_XXXX,			 /* four or two digit number */
	GD_ISO,				 /* date: ISO 8601 style */
	GD_MMDDYY,			 /* date: MM/DD/YY */
	GD_SET,				 /* daytime: noon, midnight */
} gd__token;

typedef struct gd_s {			 /* structure of table of recognised tokens */
	char *text;			 /* token text */
	gd__token type;			 /* token type */
	int value;			 /* associated value */
} gd_t;


/* Read a numeric value from "str", starting at "start" and extending for
 * "len", storing the result in "*n".
 *
 * On error, "*n" is not modified, and nonzero is returned.
 */
static inline int gd__num(const char *str, int start, int len, int *n)
{
	int a, i;

	a = 0;

	for (i = 0; i < len; i++) {
		if (!isdigit(str[start + i]))
			return (1);
		a *= 10;
		a += str[start + i] - '0';
	}

	*n = a;

	return (0);
}


/* Read two numeric values from "str" starting at "start" and extending for
 * "len" in total; the numbers will be separated by one unread character.
 * The results are stored in "*n1" and "*n2" unless an error occurs.
 *
 * "desc" describes how many digits each number should be; for instance, "42"
 * would mean that "n1" should be 4 digits long and "n2" should be 2.
 *
 * Returns nonzero on error.
 */
static inline
    int gd__num2(const char *str, int start, int len, int desc, int *n1,
		 int *n2)
{
	int l1, l2, a1, a2;

	l1 = desc / 10;
	l2 = desc % 10;

	if (len < (l1 + l2 + 1))
		return (1);		    /* string is not long enough */

	if (gd__num(str, start, l1, &a1))
		return (1);
	if (gd__num(str, start + l1 + 1, l2, &a2))
		return (1);

	*n1 = a1;
	*n2 = a2;

	return (0);
}


/* Read three numeric values from "str", just like gd__num2().
 *
 * Returns nonzero on error.
 */
static inline
    int gd__num3(const char *str, int s, int l, int d, int *n1, int *n2,
		 int *n3)
{
	int l1, l2, l3, a1, a2, a3;

	l1 = d / 100;
	l2 = (d / 10) % 10;
	l3 = d % 10;

	if (l < (l1 + l2 + l3 + 2))
		return (1);		    /* string too short */

	if (gd__num(str, s, l1, &a1))
		return (1);
	if (gd__num(str, s + l1 + 1, l2, &a2))
		return (1);
	if (gd__num(str, s + l1 + l2 + 2, l3, &a3))
		return (1);

	*n1 = a1;
	*n2 = a2;
	*n3 = a3;

	return (0);
}


/* Return the length of the token in "str" starting at position "start". If
 * no token is present there, return 0.
 *
 * A token is defined as a string containing any of the characters A-Z, a-z,
 * 0-9, or :, ., ', - and /. However, if a token contains numeric
 * characters, it will end at the first alphabetic character, and vice
 * versa. Thus "9:00am" is considered to be 2 tokens - "9:00" and "am".
 */
static inline int gd__toklen(const char *str, int start)
{
	int len, c;
	int numeric = -1;

	for (len = 0; str[start + len]; len++) {
		c = str[start + len];
		if (isdigit(c) && (numeric == -1))
			numeric = 1;
		else if (isdigit(c) && (numeric == 0))
			return (len);
		else if (isalpha(c) && (numeric == -1))
			numeric = 0;
		else if (isalpha(c) && (numeric == 1))
			return (len);
		if (isalpha(c) || isdigit(c))
			continue;
		if ((c != ':') && (c != '.') && (c != '\'')
		    && (c != '/') && (c != '-'))
			return (len);
	}

	return (len);
}


/* Return the length of the gap between tokens in "str" starting at position
 * "start". Returns 0 on error.
 *
 * A gap between tokens is considered to be any character not one of A-Z, a-z,
 * 0-9, :, ., ', -, or /.
 */
static inline int gd__gaplen(const char *str, int start)
{
	int len, c;

	for (len = 0; str[start + len]; len++) {
		c = str[start + len];
		if (isalpha(c) || isdigit(c) || (c == ':') || (c == '.')
		    || (c == '/')
		    || (c == '\'') || (c == '-'))
			return (len);
	}

	return (len);
}


/* Look up the string in "str", starting at "start" and extending for "len",
 * in the token table "table". Returns the index of a matching entry, or -1
 * if no match was found.
 *
 * Exact matches are looked for first, and then partial matches (the first
 * "len" characters of the token name).
 *
 * Alphabetic characters are compared case insensitively, and digits in "str"
 * are treated as if they were "?" characters. The table text must already
 * be in lower case and have no digits in it.
 */
static inline int gd__lookup(gd_t table[], const char *str, int start,
			     int len)
{
	int num, i, match, c1, c2;

	for (num = 0; table[num].text; num++) {	/* look for exact matches */
		match = 1;
		for (i = 0; (i < len) && match; i++) {
			c1 = table[num].text[i];
			c2 = str[start + i];
			if (isdigit(c2))
				c2 = '?';
			else if (isalpha(c2))
				c2 = tolower(c2);
			if (c1 != c2)
				match = 0;
		}
		if (i != len)
			match = 0;	    /* whole token didn't match */
		if (table[num].text[i] != 0)
			match = 0;	    /* token shorter than text */
		if (match)
			return (num);
	}

	for (num = 0; table[num].text; num++) {	/* look for partial matches */
		match = 1;
		for (i = 0; (i < len) && match; i++) {
			c1 = table[num].text[i];
			c2 = str[start + i];
			if (isdigit(c2))
				c2 = '?';
			else if (isalpha(c2))
				c2 = tolower(c2);
			if (c1 != c2)
				match = 0;
		}
		if (match)
			return (num);
	}

	return (-1);
}


/* Convert the string "str" into a time_t value relative to "*tptr", or
 * relative to now if "tptr" is 0. Returns -1 on error.
 *
 * Formats recognised:
 *
 *   Numerical only:
 *     YYYY-[M]M-[D]D (ISO 8601)
 *     YYYY/[M]M/[D]D
 *     [M]M/[D]D/[Y]Y
 *
 *   In freeform text (commas ignored):
 *
 *     now|yesterday|today|tomorrow
 *     N sec|min|hour|day|week|month|year [ago]
 *     N jan|feb|...|dec [YYYY|[']YY]           (YYYY at end of string only)
 *     jan|feb|...|dec N [YYYY|[']YY]           (YYYY at end of string only)
 *     sun|mon|...|sat
 *     [H]H[[:]MM[.SS]][am|pm]                  (HHMM not at end of string)
 *     [H]H am|pm
 *     gmt|utc|bst|...
 *     midnight|noon|midday
 *   
 */
time_t get_date(const char *str, const time_t * tptr)
{
	static gd_t table[] = {
		{"january", GD_MONTH, 0},   /* months */
		{"february", GD_MONTH, 1},
		{"march", GD_MONTH, 2},
		{"april", GD_MONTH, 3},
		{"may", GD_MONTH, 4},
		{"june", GD_MONTH, 5},
		{"july", GD_MONTH, 6},
		{"august", GD_MONTH, 7},
		{"september", GD_MONTH, 8},
		{"october", GD_MONTH, 9},
		{"november", GD_MONTH, 10},
		{"december", GD_MONTH, 11},
		{"sunday", GD_DAY, 0},	    /* days */
		{"monday", GD_DAY, 1},
		{"tuesday", GD_DAY, 2},
		{"wednesday", GD_DAY, 3},
		{"thursday", GD_DAY, 4},
		{"friday", GD_DAY, 5},
		{"saturday", GD_DAY, 6},
		{"years", GDU_YEAR, 1},	    /* time units */
		{"yrs", GDU_YEAR, 1},
		{"months", GDU_MONTH, 1},
		{"mths", GDU_MONTH, 1},
		{"fortnights", GDU_DAY, 14},
		{"weeks", GDU_DAY, 7},
		{"wks", GDU_DAY, 7},
		{"days", GDU_DAY, 1},
		{"dys", GDU_DAY, 1},
		{"hours", GDU_HOUR, 1},
		{"hrs", GDU_HOUR, 1},
		{"minutes", GDU_MIN, 1},
		{"mins", GDU_MIN, 1},
		{"seconds", GDU_SEC, 1},
		{"secs", GDU_SEC, 1},
		{"tomorrow", GDR_DAY, 1},   /* relative time units */
		{"yesterday", GDR_DAY, -1},
		{"today", GDR_DAY, 0},
		{"now", GDR_DAY, 0},
		{"ago", GDR_AGO, 0},
		{"am", GD_AMPM, 0},	    /* am or pm */
		{"pm", GD_AMPM, 1},
		{"a.m.", GD_AMPM, 0},
		{"p.m.", GD_AMPM, 1},
		{"am.", GD_AMPM, 0},
		{"pm.", GD_AMPM, 1},
		{"??:??", GD_HHMM, 22},	    /* time-of-day specifiers */
		{"??.??", GD_HHMM, 22},
		{"?:??", GD_HHMM, 12},
		{"?.??", GD_HHMM, 12},
		{"??:??.??", GD_HHMMSS, 222},
		{"??:??:??", GD_HHMMSS, 222},
		{"??.??:??", GD_HHMMSS, 222},
		{"?:??.??", GD_HHMMSS, 122},
		{"?:??:??", GD_HHMMSS, 122},
		{"?.??:??", GD_HHMMSS, 122},
		{"????.??", GD_HHMMSS, 42},
		{"midnight", GD_SET, 0},
		{"noon", GD_SET, 12},
		{"midday", GD_SET, 12},
		{"'??", GD_YY, 2},	    /* possible time or year specifiers */
		{"'?", GD_YY, 1},
		{"??????????", GD_XXXX, 10},
		{"?????????", GD_XXXX, 9},
		{"????????", GD_XXXX, 8},
		{"???????", GD_XXXX, 7},
		{"??????", GD_XXXX, 6},
		{"?????", GD_XXXX, 5},
		{"????", GD_XXXX, 4},
		{"???", GD_XXXX, 3},
		{"??", GD_XXXX, 2},
		{"?", GD_XXXX, 1},
		{"????" "-??" "-??", GD_ISO, 422},	/* full date specifiers */
		{"????" "-??" "-?", GD_ISO, 421},
		{"????" "-?-??", GD_ISO, 412},
		{"????" "-?-?", GD_ISO, 411},
		{"????" "/??" "/??", GD_ISO, 422},
		{"????" "/??" "/?", GD_ISO, 421},
		{"????" "/?/??", GD_ISO, 412},
		{"????" "/?/?", GD_ISO, 411},
		{"??" "/??" "/??", GD_MMDDYY, 222},
		{"?/??" "/??", GD_MMDDYY, 122},
		{"??" "/?/??", GD_MMDDYY, 212},
		{"?/?/??", GD_MMDDYY, 112},
		{"??" "/??" "/?", GD_MMDDYY, 211},
		{"?/??" "/?", GD_MMDDYY, 121},
		{"??" "/?/?", GD_MMDDYY, 211},
		{"?/?/?", GD_MMDDYY, 111},
		{"gmt", GD_TZ, 0},	    /* time zones */
		{"utc", GD_TZ, 0},
		{"wet", GD_TZ, 0},
		{"bst", GD_TZD, 0},
		{"wat", GD_TZ, 1},
		{"at", GD_TZ, 2},
		{"ast", GD_TZ, 4},
		{"adt", GD_TZD, 4},
		{"est", GD_TZ, 5},
		{"edt", GD_TZD, 5},
		{"cst", GD_TZ, 6},
		{"cdt", GD_TZD, 6},
		{"mst", GD_TZ, 7},
		{"mdt", GD_TZD, 7},
		{"pst", GD_TZ, 8},
		{"pdt", GD_TZD, 8},
		{"yst", GD_TZ, 9},
		{"ydt", GD_TZD, 9},
		{"hst", GD_TZ, 10},
		{"hdt", GD_TZD, 10},
		{"cat", GD_TZ, 10},
		{"ahst", GD_TZ, 10},
		{"nt", GD_TZ, 11},
		{"idlw", GD_TZ, 12},
		{"cet", GD_TZ, -1},
		{"met", GD_TZ, -1},
		{"mewt", GD_TZ, -1},
		{"mest", GD_TZD, -1},
		{"mesz", GD_TZD, -1},
		{"swt", GD_TZ, -1},
		{"sst", GD_TZD, -1},
		{"fwt", GD_TZ, -1},
		{"fst", GD_TZD, -1},
		{"eet", GD_TZ, -2},
		{"bt", GD_TZ, -3},
		{"zp4", GD_TZ, -4},
		{"zp5", GD_TZ, -5},
		{"zp6", GD_TZ, -6},
		{"wast", GD_TZ, -7},
		{"wadt", GD_TZD, -7},
		{"cct", GD_TZ, -8},
		{"jst", GD_TZ, -9},
		{"east", GD_TZ, -10},
		{"eadt", GD_TZD, -10},
		{"gst", GD_TZ, -10},
		{"nzt", GD_TZ, -12},
		{"nzst", GD_TZ, -12},
		{"nzdt", GD_TZD, -12},
		{"idle", GD_TZ, -12},
		{"a", GD_TZ, 1},	    /* military time zones */
		{"b", GD_TZ, 2},
		{"c", GD_TZ, 3},
		{"d", GD_TZ, 4},
		{"e", GD_TZ, 5},
		{"f", GD_TZ, 6},
		{"g", GD_TZ, 7},
		{"h", GD_TZ, 8},
		{"i", GD_TZ, 9},
		{"k", GD_TZ, 10},
		{"l", GD_TZ, 11},
		{"m", GD_TZ, 12},
		{"n", GD_TZ, -1},
		{"o", GD_TZ, -2},
		{"p", GD_TZ, -3},
		{"q", GD_TZ, -4},
		{"r", GD_TZ, -5},
		{"s", GD_TZ, -6},
		{"t", GD_TZ, -7},
		{"u", GD_TZ, -8},
		{"v", GD_TZ, -9},
		{"w", GD_TZ, -10},
		{"x", GD_TZ, -11},
		{"y", GD_TZ, -12},
		{"z", GD_TZ, 0},
		{0, 0, 0}		    /* end of list */
	};
	struct tm *initial_time;
	struct tm final_time;
	time_t t;
	long simple_change = 0;		 /* amount to add to time */
	int change_years = 0;		 /* number of years to add / subtract */
	int change_months = 0;		 /* number of months to add / sub */
	int fix_sec = -1;		 /* second of the day (-1 = not set) */
	int fix_min = -1;		 /* minute of the day */
	int fix_hour = -1;		 /* hour of the day */
	int fix_day = -1;		 /* day of the week */
	int fix_mday = -1;		 /* day of the month */
	int fix_month = -1;		 /* month of the year */
	int fix_year = -1;		 /* year */
	int fix_zone = -99;		 /* time zone (-99 = not set) */
	int fix_dst = -1;		 /* daylight saving time */
	int fix_ago = 0;		 /* "ago" set if nonzero */
	int trailing_value = -1;	 /* any trailing value */
	int *value_store = 0;		 /* where to put a value */
	int tok_start, tok_len, token;	 /* current token details */
	int n;				 /* temporary variable */
	int ntok;			 /* number of tokens parsed */

	if (!str)
		return (-1);
	if (!tptr)
		t = time(0);
	else
		t = *tptr;

	ntok = 0;

	for (tok_start = 0; str[tok_start]; tok_start += tok_len) {	/* scan str */

		tok_len = gd__toklen(str, tok_start);	/* find token length */

		if (tok_len < 1) {	    /* not a token */
			tok_len = gd__gaplen(str, tok_start);	/* skip to next one */
			if (tok_len < 1)
				tok_len = 1;
			continue;
		}

		token = gd__lookup(table, str, tok_start, tok_len);
		if (token < 0)
			return (-1);	    /* unknown token */

		switch (table[token].type) {

		case GD_MONTH:		    /* month name specified */

			fix_month = table[token].value;
			if (trailing_value > 0) {
				fix_mday = trailing_value;	/* n MONTH */
				value_store = 0;
			} else {	    /* MONTH n */
				value_store = &fix_mday;
			}
			trailing_value = -1;
			break;

		case GD_DAY:		    /* day name specified */

			if (trailing_value >= 0) {	/* NNNN saturday */
				if (trailing_value > 100) {	/* HHMM */
					fix_hour = trailing_value / 100;
					fix_min = trailing_value % 100;
					fix_sec = 0;
				} else {    /* [H]H */
					fix_hour = trailing_value;
					fix_min = 0;
					fix_sec = 0;
				}
			}

			fix_day = table[token].value;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDU_YEAR:		    /* number of years specified */

			if (trailing_value < 0)
				return (-1);	/* no value to apply to */
			change_years += trailing_value;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDU_MONTH:	    /* number of months specified */

			if (trailing_value < 0)
				return (-1);	/* no value to apply to */
			change_months +=
			    table[token].value * trailing_value;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDU_DAY:		    /* number of days specified */

			if (trailing_value < 0)
				return (-1);	/* no value to apply to */
			simple_change += table[token].value
			    * trailing_value * 86400;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDU_HOUR:		    /* number of hours specified */

			if (trailing_value < 0)
				return (-1);	/* no value to apply to */
			simple_change += table[token].value
			    * trailing_value * 3600;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDU_MIN:		    /* number of minutes specified */

			if (trailing_value < 0)
				return (-1);	/* no value to apply to */
			simple_change += table[token].value
			    * trailing_value * 60;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDU_SEC:		    /* number of seconds specified */

			if (trailing_value < 0)
				return (-1);	/* no value to apply to */
			simple_change += table[token].value
			    * trailing_value;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDR_DAY:		    /* plus or minus days */

			if (trailing_value >= 0) {	/* N yesterday */
				if (trailing_value > 100) {	/* HHMM */
					fix_hour = trailing_value / 100;
					fix_min = trailing_value % 100;
					fix_sec = 0;
				} else {    /* [H]H */
					fix_hour = trailing_value;
					fix_min = 0;
					fix_sec = 0;
				}
			}

			simple_change += table[token].value * 86400;
			trailing_value = -1;
			value_store = 0;
			break;

		case GDR_AGO:		    /* time subtracted from now */

			if (trailing_value >= 0)
				return (-1);	/* "N ago" invalid */
			fix_ago = 1;
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_TZ:		    /* time zone specified */

			fix_zone = table[token].value;
			fix_dst = 0;
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_TZD:		    /* daylight saving time zone */

			fix_zone = table[token].value;
			fix_dst = 1;
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_AMPM:		    /* "am" or "pm" specified */

			if (fix_hour == -1) {
				if (trailing_value < 0)
					return (-1);	/* no value */
				if (trailing_value > 100) {	/* HHMM */
					fix_hour = trailing_value / 100;
					fix_min = trailing_value % 100;
					fix_sec = 0;
				} else {    /* [H]H */
					fix_hour = trailing_value;
					fix_min = 0;
					fix_sec = 0;
				}
			}

			if ((fix_hour <= 12) && (table[token].value))
				fix_hour += 12;
			else if ((fix_hour >= 12) && (!table[token].value))
				fix_hour -= 12;

			trailing_value = -1;
			value_store = 0;
			break;

		case GD_HHMM:		    /* HHMM specified */
			if (gd__num2(str, tok_start,
				     tok_len, table[token].value,
				     &fix_hour, &fix_min))
				return (-1);
			fix_sec = 0;
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_HHMMSS:	    /* HHMMSS specified */
			if (table[token].value < 100) {
				if (gd__num2(str, tok_start,
					     tok_len, table[token].value,
					     &n, &fix_sec))
					return (-1);
				fix_hour = n / 100;
				fix_min = n % 100;
				fix_sec = 0;
			} else {
				if (gd__num3(str, tok_start,
					     tok_len, table[token].value,
					     &fix_hour, &fix_min,
					     &fix_sec))
					return (-1);
			}
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_YY:		    /* 'YY specified */
			if (gd__num
			    (str, tok_start + 1, tok_len - 1, &fix_year))
				return (-1);
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_XXXX:		    /* just a number specified */

			if (gd__num(str, tok_start, tok_len, &n))
				return (-1);

			if (value_store) {  /* can store number now */

				*value_store = n;

			} else {	    /* number is left trailing */

				if (trailing_value >= 0) {	/* another trailer */
					if (trailing_value > 100) {	/* HHMM */
						fix_hour =
						    trailing_value / 100;
						fix_min =
						    trailing_value % 100;
						fix_sec = 0;
					} else {	/* [H]H */
						fix_hour = trailing_value;
						fix_min = 0;
						fix_sec = 0;
					}
				}

				trailing_value = n;
			}
			break;

		case GD_ISO:		    /* YYYY-MM-DD specified */
			if (gd__num3(str, tok_start,
				     tok_len, table[token].value,
				     &fix_year, &fix_month, &fix_mday))
				return (-1);
			fix_month--;
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_MMDDYY:	    /* MM/DD/YY specified */
			if (gd__num3(str, tok_start,
				     tok_len, table[token].value,
				     &fix_month, &fix_mday, &fix_year))
				return (-1);
			fix_month--;
			trailing_value = -1;
			value_store = 0;
			break;

		case GD_SET:		    /* midnight or noon specified */
			fix_hour = table[token].value;
			fix_min = 0;
			fix_sec = 0;
			trailing_value = -1;
			value_store = 0;
			break;

		default:
			return (-1);
		}

		ntok++;
	}

	if (trailing_value >= 0) {	    /* trailing number left at end */
		if (ntok == 1) {	    /* at start: is a time */
			if (trailing_value > 100) {	/* HHMM */
				fix_hour = trailing_value / 100;
				fix_min = trailing_value % 100;
				fix_sec = 0;
			} else {	    /* [H]H */
				fix_hour = trailing_value;
				fix_min = 0;
				fix_sec = 0;
			}
		} else {		    /* at end: is a year */
			fix_year = trailing_value;
		}
	}

	if (fix_year >= 0) {
		if (fix_year > 1900)
			fix_year -= 1900;
		if (fix_year < 69)
			fix_year += 2000;
		else
			fix_year += 1900;
	}

	initial_time = localtime(&t);
	if (!initial_time)
		return (-1);

	change_years += change_months / 12; /* > 11 months -> years and months */
	change_months %= 12;

	if (fix_ago) {			    /* reverse time change */
		simple_change = -simple_change;
		change_years = -change_years;
		change_months = -change_months;
	}

	memcpy(&final_time, initial_time, sizeof(final_time));

	if (fix_sec >= 0)
		final_time.tm_sec = fix_sec;
	if (fix_min >= 0)
		final_time.tm_min = fix_min;
	if (fix_hour >= 0)
		final_time.tm_hour = fix_hour;
	if (fix_mday >= 0)
		final_time.tm_mday = fix_mday;
	if (fix_month >= 0)
		final_time.tm_mon = fix_month;
	if (fix_year >= 0) {
		if (fix_year < 69)
			fix_year += 100;
		else if (fix_year >= 1900)
			fix_year -= 1900;
		final_time.tm_year = fix_year;
	}
	final_time.tm_mon += change_months;
	while (final_time.tm_mon < 0) {
		final_time.tm_year--;
		final_time.tm_mon += 12;
	}
	while (final_time.tm_mon > 11) {
		final_time.tm_year++;
		final_time.tm_mon -= 12;
	}
	final_time.tm_year += change_years;
	final_time.tm_isdst = fix_dst;

	t = mktime(&final_time);
	if (t < 0)
		return (-1);

	if (fix_day != -1) {		    /* set weekday */
		initial_time = localtime(&t);
		if (!initial_time)
			return (-1);
		n = fix_day - initial_time->tm_wday;
		if (n < 0)
			n += 7;
		simple_change += n * 86400;
	}

	t += simple_change;		    /* apply simple change to time */
	if (fix_zone != -99) {		    /* add on time zone */
		t += fix_zone * 3600;
	}
	if (t < 0)
		return (-1);

	return (t);
}


#ifdef TEST				    /* TEST */
int main(void)
{
	char buf[1024];
	time_t d;

	printf("Enter date, or EOF (^D) to exit.\n> ");
	fflush(stdout);

	buf[0] = 0;
	while (fgets(buf, sizeof(buf) - 1, stdin) && buf[0]) {
		d = get_date(buf, 0);
		if (d == -1)
			printf("Bad format - couldn't convert.\n");
		else
			printf("%s", ctime(&d));
		printf("> ");
		fflush(stdout);
	}

	printf("\n");
	return (0);
}
#endif				/* TEST */

/* EOF */
