about summary refs log tree commit diff stats
path: root/src/parsdate.y
diff options
context:
space:
mode:
authorThomas E. Dickey <dickey@invisible-island.net>2008-07-01 00:01:11 -0400
committerThomas E. Dickey <dickey@invisible-island.net>2008-07-01 00:01:11 -0400
commit6512ca2735085b610dd72f0d795f0ecfc90097ff (patch)
treef3095ecfa767c8f35bc76e673f013e0645dbc2c3 /src/parsdate.y
parentd303ca5bc3cf17d268f2c9b72ee0c222914d7389 (diff)
downloadlynx-snapshots-6512ca2735085b610dd72f0d795f0ecfc90097ff.tar.gz
snapshot of project "lynx", label v2-8-7dev_9a
Diffstat (limited to 'src/parsdate.y')
-rw-r--r--src/parsdate.y919
1 files changed, 919 insertions, 0 deletions
diff --git a/src/parsdate.y b/src/parsdate.y
new file mode 100644
index 00000000..c364f7a3
--- /dev/null
+++ b/src/parsdate.y
@@ -0,0 +1,919 @@
+%{
+/*
+ *  $LynxId: parsdate.y,v 1.4 2008/06/30 23:25:14 tom Exp $
+ *
+ *  This module is adapted and extended from tin, to use for LYmktime().
+ *
+ *  Project   : tin - a Usenet reader
+ *  Module    : parsedate.y
+ *  Author    : S. Bellovin, R. $alz, J. Berets, P. Eggert
+ *  Created   : 1990-08-01
+ *  Updated   : 2008-06-30 (by Thomas Dickey, for Lynx)
+ *  Notes     : This grammar has 8 shift/reduce conflicts.
+ *
+ *              Originally written by Steven M. Bellovin <smb@research.att.com>
+ *              while at the University of North Carolina at Chapel Hill.
+ *              Later tweaked by a couple of people on Usenet.  Completely
+ *              overhauled by Rich $alz <rsalz@osf.org> and Jim Berets
+ *              <jberets@bbn.com> in August, 1990.
+ *
+ *              Further revised (removed obsolete constructs and cleaned up
+ *              timezone names) in August, 1991, by Rich.
+ *              Paul Eggert <eggert@twinsun.com> helped in September 1992.
+ *              Roland Rosenfeld added MET DST code in April 1994.
+ *
+ *  Revision  : 1.13
+ *  Copyright : This code is in the public domain and has no copyright.
+ */
+
+/* SUPPRESS 530 */ /* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab */ /* Label was not used */
+/* SUPPRESS 593 on yynewstate */ /* Label was not used */
+/* SUPPRESS 595 on yypvt */ /* Automatic variable may be used before set */
+
+#include <parsdate.h>
+
+/*
+**  Get the number of elements in a fixed-size array, or a pointer just
+**  past the end of it.
+*/
+#define ENDOF(array)	(&array[ARRAY_SIZE(array)])
+
+#define CTYPE(isXXXXX, c)	(((unsigned char)(c) < 128) && isXXXXX(((int)c)))
+
+typedef char	*STRING;
+
+extern int date_parse(void);
+
+#define yyparse		date_parse
+#define yylex		date_lex
+#define yyerror		date_error
+
+
+    /* See the LeapYears table in Convert. */
+#define EPOCH		1970
+#define END_OF_TIME	2038
+
+    /* Constants for general time calculations. */
+#define DST_OFFSET	1
+#define SECSPERDAY	(24L * 60L * 60L)
+    /* Readability for TABLE stuff. */
+#define HOUR(x)		(x * 60)
+
+#define LPAREN		'('
+#define RPAREN		')'
+#define IS7BIT(x)	((unsigned int)(x) < 0200)
+
+
+/*
+**  Daylight-savings mode:  on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+    DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+**  Meridian:  am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+    MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+**  Global variables.  We could get rid of most of them by using a yacc
+**  union, but this is more efficient.  (This routine predates the
+**  yacc %union construct.)
+*/
+static char	*yyInput;
+static DSTMODE	yyDSTmode;
+static int	yyHaveDate;
+static int	yyHaveRel;
+static int	yyHaveTime;
+static time_t	yyTimezone;
+static time_t	yyDay;
+static time_t	yyHour;
+static time_t	yyMinutes;
+static time_t	yyMonth;
+static time_t	yySeconds;
+static time_t	yyYear;
+static MERIDIAN	yyMeridian;
+static time_t	yyRelMonth;
+static time_t	yyRelSeconds;
+
+static time_t	ToSeconds(time_t, time_t, time_t, MERIDIAN);
+static time_t	Convert(time_t, time_t, time_t, time_t, time_t, time_t, MERIDIAN, DSTMODE);
+static time_t	DSTcorrect(time_t, time_t);
+static time_t	RelativeMonth(time_t, time_t);
+static int	LookupWord(char	*, int);
+static int	date_lex(void);
+static int	GetTimeInfo(TIMEINFO *Now);
+
+/*
+ * The 'date_error()' function is declared here to work around a defect in
+ * bison 1.22, which redefines 'const' further down in this file, making it
+ * impossible to put a prototype here, and the function later.  We're using
+ * 'const' on the parameter to quiet gcc's -Wwrite-strings warning.
+ */
+/*ARGSUSED*/
+static void
+date_error(const char GCC_UNUSED *s)
+{
+    /*NOTREACHED*/
+}
+
+%}
+
+%union {
+    time_t		Number;
+    enum _MERIDIAN	Meridian;
+}
+
+%token	tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
+%token	tUNUMBER tZONE tDST
+
+%type	<Number>	tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
+%type	<Number>	tSNUMBER tUNUMBER tZONE numzone zone
+%type	<Meridian>	tMERIDIAN o_merid
+
+%%
+
+spec	: /* NULL */
+	| spec item
+	;
+
+item	: time {
+	    yyHaveTime++;
+#if	defined(lint)
+	    /* I am compulsive about lint natterings... */
+	    if (yyHaveTime == -1) {
+		YYERROR;
+	    }
+#endif	/* defined(lint) */
+	}
+	| time zone {
+	    yyHaveTime++;
+	    yyTimezone = $2;
+	}
+	| date {
+	    yyHaveDate++;
+	}
+	| both {
+	    yyHaveDate++;
+	    yyHaveTime++;
+	}
+	| both zone {
+	    yyHaveDate++;
+	    yyHaveTime++;
+	    yyTimezone = $2;
+	}
+	| rel {
+	    yyHaveRel = 1;
+	}
+	;
+
+time	: tUNUMBER o_merid {
+	    if ($1 < 100) {
+		yyHour = $1;
+		yyMinutes = 0;
+	    }
+	    else {
+		yyHour = $1 / 100;
+		yyMinutes = $1 % 100;
+	    }
+	    yySeconds = 0;
+	    yyMeridian = $2;
+	}
+	| tUNUMBER ':' tUNUMBER o_merid {
+	    yyHour = $1;
+	    yyMinutes = $3;
+	    yySeconds = 0;
+	    yyMeridian = $4;
+	}
+	| tUNUMBER ':' tUNUMBER numzone {
+	    yyHour = $1;
+	    yyMinutes = $3;
+	    yyTimezone = $4;
+	    yyMeridian = MER24;
+	    yyDSTmode = DSToff;
+	}
+	| tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+	    yyHour = $1;
+	    yyMinutes = $3;
+	    yySeconds = $5;
+	    yyMeridian = $6;
+	}
+	| tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
+	    yyHour = $1;
+	    yyMinutes = $3;
+	    yySeconds = $5;
+	    yyTimezone = $6;
+	    yyMeridian = MER24;
+	    yyDSTmode = DSToff;
+	}
+	;
+
+zone	: tZONE {
+	    $$ = $1;
+	    yyDSTmode = DSToff;
+	}
+	| tDAYZONE {
+	    $$ = $1;
+	    yyDSTmode = DSTon;
+	}
+	| tDAYZONE tDST {
+	    yyTimezone = $1;
+	    yyDSTmode = DSTon;
+	}
+	| tZONE numzone {
+	    /* Only allow "GMT+300" and "GMT-0800" */
+	    if ($1 != 0) {
+		YYABORT;
+	    }
+	    $$ = $2;
+	    yyDSTmode = DSToff;
+	}
+	| numzone {
+	    $$ = $1;
+	    yyDSTmode = DSToff;
+	}
+	;
+
+numzone	: tSNUMBER {
+	    int	i;
+
+	    /* Unix and GMT and numeric timezones -- a little confusing. */
+	    if ((int)$1 < 0) {
+		/* Don't work with negative modulus. */
+		$1 = -(int)$1;
+		if ($1 > 9999 || (i = $1 % 100) >= 60) {
+			YYABORT;
+		}
+		$$ = ($1 / 100) * 60 + i;
+	    }
+	    else {
+		if ($1 > 9999 || (i = $1 % 100) >= 60) {
+			YYABORT;
+		}
+		$$ = -(($1 / 100) * 60 + i);
+	    }
+	}
+	;
+
+date	: tUNUMBER '/' tUNUMBER {
+	    yyMonth = $1;
+	    yyDay = $3;
+	}
+	| tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+	    if ($1 > 100) {
+		yyYear = $1;
+		yyMonth = $3;
+		yyDay = $5;
+	    }
+	    else {
+		yyMonth = $1;
+		yyDay = $3;
+		yyYear = $5;
+	    }
+	}
+	| tMONTH tUNUMBER {
+	    yyMonth = $1;
+	    yyDay = $2;
+	}
+	| tMONTH tUNUMBER ',' tUNUMBER {
+	    yyMonth = $1;
+	    yyDay = $2;
+	    yyYear = $4;
+	}
+	| tUNUMBER tMONTH {
+	    yyDay = $1;
+	    yyMonth = $2;
+	}
+	| tUNUMBER tMONTH tUNUMBER {
+	    yyDay = $1;
+	    yyMonth = $2;
+	    yyYear = $3;
+	}
+	| tDAY ',' tUNUMBER tMONTH tUNUMBER {
+	    yyDay = $3;
+	    yyMonth = $4;
+	    yyYear = $5;
+	}
+	| tDAY ',' tUNUMBER '-' tMONTH tSNUMBER {
+	    yyDay = $3;
+	    yyMonth = $5;
+	    yyYear = -$6;
+	}
+	| tUNUMBER tSNUMBER tSNUMBER {
+	    yyDay = $1;
+	    yyMonth = -$2;
+	    yyYear = -$3;
+	    yyDSTmode = DSToff;	/* assume midnight if no time given */
+	    yyTimezone = 0;	/* Lynx assumes GMT for this format */
+	}
+	;
+
+both	: tDAY tMONTH tUNUMBER tUNUMBER ':' tUNUMBER ':' tUNUMBER tUNUMBER {
+	    yyMonth = $2;
+	    yyDay = $3;
+	    yyYear = $9;
+	    yyHour = $4;
+	    yyMinutes = $6;
+	    yySeconds = $8;
+	}
+	;
+
+rel	: tSNUMBER tSEC_UNIT {
+	    yyRelSeconds += $1 * $2;
+	}
+	| tUNUMBER tSEC_UNIT {
+	    yyRelSeconds += $1 * $2;
+	}
+	| tSNUMBER tMONTH_UNIT {
+	    yyRelMonth += $1 * $2;
+	}
+	| tUNUMBER tMONTH_UNIT {
+	    yyRelMonth += $1 * $2;
+	}
+	;
+
+o_merid	: /* NULL */ {
+	    $$ = MER24;
+	}
+	| tMERIDIAN {
+	    $$ = $1;
+	}
+	;
+
+%%
+
+/*
+**  An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+    const char *name;
+    int		type;
+    time_t	value;
+} TABLE;
+
+/* Month and day table. */
+static const TABLE MonthDayTable[] = {
+    { "january",	tMONTH,  1 },
+    { "february",	tMONTH,  2 },
+    { "march",		tMONTH,  3 },
+    { "april",		tMONTH,  4 },
+    { "may",		tMONTH,  5 },
+    { "june",		tMONTH,  6 },
+    { "july",		tMONTH,  7 },
+    { "august",		tMONTH,  8 },
+    { "september",	tMONTH,  9 },
+    { "october",	tMONTH, 10 },
+    { "november",	tMONTH, 11 },
+    { "december",	tMONTH, 12 },
+	/* The value of the day isn't used... */
+    { "sunday",		tDAY, 0 },
+    { "monday",		tDAY, 0 },
+    { "tuesday",	tDAY, 0 },
+    { "wednesday",	tDAY, 0 },
+    { "thursday",	tDAY, 0 },
+    { "friday",		tDAY, 0 },
+    { "saturday",	tDAY, 0 },
+};
+
+/* Time units table. */
+static const TABLE	UnitsTable[] = {
+    { "year",		tMONTH_UNIT,	12 },
+    { "month",		tMONTH_UNIT,	1 },
+    { "week",		tSEC_UNIT,	7 * 24 * 60 * 60 },
+    { "day",		tSEC_UNIT,	1 * 24 * 60 * 60 },
+    { "hour",		tSEC_UNIT,	60 * 60 },
+    { "minute",		tSEC_UNIT,	60 },
+    { "min",		tSEC_UNIT,	60 },
+    { "second",		tSEC_UNIT,	1 },
+    { "sec",		tSEC_UNIT,	1 },
+};
+
+/* Timezone table. */
+static const TABLE	TimezoneTable[] = {
+    { "gmt",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
+    { "ut",	tZONE,     HOUR( 0) },	/* Universal */
+    { "utc",	tZONE,     HOUR( 0) },	/* Universal Coordinated */
+    { "cut",	tZONE,     HOUR( 0) },	/* Coordinated Universal */
+    { "z",	tZONE,     HOUR( 0) },	/* Greenwich Mean */
+    { "wet",	tZONE,     HOUR( 0) },	/* Western European */
+    { "bst",	tDAYZONE,  HOUR( 0) },	/* British Summer */
+    { "nst",	tZONE,     HOUR(3)+30 }, /* Newfoundland Standard */
+    { "ndt",	tDAYZONE,  HOUR(3)+30 }, /* Newfoundland Daylight */
+    { "ast",	tZONE,     HOUR( 4) },	/* Atlantic Standard */
+    { "adt",	tDAYZONE,  HOUR( 4) },	/* Atlantic Daylight */
+    { "est",	tZONE,     HOUR( 5) },	/* Eastern Standard */
+    { "edt",	tDAYZONE,  HOUR( 5) },	/* Eastern Daylight */
+    { "cst",	tZONE,     HOUR( 6) },	/* Central Standard */
+    { "cdt",	tDAYZONE,  HOUR( 6) },	/* Central Daylight */
+    { "mst",	tZONE,     HOUR( 7) },	/* Mountain Standard */
+    { "mdt",	tDAYZONE,  HOUR( 7) },	/* Mountain Daylight */
+    { "pst",	tZONE,     HOUR( 8) },	/* Pacific Standard */
+    { "pdt",	tDAYZONE,  HOUR( 8) },	/* Pacific Daylight */
+    { "yst",	tZONE,     HOUR( 9) },	/* Yukon Standard */
+    { "ydt",	tDAYZONE,  HOUR( 9) },	/* Yukon Daylight */
+    { "akst",	tZONE,     HOUR( 9) },	/* Alaska Standard */
+    { "akdt",	tDAYZONE,  HOUR( 9) },	/* Alaska Daylight */
+    { "hst",	tZONE,     HOUR(10) },	/* Hawaii Standard */
+    { "hast",	tZONE,     HOUR(10) },	/* Hawaii-Aleutian Standard */
+    { "hadt",	tDAYZONE,  HOUR(10) },	/* Hawaii-Aleutian Daylight */
+    { "ces",	tDAYZONE,  -HOUR(1) },	/* Central European Summer */
+    { "cest",	tDAYZONE,  -HOUR(1) },	/* Central European Summer */
+    { "mez",	tZONE,     -HOUR(1) },	/* Middle European */
+    { "mezt",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
+    { "cet",	tZONE,     -HOUR(1) },	/* Central European */
+    { "met",	tZONE,     -HOUR(1) },	/* Middle European */
+/* Additional aliases for MET / MET DST *************************************/
+    { "mez",    tZONE,     -HOUR(1) },  /* Middle European */
+    { "mewt",   tZONE,     -HOUR(1) },  /* Middle European Winter */
+    { "mest",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "mes",    tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "mesz",   tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "msz",    tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+    { "metdst", tDAYZONE,  -HOUR(1) },  /* Middle European Summer */
+/****************************************************************************/
+    { "eet",	tZONE,     -HOUR(2) },	/* Eastern Europe */
+    { "msk",	tZONE,     -HOUR(3) },	/* Moscow Winter */
+    { "msd",	tDAYZONE,  -HOUR(3) },	/* Moscow Summer */
+    { "wast",	tZONE,     -HOUR(8) },	/* West Australian Standard */
+    { "wadt",	tDAYZONE,  -HOUR(8) },	/* West Australian Daylight */
+    { "hkt",	tZONE,     -HOUR(8) },	/* Hong Kong */
+    { "cct",	tZONE,     -HOUR(8) },	/* China Coast */
+    { "jst",	tZONE,     -HOUR(9) },	/* Japan Standard */
+    { "kst",	tZONE,     -HOUR(9) },	/* Korean Standard */
+    { "kdt",	tZONE,     -HOUR(9) },	/* Korean Daylight */
+    { "cast",	tZONE,     -(HOUR(9)+30) }, /* Central Australian Standard */
+    { "cadt",	tDAYZONE,  -(HOUR(9)+30) }, /* Central Australian Daylight */
+    { "east",	tZONE,     -HOUR(10) },	/* Eastern Australian Standard */
+    { "eadt",	tDAYZONE,  -HOUR(10) },	/* Eastern Australian Daylight */
+    { "nzst",	tZONE,     -HOUR(12) },	/* New Zealand Standard */
+    { "nzdt",	tDAYZONE,  -HOUR(12) },	/* New Zealand Daylight */
+
+    /* For completeness we include the following entries. */
+#if	0
+
+    /* Duplicate names.  Either they conflict with a zone listed above
+     * (which is either more likely to be seen or just been in circulation
+     * longer), or they conflict with another zone in this section and
+     * we could not reasonably choose one over the other. */
+    { "fst",	tZONE,     HOUR( 2) },	/* Fernando De Noronha Standard */
+    { "fdt",	tDAYZONE,  HOUR( 2) },	/* Fernando De Noronha Daylight */
+    { "bst",	tZONE,     HOUR( 3) },	/* Brazil Standard */
+    { "est",	tZONE,     HOUR( 3) },	/* Eastern Standard (Brazil) */
+    { "edt",	tDAYZONE,  HOUR( 3) },	/* Eastern Daylight (Brazil) */
+    { "wst",	tZONE,     HOUR( 4) },	/* Western Standard (Brazil) */
+    { "wdt",	tDAYZONE,  HOUR( 4) },	/* Western Daylight (Brazil) */
+    { "cst",	tZONE,     HOUR( 5) },	/* Chile Standard */
+    { "cdt",	tDAYZONE,  HOUR( 5) },	/* Chile Daylight */
+    { "ast",	tZONE,     HOUR( 5) },	/* Acre Standard */
+    { "adt",	tDAYZONE,  HOUR( 5) },	/* Acre Daylight */
+    { "cst",	tZONE,     HOUR( 5) },	/* Cuba Standard */
+    { "cdt",	tDAYZONE,  HOUR( 5) },	/* Cuba Daylight */
+    { "est",	tZONE,     HOUR( 6) },	/* Easter Island Standard */
+    { "edt",	tDAYZONE,  HOUR( 6) },	/* Easter Island Daylight */
+    { "sst",	tZONE,     HOUR(11) },	/* Samoa Standard */
+    { "ist",	tZONE,     -HOUR(2) },	/* Israel Standard */
+    { "idt",	tDAYZONE,  -HOUR(2) },	/* Israel Daylight */
+    { "idt",	tDAYZONE,  -(HOUR(3)+30) }, /* Iran Daylight */
+    { "ist",	tZONE,     -(HOUR(3)+30) }, /* Iran Standard */
+    { "cst",	 tZONE,     -HOUR(8) },	/* China Standard */
+    { "cdt",	 tDAYZONE,  -HOUR(8) },	/* China Daylight */
+    { "sst",	 tZONE,     -HOUR(8) },	/* Singapore Standard */
+
+    /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+    { "gst",	tZONE,     HOUR( 3) },	/* Greenland Standard */
+    { "wat",	tZONE,     -HOUR(1) },	/* West Africa */
+    { "at",	tZONE,     HOUR( 2) },	/* Azores */
+    { "gst",	tZONE,     -HOUR(10) },	/* Guam Standard */
+    { "nft",	tZONE,     HOUR(3)+30 }, /* Newfoundland */
+    { "idlw",	tZONE,     HOUR(12) },	/* International Date Line West */
+    { "mewt",	tZONE,     -HOUR(1) },	/* Middle European Winter */
+    { "mest",	tDAYZONE,  -HOUR(1) },	/* Middle European Summer */
+    { "swt",	tZONE,     -HOUR(1) },	/* Swedish Winter */
+    { "sst",	tDAYZONE,  -HOUR(1) },	/* Swedish Summer */
+    { "fwt",	tZONE,     -HOUR(1) },	/* French Winter */
+    { "fst",	tDAYZONE,  -HOUR(1) },	/* French Summer */
+    { "bt",	tZONE,     -HOUR(3) },	/* Baghdad */
+    { "it",	tZONE,     -(HOUR(3)+30) }, /* Iran */
+    { "zp4",	tZONE,     -HOUR(4) },	/* USSR Zone 3 */
+    { "zp5",	tZONE,     -HOUR(5) },	/* USSR Zone 4 */
+    { "ist",	tZONE,     -(HOUR(5)+30) }, /* Indian Standard */
+    { "zp6",	tZONE,     -HOUR(6) },	/* USSR Zone 5 */
+    { "nst",	tZONE,     -HOUR(7) },	/* North Sumatra */
+    { "sst",	tZONE,     -HOUR(7) },	/* South Sumatra */
+    { "jt",	tZONE,     -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+    { "nzt",	tZONE,     -HOUR(12) },	/* New Zealand */
+    { "idle",	tZONE,     -HOUR(12) },	/* International Date Line East */
+    { "cat",	tZONE,     HOUR(10) },	/* -- expired 1967 */
+    { "nt",	tZONE,     HOUR(11) },	/* -- expired 1967 */
+    { "ahst",	tZONE,     HOUR(10) },	/* -- expired 1983 */
+    { "hdt",	tDAYZONE,  HOUR(10) },	/* -- expired 1986 */
+#endif	/* 0 */
+};
+
+static time_t
+ToSeconds(
+    time_t	Hours,
+    time_t	Minutes,
+    time_t	Seconds,
+    MERIDIAN	Meridian)
+{
+    if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+	return -1;
+    if (Meridian == MER24) {
+	if (Hours < 0 || Hours > 23)
+	    return -1;
+    }
+    else {
+	if (Hours < 1 || Hours > 12)
+		return -1;
+	if (Hours == 12)
+		Hours = 0;
+	if (Meridian == MERpm)
+		Hours += 12;
+    }
+    return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(
+    time_t	Month,
+    time_t	Day,
+    time_t	Year,
+    time_t	Hours,
+    time_t	Minutes,
+    time_t	Seconds,
+    MERIDIAN	Meridian,
+    DSTMODE	dst)
+{
+    static const int	DaysNormal[13] = {
+	0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static const int	DaysLeap[13] = {
+	0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+    };
+    static const int	LeapYears[] = {
+	1972, 1976, 1980, 1984, 1988, 1992, 1996,
+	2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+    };
+    const int *yp;
+    const int *mp;
+    int i;
+    time_t Julian;
+    time_t tod;
+
+    if (Year < 0)
+	Year = -Year;
+    if (Year < 70)
+        Year += 2000;
+    if (Year < 100)
+	Year += 1900;
+    if (Year < EPOCH)
+	Year += 100;
+    for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+	if (Year == *yp) {
+	    mp = DaysLeap;
+	    break;
+	}
+    if (Year < EPOCH || Year > END_OF_TIME
+     || Month < 1 || Month > 12
+     /* NOSTRICT */ /* conversion from long may lose accuracy */
+     || Day < 1 || Day > mp[(int)Month]) {
+	return -1;
+    }
+
+    Julian = Day - 1 + (Year - EPOCH) * 365;
+    for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++)
+	if (Year <= *yp)
+	    break;
+    for (i = 1; i < Month; i++)
+	Julian += *++mp;
+    Julian *= SECSPERDAY;
+    Julian += yyTimezone * 60L;
+    if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0) {
+	return -1;
+    }
+    Julian += tod;
+    tod = Julian;
+    if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+	Julian -= DST_OFFSET * 60 * 60;
+    return Julian;
+}
+
+
+static time_t
+DSTcorrect(
+    time_t	Start,
+    time_t	Future)
+{
+    time_t	StartDay;
+    time_t	FutureDay;
+
+    StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+    FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+    return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60 * 60;
+}
+
+
+static time_t
+RelativeMonth(
+    time_t	Start,
+    time_t	RelMonth)
+{
+    struct tm	*tm;
+    time_t	Month;
+    time_t	Year;
+
+    tm = localtime(&Start);
+    Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+    Year = Month / 12 + 1900;
+    Month = Month % 12 + 1;
+    return DSTcorrect(Start,
+	    Convert(Month, (time_t)tm->tm_mday, Year,
+		(time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+		MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(
+    char *buff,
+    int length)
+{
+    char	*p;
+    const char *q;
+    const TABLE *tp;
+    int	c;
+
+    p = buff;
+    c = p[0];
+
+    /* See if we have an abbreviation for a month. */
+    if (length == 3 || (length == 4 && p[3] == '.'))
+	for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+	    q = tp->name;
+	    if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+		yylval.Number = tp->value;
+		return tp->type;
+	    }
+	}
+    else
+	for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++)
+	    if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+		yylval.Number = tp->value;
+		return tp->type;
+	    }
+
+    /* Try for a timezone. */
+    for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+	if (c == tp->name[0] && p[1] == tp->name[1]
+	 && strcmp(p, tp->name) == 0) {
+	    yylval.Number = tp->value;
+	    return tp->type;
+	}
+
+    if (strcmp(buff, "dst") == 0)
+      return tDST;
+
+    /* Try the units table. */
+    for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+	if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+	    yylval.Number = tp->value;
+	    return tp->type;
+	}
+
+    /* Strip off any plural and try the units table again. */
+    if (--length > 0 && p[length] == 's') {
+	p[length] = '\0';
+	for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++)
+	    if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+		p[length] = 's';
+		yylval.Number = tp->value;
+		return tp->type;
+	    }
+	p[length] = 's';
+    }
+    length++;
+
+    /* Drop out any periods. */
+    for (p = buff, q = (STRING)buff; *q; q++)
+	if (*q != '.')
+	    *p++ = *q;
+    *p = '\0';
+
+    /* Try the meridians. */
+    if (buff[1] == 'm' && buff[2] == '\0') {
+	if (buff[0] == 'a') {
+	    yylval.Meridian = MERam;
+	    return tMERIDIAN;
+	}
+	if (buff[0] == 'p') {
+	    yylval.Meridian = MERpm;
+	    return tMERIDIAN;
+	}
+    }
+
+    /* If we saw any periods, try the timezones again. */
+    if (p - buff != length) {
+	c = buff[0];
+	for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++)
+	    if (c == tp->name[0] && p[1] == tp->name[1]
+	    && strcmp(p, tp->name) == 0) {
+		yylval.Number = tp->value;
+		return tp->type;
+	    }
+    }
+
+    /* Unknown word -- assume GMT timezone. */
+    yylval.Number = 0;
+    return tZONE;
+}
+
+
+static int
+date_lex(void)
+{
+    int	c;
+    char	*p;
+    char		buff[20];
+    int	sign;
+    int	i;
+    int	nesting;
+
+    for(;;) {
+	/* Get first character after the whitespace. */
+	for(;;) {
+	    while (CTYPE(isspace, *yyInput))
+		yyInput++;
+	    c = *yyInput;
+
+	    /* Ignore RFC 822 comments, typically time zone names. */
+	    if (c != LPAREN)
+		break;
+	    for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+		if (c == LPAREN)
+		    nesting++;
+		else if (!IS7BIT(c) || c == '\0' || c == '\r'
+		     || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c)))) {
+		    /* Lexical error: bad comment. */
+		    return '?';
+		}
+	    yyInput++;
+	}
+
+	/* A number? */
+	if (CTYPE(isdigit, c) || c == '-' || c == '+') {
+	    if (c == '-' || c == '+') {
+		sign = c == '-' ? -1 : 1;
+		yyInput++;
+		if (!CTYPE(isdigit, *yyInput)) {
+		    /* Return the isolated plus or minus sign. */
+		    --yyInput;
+		    return *yyInput++;
+		}
+	    }
+	    else
+		sign = 0;
+	    for (i = 0; (c = *yyInput++) != '\0' && CTYPE(isdigit, c); )
+		i = 10 * i + c - '0';
+	    yyInput--;
+	    yylval.Number = sign < 0 ? -i : i;
+	    return sign ? tSNUMBER : tUNUMBER;
+	}
+
+	/* A word? */
+	if (CTYPE(isalpha, c)) {
+	    for (p = buff; (c = *yyInput++) == '.' || CTYPE(isalpha, c); )
+		if (p < &buff[sizeof buff - 1])
+		    *p++ = CTYPE(isupper, c) ? tolower(c) : c;
+	    *p = '\0';
+	    yyInput--;
+	    return LookupWord(buff, p - buff);
+	}
+
+	return *yyInput++;
+    }
+}
+
+
+static int
+GetTimeInfo(
+    TIMEINFO		*Now)
+{
+    static time_t	LastTime;
+    static long		LastTzone;
+    struct tm		*tm;
+#if	defined(HAVE_GETTIMEOFDAY)
+    struct timeval	tv;
+#endif	/* defined(HAVE_GETTIMEOFDAY) */
+#if	defined(DONT_HAVE_TM_GMTOFF)
+    struct tm		local;
+    struct tm		gmt;
+#endif	/* !defined(DONT_HAVE_TM_GMTOFF) */
+
+    /* Get the basic time. */
+#if defined(HAVE_GETTIMEOFDAY)
+    if (gettimeofday(&tv, (struct timezone *)NULL) == -1)
+	return -1;
+    Now->time = tv.tv_sec;
+    Now->usec = tv.tv_usec;
+#else
+    /* Can't check for -1 since that might be a time, I guess. */
+    (void)time(&Now->time);
+    Now->usec = 0;
+#endif /* defined(HAVE_GETTIMEOFDAY) */
+
+    /* Now get the timezone if it's been an hour since the last time. */
+    if (Now->time - LastTime > 60 * 60) {
+	LastTime = Now->time;
+	if ((tm = localtime(&Now->time)) == NULL)
+	    return -1;
+#if	defined(DONT_HAVE_TM_GMTOFF)
+	/* To get the timezone, compare localtime with GMT. */
+	local = *tm;
+	if ((tm = gmtime(&Now->time)) == NULL)
+	    return -1;
+	gmt = *tm;
+
+	/* Assume we are never more than 24 hours away. */
+	LastTzone = gmt.tm_yday - local.tm_yday;
+	if (LastTzone > 1)
+	    LastTzone = -24;
+	else if (LastTzone < -1)
+	    LastTzone = 24;
+	else
+	    LastTzone *= 24;
+
+	/* Scale in the hours and minutes; ignore seconds. */
+	LastTzone += gmt.tm_hour - local.tm_hour;
+	LastTzone *= 60;
+	LastTzone += gmt.tm_min - local.tm_min;
+#else
+	LastTzone =  (0 - tm->tm_gmtoff) / 60;
+#endif	/* defined(DONT_HAVE_TM_GMTOFF) */
+    }
+    Now->tzone = LastTzone;
+    return 0;
+}
+
+
+time_t
+parsedate(
+    char		*p,
+    TIMEINFO		*now)
+{
+    struct tm		*tm;
+    TIMEINFO		ti;
+    time_t		Start;
+
+    yyInput = p;
+    if (now == NULL) {
+	now = &ti;
+	(void)GetTimeInfo(&ti);
+    }
+
+    tm = localtime(&now->time);
+    yyYear = tm->tm_year + 1900;
+    yyMonth = tm->tm_mon + 1;
+    yyDay = tm->tm_mday;
+    yyTimezone = now->tzone;
+    if (tm->tm_isdst)                   /* Correct timezone offset for DST */
+      yyTimezone += DST_OFFSET * 60;
+    yyDSTmode = DSTmaybe;
+    yyHour = 0;
+    yyMinutes = 0;
+    yySeconds = 0;
+    yyMeridian = MER24;
+    yyRelSeconds = 0;
+    yyRelMonth = 0;
+    yyHaveDate = 0;
+    yyHaveRel = 0;
+    yyHaveTime = 0;
+
+    if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+	return -1;
+
+    if (yyHaveDate || yyHaveTime) {
+	Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+		    yyMeridian, yyDSTmode);
+	if (Start < 0)
+	    return -1;
+    } else {
+	Start = now->time;
+	if (!yyHaveRel)
+	    Start -= (tm->tm_hour * 60L + tm->tm_min) * 60L + tm->tm_sec;
+    }
+
+    Start += yyRelSeconds;
+    if (yyRelMonth)
+	Start += RelativeMonth(Start, yyRelMonth);
+
+    /* Have to do *something* with a legitimate -1 so it's distinguishable
+     * from the error return value.  (Alternately could set errno on error.) */
+    return Start == -1 ? 0 : Start;
+}