[net.sources] Date-string parser package

wales@ucla-cs.UUCP (09/01/84)

: This is a "shar" archive.  Extract with sh, not csh.
echo extracting README . . .
sed 's/^X//' > README << 'EOF'
X		    DATE MANIPULATION PACKAGE
X			Richard B. Wales
X	  UCLA Center for Experimental Computer Science
X
XThis is a software package for manipulating character strings repre-
Xsenting dates.  It can do the following:
X
X(1) Interpret an (almost) arbitrary date string and break it down into
X    year, month, day, hour, minute, second, time zone, day of the week,
X    and UNIX internal time (seconds since 1970).
X
X(2) Compute the day of the week and UNIX internal time corresponding to
X    a year/month/day/hour/minute/second/zone combination.
X
X(3) Compute the year/month/day/hour/minute/second/zone/day-of-the-week
X    combination corresponding to a UNIX internal time.
X
X(4) Generate a character string corresponding to a year/month/day/hour/
X    minute/second/zone combination.  Routines exist for creating strings
X    in either RFC822 (ARPANET mail) or UUCP mail formats.
X
XThe date parser accepts a superset of RFC822, UUCP, USENET, and UNIX
X("date" command) formats.  Numerous time zone abbreviations -- including
Xmany used in various parts of the world but not acknowledged in RFC822
X-- are understood.  The date parser is NOT suitable for applications
Xwhich require strict conformance with RFC822 and need to know whether a
Xgiven date string follows RFC822 exactly.  RFC822-format date strings
Xgenerated by this package, however, do conform strictly to RFC822; non-
XRFC822 time zone abbreviations which might have been used on input are
Xreplaced by numeric offsets on output.
X
XThe date parser was written using YACC.  However, it should be possible
Xto use these routines in programs which already use LEX and/or YACC for
Xother purposes, since all the external symbols beginning with "_yy" have
Xbeen renamed to begin with "_date_yy" instead.
X
XThis code was written and tested in a 4.1BSD environment.  Some mods may
Xbe necessary to make it run on other UNIX systems; in particular, people
Xusing AT&T System V and other systems which restrict the length of
Xexternal symbols may encounter some problems with long variable and rou-
Xtine names.
X
XThis package contains a copyright notice, as follows:
X
X		Copyright (c) 1984 by Richard B. Wales
X
XThe author hereby grants permission to use or redistribute this package
Xfreely and without charge, subject to the following restrictions:
X
X(1) The copyright notice must be retained in all copies of the source.
X
X(2) Any changes made to the source must be clearly documented (such as
X    by #ifdef's or by use of a source-code control system such as RCS or
X    SCCS), so that the original version of the source as distributed by
X    the author can be reconstructed if necessary and distinguished from
X    modifications made by others.
X
XThe author is interested in any comments, bug reports, etc., concerning
Xthis package, and will try to help out with problems as time permits.
XHowever, since this package is being made available without charge, the
Xauthor and his employer disclaim any responsibility, obligation, or com-
Xmitment to supply bug fixes or enhancements to this package, to adapt it
Xto run under any particular computer system, to supply consulting
Xassistance in its use, or to support it in any other manner whatsoever.
XThe author and his employer specifically and expressly disclaim any lia-
Xbility whatsoever for incidental or consequential damages which might
Xarise out of the use of this package.
X
X    Rich Wales
X    UCLA Computer Science Department
X    3531 Boelter Hall // Los Angeles, CA 90024 // (213) 825-5683
X    ARPA:  wales@UCLA-LOCUS.ARPA
X    UUCP:  ...!{cepu,ihnp4,trwspp,ucbvax}!ucla-cs!wales
EOF
echo extracting date.3 . . .
sed 's/^X//' > date.3 << 'EOF'
X.\~ $Log:	/s/uclasrc/mail/date/RCS/date.3,v $
X.\~ Revision 1.2  84/09/01  15:11:42  wales
X.\~ Changed the positions of tab stops so that the listing of the
X.\~ "parsedate" structure will be lined up properly.
X.\~ 
X.\~ Revision 1.1  84/09/01  15:01:08  wales
X.\~ Initial revision
X.\~ 
X.\~ Copyright (c) 1984 by Richard B. Wales
X.\~
X.\~ $Header: /s/uclasrc/mail/date/RCS/date.3,v 1.2 84/09/01 15:11:42 wales UCLA $
X.\~
X.TH DATE 3 UCLA
X.SH "NAME"
Xparsedate \- interpret character strings representing dates
X.SH "SYNOPSIS"
X.nf
X#include "parsedate.h"
X.sp
Xchar date;
Xstruct parsedate *pd;
X.sp
Xpd = parsedate (date);
X.sp
Xcompute_unixtime (pd);
X.sp
Xbreak_down_unixtime (pd);
X.sp
Xdate = mail_date_string (pd);
X.sp
Xdate = uucp_date_string (pd);
X.fi
X.SH "DESCRIPTION"
XThese routines manipulate character strings representing dates.
X.I parsedate
Xreturns a pointer to a structure of the following form
X(described in the include-file
X.IR parsedate.h ):
X.nf
X.ta 0.5i 2.5i
X.sp
Xstruct parsedate {
X	long unixtime;	/* as returned by time(2) */
X	char *error;	/* non-NULL = error */
X	int year;	/* year (1600 on) */
X	int month;	/* month (1-12) */
X	int day;	/* day of month (1-31) */
X	int hour;	/* hour (0-23) */
X	int minute;	/* minute (0-59) */
X	int second;	/* second (0-59) */
X	int zone;	/* time zone offset */
X	int dst;	/* daylight savings time */
X	int weekday;	/* real day of week */
X	int c_weekday;	/* claimed day of week */
X};
X.sp
X.fi
XAny field containing the value \-1 (except for
X.IR error )
Xindicates information which was either not supplied or was invalid.
X.PP
X.I unixtime
Xis the UNIX internal representation of the date
X(i.e., number of seconds since 1970).
X.I error
Xis
X.B NULL
Xif the date string did not contain a syntax error;
Xotherwise, it points to the position in the date string
Xwhere a syntax error was discovered.
X.I zone
Xindicates the time-zone offset in minutes from UTC.
XA positive value denotes a time zone east of Greenwich;
Xa negative value denotes a zone west of Greenwich.
X.I dst
Xis equal to 1 if the indicated time zone denotes ``daylight
Xsavings time'', or 0 if daylight savings time is not indicated.
X.PP
X.I weekday
Xand
X.I c_weekday
Xindicate a day of the week via a value in the range 0\-6
X(0 = Sunday, 6 = Saturday).
X.I c_weekday
Xshows which day of the week was actually specified in the date string;
X.I weekday
Xis derived from
X.IR year ,
X.IR month ,
Xand
X.I day
Xvia a perpetual-calendar algorithm.
X.PP
X.I compute_unixtime
Xtakes a
X.I struct parsedate
Xin which the
X.IR year ,
X.IR month ,
X.IR day ,
X.IR hour ,
X.IR minute ,
X.IR second ,
Xand
X.I zone
Xfields have been filled in, and fills in the
X.I unixtime
Xand
X.I weekday
Xfields as appropriate.
X.PP
X.I break_down_unixtime
Xtakes a
X.I struct parsedate
Xin which the
X.I unixtime
Xand
X.I zone
Xfields have been filled in, and fills in the
X.IR year ,
X.IR month ,
X.IR day ,
X.IR hour ,
X.IR minute ,
X.IR second ,
Xand
X.I weekday
Xfields as appropriate.
X.PP
X.I mail_date_string
Xreturns a pointer to a RFC822 (ARPANET mail standard) format character
Xstring corresponding to a
X.IR "struct parsedate" .
XSimilarly,
X.I uucp_date_string
Xreturns a pointer to a UUCP-mail format character string corresponding
Xto a
X.IR "struct parsedate" .
XIn each case, the
X.IR year ,
X.IR month ,
X.IR day ,
X.IR hour ,
X.IR minute ,
X.IR second ,
Xand
X.I zone
Xfields should be set first.
XIf only the
X.I unixtime
Xand
X.I zone
Xvalues are initially avaiable, then
X.I break_down_unixtime
Xshould be called before trying to generate a date string.
X.SH "DIAGNOSTICS"
XIf the character string supplied as an argument to
X.I parsedate
Xcontains a syntax error, the
X.I error
Xvariables in the returned
X.I struct parsedate
Xwill be set to point to the place in the argument string
Xwhere the error was discovered.
X.PP
XAny field which is either unspecified or given an invalid value
Xin a call to
X.I parsedate
Xis set to \-1.
X.I compute_unixtime
Xand
X.I break_down_unixtime
Xalso set the appropriate fields to \-1 if they are unable to compute
Xthe requested values.
X.PP
X.I mail_date_string
Xand
X.I uucp_date_string
Xreturn
X.B NULL
Xif the information required to generate the date string
Xis missing or invalid.
X.SH "BUGS"
XThe returned value from a call to
X.IR parsedate ,
X.IR mail_date_string ,
Xor
X.I uucp_date_string
Xpoints to a static area whose contents will be overwritten by the next
Xcall to the same routine.
X.PP
XA time-zone offset of ``\-0001'' (i.e., one minute west of Greenwich)
Xresults in a
X.I zone
Xvalue of \-1, and is thus indistinguishable from an invalid or missing
Xtime zone.
XSince this particular time-zone offset is not (and almost certainly
Xnever will be) used anywhere in the world, the fact that it cannot be
Xdistinguished from an invalid or missing value is probably unimportant.
X.SH "AUTHOR"
XRichard B. Wales
X.br
XUCLA Center for Experimental Computer Science
EOF
echo extracting Makefile . . .
sed 's/^X//' > Makefile << 'EOF'
X# $Log:	/s/uclasrc/mail/date/RCS/Makefile,v $
X# Revision 1.1  84/09/01  15:00:58  wales
X# Initial revision
X# 
X# $Header: /s/uclasrc/mail/date/RCS/Makefile,v 1.1 84/09/01 15:00:58 wales UCLA $
X#
X# Makefile for "date" library routines
X#
X# Copyright (c) 1984 by Richard B. Wales
X
XDEFS   =
XCFLAGS = -O $(DEFS) -DRCSIDENT
X
Xall:		libdate.a
X
Xstrip:		libdate.a
X
Xlibdate.a:	datelex.o dateyacc.o parsedate.o
X	rm -f libdate.a
X	ar rc libdate.a datelex.o dateyacc.o parsedate.o
X	ranlib libdate.a
X
X.c.o:
X	cc -S $(CFLAGS) $*.c
X	sed 's/_yy/_date_yy/g' $*.s | as -o $*.o
X	rm -f $*.s
X
Xdateyacc.c dateyacc.h:	dateyacc.y
X	yacc -d dateyacc.y
X	mv y.tab.c dateyacc.c
X	mv y.tab.h dateyacc.h
X
Xclean:
X	rm -f *.o *.s\
X	      dateyacc.c dateyacc.h y.tab.c y.tab.h y.output\
X	      libdate.a
X
Xdatelex.o:	parsedate.h dateyacc.h
Xdateyacc.o:	parsedate.h
Xparsedate.o:	parsedate.h
EOF
echo extracting datelex.c . . .
sed 's/^X//' > datelex.c << 'EOF'
X/*$Log:	/s/uclasrc/mail/date/RCS/datelex.c,v $
X * Revision 1.1  84/09/01  15:01:14  wales
X * Initial revision
X * 
X * Copyright (c) 1984 by Richard B. Wales
X *
X * Purpose:
X *
X *     Lexical analyzer for "parsedate" routine.  This lexer was orig-
X *     inally written in LEX, but rewriting it as an ad-hoc routine
X *     resulted in an enormous savings in space and a significant
X *     increase in speed.
X *
X * Usage:
X *
X *     Called as needed by the YACC parser ("dateyacc.c").  Not intended
X *     to be called from any other routine.
X *
X * Notes:
X *
X * Global contents:
X *
X *     int yylex ()
X *         Returns the token number (from the YACC grammar) of the next
X *         token in the input string pointed to by the global variable
X *         "yyinbuf".  The global variable "yylval" is set to the lexi-
X *         cal value (if any) of the token.  "yyinbuf" is set to point
X *         to the first character in the input string which is not a
X *         part of the token just recognized.
X *
X * Local contents:
X *
X *     struct wordtable *find_word (word) char *word;
X *         Returns a pointer to the entry in the "wordtable" array cor-
X *         responding to the string "word".  If "word" is not found, the
X *         returned value is NULL.
X */
X#ifdef RCSIDENT
Xstatic char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/datelex.c,v 1.1 84/09/01 15:01:14 wales UCLA $";
X#endif RCSIDENT
X
X#include <stdio.h>
X#include "dateyacc.h"
X#include "parsedate.h"
X
X/* pointer to the input string */
Xchar *yyinbuf;
X
X/* "answer" structure */
Xstruct parsedate yyans;
X
X/* Binary-search word table.
X * Entries must be sorted in ascending order on "text" value, and the
X * total number of entries must be one less than a power of 2.  "Filler"
X * entries (with "token" values of -1) are inserted at the beginning and
X * end of the table to pad it as necessary.
X */
X#define WORDTABLE_SIZE 127	/* MUST be one less than power of 2 */
X#define MAX_WORD_LENGTH 20	/* used to weed out overly long words
X				 * in "yylex".  Must be at least as long
X				 * as the longest word in "wordtable",
X				 * but may be longer.
X				 */
Xstruct wordtable
X    {	char *text;
X	int   token;
X	int   lexval;
X    } wordtable[WORDTABLE_SIZE] =
X    {/* text            token           lexval */
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"",		-1,		0,
X	"A",		STD_ZONE,	60,	/* UTC+1h */
X	"ACSST",	DST_ZONE,	630,	/* Cent. Australia */
X	"ACST",		STD_ZONE,	570,	/* Cent. Australia */
X	"ADT",		DST_ZONE,	-180,	/* Atlantic (Canada) */
X	"AESST",	DST_ZONE,	660,	/* E. Australia */
X	"AEST",		STD_ZONE,	600,	/* E. Australia */
X	"AM",		AMPM,		0,
X	"APR",		MONTH_NAME,	4,
X	"APRIL",	MONTH_NAME,	4,
X	"AST",		STD_ZONE,	-240,	/* Atlantic (Canada) */
X	"AT",		0,		0,	/* "at" (throwaway) */
X	"AUG",		MONTH_NAME,	8,
X	"AUGUST",	MONTH_NAME,	8,
X	"AWSST",	DST_ZONE,	540,	/* W. Australia */
X	"AWST",		STD_ZONE,	480,	/* W. Australia */
X	"B",		STD_ZONE,	120,	/* UTC+2h */
X	"BST",		DST_ZONE,	60,	/* Great Britain */
X	"C",		STD_ZONE,	180,	/* UTC+3h */
X	"CDT",		DST_ZONE,	-300,
X	"CST",		STD_ZONE,	-360,
X	"D",		STD_ZONE,	240,	/* UTC+4h */
X	"DEC",		MONTH_NAME,	12,
X	"DECEMBER",	MONTH_NAME,	12,
X	"DST",		DST_SUFFIX,	0,
X	"E",		STD_ZONE,	300,	/* UTC+5h */
X	"EDT",		DST_ZONE,	-240,
X	"EET",		STD_ZONE,	120,	/* Eastern Europe */
X	"EETDST",	DST_ZONE,	180,	/* Eastern Europe */
X	"EST",		STD_ZONE,	-300,
X	"F",		STD_ZONE,	360,	/* UTC+6h */
X	"FEB",		MONTH_NAME,	2,
X	"FEBRUARY",	MONTH_NAME,	2,
X	"FRI",		DAY_NAME,	5,
X	"FRIDAY",	DAY_NAME,	5,
X	"G",		STD_ZONE,	420,	/* UTC+7h */
X	"GMT",		STD_ZONE,	0,
X	"H",		STD_ZONE,	480,	/* UTC+8h */
X	"HDT",		DST_ZONE,	-540,	/* Hawaii/Alaska */
X	"HST",		STD_ZONE,	-600,	/* Hawaii/Alaska */
X	"I",		STD_ZONE,	540,	/* UTC+9h */
X	"IST",		STD_ZONE,	120,	/* Israel */
X	"JAN",		MONTH_NAME,	1,
X	"JANUARY",	MONTH_NAME,	1,
X	"JUL",		MONTH_NAME,	7,
X	"JULY",		MONTH_NAME,	7,
X	"JUN",		MONTH_NAME,	6,
X	"JUNE",		MONTH_NAME,	6,
X	"K",		STD_ZONE,	600,	/* UTC+10h */
X	"L",		STD_ZONE,	660,	/* UTC+11h */
X	"M",		STD_ZONE,	720,	/* UTC+12h */
X	"MAR",		MONTH_NAME,	3,
X	"MARCH",	MONTH_NAME,	3,
X	"MAY",		MONTH_NAME,	5,
X	"MDT",		DST_ZONE,	-360,
X	"MET",		STD_ZONE,	60,	/* Central Europe */
X	"METDST",	DST_ZONE,	120,	/* Central Europe */
X	"MON",		DAY_NAME,	1,
X	"MONDAY",	DAY_NAME,	1,
X	"MST",		STD_ZONE,	-420,
X	"N",		STD_ZONE,	-60,	/* UTC-1h */
X	"NDT",		DST_ZONE,	-150,	/* Nfld. (Canada) */
X	"NOV",		MONTH_NAME,	11,
X	"NOVEMBER",	MONTH_NAME,	11,
X	"NST",		STD_ZONE,	-210,	/* Nfld. (Canada) */
X	"O",		STD_ZONE,	-120,	/* UTC-2h */
X	"OCT",		MONTH_NAME,	10,
X	"OCTOBER",	MONTH_NAME,	10,
X	"ON",		0,		0,	/* "on" (throwaway) */
X	"P",		STD_ZONE,	-180,	/* UTC-3h */
X	"PDT",		DST_ZONE,	-420,
X	"PM",		AMPM,		12,
X	"PST",		STD_ZONE,	-480,
X	"Q",		STD_ZONE,	-240,	/* UTC-4h */
X	"R",		STD_ZONE,	-300,	/* UTC-5h */
X	"S",		STD_ZONE,	-360,	/* UTC-6h */
X	"SAT",		DAY_NAME,	6,
X	"SATURDAY",	DAY_NAME,	6,
X	"SEP",		MONTH_NAME,	9,
X	"SEPT",		MONTH_NAME,	9,
X	"SEPTEMBER",	MONTH_NAME,	9,
X	"SUN",		DAY_NAME,	0,
X	"SUNDAY",	DAY_NAME,	0,
X	"T",		STD_ZONE,	-420,	/* UTC-7h */
X	"THU",		DAY_NAME,	4,
X	"THUR",		DAY_NAME,	4,
X	"THURS",	DAY_NAME,	4,
X	"THURSDAY",	DAY_NAME,	4,
X	"TUE",		DAY_NAME,	2,
X	"TUES",		DAY_NAME,	2,
X	"TUESDAY",	DAY_NAME,	2,
X	"U",		STD_ZONE,	-480,	/* UTC-8h */
X	"UT",		STD_ZONE,	0,
X	"UTC",		STD_ZONE,	0,
X	"V",		STD_ZONE,	-540,	/* UTC-9h */
X	"W",		STD_ZONE,	-600,	/* UTC-10h */
X	"WED",		DAY_NAME,	3,
X	"WEDNESDAY",	DAY_NAME,	3,
X	"WEDS",		DAY_NAME,	3,
X	"WET",		STD_ZONE,	0,	/* Western Europe */
X	"WETDST",	DST_ZONE,	60,	/* Western Europe */
X	"X",		STD_ZONE,	-660,	/* UTC-11h */
X	"Y",		STD_ZONE,	-720,	/* UTC-12h */
X	"YDT",		DST_ZONE,	-480,	/* Yukon */
X	"YST",		STD_ZONE,	-540,	/* Yukon */
X	"Z",		STD_ZONE,	0,	/* UTC */
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X	"\177",		-1,		0,
X    };
Xstruct wordtable *find_word();
X
X/* int yylex ()
X *     Return the next token for the YACC parser.
X */
Xint
Xyylex ()
X{   static char buffer[MAX_WORD_LENGTH+1];
X    register char *c, *d;
X    register struct wordtable *wt;
X    register int num, ndgts;
X
X  restart:
X    /* We will return here if an invalid input token is detected. */
X    c = buffer; d = yyinbuf;
X
X    /* Skip over blanks, tabs, commas, and parentheses. */
X    do { *c = *d++; }
X	while (*c == ' ' || *c == '\t' || *c == ','
X	       || *c == '(' || *c == ')');
X
X    /* A zero (null) byte signals the end of the input. */
X    if (*c == 0)
X    {	yyinbuf = --d;		/* stay put on the null */
X	return 0;
X    }
X
X    /* Process a word (looking it up in "wordtable"). */
X    if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z'))
X    {	if (*c >= 'a' && *c <= 'z') *c += 'A' - 'a';
X	while (c < buffer + MAX_WORD_LENGTH
X	       && ((*d >= 'A' && *d <= 'Z')
X		   || (*d >= 'a' && *d <= 'z')))
X	{   *++c = *d++;
X	    if (*c >= 'a' && *c <= 'z') *c += 'A' - 'a';
X	}
X	if ((*d >= 'A' && *d <= 'Z') || (*d >= 'a' && *d <= 'z'))
X	{   /* Word is too long (over MAX_WORD_LENGTH characters). */
X	    do { d++; } while ((*d >= 'A' && *d <= 'Z')
X			       || (*d >= 'a' && *d <= 'z'));
X	    yyinbuf = d;
X	    goto error;
X	}
X	*++c = 0; yyinbuf = d;
X	if ((wt = find_word (buffer)) == NULL) goto error;
X	if (wt->token == 0) goto restart;	/* ignore this word */
X	yylval.IntVal = wt->lexval;
X	return wt->token;
X    }
X
X    /* Process a number. */
X    if (*c >= '0' && *c <= '9')
X    {	num = *c - '0'; ndgts = 1;
X	for (ndgts = 1; ndgts < 6 && *d >= '0' && *d <= '9'; ndgts++)
X	    num = 10*num + (*d++ - '0');
X	if (*d >= '0' && *d <= '9')
X	{   /* Number is too long (over 6 digits). */
X	    do { d++; } while (*d >= '0' && *d <= '9');
X	    yyinbuf = d;
X	    goto error;
X	}
X	yyinbuf = d;
X	yylval.IntVal = num;
X	switch (ndgts)
X	{   case 1:  return NUM9;
X	    case 2:  if (num <= 23) return NUM23;
X		     if (num <= 59) return NUM59;
X		     /*otherwise*/  return NUM99;
X	    case 3:
X	    case 4:  if (num/100 <= 23 && num%100 <= 59) return NUM2359;
X		     /*otherwise*/                       return NUM9999;
X	    case 5:
X	    case 6:  if (num/10000 <= 23
X			 && (num%10000)/100 <= 59
X			 && num%100 <= 59)
X			 return NUM235959;
X		     goto error;
X	    default: goto error;
X    }	}
X
X    /* Pass back the following delimiter tokens verbatim.. */
X    if (*c == '-' || *c == '+' || *c == '/' || *c == ':' || *c == '.')
X    {	yyinbuf = d;
X	return *c;
X    }
X
X  error:
X    /* An unidentified character was found in the input. */
X    yyinbuf = d;
X    if (yyans.error == NULL) yyans.error = yyinbuf;
X    goto restart;
X}
X
X/* struct wordtable *find_word (word) char *word;
X *     Look up a word in the "wordtable" array via a binary search.
X */
Xstatic
Xstruct wordtable *
Xfind_word (word)
X    register char *word;
X{   register int low, mid, high;
X    register int comparison;
X
X    low = -1;
X    high = WORDTABLE_SIZE;
X    while (low+1 < high)
X    {	mid = (low + high) / 2;
X	comparison = strcmp (wordtable[mid].text, word);
X	if (comparison == 0) return wordtable+mid;
X	if (comparison > 0)  high = mid;
X	else                 low = mid;
X    }
X    return NULL;
X}
EOF
echo extracting dateyacc.y . . .
sed 's/^X//' > dateyacc.y << 'EOF'
X/*$Log:	/s/uclasrc/mail/date/RCS/dateyacc.y,v $
X * Revision 1.1  84/09/01  15:01:22  wales
X * Initial revision
X * 
X * Copyright (c) 1984 by Richard B. Wales
X *
X * Purpose:
X *
X *     YACC parser for "parsedate" routine.
X *
X * Usage:
X *
X *     Called as needed by the "parsedate" routine in "parsedate.c".
X *     Not intended to be called from any other routine.
X *
X * Notes:
X *
X * Global contents:
X *
X *     int yyparse ()
X *         Parses the date string pointed to by the global variable
X *         "yyinbuf".  Sets the appropriate fields in the global data
X *         structure "yyans".  The returned value is 1 if there was a
X *         syntax error, 0 if there was no error.
X *
X * Local contents:
X *
X *     None.
X */
X
X%{
X#ifdef RCSIDENT
Xstatic char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/dateyacc.y,v 1.1 84/09/01 15:01:22 wales UCLA $";
X#endif RCSIDENT
X
X#include <stdio.h>
X#include "parsedate.h"
Xstruct parsedate yyans;
X
X/* No error routine is needed here. */
X#define yyerror(s)
X%}
X
X%union {
X    int IntVal;
X}
X
X%token	DAY_NAME
X%token	MONTH_NAME
X%token	NUM9 NUM23 NUM59 NUM99 NUM2359 NUM9999 NUM235959
X%token	AMPM
X%token	STD_ZONE DST_ZONE DST_SUFFIX
X
X%type	<IntVal>	DAY_NAME
X%type	<IntVal>	MONTH_NAME
X%type	<IntVal>	NUM9 NUM23 NUM59 NUM99 NUM2359 NUM9999 NUM235959
X%type	<IntVal>	AMPM
X%type	<IntVal>	STD_ZONE DST_ZONE
X%type	<IntVal>	num59 num zone.offset
X
X%start	goal
X%%
X
Xnum59:
X    NUM23
X  | NUM59
X
Xnum:
X    NUM9
X  | num59
X
Xgoal:
X    date
X  | date dayname
X  | date dayname time
X  | date dayname time year
X  | date dayname year
X  | date dayname year time
X  | date time
X  | date time dayname
X  | date time dayname year
X  | date time year
X  | date time year dayname
X  | date.year
X  | date.year dayname
X  | date.year dayname time
X  | date.year time
X  | date.year time dayname
X  | dayname date
X  | dayname date time
X  | dayname date time year
X  | dayname date.year
X  | dayname date.year time
X  | dayname time date
X  | dayname time date.year
X  | dayname time year.date
X  | dayname year.date
X  | dayname year.date time
X  | dayname year time date
X  | time
X  | time date
X  | time date dayname
X  | time date dayname year
X  | time date.year
X  | time date.year dayname
X  | time dayname date
X  | time dayname date.year
X  | time dayname year.date
X  | time year.date
X  | time year.date dayname
X  | time year dayname date
X  | year.date
X  | year.date dayname
X  | year.date dayname time
X  | year.date time
X  | year.date time dayname
X  | year dayname date
X  | year dayname date time
X  | year dayname time date
X  | year time date
X  | year time date dayname
X  | year time dayname date
X  | NUM2359
X	{ yyans.hour   = $1 / 100;
X	  yyans.minute = $1 % 100;
X	  yyans.second = -1;		/* unspecified */
X	}
X  | dayname
X  | error
X	{ extern char *yyinbuf;
X	  if (yyans.error == NULL) yyans.error = yyinbuf;
X	}
X
Xdayname:
X    DAY_NAME
X	{ yyans.c_weekday = $1; }
X  | DAY_NAME '.'
X	{ yyans.c_weekday = $1; }
X
Xdate.year:
X    date year
X  | hyphen.date '-' year
X  | slash.date '/' year
X
Xyear.date:
X    year date
X  /* | year '-' hyphen.date	(leads to parser conflict) */
X  | year '/' slash.date
X
Xdate:
X    num month.name
X	{ yyans.day = $1; }
X  | month.name num
X	{ yyans.day = $2; }
X  | num num
X	{ yyans.month = $1; yyans.day = $2; }
X
Xhyphen.date:
X    num '-' month.name
X	{ yyans.day = $1; }
X  | month.name '-' num
X	{ yyans.day = $3; }
X  | num '-' num
X	{ yyans.month = $1; yyans.day = $3; }
X
Xslash.date:
X    num '/' month.name
X	{ yyans.day = $1; }
X  | month.name '/' num
X	{ yyans.day = $3; }
X  | num '/' num
X	{ yyans.month = $1; yyans.day = $3; }
X
Xyear:
X    NUM99		/* precludes two-digit date before 1960 */
X	{ yyans.year = 1900 + $1; }
X  | NUM2359
X	{ yyans.year = $1; }
X  | NUM9999
X	{ yyans.year = $1; }
X
Xmonth.name:
X    MONTH_NAME
X	{ yyans.month = $1; }
X  | MONTH_NAME '.'
X	{ yyans.month = $1; }
X
Xtime:
X    hour.alone
X  | hour am.pm
X  | hour zone
X  | hour am.pm zone
X
Xhour:
X    NUM2359
X	{ yyans.hour   = $1 / 100;
X	  yyans.minute = $1 % 100;
X	  yyans.second = -1;		/* unspecified */
X	}
X  | hour.alone
X
Xhour.alone:
X    NUM9 ':' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = -1;		/* unspecified */
X	}
X  | NUM9 '.' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = -1;		/* unspecified */
X	}
X  | NUM9 ':' num59 ':' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = $5;
X	}
X  | NUM9 '.' num59 '.' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = $5;
X	}
X  | NUM23 ':' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = -1;		/* unspecified */
X	}
X  | NUM23 '.' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = -1;		/* unspecified */
X	}
X  | NUM23 ':' num59 ':' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = $5;
X	}
X  | NUM23 '.' num59 '.' num59
X	{ yyans.hour   = $1;
X	  yyans.minute = $3;
X	  yyans.second = $5;
X	}
X  | NUM2359 ':' num59
X	{ yyans.hour   = $1 / 100;
X	  yyans.minute = $1 % 100;
X	  yyans.second = $3;
X	}
X  | NUM2359 '.' num59
X	{ yyans.hour   = $1 / 100;
X	  yyans.minute = $1 % 100;
X	  yyans.second = $3;
X	}
X  | NUM235959
X	{ yyans.hour   = $1 / 10000;
X	  yyans.minute = ($1 % 10000) / 100;
X	  yyans.second = $1 % 100;
X	}
X
Xam.pm:
X    AMPM
X	{ if (yyans.hour < 1 || yyans.hour > 12)
X	    yyans.hour = -1;		/* invalid */
X	  else
X	  { if (yyans.hour == 12) yyans.hour = 0;
X	    yyans.hour += $1;		/* 0 for AM, 12 for PM */
X	} }
X
Xzone:
X    STD_ZONE
X	{ yyans.zone = $1; yyans.dst = 0; }
X  | STD_ZONE DST_SUFFIX
X	{ yyans.zone = $1 + 60; yyans.dst = 1; }
X  | '-' STD_ZONE
X	{ yyans.zone = $2; yyans.dst = 0; }
X  | '-' STD_ZONE DST_SUFFIX
X	{ yyans.zone = $2 + 60; yyans.dst = 1; }
X  | DST_ZONE
X	{ yyans.zone = $1; yyans.dst = 1; }
X  | '-' DST_ZONE
X	{ yyans.zone = $2; yyans.dst = 1; }
X  | '+' zone.offset
X	{ yyans.zone = $2; yyans.dst = 0; }
X  | '-' '+' zone.offset
X	{ yyans.zone = $3; yyans.dst = 0; }
X  | '-' zone.offset
X	{ yyans.zone = - $2; yyans.dst = 0; }
X  | '-' '-' zone.offset
X	{ yyans.zone = - $3; yyans.dst = 0; }
X
Xzone.offset:
X    NUM9
X	{ $$ = 60 * $1; }
X  | NUM9 ':' num59
X	{ $$ = 60 * $1 + $3; }
X  | NUM9 '.' num59
X	{ $$ = 60 * $1 + $3; }
X  | NUM23
X	{ $$ = 60 * $1; }
X  | NUM23 ':' num59
X	{ $$ = 60 * $1 + $3; }
X  | NUM23 '.' num59
X	{ $$ = 60 * $1 + $3; }
X  | NUM2359
X	{ $$ = 60 * ($1 / 100) | ($1 % 100); }
X
X%%
EOF
echo extracting parsedate.c . . .
sed 's/^X//' > parsedate.c << 'EOF'
X/*LINTLIBRARY*/
X
X/*$Log:	/s/uclasrc/mail/date/RCS/parsedate.c,v $
X * Revision 1.1  84/09/01  15:01:30  wales
X * Initial revision
X * 
X * Copyright (c) 1984 by Richard B. Wales
X *
X * Purpose:
X *
X *     Manipulate character strings representing dates.
X *
X * Usage:
X *
X *     #include <parsedate.h>
X *
X *     char date;
X *     struct parsedate *pd;
X *
X *     pd = parsedate (date);
X *
X *     compute_unixtime (pd);
X *
X *     break_down_unixtime (pd);
X *
X *     date = mail_date_string (pd);
X *
X *     date = uucp_date_string (pd);
X *
X * Notes:
X *
X *     The returned value from "parsedate", "mail_date_string", or
X *     "uucp_date_string" points to static data whose contents are
X *     overwritten by the next call to the same routine.
X *
X *     "compute_unixtime" is implicitly called by "parsedate".
X *
X * Global contents:
X *
X *     struct parsedate *parsedate (date) char *date;
X *         Parse a character string representing a date and time into
X *         individual values in a "struct parsedate" data structure.
X *    
X *     compute_unixtime (pd) struct parsedate *pd;
X *         Given a mostly filled-in "struct parsedate", compute the day
X *         of the week and the internal UNIX representation of the date.
X *    
X *     break_down_unixtime (pd) struct parsedate *pd;
X *         Compute the date and time corresponding to the "unixtime" and
X *         "zone" values in a "struct parsedate".
X *    
X *     char *mail_date_string (pd) struct parsedate *pd;
X *         Generate a character string representing a date and time in
X *         the RFC822 (ARPANET mail standard) format.
X *    
X *     char *uucp_date_string (pd) struct parsedate *pd;
X *         Generate a character string representing a date and time in
X *         the UUCP mail format.
X *
X * Local contents:
X *
X *     None.
X */
X
X#include <stdio.h>
X#include "parsedate.h"
X
X#ifdef RCSIDENT
Xstatic char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/parsedate.c,v 1.1 84/09/01 15:01:30 wales UCLA $";
Xstatic char rcs_parsedate_hdr[] = RCS_PARSEDATE_HDR;
X#endif RCSIDENT
X
X/* Number of seconds in various time intervals. */
X#define SEC_PER_MIN  60
X#define SEC_PER_HOUR (60*SEC_PER_MIN)
X#define SEC_PER_DAY  (24*SEC_PER_HOUR)
X#define SEC_PER_YEAR (365*SEC_PER_DAY)
X
X/* Number of days in each month. */
Xstatic int monthsize[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
X
X/* Three-letter abbreviations of month and day names. */
Xstatic char monthname[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
Xstatic char dayname[]   = "SunMonTueWedThuFriSat";
X
X/* struct parsedate *parsedate (date) char *date;
X *     Analyze a character string representing a date and time.  The
X *     returned value points to a data structure with the desired
X *     information.  (NOTE:  The returned value points to static data
X *     whose contents are overwritten by each call.)
X */
Xstruct parsedate *
Xparsedate (date)
X    register char *date;
X{   register char *c;
X    register int year_save;
X    extern struct parsedate yyans;
X    extern char *yyinbuf;
X    extern char *sprintf();
X
X    /* Initialize the returned-value structure. */
X    yyans.unixtime  = -1;
X    yyans.year      = -1;
X    yyans.month     = -1;
X    yyans.day       = -1;
X    yyans.hour      = -1;
X    yyans.minute    = -1;
X    yyans.second    = -1;
X    yyans.zone      = -1;
X    yyans.dst       = -1;
X    yyans.weekday   = -1;
X    yyans.c_weekday = -1;
X    yyans.error     =  NULL;
X
X    /* Parse the argument string. */
X    yyinbuf = date;
X    if (yyparse () != 0 && yyans.error == NULL) yyans.error = yyinbuf;
X
X    /* Validate the day of the month, compute/validate the day of the
X     * week, and compute the internal UNIX form of the time.  See if
X     * "compute_unixtime" found fault with the year or the day of the
X     * month.  (Note that we have to remember the original "year" value
X     * because it might legitimately have been -1 to begin with.)
X     */
X    year_save = yyans.year; compute_unixtime (&yyans);
X    if (yyans.error == NULL
X	&& (yyans.year != year_save
X	    || (yyans.month > 0 && yyans.day < 0)
X	    || (yyans.month < 0 && yyans.day > 0)))
X	yyans.error = yyinbuf;
X
X    return &yyans;
X}
X
X/* compute_unixtime (pd) struct parsedate *pd;
X *     Given a mostly filled-in "struct parsedate", compute the day of
X *     the week and the internal UNIX representation of the date.
X *
X *     A year before 1600 will be rejected and replaced with -1.  A
X *     date from 1600 on which falls outside the range representable in
X *     internal UNIX form will still have the correct day of the week
X *     computed.
X *
X *     The day of the week is always computed on the assumption that the
X *     Gregorian calendar is in use.  Days of the week for dates in the
X *     far future may turn out to be incorrect if any changes are made
X *     to the calendar between now and then.
X */
Xcompute_unixtime (pd)
X    register struct parsedate *pd;
X{   register int weekday, n, l, a;
X
X    /* Validate the year. */
X    if (pd->year >= 0 && pd->year < 1600) pd->year = -1;
X
X    /* Validate the day of the month.  Also calculate the number of days
X     * in February (even if this is not February, we will need the num-
X     * ber of days in February later on when computing the UNIX time).
X     */
X    if (pd->month > 0)
X    {	if      (pd->year < 0)        monthsize[2] = 29;
X	else if (pd->year %   4 != 0) monthsize[2] = 28;
X	else if (pd->year % 100 != 0) monthsize[2] = 29;
X	else if (pd->year % 400 != 0) monthsize[2] = 28;
X	else                          monthsize[2] = 29;
X	if (pd->day <= 0 || pd->day > monthsize[pd->month])
X	    pd->day = -1;
X    }
X
X    /* Compute the day of the week.  The next several lines constitute a
X     * perpetual-calendar formula.  Note, of course, that the "claimed"
X     * day of the week (pd->c_weekday) is ignored here.
X     */
X    if (pd->year > 0 && pd->month > 0 && pd->day > 0)
X    {	if (pd->month >= 3) n = pd->year / 100,
X			    l = pd->year % 100;
X	else                n = (pd->year-1) / 100,
X			    l = (pd->year-1) % 100;
X	a = (26 * ((pd->month+9)%12 + 1) - 2) / 10;
X	weekday = (a+(l>>2)+(n>>2)+l-(n+n)+pd->day);
X	while (weekday < 0) weekday += 7;
X	pd->weekday = weekday % 7;
X    }
X
X    /* Figure out the internal UNIX form of the date. */
X    if (pd->year >= 1969 && pd->year <= 2038
X	&& pd->month > 0 && pd->day > 0
X	&& pd->hour >= 0 && pd->minute >= 0
X	&& pd->zone != -1 && pd->zone > -1440 && pd->zone < 1440)
X    {	pd->unixtime =
X	      SEC_PER_YEAR * (pd->year - 1970)
X	    + SEC_PER_DAY  * ((pd->year - 1969) / 4)
X	    /* month is taken care of later */
X	    + SEC_PER_DAY  * (pd->day - 1)
X	    + SEC_PER_HOUR * pd->hour
X	    + SEC_PER_MIN  * pd->minute
X	    /* seconds are taken care of later */
X	    - SEC_PER_MIN  * pd->zone;
X	if (pd->second >= 0)
X	    pd->unixtime += pd->second;
X	for (n = pd->month - 1; n > 0; n--)
X	    pd->unixtime += SEC_PER_DAY * monthsize[n];
X	if (pd->unixtime < 0) pd->unixtime = -1;
X    }
X    else pd->unixtime = -1;
X}
X
X/* break_down_unixtime (pd) struct parsedate *pd;
X *     Given the "unixtime" and "zone" fields of a "struct parsedate",
X *     compute the values of the "year", "month", "day", "hour", "min-
X *     ute", "second", and "weekday" fields.  The "dst" and "error"
X *     fields of the structure are not used or modified.
X */
Xbreak_down_unixtime (pd)
X    register struct parsedate *pd;
X{   register unsigned long timevalue;
X    register int m, n;
X
X    /* Validate the "unixtime" and "zone" fields. */
X    if (pd->unixtime < 0
X	|| pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
X    {	/* Sorry, can't do it. */
X	pd->year = -1; pd->month = -1; pd->day = -1;
X	pd->hour = -1; pd->minute = -1; pd->second = -1;
X	pd->weekday = -1;
X	return;
X    }
X
X    /* Even though "pd->unixtime" must be non-negative, and thus cannot
X     * indicate a time earlier than 1970, a negative "pd->zone" could
X     * cause the local date to be Wednesday, 31 December 1969.  Such a
X     * date requires special handling.
X     *
X     * A local date earlier than 31 December 1969 is impossible because
X     * "pd->zone" must represent a time-zone shift of less than a day.
X     */
X    if (pd->zone < 0 && pd->unixtime + SEC_PER_MIN * pd->zone < 0)
X    {	pd->year = 1969; pd->month = 12; pd->day = 31;
X	pd->weekday = 3;    /* Wednesday */
X	timevalue = pd->unixtime + SEC_PER_MIN * pd->zone + SEC_PER_DAY;
X	/* Note:  0 <= timevalue < SEC_PER_DAY */
X	pd->hour = timevalue / SEC_PER_HOUR;
X	pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
X	pd->second = timevalue % SEC_PER_MIN;
X	return;
X    }
X
X    /* Handle the general case (local time is 1970 or later). */
X    timevalue = pd->unixtime + SEC_PER_MIN * pd->zone;
X
X    /* day of the week (1 January 1970 was a Thursday) . . . */
X    pd->weekday = (timevalue/SEC_PER_DAY + 4 /* Thursday */) % 7;
X
X    /* year (note that the only possible century year here is 2000,
X     * a leap year -- hence no special tests for century years are
X     * needed) . . .
X     */
X    for (m = 1970; ; m++)
X    {	n = (m%4==0) ? SEC_PER_YEAR+SEC_PER_DAY : SEC_PER_YEAR;
X	if (n > timevalue) break;
X	timevalue -= n;
X    }
X    pd->year = m;
X    monthsize[2] = (m%4==0) ? 29 : 28;
X
X    /* month . . . */
X    for (m = 1; ; m++)
X    {	n = SEC_PER_DAY * monthsize[m];
X	if (n > timevalue) break;
X	timevalue -= n;
X    }
X    pd->month = m;
X
X    /* day, hour, minute, and second . . . */
X    pd->day    = (timevalue / SEC_PER_DAY) + 1;
X    pd->hour   = (timevalue % SEC_PER_DAY) / SEC_PER_HOUR;
X    pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
X    pd->second = timevalue % SEC_PER_MIN;
X}
X
X/* char *mail_date_string (pd) struct parsedate *pd;
X *     Generate a character string representing a date and time in the
X *     RFC822 (ARPANET mail standard) format.  A value of NULL is re-
X *     turned if "pd" does not contain all necessary data fields.
X *     (NOTE:  The returned value points to static data whose contents
X *     are overwritten by each call.)
X */
Xchar *
Xmail_date_string (pd)
X    register struct parsedate *pd;
X{   register char *c;
X    static char answer[50];
X
X    /* Check the day of the month and compute the day of the week. */
X    compute_unixtime (pd);
X
X    /* Make sure all required fields are present. */
X    if (pd->year < 0 || pd->month < 0 || pd->day < 0
X	|| pd->hour < 0 || pd->minute < 0
X	|| pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
X	return NULL;		/* impossible to generate string */
X
X    /* Generate the answer string. */
X    sprintf (answer,
X	     "%.3s, %d %.3s %d %02d:%02d",
X	     dayname + 3*pd->weekday,
X	     pd->day, monthname + 3*(pd->month-1),
X	     (pd->year >= 1960 && pd->year <= 1999)
X		 ? pd->year - 1900 : pd->year,
X	     pd->hour, pd->minute);
X    c = answer + strlen (answer);
X    if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
X    *c++ = ' ';
X    switch (pd->zone)
X    {	/* NOTE:  Only zone abbreviations in RFC822 are used here. */
X	case    0: strcpy (c, pd->dst ? "+0000" : "GMT");   break;
X	case -240: strcpy (c, pd->dst ? "EDT"   : "-0400"); break;
X	case -300: strcpy (c, pd->dst ? "CDT"   : "EST");   break;
X	case -360: strcpy (c, pd->dst ? "MDT"   : "CST");   break;
X	case -420: strcpy (c, pd->dst ? "PDT"   : "MST");   break;
X	case -480: strcpy (c, pd->dst ? "-0800" : "PST");   break;
X	default:
X	    if (pd->zone >= 0)
X		 sprintf (c, "+%02d%02d",  pd->zone/60,  pd->zone%60);
X	    else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
X    }
X
X    return answer;
X}
X
X/* char *uucp_date_string (pd) struct parsedate *pd;
X *     Generate a character string representing a date and time in the
X *     UUCP mail format.  A value of NULL is returned if "pd" does not
X *     contain all necessary data fields.  (NOTE:  The returned value
X *     points to static data whose contents are overwritten by each
X *     call.)
X */
Xchar *
Xuucp_date_string (pd)
X    register struct parsedate *pd;
X{   register char *c;
X    static char answer[50];
X
X    /* Check the day of the month and compute the day of the week. */
X    compute_unixtime (pd);
X
X    /* Make sure all required fields are present. */
X    if (pd->year < 0 || pd->month < 0 || pd->day < 0
X	|| pd->hour < 0 || pd->minute < 0
X	|| pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
X	return NULL;		/* impossible to generate string */
X
X    /* Generate the answer string. */
X    sprintf (answer,
X	     "%.3s %.3s %d %02d:%02d",
X	     dayname + 3*pd->weekday,
X	     monthname + 3*(pd->month-1), pd->day,
X	     pd->hour, pd->minute);
X    c = answer + strlen (answer);
X    if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
X    switch (pd->zone)
X    {	/* NOTE:  Only zone abbreviations in RFC822 are used here. */
X	case    0: strcpy (c, pd->dst ? "+0000" : "-GMT");   break;
X	case -240: strcpy (c, pd->dst ? "-EDT"  : "-0400"); break;
X	case -300: strcpy (c, pd->dst ? "-CDT"  : "-EST");   break;
X	case -360: strcpy (c, pd->dst ? "-MDT"  : "-CST");   break;
X	case -420: strcpy (c, pd->dst ? "-PDT"  : "-MST");   break;
X	case -480: strcpy (c, pd->dst ? "-0800" : "-PST");   break;
X	default:
X	    if (pd->zone >= 0)
X		 sprintf (c, "+%02d%02d",  pd->zone/60,  pd->zone%60);
X	    else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
X    }
X    c = answer + strlen (answer);
X    sprintf (c, " %d", pd->year);
X
X    return answer;
X}
EOF
echo extracting parsedate.h . . .
sed 's/^X//' > parsedate.h << 'EOF'
X/* $Log:	/s/uclasrc/mail/date/RCS/parsedate.h,v $
X * Revision 1.1  84/09/01  15:01:38  wales
X * Initial revision
X * 
X * Copyright (c) 1984 by Richard B. Wales
X *
X */
X#ifdef RCSIDENT
X#define RCS_PARSEDATE_HDR "$Header: /s/uclasrc/mail/date/RCS/parsedate.h,v 1.1 84/09/01 15:01:38 wales UCLA $"
X#endif RCSIDENT
X
X/* Data structure returned by "parsedate".
X *
X * A value of NULL for "error" means that no syntax errors were detected
X * in the argument value.  A non-NULL value points to the byte position
X * within the argument string at which it was discovered that an error
X * existed.
X *
X * A value of -1 means that the field was never given a value, or that
X * the value supplied was invalid.  (A side effect of this convention is
X * that a time zone offset of -1 -- i.e., one minute west of GMT -- is
X * indistinguishable from an invalid or unspecified time zone offset.
X * Since the likelihood of "-0001" being a legitimate time zone is nil,
X * banning it is a small price to pay for the uniformity of using -1 as
X * a "missing/invalid" indication for all fields.)
X */
Xstruct parsedate
X    {	long unixtime;	/* UNIX internal representation of time */
X	char *error;	/* NULL = OK; non-NULL = error */
X	int year;	/* year (1600 on) */
X	int month;	/* month (1-12) */
X	int day;	/* day of month (1-31) */
X	int hour;	/* hour (0-23) */
X	int minute;	/* minute (0-59) */
X	int second;	/* second (0-59) */
X	int zone;	/* time zone offset in minutes -- "+" or "-" */
X	int dst;	/* daylight savings time (0 = no, 1 = yes) */
X	int weekday;	/* real day of week (0-6; 0 = Sunday) */
X	int c_weekday;	/* claimed day of week (0-6; 0 = Sunday) */
X    };
X
Xstruct parsedate *parsedate();
EOF
echo all done
exit
-- 
    Rich Wales
    UCLA Computer Science Department
    3531 Boelter Hall // Los Angeles, CA 90024 // (213) 825-5683
    ARPA:  wales@UCLA-LOCUS.ARPA
    UUCP:  ...!{cepu,ihnp4,trwspp,ucbvax}!ucla-cs!wales