[comp.sources.misc] v08i011: moon

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (08/20/89)

Posting-number: Volume 8, Issue 11
Submitted-by: ajs@hpfcajs.hp.com (Alan Silverstein)
Archive-name: moon

[Alan asked me if it was an imposition to submit this.  Oy veh.  ++bsa]

OK, here you go.  Note that the sharchive includes other stuff you
need to build moon.c (parsedate), and stuff you might want to run
the accuracy script (anod, stddev).  I'll let you decide if it's all
worth shipping or not.

Major features:  clean source code; lots of options; includes drawing
an ASCII picture (also can print phase as a number for feeding to a true
graphics program); very accurate; manual entry; test scripts.

Alan Silverstein


# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by ajs at hpfcajs on Thu Aug 10 17:44:21 1989
#
# This archive contains:
#	misc		moon		parsedate	
#
# Error checking via wc(1) will be performed.

echo mkdir - misc
mkdir misc

echo x - misc/anod.1
sed 's/^@//' >misc/anod.1 <<'@EOF'
.TH ANOD 1 "Unsupported Utility, Version 4"
.SH NAME
anod \- add number of days to dates/values
.SH SYNOPSIS
.B anod
[
.B \-ipwWmM
] [
.BI \-c \0date
] [
.B \-C
] [
.I files...
]
.ad b
.SH DESCRIPTION
This program adds a ``number of days'' (or weeks or months) column to data
whose first field is a date in the form ``YYMM[DD]''
(or optionally including a time).
It is useful for quick interactive inquiries of amounts of time between dates,
accurately plotting data points gathered on non-periodic dates,
and converting occurrence dates to interval sizes (e.g. for scattergramming).
.PP
The program reads the concatenation of named
.I files
(use ``\-'' for standard input),
or reads standard input if no filenames are given,
and writes results to standard output.
.PP
Input lines which are comments (first field starts with ``#'') or blank lines
are passed through to standard output unaltered.
The first field of each data line must be a four or six digit date
(YYMM or YYMMDD) in the 20th century.
If no day of month is present, ``01'' is assumed.
.PP
Output lines have one blank and a number appended after the first (date) field:
the number of days before or after the date given on the first data line
(always zero for the first data line).
.PP
Options are:
.TP
.B \-i
Intervals:  On each data line,
add (insert) the delta from the date on the previous data line,
not from the first date.
.TP
.B \-p
Higher precision:
Each data line can contain a time after the date, in this form:
YYMM[DD[.[HH[MM[SS]]]]].
If any part of the time (hours, minutes, or seconds) is missing,
``00'' is assumed.
This option is allowed when adding days or weeks only.
When adding days, fractional days are printed when appropriate.
.TP
.B \-w
Add the number of whole (seven-day) weeks, not days, since the first date.
Fractions of weeks are ignored.
.TP
.B \-W
Like
.BR \-w ,
but add exact (fractional) weeks, not whole weeks.
.TP
.B \-m
Add the number of whole calendar months, not days, since the first date.
A month is the amount of time between successive same days-of-month,
e.g. between 850812 and 850912 is one month.
Fractions of months are ignored.
.TP
.B \-M
Like
.BR \-m ,
but add approximate (fractional) months, not whole months.
The fractional part is the number of excess days divided by the average days
per month (365.25 / 12).
.TP
.BI \-c \0date
Convert the matching date,
if it appears in the input data,
to a dot (``.'') in output.
In other words, emit a dot instead of repeating the date field as given,
with the rest of the line the same (including the added number of days).
.IP
This is intended for use with
.IR dataplot (1),
which understands a dot to mean ``don't plot this label''.
Give as many
.B -c
options as you need,
one per date to convert.
If
.I date
is of the form YYMM,
DD is assumed to be 01,
e.g. 8605 matches 860501.
.TP
.B \-C
Convert all input dates to ``.'' in output.
Giving any
.B \-c
options is redundant.
The same results could be gained by piping the data through
.IR sed (1)
or
.IR awk (1),
but this is more convenient.
.PP
You can only give one of
.BR \-w ,
.BR \-W ,
.BR \-m ,
or
.BR \-M .
.PP
The program doesn't worry about aligning its output,
which normally is passed directly to another program, e.g.
.IR dataplot (1).
.SH EXAMPLES
.TP
anod data1 > data1.plot
Add day intervals since the first date to dates in file ``data1''
and save results in ``data1.plot''.
.TP
anod -im -c861214 -c 8701 < t
Add number-of-months column to data in file ``t'',
where each number of months is incremental from the previous date,
and convert dates 861214 and 870101 to ``.'' in the output.
.SH SEE ALSO
bars(1), dataplot(1)
.SH LIMITATIONS
Dates input are restricted to format YYMM[DD].
However, most ``rational'' date formats can be converted to/from this style
with little trouble.
.SH DIAGNOSTICS
Prints a message standard error and exits
if invoked wrong or it detects invalid-format data.
@EOF
if test "`wc -lwc <misc/anod.1`" != '    137    703   3917'
then
	echo ERROR: wc results of misc/anod.1 are `wc -lwc <misc/anod.1` should be     137    703   3917
fi

chmod 444 misc/anod.1

echo x - misc/anod.c
cat >misc/anod.c <<'@EOF'
static char *version = "@(#) 4 880804";
/*
 * Add number of days (Julian days from start date) to a list of dates.
 * See the manual entry (anod(1)) for details.
 *
 * Compile with -DDEBUG to test JulianDate().
 */

#include <stdio.h>
#include <string.h>


/*********************************************************************
 * MISCELLANEOUS GLOBAL VALUES:
 */

#define	PROC				/* null; easy to find procs */
#define	FALSE	0
#define	TRUE	1
#define	CHNULL	('\0')
#define	CPNULL	((char *) NULL)
#define	REG	register

char *usage[] = {
    "usage: %s [-iwWmM] [-c date] [-C] [files...]",
    "-i intervals: show each delta from previous date, not from first date",
    "-p higher precision: input has times of days; add fractional days",
    "-w add number of whole (seven-day) weeks, not days, since the first date",
    "-W like -w, but add exact (fractional) weeks",
    "-m add number of whole calendar months, not days, since the first date",
    "-M like -m, but add approximate (fractional) months",
    "-c convert matching date to \".\" in output",
    "-C convert all dates to \".\" in output",
    "First fields of non-comment input lines must be dates in the form",
    "YYMM[DD].  If DD is missing, 01 is assumed.  If -p, dates can be",
    "followed by times: .HH[MM[SS]]] (default .000000).  Number fields are",
    "inserted after the date field.",
    CPNULL,
};

char	*defargv[] = { "-" };		/* default argument list	*/

char	*myname;			/* how program was invoked	*/
int	iflag = FALSE;			/* -i (intervals) option	*/
int	pflag = FALSE;			/* -p (precision) option	*/
int	wflag = FALSE;			/* -w (weeks, whole) option	*/
int	Wflag = FALSE;			/* -W (weeks, exact) option	*/
int	mflag = FALSE;			/* -m (months, whole) option	*/
int	Mflag = FALSE;			/* -M (months, exact) option	*/
int	Cflag = FALSE;			/* -C (convert all dates) opt	*/

#define	MAXCONV	500			/* maximum dates to convert	*/
#define	DOT	'.'			/* what to convert dates to	*/
double	convdate [MAXCONV];		/* Julian dates from -c options	*/
int	convcount = 0;			/* number of -c dates given	*/

double	prevdate = 0;			/* Julian of previous date	*/

int	prevyear, prevmonth, prevday;	/* parts  of previous date	*/
int	year,	  month,     day;	/* parts  of current  date	*/
int	length;				/* length of current  date	*/

#define	SHORTLEN  4			/* length of short date (YYMM)	*/
#define	DATELEN   6			/* length of normal date	*/
#define	HHLEN	  2			/* length of hour only		*/
#define	HHMMLEN	  4			/* length of hour plus minutes	*/
#define	HHMMSSLEN 6			/* length of complete time	*/

/*
 * Days in each month (February is special) and in year before each month:
 *
 *		    0  Jan Feb Mar  Apr May Jun  Jul Aug Sep  Oct Nov Dec */
int monthdays[] = { 0,  31, 28, 31,  30, 31, 30,  31, 31, 30,  31, 30, 31 };
int priordays[] = { 0,   0, 31, 59,  90,120,151, 181,212,243, 273,304,334 };

#define	DAYSperMONTH (365.25 / 12)	/* for -M conversions		*/

double	JulianDate();
double	ParseTime();


/************************************************************************
 * M A I N
 */

PROC main (argc, argv)
	int	argc;
	char	**argv;
{
extern	int	optind;			/* from getopt()	*/
extern	char	*optarg;		/* from getopt()	*/
REG	int	option;			/* option "letter"	*/

REG	FILE	*filep;			/* open input file	*/
	char	*filename;		/* name to use		*/

/*
 * PARSE ARGUMENTS:
 */

	myname = *argv;

	while ((option = getopt (argc, argv, "ipwWmMc:C")) != EOF)
	{
	    switch (option)
	    {
	    case 'i':	iflag = TRUE;	break;
	    case 'p':	pflag = TRUE;	break;
	    case 'w':	wflag = TRUE;	break;
	    case 'W':	Wflag = TRUE;	break;
	    case 'm':	mflag = TRUE;	break;
	    case 'M':	Mflag = TRUE;	break;
	    case 'C':	Cflag = TRUE;	break;

	    case 'c':	if (convcount >= MAXCONV)
			{
			    Error ("can't handle more than %d -c options",
				    MAXCONV);
			}

			if ((convdate [convcount++] = JulianDate (optarg)) < 0)
			{
			    Error ("invalid date with -c option: \"%s\"",
				   optarg);
			}

			break;

	    default:	Usage();
	    }
	}

	argc -= optind;			/* skip options	*/
	argv += optind;

/*
 * MORE ARGUMENT CHECKS:
 */

	if (wflag + Wflag + mflag + Mflag > TRUE)
	    Error ("you can only give one of -w, -W, -m, or -M");

	if (pflag && (mflag || Mflag))
	    Error ("-p is not allowed with -m or -M");
	    /* the math is too hard and result meaningless; see PrintLine() */

/*
 * READ FROM LIST OF FILES OR STDIN:
 */

	if (argc < 1)				/* no file names */
	{
	    argc = 1;
	    argv = defargv;			/* use default */
	}

	while (argc-- > 0)
	{
	    if (strcmp (*argv, "-") == 0)	/* read stdin */
	    {
		filename = "<stdin>";
		filep	 = stdin;
	    }
	    else if ((filep = fopen ((filename = *argv), "r")) == (FILE *) NULL)
		Error ("can't open file \"%s\" to read it", filename);

	    ReadFile (filename, filep);

	    if (ferror (filep))
		Error ("read failed from file \"%s\"", filename);

	    if (filep != stdin)
		fclose (filep);			/* assume it works */

	    argv++;
	}

	exit (0);

} /* main */


/************************************************************************
 * R E A D   F I L E
 *
 * Given a filename and stream pointer for reading, read lines from the file,
 * skip comment or blank lines, expect dates as first fields of other lines,
 * and insert number-of-days fields.  Error out if an invalid date appears in a
 * data line.
 */

PROC ReadFile (filename, filep)
	char	*filename;
REG	FILE	*filep;
{
REG	int	linenum = 0;		/* input line number	*/
#define	LINESIZE  500
	char	line [LINESIZE];	/* read from file	*/
REG	char	*cp;			/* place in line	*/
REG	double	date;			/* Julian date		*/

/*
 * READ LINE, REMOVE NEWLINE, SKIP LEADING WHITESPACE:
 */

	while ((cp = fgets (line, LINESIZE, filep)) != CPNULL)
	{
	    linenum++;

	    while ((*cp != '\n') && (*cp != CHNULL))
		cp++;				/* look for end of line */

	    *cp = CHNULL;
	    cp  = line;

	    while ((*cp == ' ') || (*cp == '\t'))
		cp++;				/* skip leading whitespace */

/*
 * HANDLE COMMENT, BLANK, OR DATA LINE:
 */

	    if ((*cp == '#') || (*cp == CHNULL))
		puts (line);
	    else
	    {
		if ((date = JulianDate (cp)) < 0)
		{
		    Error ("file \"%s\", line %d: invalid date:\n%s",
			   filename, linenum, line);
		}

		PrintLine (line, cp, date);
	    }
	} /* while */

} /* ReadFile */


/************************************************************************
 * J U L I A N   D A T E
 *
 * Given a string supposed to start with a date (format YYMM[DD], or if pflag,
 * YYMM[DD[.[HH[MM[SS]]]]]), and global monthdays[], check for a valid date
 * (which must be terminated by CHNULL, blank, or tab) and return its Julian
 * equivalent (where date 000101 == day 1.0).  In case of invalid date, return
 * a negative value.
 * 
 * As a side-effect, use and set globals year, month, day, and length, so
 * they're remembered and available if needed by callers.  Only valid if
 * successful.
 */

PROC double JulianDate (datestr)
	char	*datestr;		/* value to check */
{
	char	*endstr;		/* end of datestr */
	long	strtol();
REG	long	date = strtol (datestr, & endstr, 10);
	int	havetime;		/* have time too?    */
	double	time = 0.0;		/* fraction of day   */
	int	leapyear;		/* is it a leapyear? */

/*
 * CHECK FOR SHORT DATE (NO "DD"):
 */

	if ((length = (endstr - datestr)) == SHORTLEN)
	    date = (date * 100) + 1;		/* assume missing DD == 01 */

/*
 * CHECK FOR AND RETURN VALID DATE [AND TIME]:
 *
 * Hope to get through all checks to the "return" statement.
 * Any failure drops out to below.
 */

	havetime = pflag && (*endstr == '.');

	if (((length  == SHORTLEN)
	  || (length  == DATELEN))	/* OK length */
	 && ((*endstr == CHNULL)
	  || (*endstr == ' ')
	  || (*endstr == '\t')
	  || havetime))			/* OK end */
	{
	    year  = date / 10000;
	    month = date / 100 % 100;
	    day	  = date % 100;

	    leapyear = (year > 0) && ((year % 4) == 0);

	    if ((month >= 1) && (month <= 12) && (day >= 1)
	     && (day <= monthdays [month] + (leapyear && (month == 2)))
	     && ((! havetime)
		 || ((time = ParseTime (datestr + (++length))) >= 0)))
	    {
		return ((year * 365)			/* previous years */
			+ (priordays [month])		/* before month	  */
			+ day				/* in this month  */
			+ (year / 4)			/* prior leapdays */
			- (leapyear && (month <= 2))	/* before Feb 29  */
			+ time);			/* return double  */
	    }
	} /* if */

/*
 * HANDLE ERROR:
 */

	return (-1.0);

} /* JulianDate */


/************************************************************************
 * P A R S E   T I M E
 *
 * Given a string supposed to start with a time (format [HH[MM[SS]]]), check
 * for a valid time (which must be terminated by CHNULL, blank, or tab) and
 * return its value as a fraction of 24 hours.  In case of invalid time, return
 * a negative value.
 * 
 * As a side-effect, add to global length the length of the time string.  Only
 * valid if successful.
 */

PROC double ParseTime (timestr)
	char	*timestr;		/* value to check */
{
	char	*endstr;		/* end of datestr */
	long	strtol();
REG	long	time = strtol (timestr, & endstr, 10);
	int	timelen;		/* length of time */
	int	hour, minute, second;	/* parts of time  */

/*
 * CHECK FOR SHORT TIME (NOT HHMMSS):
 */

	if (timestr == endstr)			/* no time value */
	    return (0.0);

	if	((timelen = (endstr - timestr)) == HHLEN)	time *= 10000;
	else if	(timelen == HHMMLEN)				time *= 100;

/*
 * CHECK FOR AND RETURN VALID TIME:
 *
 * Hope to get through all checks to the "return" statement.
 * Any failure drops out to below.
 */

	if (((timelen == HHLEN)
	  || (timelen == HHMMLEN)
	  || (timelen == HHMMSSLEN))		/* OK length */
	 && ((*endstr == CHNULL)
	  || (*endstr == ' ')
	  || (*endstr == '\t')))		/* OK end */
	{
	    length += timelen;

	    hour   = time / 10000;
	    minute = time / 100 % 100;
	    second = time % 100;

	    if ((hour <= 23) && (minute <= 59) && (second <= 59))
		return (((((second / 60.0) + minute) / 60) + hour) / 24);

	} /* if */

/*
 * HANDLE ERROR:
 */

	return (-1.0);

} /* ParseTime */


/************************************************************************
 * P R I N T   L I N E
 *
 * Given an input line, a pointer to a valid date string in the line, the
 * Julian number for that date, and globals convcount, prevdate, prevyear,
 * prevmonth, prevday, year, month, day, length, and monthdays[], print the
 * line with the number-of-days (or weeks or months) inserted after the date.
 * Also update prevdate (and optionally prevyear, prevmonth, and prevday) if
 * this is the first data line (currently prevdate == 0.0) or if iflag is set.
 */

PROC PrintLine (cp, datestr, date)
REG	char	*cp;		/* place in line, initially start */
REG	char	*datestr;	/* start of date in line	  */
	double	date;		/* Julian value of input date	  */
{
REG	int	cflag = Cflag || (convcount && IsConv (date));

#ifdef DEBUG
	printf ("%g\n", date);
	return;
#endif

/*
 * PRINT INDENTATION PART:
 */

	while (cp < datestr)
	{
	    putchar (*cp);
	    cp++;		/* because putchar() is a macro */
	}

/*
 * PRINT DATE STRING, POSSIBLY CONVERTED TO DOT:
 */

	if (cflag)
	{
	    putchar (DOT);
	    cp += length;
	}
	else
	{
	    for (datestr += length; cp < datestr; cp++)
		putchar (*cp);
	}

	/* now cp is at the next char after the date input */

/*
 * PRINT INITIAL "ADDED" VALUE:
 */

	if (prevdate == 0.0)			/* no previous date yet */
	    printf (" 0");

/*
 * PRINT "ADDED" NUMBER OF MONTHS:
 *
 * This computation compares previous and current year, day, and month, rather
 * than prevdate and date, in order to account for months varying in size.
 */

	else if (mflag || Mflag)
	{
	    long interval = ((year - prevyear) * 12) + (month - prevmonth);

	    if (mflag)				/* whole months */
	    {
		/* reduce for incomplete month: */

		if	((interval > 0) && (prevday > day))	interval--;
		else if	((interval < 0) && (prevday < day))	interval++;

		printf (" %ld", interval);
	    }
	    else				/* fractional months */
	    {
		/* not precise, but close enough: */

		printf (" %g", interval + ((day - prevday) / DAYSperMONTH));
	    }
	}

/*
 * PRINT "ADDED" NUMBER OF WEEKS OR DAYS:
 */

	else
	{
	    double interval = date - prevdate;

	    if	    (Wflag)	printf (" %g",  interval / 7);
	    else if (wflag)	printf (" %ld", ((long) interval) / 7);
	    else if (pflag)	printf (" %g",  interval);
	    else		printf (" %ld", (long) interval);
	}

/*
 * PRINT REST OF INPUT LINE; SAVE VALUES:
 */

	puts (cp);

	if (iflag || (prevdate == 0.0))	/* always, or first input line */
	{
	    prevdate = date;		/* save this date as previous */

	    if (mflag || Mflag)		/* need to save them */
	    {
		prevyear  = year;
		prevmonth = month;
		prevday	  = day;
	    }
	}

} /* PrintLine */


/************************************************************************
 * I S   C O N V
 *
 * Given a Julian date and globals convdate[] and convcount, return TRUE if
 * the given date is in convdate[], FALSE otherwise.
 */

PROC int IsConv (date)
REG	double	date;
{
REG	int	index;

	for (index = 0; index < convcount; index++)
	    if (convdate [index] == date)
		return (TRUE);

	return (FALSE);

} /* IsConv */


/************************************************************************
 * U S A G E
 *
 * Print usage messages (char *usage[]) to stderr and exit nonzero.
 * Each message is followed by a newline.
 */

PROC Usage()
{
REG	int	which = 0;		/* current line */

	while (usage [which] != CPNULL)
	{
	    fprintf (stderr, usage [which++], myname);
	    putc ('\n', stderr);
	}

	exit (1);

} /* Usage */


/************************************************************************
 * E R R O R
 *
 * Print an error message to stderr and exit nonzero.  Message is preceded
 * by "<myname>: " using global char *myname, and followed by a newline.
 */

/* VARARGS */
PROC Error (message, arg1, arg2, arg3, arg4)
	char	*message;
	long	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s: ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	putc ('\n', stderr);

	exit (1);

} /* Error */
@EOF
if test "`wc -lwc <misc/anod.c`" != '    562   2363  13942'
then
	echo ERROR: wc results of misc/anod.c are `wc -lwc <misc/anod.c` should be     562   2363  13942
fi

chmod 444 misc/anod.c

echo x - misc/stddev
cat >misc/stddev <<'@EOF'
#! /bin/ksh
# Compute standard deviation.

# Usage: <script> [files...]

# Reads standard input or the concatenation of files.  The first field of
# each non-blank line is taken to be a number (no error checking).  The
# script reports the number of numbers read, the average, and the standard
# deviation.

# Standard deviation, easy computational form:
#
#	sqrt (( sum (Xi^2) - ((sum (Xi))^2 / N) ) / (N - 1))
#
# Empirical rule:  68% within 1 standard deviation; 95% within 2; all within 3.

cat "$@" |

awk '

(NF > 0) {			# non blank line.
	count ++;
	sum   += $1;
	sumsq += $1 * $1;
}

END {
	if (count == 0)
	{
	    print "'"$0"': no data lines";
	    exit;
	}

	printf ("count:    %d\n", count);
	printf ("average:  %f\n", sum / count);
	printf ("std dev:  %f\n", \
		sqrt ((sumsq - (sum * sum / count)) / (count - 1)));
}'
@EOF
if test "`wc -lwc <misc/stddev`" != '     38    155    832'
then
	echo ERROR: wc results of misc/stddev are `wc -lwc <misc/stddev` should be      38    155    832
fi

chmod 555 misc/stddev

chmod 750 misc

echo mkdir - moon
mkdir moon

echo x - moon/Makefile
cat >moon/Makefile <<'@EOF'
# Makefile for phase-of-moon program.
# Necessary for libm and libdate.

CC	= cc
CFLAGS	= -Ovs

INCLUDE	= /usr/local/include
LIB	= /usr/local/lib

all:	moon

moon:	moon.c $(INCLUDE)/parsedate.h $(LIB)/libdate.a
	$(CC) $(CFLAGS)   -o moon moon.c -lm -ldate -I $(INCLUDE) -L $(LIB)

debug:	moon.c $(INCLUDE)/parsedate.h $(LIB)/libdate.a
	$(CC) -gv -DDEBUG -o moon moon.c -lm -ldate -I $(INCLUDE) -L $(LIB)

lint:	moon.c
	lint $(MATH) moon.c -lm -I $(INCLUDE)

clean:
	rm -f moon *.o core
@EOF
if test "`wc -lwc <moon/Makefile`" != '     22     70    486'
then
	echo ERROR: wc results of moon/Makefile are `wc -lwc <moon/Makefile` should be      22     70    486
fi

chmod 444 moon/Makefile

echo x - moon/README
cat >moon/README <<'@EOF'
# This directory contains the source, manual entry, and test scripts and files
# for the phase of the moon program by Alan Silverstein.

# To build moon, you need libdate.a, which is created from the parsedate
# sources available separately.  After making parsedate, copy libdate.a to
# /usr/local/lib and parsedate.h to /usr/local/include (or anywhere you like,
# but change moon/Makefile if not the above locations).

# I welcome and solicit your feedback on the design (including usability) and
# implementation (including coding style) of this software.

Makefile	needed for libdate.a and libm.a

moon.c		source file
moon.1		manual entry (tbl | nroff -man)

accuracy	script to check program accuracy against known lunar events
		(needs anod(1) and stddev(1))

test.script	script to run moon a variety of ways, for testing
test.out	expected output to diff against
@EOF
if test "`wc -lwc <moon/README`" != '     21    134    867'
then
	echo ERROR: wc results of moon/README are `wc -lwc <moon/README` should be      21    134    867
fi

chmod 440 moon/README

echo x - moon/accuracy
cat >moon/accuracy <<'@EOF'

# CHECK ACCURACY OF moon(1) AGAINST KNOWN LUNAR EVENT TIMES

# Usage:  <script> (ignores arguments)

# Compares known lunar events against moon(1) predictions.  Uses unsupported
# anod(1) and stddev(1) commands.

# In the data below:
#
# Deltas between all events (days):
#
#	count:    36
#	average:  7.363908
#	std dev:  0.562316 (13.5 hours)
#
# Deltas between new moons (days):
#
#	count:    9
#	average:  29.455633
#	std dev:  0.111066 (2.67 hours)


# INITIALIZE:

	start='890105'		# surrounds data points below.
	end='890930'

	temp1="/tmp/mooncka$$"
	temp2="/tmp/moonckb$$"
	trap "rm -f $temp1 $temp2; exit" 0 1 2 3 15


# DATA FROM SKY AND TELESCOPE MAGAZINE, UTC:

	cat > $temp1 <<-'!'
		890107.1922 new
		890114.1358 first
		890121.2133 full
		890130.0202 last
		890206.0737 new
		890212.2315 first
		890220.1532 full
		890228.2008 last
		890307.1819 new
		890314.1011 first
		890322.0958 full
		890330.1021 last
		890406.0333 new
		890412.2313 first
		890421.0313 full
		890428.2046 last
		890505.1146 new
		890512.1419 first
		890520.1816 full
		890528.0401 last
		890603.1953 new
		890611.0659 first
		890619.0657 full
		890626.0909 last
		890703.0459 new
		890711.0019 first
		890718.1742 full
		890725.1331 last
		890801.1606 new
		890809.1728 first
		890817.0307 full
		890823.1840 last
		890831.0544 new
		890908.0949 first
		890915.1151 full
		890922.0210 last
		890929.2147 new
	!


# RUN moon AND MERGE DATA:

	TZ='UTC0' moon any $start $end	|
	sed -e 's/ /./'	-e 's/://'	|  # convert time format to YYMMDD.HHMM.
	paste $temp1 -			|

	# Data past here:  known-time phase moon-time the moon is phase ...
	# where phase might be "at phase".


# CHECK AND COMPARE DATA:

	awk '{
	    if ((phase = $7) == "at")
		phase = $8;

	    if ($2 != phase)
	    {
		print "Mismatched phases:", $0;
		exit;
	    }

	    printf ("%s\n%s\t%s\n", $1, $3, phase);
	}' |

	# Data past here:  known-time<newline>moon-time phase

	anod -ip |			# exact incremental numbers of days.

	# Ignore known-time lines; round to whole minutes since times are
	# only to nearest minute anyway:

	awk > $temp2 \
	    '(NF > 2) { printf ("%+5.0f  %s  %s\n", ($2 * 24 * 60), $1, $3) }'

	# Data in file:  delta-minutes  moon-time  phase


# PRINT RESULTS:

	echo \
"Error (minutes) versus known times, moon predicted time (UTC), and phase:\n"
	cat $temp2

	echo
	stddev < $temp2
@EOF
if test "`wc -lwc <moon/accuracy`" != '    122    347   2365'
then
	echo ERROR: wc results of moon/accuracy are `wc -lwc <moon/accuracy` should be     122    347   2365
fi

chmod 554 moon/accuracy

echo x - moon/moon.1
cat >moon/moon.1 <<'@EOF'
.\" Matches program @(#) $Revision:  9, 890809 $
.\" Print with tbl | nroff -man
.TH MOON 1
.SH NAME
moon \- tell the phase of the moon
.SH SYNOPSIS
.B moon
.RB [ \-ntTuUp ]
.RB [ \-dD ]
[\fB\-x \fIxyratio\fR]
.RI [ quarter ]
.RI [ time
.RI [ endtime
.RI [ increment ]]]
.\" ==========
.SH DESCRIPTION
.\" ==========
This program prints information about the phase of the moon
at the current time, at a specified time, or during a range of specified times.
It can also print information about when moon phases (quarter moons) occur.
.\" ----------
.PP
By default,
.I moon
prints to standard output
a long one-line description of the current phase of the moon
at the current system clock time.
The description includes the date and time in the format
.SM "YYMMDD HH:MM"
(rounded to the nearest minute),
a description of the phase,
and the percentage of the moon which is currently illuminated
as seen from the earth.
.\" ----------
.PP
The phase is considered to be exactly at a quarter moon
(new, first, full, last)
if it is within 1% of that quarter.
.\" ==========
.SS Options
.\" ==========
The options are:
.\" ----------
.TP
.B \-n
(numeric)
Print the phase value as a number from 0 to 1,
where 0.0 means ``new'',
0.5 means ``full'',
and 1.0 is the next new moon.
.\" ----------
.TP
.B \-t
(terse text)
Print the phase name only, not a longer string.
.\" ----------
.TP
.B \-T
(text)
Print a description of the moon's phase.
This option is the default.
.\" ----------
.TP
.B \-u
(until)
Tell how long it is from the current system clock time
since or until each specified lunar event,
in days, hours, and minutes, in fractional days,
and as a percentage of an average lunar month (29.5306 days).
.\" ----------
.TP
.B \-U
(until)
Tell how long it is from the start time to the first specified lunar event,
and from each event until the next one if a time range is specified.
.\" ----------
.TP
.B \-p
(picture)
Draw a picture of the moon's phase using
.SM ASCII
characters to represent
dark areas (blanks),
lit areas (``@''),
and
dark limbs (``('' or ``)'').
The size of the picture is determined by the
.SM LINES
and
.SM COLUMNS
environment variables, if present.
The defaults are 24 lines and 80 columns.
Only 80% of the lines are used,
plus one blank line is emitted before and after each picture.
Each picture is the largest approximate circle (see the
.B \-x
option) that fits in the rectangle formed by the specified lines and columns.
If the number of lines is the limiter,
the circle is centered in the available columns.
.\" ----------
.TP
.B \-d
Don't print the date and time with the
.B \-ntT
options.
Just print the phase information.
.\" ----------
.TP
.B \-D
Print a long form date and time with the
.B \-ntT
options using
.IR ctime (3).
This date and time format includes seconds,
but the program is not really that accurate (see
.I Accuracy
below).
.\" ----------
.TP
.BI \-x \0xyratio
Set the per-character
.SM X/Y
ratio to match that of the the display or font used,
so the picture printed with the
.B \-p
option is more circular.
The default
.I xyratio
is 0.5, which is correct if character cells
are half as many pixels wide as they are tall.
The larger the
.I xyratio
value, the more the picture is ``squashed'' horizontally
so it is represented as a circle when displayed.
To use this option,
determine your display's or font's character cell pixel ratio,
or experiment with values until you like what you see.
.\" ==========
.SS Arguments
.\" ==========
Various positional arguments are allowed.
.\" ----------
.TP 10
.I quarter
The first argument can be the name of a quarter moon
to select the next occurrence of that phase after the current or specified time.
.I Quarter
must be one of:
.BR new ,
.BR first ,
.BR full ,
.BR last ,
.BR next ,
.BR any .
The program prints when the specified quarter next occurs,
or prints all occurrences of that quarter in the specified time range
(see below).
Saying
.B next
or
.B any
specifies whatever quarter first occurs
after the current or specified starting time.
If you give a range of times,
.B next
shows only when the next quarter occurs during that range;
.B any
shows all quarters during that range.
.\" ----------
.TP
.I time
Set the time value to use or the initial time for a range.
.\" ----------
.TP
.I endtime
Specify the end time for a range of times.
.I Moon
prints phase or quarter information from
.I time
to
.IR endtime .
.\" ----------
.PP
Enter dates and times in almost any reasonable format,
as single quoted arguments if they contains blanks.
The year, month, and day default to the current system clock.
The hour, minute, and second default to zero (midnight).
The timezone defaults to the current value in the
.SM TZ
environment variable (assumed to be west of Greenwich),
including daylight savings time if it's in effect.
Beware of timezone confusion, such as that caused by daylight savings.
.\" ----------
.PP
To control the default timezone used for all input and output, set the
.SM TZ
environment variable.
For example:
.B "TZ=UTC0 moon"
.\" ----------
.TP 10
.I increment
Specify the step size for stepping through a range of times
starting with the initial
.IR time .
.I Increment
must end in one of:
.B d
(days),
.B h
(hours),
.B m
(minutes),
.B s
(seconds).
The default is
.B 1d
(step one day at a time).
.\" ----------
.PP
The options and arguments can be combined in numerous ways
to make complex requests.
.I Moon
complains if you specify a nonsensical combination.
.\" ==========
.SS Accuracy
.\" ==========
This program includes many real-world constant values,
some parameterized and some not,
with varying numbers of significant digits.
When run by the ``accuracy'' script which accompanies the sources,
.I moon
predicted 37 lunar events (nine cycles of new, first, full, last)
beginning with the new moon on 890107.
Comparing the predicted times (to the nearest minute, in
.SM UT)
with those published in Sky and Telescope Magazine
(also to the nearest minute), its accuracy was:
.\" ----------
.PP
.TS
center;
l l.
average error	2.16 minutes (too late)
standard deviation of errors	12.5 minutes
maximum errors	-17, +28 minutes
.TE
.\" ==========
.SS Portability
.\" ==========
The Epoch (time of known orbital positions) is 473299200 seconds (19841231.0
.SM GMT
in
.SM UNIX
system time).
To build a program for converting an Epoch date
to a particular system's equivalent clock time in seconds,
edit the GetEpoch() routine to change the date if needed,
then compile moon.c with
.SM "-DGET_EPOCH"
to produce a special-purpose conversion program, and run it.
Use the output to revise for your system the value of
.SM EPOCH
in the source code.
.\" ==========
.SS Debugging
.\" ==========
The sources include a
.B test.script
and a hand checked
.B test.out
file.
To test
.IR moon ,
run
.B test.script
and compare its output to
.BR test.out .
Some output lines vary with the system clock;
they are marked in
.B test.script
output.
.\" ----------
.PP
For extra output during NextQuarter() calculations, compile moon.c with
.SM -DDEBUG.
.\" ==========
.SH RETURN VALUE
.\" ==========
.I Moon
returns 0 if it succeeds or 1 if it detects and reports any errors.
.\" ==========
.SH DIAGNOSTICS
.\" ==========
.I Moon
prints an explanatory message to standard error
and exits if it detects any of about 14 invocation errors.
If it is invoked correctly, no other errors are expected.
.\" ==========
.SH EXAMPLES
.\" ==========
Print the current phase of the moon:
.IP
.B moon
.\" ----------
.PP
Print when the next quarter occurs, and draw a small picture of that phase:
.IP
.B "COLUMNS=20 moon \-Tp next"
.\" ----------
.PP
Print when the next new moon occurs (short form):
.IP
.B "moon \-t new"
.\" ----------
.PP
Print when the moon is next last quarter, and how long it is until then,
using long form dates:
.IP
.B "moon \-uD last"
.\" ----------
.PP
Print the current phase of the moon both numerically and tersely:
.IP
.B "moon \-nt"
.\" ----------
.PP
Print the next quarter of the moon after midnight May 1, 1989,
without including the date in the output.
.IP
.B "moon \-d any 890501"
.\" ----------
.PP
Print all first quarter moons in a two month period,
along with time intervals from the start time and between quarters:
.IP
.B "moon \-U first 890530 890730"
.\" ----------
.PP
Print the phase of the moon every 5 days, in Universal Time,
starting and ending at specified times:
.IP
.B "TZ=UT0 moon 'may 1, 1988 10:00pm' 'August 4, 1988' 5d"
.\" ==========
.SH AUTHOR
.\" ==========
.I Moon
was developed by
Alan Silverstein at Hewlett-Packard.
It is based on sources
``stolen from ArchMach and converted from PL/I by Brian Hess;
extensively cleaned up by Rich $alz,''
with a higher-precision Phase() algorithm from
moon.c by Keith E. Brandt (1984), based on routines from
``Practical Astronomy with Your Calculator'', by Duffett-Smith.
.\" ==========
.SH SEE ALSO
.\" ==========
sun(1), ctime(3), parsedate(3).
@EOF
if test "`wc -lwc <moon/moon.1`" != '    359   1558   8928'
then
	echo ERROR: wc results of moon/moon.1 are `wc -lwc <moon/moon.1` should be     359   1558   8928
fi

chmod 444 moon/moon.1

echo x - moon/moon.c
cat >moon/moon.c <<'@EOF'
static char * version =	"@(#) $Revision:  9, 890809 $";
/*
 * Tell the phase of the moon.
 *
 * See the manual entry, or run with "-?" for a usage summary.
 *
 * Uses the unsupported parsedate(3) and compute_unixtime(3) library calls for
 * date parsing.
 *
 * For extra output during NextQuarter() calculations, compile with -DDEBUG.
 *
 * This code has been carefully code-read, lint'd, and even BFA'd (97%+).
 *
 * Possible enhancements:
 * - allow case-insensitive quarter and time unit specification
 */

#include <sys/types.h>			/* for time_t			*/
#include <stdio.h>
#include <ctype.h>			/* for isspace() and isdigit()	*/
#include <string.h>			/* for strcmp() and strncpy()	*/
#include <math.h>			/* for trig funcs and fmod()	*/
#include <time.h>			/* for time conversion		*/
#include <parsedate.h>			/* for time conversion		*/

#define	CTIMELEN 26			/* length of ctime() return	*/


/*********************************************************************
 * MISCELLANEOUS GLOBAL VALUES:
 */

#define	PROC				/* null; easy to find procs */
#define	FALSE	0
#define	TRUE	1
#define	CHNULL	('\0')
#define	CPNULL	((char *) NULL)
#define	REG	register

char *usage[] = {
"usage: %s [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]",
"",
"-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)",
"-t (terse text) print only the date, time, and phase name",
"-T (text) print date, time, and a description of the moon's phase (default)",
"-u (until) tell how long from now until each specified event",
"-U (until) tell how long from time to quarter and/or each quarter to next",
"-p (picture) draw a picture of the moon's phase using characters",
"-d don't print the date and time with -ntT, just the phase information",
"-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)",
"-x set display/font character X/Y ratio (for square picture; default:  0.5)",
"",
"quarter:    any of \"new first full last next any\"; if specified, tell when",
"            that quarter next occurs, or all occurrences in the time range",
"time:       value to use (default:  current system time)",
"endtime:    print phase or quarter information from time to endtime",
"increment:  amount to step through range of times; must end in one of:",
"            d(ays) h(ours) m(inutes) s(econds) (default:  1d)",
"",
"Enter date/time in almost any reasonable format, quoted if it contains",
"blanks.  Year, month, and day default to current; hour, minute, and second to",
"zero; timezone to current, including DST if in effect (assumed west of",
"Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 %s",
CPNULL,
};

char	*myname;			/* how program was invoked	*/

int	nflag = FALSE;			/* -n (print number) option	*/
int	tflag = FALSE;			/* -t (print terse text) option	*/
int	Tflag = FALSE;			/* -T (print text) option	*/
int	uflag = FALSE;			/* -u (print until) option	*/
int	Uflag = FALSE;			/* -U (print until) option	*/
int	pflag = FALSE;			/* -p (print picture) option	*/

int	dflag = FALSE;			/* -d (skip date) option	*/
int	Dflag = FALSE;			/* -D (long date) option	*/

#define	SHORTDATEFORM  "%02d%02d%02d %02d:%02d  "	/* change to suit */

#define	USAGE	0			/* for Error() */
#define	NOUSAGE	1

#define	OKEXIT  0			/* for exit() */
#define	ERREXIT 1


/*********************************************************************
 * GLOBALS FOR TIME AND PHASE FIGURING:
 */

time_t	currtime;			/* current time, set once */

#define	SECperMIN    60			/* seconds per minute	*/
#define	SECperHOUR  (SECperMIN  * 60)	/* seconds per hour	*/
#define	SECperDAY   (SECperHOUR * 24)	/* seconds per day	*/
#define	DAYSperMON   29.5306		/* approximate, varies	*/
#define	SECperMON   (SECperDAY * DAYSperMON)
#define	DAYSperYEAR 365.2422;		/* exact as possible	*/

typedef	double	phase_t;

#define	P_NEW	   ((phase_t) 0.000)	/* lunar phase values; see Phase() */
#define	P_FIRST    ((phase_t) 0.250)
#define	P_FULL	   ((phase_t) 0.500)
#define	P_LAST	   ((phase_t) 0.750)
#define	P_NEXTNEW  ((phase_t) 1.000)	/* next new moon */

#define	P_MONTH	     P_NEXTNEW		/* size of one lunar cycle (month) */
#define	P_PRECISION  ((phase_t) 0.01)	/* consider at quarter if within   */

#define	P_NONE	   ((phase_t) 2.0)	/* magic value:  none specified	*/
#define	P_NEXT	   ((phase_t) 3.0)	/* magic value:  next quarter	*/
#define	P_ANY	   ((phase_t) 4.0)	/* magic value:  any  quarter	*/

#define	TWOPI	   (2 * M_PI)		/* hope compiler makes it a const */
#define	EPSILON	   (0.00001)		/* a small real number		  */


/*********************************************************************
 * GLOBALS FOR SELECTING QUARTER BY NAME:
 */

struct	{
	char	*name;		/* name of quarter	  */
	phase_t	phase;		/* equivalent phase value */
} quarter [] = {
	{ "new",   P_NEW   },
	{ "first", P_FIRST },
	{ "full",  P_FULL  },
	{ "last",  P_LAST  },
	{ "next",  P_NEXT  },
	{ "any",   P_ANY   },
	{ CPNULL,  P_NONE  },	/* marks end */
};


/*********************************************************************
 * GLOBALS FOR DRAWING PICTURE:
 *
 * For greatest accuracy, centercol and halfcols are kept as decimal numbers
 * and truncating to a whole column position happens as late as possible.
 */

double	xyratio = 0.5;			/* X/Y aspect ratio of display	*/
int	lines;				/* picture height		*/
double	halfcols;			/* 1/2 of picture width		*/
double	centercol;			/* center column, base 0	*/

#define	LINES		 24		/* default lines on display	*/
#define	COLUMNS		 80		/* default columns on display	*/
#define	PICTFRACT	0.8		/* fraction of lines to use	*/

#define	LINESVAR	"LINES"		/* environment variable names	*/
#define	COLUMNSVAR	"COLUMNS"

#define	CH_LEFTEDGE	'('		/* left edge of moon		*/
#define	CH_RIGHTEDGE	')'		/* right edge of moon		*/
#define	CH_LIT		'@'		/* lit part of moon		*/


phase_t	ParseQuarter();
time_t	ParseTime();
long	ParseIncrement();
time_t	NextQuarter();
phase_t	Phase();
double	Mod2Pi();
char *	DateString();
char *	PhaseString();


/************************************************************************
 * M A I N
 *
 * Parse options and arguments and print the requested information.
 */

PROC main (argc, argv)
REG	int	argc;
REG	char	**argv;
{
extern	int	optind;			/* from getopt()		*/
extern	char	*optarg;		/* from getopt()		*/
REG	int	option;			/* option "letter"		*/

REG	time_t	basetime;		/* base for -U option		*/
REG	time_t	attime;			/* time to use			*/
REG	time_t	endtime;		/* end of time range		*/
	int	have_endtime = FALSE;	/* flag:  was one specified?	*/
REG	long	increment = SECperDAY;	/* step size in seconds		*/

	phase_t	phase;			/* moon phase, P_NEW..P_NEXTNEW	*/
	phase_t	wantphase = P_NONE;	/* quarter desired, if any	*/

	double	atof();
	long	time();

#ifdef GET_EPOCH
	GetEpoch();			/* does not return */
#endif;

/*
 * PARSE OPTIONS:
 */

	myname = *argv;

	while ((option = getopt (argc, argv, "ntTuUpdDx:")) != EOF)
	{
	    switch (option)
	    {
	    case 'n':	nflag = TRUE;	break;
	    case 't':	tflag = TRUE;	break;
	    case 'T':	Tflag = TRUE;	break;
	    case 'u':	uflag = TRUE;	break;
	    case 'U':	Uflag = TRUE;	break;
	    case 'p':	pflag = TRUE;	break;

	    case 'd':	dflag = TRUE;	break;
	    case 'D':	Dflag = TRUE;	break;

	    case 'x':	if ((xyratio = atof (optarg)) <= 0)
			    Error (NOUSAGE, "invalid X/Y ratio with -x option");
			break;

	    default:	Usage();
	    }
	}

	argc -= optind;
	argv += optind;

/*
 * MORE OPTION CHECKS:
 */

	if ((! nflag) && (! tflag) && (uflag || Uflag || (! pflag)))
	    Tflag = TRUE;				/* use default */

	if (dflag && Dflag)
	    Error (NOUSAGE, "only one of -d and -D is allowed");

	if ((dflag || Dflag) && ! (nflag || tflag || Tflag))
	    Error (NOUSAGE, "-d and -D make no sense without one of -ntT");

	if (pflag)
	    SetPictSize();

/*
 * PARSE QUARTER ARGUMENT:
 */

	if ((argc >= 1)						/* have arg   */
	 && ((wantphase = ParseQuarter (argv [0])) != P_NONE))	/* recognized */
	{
	    argv++;			/* skip argument for quarter */

	    if (--argc >= 3)
	    {
		Error (NOUSAGE, "increment is not allowed with quarter \
specified too");
	    }
	}

/*
 * PARSE TIME, ENDTIME, AND INCREMENT ARGUMENTS:
 */

	currtime = (time_t) time ((long *) 0);

	if (argc == 0)					/* none given */
	    attime = currtime;
	else
	{
	    attime = ParseTime (argv [0]);	/* use given time */

	    if (argc >= 2)
	    {
		if ((endtime = ParseTime (argv [1])) < attime)
		    Error (NOUSAGE, "endtime must be at or after start time");

		have_endtime = TRUE;

		if (argc >= 3)
		    increment = ParseIncrement (argc - 2, argv [2], argv [3]);
	    }
	}

/*
 * YET MORE OPTION CHECKS:
 */

	if (uflag && (wantphase == P_NONE) && (argc == 0))
	{
	    Error (NOUSAGE, "-u makes no sense without a quarter and/or \
time specified");
	}

	if (Uflag && ((wantphase == P_NONE) || (argc == 0)))
	{
	    Error (NOUSAGE, "-U makes no sense without a quarter and a \
time specified");
	}

/*
 * FIGURE INITIAL PHASE:
 */

	phase = Phase (attime);

/*
 * PRINT PHASE INFORMATION FOR ONE TIME ONLY:
 */

	if (! have_endtime)
	{
	    basetime = attime;

	    if (wantphase != P_NONE)	/* advance to specified phase */
		attime = NextQuarter (attime, & phase, & wantphase);

	    PrintAll (basetime, attime, phase);
	}

/*
 * PRINT PHASE INFORMATION FOR A SERIES OF TIMES:
 */

	else if (wantphase == P_NONE)
	{
	    while (TRUE)			/* until break */
	    {
		PrintAll ((time_t) 0, attime, phase);	/* basetime is unused */

		if ((attime += increment) > endtime)
		    break;

		phase = Phase (attime);
	    }
	}

/*
 * PRINT PHASE INFORMATION FOR A SERIES OF PHASES:
 */

	else
	{
	    basetime = attime;

	    while ((attime = NextQuarter (attime, & phase, & wantphase))
		   <= endtime)
	    {
		PrintAll (basetime, attime, phase);
		basetime = attime;
	    }
	}

	exit (OKEXIT);

} /* main */


/************************************************************************
 * S E T   P I C T   S I Z E
 *
 * Given global xyratio, set picture size and center (globals lines, halfcols,
 * centercol) based on screen size (default values or those from environment
 * variables), with various adjustments:
 *
 * - use a fraction (PICTFRACT) of the total display lines;
 * - scale the number of columns to match the number of lines using xyratio, or
 * - reduce lines to fit if the number of columns is insufficient (try to keep
 *   the picture "square").
 *
 * Error out if the result is too small (less than three lines or columns).
 */

PROC SetPictSize()
{
REG	double	columns;	/* number in picture	 */
	double	trycols;	/* test value of columns */
	char	*cp;		/* temporary pointer	 */
	char	*getenv();

/*
 * GET SCREEN SIZE:
 */

	lines   = ((cp = getenv (LINESVAR))   == CPNULL) ? LINES   : atoi (cp);
	columns = ((cp = getenv (COLUMNSVAR)) == CPNULL) ? COLUMNS : atoi (cp);

/*
 * MAKE ADJUSTMENTS:
 *
 * Divide the number of columns by two for a zero-based center value.  For
 * halfcols, subtract EPSILON from columns to avoid reaching the column after
 * the last one.
 */

	lines *= PICTFRACT;			/* truncates */

	centercol = columns / 2;		/* before more adjusting */

	if (columns >= (trycols = lines / xyratio))	/* typical case */
	    columns = trycols;
	else
	    lines = columns * xyratio;		/* shrink lines */

	halfcols = (columns - EPSILON) / 2;

/*
 * CHECK IF BIG ENOUGH:
 */

	if ((lines < 3) || (columns < 3))
	{
	    Error (NOUSAGE, "too few lines or columns for picture; check %s \
and %s vars", LINESVAR, COLUMNSVAR);
	}

} /* SetPictSize */


/************************************************************************
 * P A R S E   Q U A R T E R
 *
 * Given a string alleged to match the string in one of the elements of global
 * struct quarter[], search for that element.  If found, return the phase value
 * in the element, else return P_NONE.
 */

PROC phase_t ParseQuarter (string)
REG	char	*string;
{
REG	int	index = 0;		/* in struct quarter[] */

	while (strcmp (string, quarter [index] . name))	 /* no match	*/
	    if ((quarter [++index] . name) == CPNULL)	 /* end of list	*/
		return (P_NONE);

	return (quarter [index] . phase);

} /* ParseQuarter */


/************************************************************************
 * P A R S E   T I M E
 *
 * Given a string alleged to contain a date string, and global currtime, parse
 * the string and return the equivalent system clock time in seconds.  Fill in
 * default values for missing time elements using currtime.  Error out if
 * parsing fails.
 *
 * Due to a deficiency in tzset(3) (extern timezone is always positive),
 * timezones are assumed to be west of Greenwich.
 */

PROC time_t ParseTime (string)
	char	*string;
{
REG	struct	parsedate *pd;

/*
 * PARSE DATE STRING, CHECK FOR ERROR:
 */

	pd = parsedate (string);

	if ((pd -> error) != CPNULL)		/* some sort of failure */
	{
	    Error (NOUSAGE, "invalid date specified (was it a single, quoted \
argument?):\n%s\n%*s^",
		   string, (pd -> error) - string, "");  /* pointer under it */
	}

/*
 * MISSING TIME ELEMENTS; SUPPLY DEFAULTS:
 */

	if ((pd -> unixtime) < 0)
	{
	    if (((pd -> year) < 0) || ((pd -> month) < 0) || ((pd -> day) < 0))
	    {
		struct	tm *tm = localtime (& currtime);

		if ((pd -> year)  < 0)	(pd -> year)  = tm -> tm_year + 1900;
		if ((pd -> month) < 0)	(pd -> month) = tm -> tm_mon  + 1;
		if ((pd -> day)   < 0)	(pd -> day)   = tm -> tm_mday;
	    }

	    if ((pd -> hour)   < 0)	(pd -> hour)   = 0;
	    if ((pd -> minute) < 0)	(pd -> minute) = 0;
	    if ((pd -> second) < 0)	(pd -> second) = 0;

	    if ((pd -> zone) == -1)
	    {
		(void) tzset();		/* sets extern timezone and daylight */

		(pd -> zone) =		/* value in minutes, negative west */
		    - ((timezone - (daylight ? SECperHOUR : 0)) / SECperMIN);
	    }

/*
 * RECOMPUTE TIME USING DEFAULT ELEMENTS:
 */

	    compute_unixtime (pd);

	    if ((pd -> unixtime) < 0)		/* should never happen */
		Error (NOUSAGE, "cannot fill in default time values (reason \
unknown)");

	} /* if */

	return ((time_t) (pd -> unixtime));

} /* ParseTime */


/************************************************************************
 * P A R S E   I N C R E M E N T
 *
 * Given a string count and two strings alleged to contain an integer followed
 * by a time unit (d(ays), h(ours), m(inutes), or s(econds)), either both in
 * the first string or the number in the first string and the unit in the
 * second string, parse the string(s) and return the equivalent number of
 * seconds.  Ignore whitespace before and after the time value and before the
 * time unit.  Accept any time unit string which begins with a recognized
 * letter.  Error out if:
 *
 * - the number in the first string is invalid
 * - the unit does not appear or is invalid
 * - a unit appears in the first string and the string count is two or more
 * - the string count is three or more
 *
 * Recognizing the integer and time unit as two different arguments is an
 * undocumented usability feature.
 */

PROC long ParseIncrement (count, string1, string2)
	int	count;
	char	*string1, *string2;
{
REG	char	*cp = string1;	/* place in string */
REG	long	result;		/* to return	   */

	long	atol();

/*
 * CONVERT, CHECK, AND SKIP NUMBER:
 */

	while (isspace (*cp))	cp++;

	if ((result = atol (cp)) <= 0)
	    Error (USAGE, "invalid increment value");

	while (isdigit (*cp))	cp++;
	while (isspace (*cp))	cp++;

/*
 * LOOK FOR TIME UNIT:
 */

	if (*cp == CHNULL)		/* end of string1 */
	{
	    count--;
	    cp = string2;

	    while (isspace (*cp))
		cp++;
	}

	if (count < 1)
	    Error (USAGE, "missing time unit on increment value");

	if (count > 1)
	    Error (USAGE, "too many arguments specified");

/*
 * CHECK TIME UNIT:
 */

	switch (*cp)
	{
	case 'd':   result *= SECperDAY;	break;
	case 'h':   result *= SECperHOUR;	break;
	case 'm':   result *= SECperMIN;	break;
	case 's':    /* do nothing */		break;
	default:    Error (USAGE, "invalid suffix on increment value");
	}

	return (result);

} /* ParseIncrement */


/************************************************************************
 * N E X T   Q U A R T E R
 *
 * Given a system clock time in seconds, a pointer to the moon phase at that
 * time (P_NEW..P_NEXTNEW), and a pointer to the wanted phase value (quarter
 * moon, which can be a special value P_NONE, P_NEXT, or P_ANY to mean any
 * quarter is acceptable), find and return the time of the next specified
 * quarter to the nearest second, and change the current phase to match the
 * phase of that event.  If *wantphasep is P_NEXT, set it to the next specific
 * value.
 *
 * Note:  If *wantphasep is not a special value, it can actually be any legal
 * value, not just quarter values.
 *
 * For lack of better understanding of the math involved in determining when
 * the quarter occurs, call Phase() repeatedly to approximation search to the
 * time which is closest to the event.  Always return the first time after the
 * initial time whose phase value is at or after the desired phase value.
 * Assume successive times produce monotonically increasing Phase() values
 * (except for rolling past P_NEXTNEW back to P_NEW).
 *
 * The approximation method is similar but not identical to Newtonian.  It does
 * not know or use the actual slope at each point in the time-phase curve, but
 * revises the slope based on each approximation.  This doesn't make a huge
 * difference, since the moon's orbit isn't very eccentric, but tests show it
 * saves about two calls to Phase() each time.
 */

PROC time_t NextQuarter (attime, phasep, wantphasep)
REG	time_t	attime;
	phase_t	*phasep;
	phase_t	*wantphasep;
{
REG	phase_t	prevphase;		/* previous approximation	*/
REG	phase_t	phase	  = *phasep;	/* current values		*/
REG	phase_t	wantphase = *wantphasep;
REG	phase_t	wrapphase;		/* "wrapped" value, see below	*/
REG	long	deltatime;		/* between two approximations	*/
REG	double	invslope;		/* inverse of slope of curve	*/

/*
 * IF ANY QUARTER IS ACCEPTABLE, SET NEXT PHASE TO FIND:
 */

	if ((wantphase == P_NONE)
	 || (wantphase == P_NEXT)
	 || (wantphase == P_ANY ))
	{
	    wantphase = (phase < P_FIRST) ? P_FIRST :
			(phase < P_FULL ) ? P_FULL  :
			(phase < P_LAST ) ? P_LAST  :
					    P_NEW;	/* wrap around */

	    if (*wantphasep == P_NEXT)
		*wantphasep = wantphase;	/* change caller's value */
	}

/*
 * COMPUTE FIRST APPROXIMATE TIME OF WANTED PHASE:
 *
 * The initial approximate time may be as much as one cycle ahead, in the case
 * where wantphase == phase now.
 *
 * Take care to "wrap" phase values around P_NEW.  If a phase value is late in
 * the cycle and the goal is P_NEW, use a value less than P_NEW.
 */

	attime += SECperMON *
		  (wantphase - phase + ((wantphase <= phase) ? P_MONTH : 0));

#define	WRAP(phase) \
	(((wantphase == P_NEW) && (phase > P_LAST)) ? (phase - P_MONTH) : phase)

	phase	  = Phase (attime);
	wrapphase = WRAP (phase);
	invslope  = SECperMON / P_MONTH;  /* initially over a whole month */

/*
 * SEARCH FOR TIME OF NEXT EVENT:
 */

	while (TRUE)			/* until break */
	{
	    deltatime = invslope * (wantphase - wrapphase);	/* truncates */

#ifdef DEBUG
	    printf ("wp: %.2f at: %d p: %12.9f dt: %8d is: %f\n",
		    wantphase, attime, wrapphase, deltatime, invslope);
#endif
	    if (deltatime == 0)		/* as close as we can get */
		break;

	    prevphase = wrapphase;
	    attime   += deltatime;		/* positive or negative */
	    phase     = Phase (attime);
	    wrapphase = WRAP (phase);
	    invslope  = deltatime / (wrapphase - prevphase);
	}

/*
 * DECREMENT/INCREMENT TO EXACT TIME:
 *
 * For repeatability, find the first time on which phase >= wantphase.  The
 * following code is overkill in all but the rarest cases.  Usually the attime
 * already found is exactly right or just one second too low.
 */

	while (wrapphase > wantphase)
	{
	    phase     = Phase (--attime);
	    wrapphase = WRAP (phase);
#ifdef DEBUG
	    printf ("wp: %.2f at: %d p: %12.9f (down)\n",
		    wantphase, attime, wrapphase);
#endif
	}

	while (wrapphase < wantphase)
	{
	    phase     = Phase (++attime);
	    wrapphase = WRAP (phase);
#ifdef DEBUG
	    printf ("wp: %.2f at: %d p: %12.9f (up)\n",
		    wantphase, attime, wrapphase);
#endif
	}

/*
 * RETURN:
 */

	*phasep = phase;
	return (attime);

} /* NextQuarter */


/************************************************************************
 * P H A S E
 *
 * Given a system clock time in seconds, compute and return the phase of the
 * moon at that time, P_NEW (inclusive) .. P_FULL .. P_NEXTNEW (exclusive).
 *
 * Section numbers in comments refer to "Practical Astronomy with Your
 * Calculator".  Unfortunately, an earlier version of this program provided no
 * explanation of the calculations or variable names.  The descriptions of
 * variable names are guesses.
 *
 * It appears this code figures the geocentric longitudes of the sun and moon
 * at the given time, using their actual geocentric orbits (ellipses).  Simple
 * geometry shows that the difference in their longitudes is also the portion
 * of the moon which is lit as seen from earth, assuming the sun's rays are
 * parallel at both earth and moon, which is very nearly true since the moon's
 * orbital radius is only about 0.2% of the earth's.
 *
 * This code seems to assume the orbits are coplanar, which might introduce
 * some small error since they're not.  (The difference is about 18 degrees.)
 *
 * Some numbers expressed as manifest constants should be parameterized, but
 * I'm not sure what they mean.  Some of their accuracy is overstated.  They
 * used to be in degrees, to only 2-4 digits, but I converted them to radians
 * without rounding.
 */

PROC phase_t Phase (attime)
	time_t	attime;
{
#define	EPOCH	 473299200	/* 19841231.0 GMT in UNIX system time	*/

/* All the longitudes are values in radians at EPOCH: */

#define	EPSILONg 4.88013905	/* solar ecliptic longitude		*/
#define	RHOg	 4.9337037632	/* solar ecliptic longitude of perigee	*/
#define	e	 0.01671542	/* solar orbit eccentricity		*/
#define	lzero	 0.3185558719	/* lunar mean longitude			*/
#define	Pzero	 3.3670470432	/* lunar mean longitude of perigee	*/
#define	Nzero	 0.963504179	/* lunar mean longitude of node		*/
#define	lPerDay	 0.2299715042	/* lunar longitude change per day	*/

	double	days;		/* since EPOCH				*/
	double	N;		/* radians of Earth orbit since EPOCH	*/
	double	Msol;		/* sun position in orbit versus perigee	*/
	double	sinMsol;	/* sin (Msol)				*/
	double	Ec;		/* eccentricity correction		*/
	double	LambdaSol;	/* ecliptic longitude of sun		*/
	double	l;		/* lunar mean longitude			*/
	double	Mm;		/* moon position in orbit vs. perigee	*/
     /*	double	Nm;		/* (not used)				*/
	double	Ev;		/* based on angle between sun and moon?	*/
	double	Ac;		/* correction factor?			*/
	double	A3;		/* correction factor?			*/
	double	Mmprime;	/* corrected Mm				*/
	double	A4;		/* correction factor?			*/
	double	lprime;		/* corrected lunar longitude		*/
	double	V;		/* correction factor?			*/
	double	ldprime;	/* recorrected lunar longitude		*/
	double	D;		/* sun - moon - Earth angle		*/

/*
 * CALCULATE SOLAR LONGITUDE:
 */

	days	  = ((double) (attime - EPOCH)) / SECperDAY;

	N	  = TWOPI * days / DAYSperYEAR;			/* sec 42 #3  */
	Msol	  = EPSILONg - RHOg + N;			/* sec 42 #4  */
	sinMsol	  = sin (Msol);
	Ec	  = 2 * e * sinMsol;				/* sec 42 #5  */
	LambdaSol = Mod2Pi (EPSILONg + N + Ec);			/* sec 42 #6  */

/*
 * CALCULATE LUNAR LONGITUDE:
 */

	l	  = Mod2Pi (lzero +	(lPerDay      * days));	/* sec 61 #4  */
	Mm	  = Mod2Pi (l - Pzero -	(0.0019443683 * days));	/* sec 61 #5  */
     /*	Nm	  = Mod2Pi (Nzero -	(0.0009242199 * days));	/* sec 61 #6  */

	Ev	  = 0.0222337493 * sin (2 * (l - LambdaSol) - Mm);
								/* sec 61 #7  */
	Ac	  = 0.0032428218 * sinMsol;			/* sec 61 #8  */
	A3	  = 0.0064577182 * sinMsol;
	Mmprime	  = Mm + Ev - Ac - A3;				/* sec 61 #9  */

	Ec	  = 0.1097567753 * sin (Mmprime);		/* sec 61 #10 */
	A4	  = 0.0037350046 * sin (2 * Mmprime);		/* sec 61 #11 */
	lprime	  = l + Ev + Ec - Ac + A4;			/* sec 61 #12 */
	V	  = 0.0114895025 * sin (2 * (lprime - LambdaSol));
								/* sec 61 #13 */

	ldprime	  = lprime + V;					/* sec 61 #14 */

/*
 * CALCULATE AND RETURN PHASE:
 *
 * The difference angle is lunar - solar longitude because longitudes increase
 * counter clockwise.  (I wish I could include a simple drawing.)
 */

	D = ldprime - LambdaSol;				/* sec 63 #2 */

	return ((phase_t) (Mod2Pi (D) / TWOPI));

} /* Phase */


/************************************************************************
 * M O D   2   P I
 *
 * Given an angle, adjust it to be in the range 0 <= angle < TWOPI.
 */

PROC double Mod2Pi (angle)
REG	double	angle;
{
	if (((angle < 0) || (angle >= TWOPI))		/* if needed	*/
	 && ((angle = fmod (angle, TWOPI)) < 0))	/* was negative	*/
	{
	    angle += TWOPI;				/* bring in range */
	}

	return (angle);

} /* Mod2Pi */


/************************************************************************
 * P R I N T   A L L
 *
 * Given base and target system clock times in seconds, a moon phase
 * (P_NEW..P_NEXTNEW) at the target time, and global currtime, print various
 * output lines, depending on global options, to represent the target time and
 * phase, and the time until the target time and phase (and/or from the
 * basetime to them, if basetime != currtime).
 */

PROC PrintAll (basetime, attime, phase)
	time_t	basetime;
	time_t	attime;
	phase_t	phase;
{
	int	Uflag2 = (Uflag && (basetime != currtime));
	phase_t	basephase;		/* at basetime */

	char	*datestring;		/* fast values */
	char	*phasestring;

/*
 * PRINT "UNTIL" INFORMATION:
 */

	if (uflag)
	    PrintDiff (currtime, attime, TRUE, Uflag2);

	if (Uflag2)
	{
	    PrintDiff (basetime, attime, FALSE, FALSE);

	    basephase = Phase (basetime);

	    PrintLong (DateString (basetime),
			PhaseString (basephase), basephase, " until");
	}

/*
 * PRINT PHASE INFORMATION:
 */

	if (nflag || tflag || Tflag)
	    datestring = DateString (attime);

	if (tflag || Tflag)
	    phasestring = PhaseString (phase);

	if (nflag)	printf ("%s%f\n", datestring, phase);
	if (tflag)	printf ("%s%s\n", datestring, phasestring);
	if (Tflag)	PrintLong (datestring, phasestring, phase, "");
	if (pflag)	PrintPicture (phase);

} /* PrintAll */


/************************************************************************
 * P R I N T   D I F F
 *
 * Given base and target system clock times in seconds, a flag whether to
 * compute time direction, and a flag whether to print a suffix, describe the
 * amount of time between the given times.  If dirflag is FALSE, assume the
 * time difference is positive and print "from"; otherwise, note and handle the
 * case where the base time is after the current time and print "since" or
 * "until" accordingly.  If sufflag is FALSE, print no suffix, else print
 * ", and".
 */

PROC PrintDiff (basetime, attime, dirflag, sufflag)
	time_t	basetime;
	time_t	attime;
	int	dirflag;
	int	sufflag;
{
	char	*desc;				/* trailing description	  */
REG	time_t	delta = attime - basetime;	/* difference, in seconds */
REG	time_t	delta2;				/* modified copy	  */
	int	days;				/* whole days		  */
	int	hours;				/* whole hours		  */
	int	minutes;			/* whole minutes	  */

	if (! dirflag)
	    desc = "from";

	else if (delta >= 0)
	    desc = "until";

	else
	{
	    desc  = "since";
	    delta = -delta;
	}

	delta2	 = delta  + (SECperMIN / 2);	/* round up  */
	days	 = delta2 / SECperDAY;		/* truncates */
	delta2	-= days   * SECperDAY;
	hours	 = delta2 / SECperHOUR;		/* truncates */
	delta2	-= hours  * SECperHOUR;
	minutes	 = delta2 / SECperMIN;		/* truncates */

	printf ("it is %d day%s, %d hour%s, %d minute%s (%.3f days, %.0f%% \
of a lunar cycle) %s%s\n",
		days,	 ((days    == 1) ? "" : "s"),
		hours,	 ((hours   == 1) ? "" : "s"),
		minutes, ((minutes == 1) ? "" : "s"),
		((double) delta) / SECperDAY,
		100.0 * delta / SECperMON,
		desc,
		sufflag ? ", and" : "");

} /* PrintDiff */


/************************************************************************
 * P R I N T   L O N G
 *
 * Given strings describing the date and time and phase, a moon phase
 * (P_NEW..P_NEXTNEW), and a suffix string, print a long one-line description of
 * the date, time, and phase.
 *
 * Percent illuminated = 50 * (1 - cos (phase * TWOPI)) because any great
 * circle on the moon has that percentage illuminated, and the moon can be seen
 * as a stack of great circles (assuming coplanar orbits).
 */

PROC PrintLong (datestring, phasestring, phase, suffix)
	char	*datestring;
	char	*phasestring;
	phase_t	phase;
	char	*suffix;
{
	printf ("%sthe moon is %s (%.0f%% illuminated)%s\n",
		datestring, phasestring,
		50 * (1 - cos (phase * TWOPI)),
		suffix);

} /* PrintLong */


/************************************************************************
 * P R I N T   P I C T U R E
 *
 * Given a moon phase (P_NEW..P_NEXTNEW) and globals lines, halfcols, and
 * centercol, "draw" a picture of the moon phase as an array of characters,
 * with one blank line before and after.  Print one line at a time, from the
 * top to the bottom.  Each line is a slice through a "circle" which represents
 * the visible face of the moon.
 *
 * This problem is surprisingly complex due to corner cases:
 * - new and full moons
 * - appropriate rounding of terminator X to column position
 *
 * The start and end X positions of the lit part (terminator to edge or edge to
 * terminator) are:
 *
 *	P_NEW  < phase < P_FULL:     cos (angle) .. 1     (left  side: 1 -> -1)
 *	P_FULL < phase < P_NEXTNEW:  -1 .. -cos (angle)   (right side: 1 -> -1)
 *
 * where angle = phase * TWOPI.
 */

PROC PrintPicture (phase)
	phase_t	phase;
{
	double	yperline = 2.0 / lines;	/* Y step per output line	*/
REG	double	ypos;			/* Y position in drawing, -1..1	*/

REG	double	edgexpos;		/* X pos of edge of moon, 0..1	*/
REG	double	termxpos = cos (phase * TWOPI);
					/* X pos of terminator,  -1..1	*/

	int	waxing =  (phase <= P_FULL);	/* flag:  waxing moon?	*/

	int	new    = ((phase <= P_NEW     + P_PRECISION)
		       || (phase >= P_NEXTNEW - P_PRECISION));

	int	full   = ((phase >= P_FULL    - P_PRECISION)
		       && (phase <= P_FULL    + P_PRECISION));

REG	int	currcol;		/* current column		*/
REG	int	edgelcol, edgercol;	/* left and right edge columns	*/
REG	int	litlcol,  litrcol;	/* left and right lit part cols	*/

/*
 * X POS TO COLUMN NUMBER MACRO:
 *
 * Note that small negative numbers truncate to 0.
 */

#define	COLPOS(xpos, round) ((int) (centercol + ((xpos) * halfcols) + round))

/*
 * DRAW EACH LINE:
 */

	puts ("");

	for (ypos = 1.0 - (yperline / 2); ypos >= -1.0; ypos -= yperline)
	{

/*
 * FIGURE EDGE AND LIT-PART COLUMN NUMBERS:
 *
 * For edge columns, simply truncate to the whole column number if xpos is
 * anywhere in the column because the whole column (character cell) is used to
 * print one char.  However, handle the terminator column carefully to avoid
 * round-off error into an excess column.  Only mark a column as lit if more
 * than half of it is within the lit part, by adding (if waxing moon) 0.5 to or
 * subtracting (if waning) 0.5 from the X position before truncating.
 */

	    edgexpos = sqrt (1.0 - (ypos * ypos));

	    edgelcol = COLPOS (-edgexpos, 0);
	    edgercol = COLPOS ( edgexpos, 0);

	    if (new)
	    {
		litrcol = -1;			/* no lit portion */
	    }
	    else if (full)
	    {
		litlcol = edgelcol;
		litrcol = edgercol;
	    }
	    else if (waxing)
	    {
		litrcol = edgercol;		/* right edge is lit */

		if ((litlcol = COLPOS (edgexpos * termxpos, 0.5)) > litrcol)
		    litlcol = litrcol;		/* consistent single column */
	    }
	    else /* waning */
	    {
		litlcol = edgelcol;		/* left edge is lit */

		if ((litrcol = COLPOS (edgexpos * (-termxpos), -0.5)) < litlcol)
		    litrcol = litlcol;
	    }

/*
 * PRINT A LINE OF CHARS:
 */

	    for (currcol = 0; currcol <= edgercol; currcol++)
	    {
		putchar (
		    ((currcol <= litrcol) && (currcol >= litlcol)) ? CH_LIT :
		    (currcol == edgelcol) ? CH_LEFTEDGE  :
		    (currcol == edgercol) ? CH_RIGHTEDGE : ' ');
	    }

	    putchar ('\n');

	} /* for */

	puts ("");

} /* PrintPicture */


/************************************************************************
 * D A T E   S T R I N G
 *
 * Given a system clock time in seconds, return a pointer to a date/time string
 * representing the time as null (if global dflag), YYMMDD HH:MM (default,
 * rounded to nearest minute, in the local timezone), or in ctime(3) format (if
 * Dflag), in static memory which should not be altered by the caller.
 * Non-null return values are followed by two blanks.
 */

PROC char * DateString (attime)
	time_t	attime;
{
REG	struct	tm *tm;		/* for long form */
static	char	result [CTIMELEN + 1];

	if (dflag)
	{
	    result [0] = CHNULL;
	}
	else if (Dflag)
	{
	    strncpy (result, ctime (& attime), CTIMELEN - 2);
	    result [CTIMELEN - 2] = result [CTIMELEN - 1] = ' ';
	    result [CTIMELEN] = CHNULL;
	}
	else
	{
	    attime += (SECperMIN / 2);		/* for rounding */
	    tm	    = localtime (& attime);

	    sprintf (result, SHORTDATEFORM,
		    tm -> tm_year, (tm -> tm_mon) + 1, tm -> tm_mday,
		    tm -> tm_hour,  tm -> tm_min);
	}

	return (result);

} /* DateString */


/************************************************************************
 * P H A S E   S T R I N G
 *
 * Given a moon phase (P_NEW..P_NEXTNEW), return a minimum string which
 * describes that phase, in private memory (caller should not overwrite it).
 */

PROC char * PhaseString (phase)
REG	phase_t	phase;
{
	return ((phase < P_NEW	   + P_PRECISION) ? "new"		:
		(phase < P_FIRST   - P_PRECISION) ? "waxing crescent"	:
		(phase < P_FIRST   + P_PRECISION) ? "first quarter"	:
		(phase < P_FULL	   - P_PRECISION) ? "waxing gibbous"	:
		(phase < P_FULL	   + P_PRECISION) ? "full"		:
		(phase < P_LAST	   - P_PRECISION) ? "waning gibbous"	:
		(phase < P_LAST	   + P_PRECISION) ? "last quarter"	:
		(phase < P_NEXTNEW - P_PRECISION) ? "waning crescent"	:
						    "new");

} /* PhaseString */


/************************************************************************
 * E R R O R
 *
 * Given a usage flag and a message string and arguments, print an error
 * message to stderr.  If usage flag is USAGE, call Usage(), else exit with
 * ERREXIT.  Message is preceded by "<myname>:  " using global char *myname,
 * and followed by a newline.
 */

/* VARARGS2 */
PROC Error (usageflag, message, arg1, arg2, arg3, arg4)
	int	usageflag;
	char	*message;
	long	arg1, arg2, arg3, arg4;
{
	fprintf (stderr, "%s:  ", myname);
	fprintf (stderr, message, arg1, arg2, arg3, arg4);
	putc ('\n', stderr);

	if (usageflag == USAGE)
	    Usage();

	exit (ERREXIT);

} /* Error */


/************************************************************************
 * U S A G E
 *
 * Print usage messages (char *usage[]) to stderr and exit with ERREXIT.
 * Each message is followed by a newline.
 */

PROC Usage()
{
REG	int	which = 0;		/* current line */

	while (usage [which] != CPNULL)
	{
	    fprintf (stderr, usage [which++], myname);
	    putc ('\n', stderr);
	}

	exit (ERREXIT);

} /* Usage */


#ifdef GET_EPOCH

/************************************************************************
 * G E T   E P O C H
 *
 * Given a date (Epoch) hardwired in (see below), binary search to find the
 * equivalent system clock time in seconds, then exit.
 *
 * This could be accomplished using parsedate() on a string, but this method
 * is more portable (and was already written).
 */

PROC GetEpoch()
{
	struct	tm *tm;
	long	time();

	time_t	trytime	= (time_t) time ((long *) 0);
REG	time_t	range	= trytime;

	int	e_year	= 84;		/* time of Epoch */
	int	e_yday	= 365;
	int	e_hour	= 0;
	int	e_min	= 0;
	int	e_sec	= 0;

	int	d_year;			/* delta values */
	int	d_yday;
	int	d_hour;
	int	d_min;
	int	d_sec;

/*
 * TRY NEXT TIME:
 */

	while (TRUE)			/* until exit */
	{
	    tm = gmtime (& trytime);

	    d_year = e_year - (tm -> tm_year);
	    d_yday = e_yday - (tm -> tm_yday);
	    d_hour = e_hour - (tm -> tm_hour);
	    d_min  = e_min  - (tm -> tm_min);
	    d_sec  = e_sec  - (tm -> tm_sec);

	    printf ("try: %9ld  delta: %3d %4d %3d %3d %3d  range: %9ld\n",
		    trytime, d_year, d_yday, d_hour, d_min, d_sec, range);

/*
 * SEE IF DONE:
 */

	    if ((d_year == 0) && (d_yday == 0)
	     && (d_hour == 0) && (d_min  == 0) && (d_sec == 0))
	    {
		printf ("in local time:  %s", ctime (& trytime));
		exit (OKEXIT);
	    }

/*
 * ADJUST UP OR DOWN:
 */

	    if (range > 1)
		range /= 2;

	    if	    (d_year > 0)	trytime += range;
	    else if (d_year < 0)	trytime -= range;
	    else if (d_yday > 0)	trytime += range;
	    else if (d_yday < 0)	trytime -= range;
	    else if (d_hour > 0)	trytime += range;
	    else if (d_hour < 0)	trytime -= range;
	    else if (d_min > 0)		trytime += range;
	    else if (d_min < 0)		trytime -= range;
	    else if (d_sec > 0)		trytime += range;
	    else			trytime -= range;

	} /* while */

} /* GetEpoch */

#endif /* GET_EPOCH */
@EOF
if test "`wc -lwc <moon/moon.c`" != '   1320   6103  36836'
then
	echo ERROR: wc results of moon/moon.c are `wc -lwc <moon/moon.c` should be    1320   6103  36836
fi

chmod 444 moon/moon.c

echo x - moon/test.out
sed 's/^@//' >moon/test.out <<'@EOF'
=== expect errors ===

$ moon -?
moon: illegal option -- ?
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ moon -x 0
moon:  invalid X/Y ratio with -x option
returns: 1

$ moon -dD
moon:  only one of -d and -D is allowed
returns: 1

$ moon -pd
moon:  -d and -D make no sense without one of -ntT
returns: 1

$ moon -pD
moon:  -d and -D make no sense without one of -ntT
returns: 1

$ moon new 890401 890402 1d
moon:  increment is not allowed with quarter specified too
returns: 1

$ moon 890402 890401
moon:  endtime must be at or after start time
returns: 1

$ moon -u
moon:  -u makes no sense without a quarter and/or time specified
returns: 1

$ moon -U new
moon:  -U makes no sense without a quarter and a time specified
returns: 1

$ moon -U 890401
moon:  -U makes no sense without a quarter and a time specified
returns: 1

$ moon 890431
moon:  invalid date specified (was it a single, quoted argument?):
890431
      ^
returns: 1

$ moon may 1
moon:  invalid date specified (was it a single, quoted argument?):
may
   ^
returns: 1

$ moon 890401 890402 -1
moon:  invalid increment value
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ moon 890401 890402 0d
moon:  invalid increment value
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ moon 890401 890402 1
moon:  missing time unit on increment value
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ moon 890401 890402 1d x
moon:  too many arguments specified
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ moon 890401 890402 1x
moon:  invalid suffix on increment value
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ moon 890401 890402 1 x
moon:  invalid suffix on increment value
usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]

-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
-t (terse text) print only the date, time, and phase name
-T (text) print date, time, and a description of the moon's phase (default)
-u (until) tell how long from now until each specified event
-U (until) tell how long from time to quarter and/or each quarter to next
-p (picture) draw a picture of the moon's phase using characters
-d don't print the date and time with -ntT, just the phase information
-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
-x set display/font character X/Y ratio (for square picture; default:  0.5)

quarter:    any of "new first full last next any"; if specified, tell when
            that quarter next occurs, or all occurrences in the time range
time:       value to use (default:  current system time)
endtime:    print phase or quarter information from time to endtime
increment:  amount to step through range of times; must end in one of:
            d(ays) h(ours) m(inutes) s(econds) (default:  1d)

Enter date/time in almost any reasonable format, quoted if it contains
blanks.  Year, month, and day default to current; hour, minute, and second to
zero; timezone to current, including DST if in effect (assumed west of
Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
returns: 1

$ LINES=x moon -p
moon:  too few lines or columns for picture; check LINES and COLUMNS vars
returns: 1

$ LINES=2 moon -p
moon:  too few lines or columns for picture; check LINES and COLUMNS vars
returns: 1

$ COLUMNS=2 moon -p
moon:  too few lines or columns for picture; check LINES and COLUMNS vars
returns: 1

=== expect successes ===

$ moon
(OK if changes) 890613 11:20  the moon is waxing gibbous (72% illuminated)
returns: 0

$ moon next
(OK if changes) 890619 01:19  the moon is full (100% illuminated)
returns: 0

$ moon any
(OK if changes) 890619 01:19  the moon is full (100% illuminated)
returns: 0

$ moon -n new
(OK if changes) 890702 23:14  0.000000
returns: 0

$ moon -t first
(OK if changes) 890710 18:05  first quarter
returns: 0

$ moon -T full
(OK if changes) 890619 01:19  the moon is full (100% illuminated)
returns: 0

$ moon -u last
(OK if changes) it is 12 days, 15 hours, 44 minutes (12.655 days, 43% of a lunar cycle) until
890626 03:04  the moon is last quarter (50% illuminated)
returns: 0

$ LINES=8   COLUMNS=100 moon -p first

                                              (   @@@@
                                            (     @@@@@@
                                            (     @@@@@@
                                            (     @@@@@@
                                            (     @@@@@@
                                              (   @@@@

returns: 0

$ LINES=100 COLUMNS=20  moon -px0.4 last

     @@@@@    )
  @@@@@@@@       )
@@@@@@@@@@@         )
@@@@@@@@@@@         )
@@@@@@@@@@@         )
@@@@@@@@@@@         )
  @@@@@@@@       )
     @@@@@    )

returns: 0

$ COLUMNS=10 moon -p first

  (  @@@
(    @@@@@
(    @@@@@
(    @@@@@
  (  @@@

returns: 0

$ COLUMNS=10 moon -p last

  @@@  )
@@@@@@    )
@@@@@@    )
@@@@@@    )
  @@@  )

returns: 0

$ COLUMNS=11 moon -p first

  (  @@@@
(    @@@@@@
(    @@@@@@
(    @@@@@@
  (  @@@@

returns: 0

$ COLUMNS=11 moon -p last

  @@@   )
@@@@@@     )
@@@@@@     )
@@@@@@     )
  @@@   )

returns: 0

$ moon -ntd new
0.000000
new
returns: 0

$ moon -tTD full
(OK if changes) Mon Jun 19 01:18:57 1989  full
Mon Jun 19 01:18:57 1989  the moon is full (100% illuminated)
returns: 0

$ moon -tu new
(OK if changes) it is 19 days, 11 hours, 54 minutes (19.496 days, 66% of a lunar cycle) until
890702 23:14  new
returns: 0

$ moon -uD new
(OK if changes) it is 19 days, 11 hours, 54 minutes (19.496 days, 66% of a lunar cycle) until
Sun Jul  2 23:13:31 1989  the moon is new (0% illuminated)
returns: 0

$ moon -d any 890501
the moon is new (0% illuminated)
returns: 0

$ moon -U next 890501
(OK if changes) it is 4 days, 5 hours, 42 minutes (4.237 days, 14% of a lunar cycle) from
890501 00:00  the moon is waning crescent (24% illuminated) until
890505 05:42  the moon is new (0% illuminated)
returns: 0

$ moon new 890530.0500 890530.0500
returns: 0

$ moon -U first 890530 890730
(OK if changes) it is 12 days, 0 hours, 59 minutes (12.041 days, 41% of a lunar cycle) from
890530 00:00  the moon is waning crescent (27% illuminated) until
890611 00:59  the moon is first quarter (50% illuminated)
it is 29 days, 17 hours, 6 minutes (29.712 days, 101% of a lunar cycle) from
890611 00:59  the moon is first quarter (50% illuminated) until
890710 18:05  the moon is first quarter (50% illuminated)
returns: 0

$ moon -uU last 890430 890630
(OK if changes) it is 16 days, 13 hours, 17 minutes (16.554 days, 56% of a lunar cycle) since, and
it is 27 days, 22 hours, 2 minutes (27.918 days, 95% of a lunar cycle) from
890430 00:00  the moon is waning crescent (35% illuminated) until
890527 22:02  the moon is last quarter (50% illuminated)
it is 12 days, 15 hours, 44 minutes (12.655 days, 43% of a lunar cycle) until, and
it is 29 days, 5 hours, 1 minute (29.209 days, 99% of a lunar cycle) from
890527 22:02  the moon is last quarter (50% illuminated) until
890626 03:04  the moon is last quarter (50% illuminated)
returns: 0

$ moon -tuU any 890815 891001
(OK if changes) it is 64 days, 9 hours, 48 minutes (64.408 days, 218% of a lunar cycle) until, and
it is 1 day, 21 hours, 8 minutes (1.881 days, 6% of a lunar cycle) from
890815 00:00  the moon is waxing gibbous (95% illuminated) until
890816 21:08  full
it is 71 days, 1 hour, 29 minutes (71.062 days, 241% of a lunar cycle) until, and
it is 6 days, 15 hours, 41 minutes (6.654 days, 23% of a lunar cycle) from
890816 21:08  the moon is full (100% illuminated) until
890823 12:49  last quarter
it is 78 days, 12 hours, 17 minutes (78.512 days, 266% of a lunar cycle) until, and
it is 7 days, 10 hours, 48 minutes (7.450 days, 25% of a lunar cycle) from
890823 12:49  the moon is last quarter (50% illuminated) until
890830 23:37  new
it is 86 days, 16 hours, 22 minutes (86.682 days, 294% of a lunar cycle) until, and
it is 8 days, 4 hours, 4 minutes (8.170 days, 28% of a lunar cycle) from
890830 23:37  the moon is new (0% illuminated) until
890908 03:41  first quarter
it is 93 days, 18 hours, 19 minutes (93.763 days, 318% of a lunar cycle) until, and
it is 7 days, 1 hour, 57 minutes (7.082 days, 24% of a lunar cycle) from
890908 03:41  the moon is first quarter (50% illuminated) until
890915 05:39  full
it is 100 days, 9 hours, 14 minutes (100.385 days, 340% of a lunar cycle) until, and
it is 6 days, 14 hours, 55 minutes (6.622 days, 22% of a lunar cycle) from
890915 05:39  the moon is full (100% illuminated) until
890921 20:34  last quarter
it is 108 days, 4 hours, 14 minutes (108.177 days, 366% of a lunar cycle) until, and
it is 7 days, 19 hours, 0 minutes (7.792 days, 26% of a lunar cycle) from
890921 20:34  the moon is last quarter (50% illuminated) until
890929 15:34  new
returns: 0

$ moon -n "may 1" "August 4" " 5 d"
(OK if changes) 890501 00:00  0.836004
890506 00:00  0.029493
890511 00:00  0.206809
890516 00:00  0.360946
890521 00:00  0.515352
890526 00:00  0.682113
890531 00:00  0.863981
890605 00:00  0.051936
890610 00:00  0.218086
890615 00:00  0.370084
890620 00:00  0.531955
890625 00:00  0.708963
890630 00:00  0.892533
890705 00:00  0.069861
890710 00:00  0.227254
890715 00:00  0.381261
890720 00:00  0.554169
890725 00:00  0.738638
890730 00:00  0.917511
890804 00:00  0.083191
returns: 0

$ moon -n "may 1, 1988 10:00pm" "August 4, 1988" 5d"
880501 22:00  0.506121
880506 22:00  0.681008
880511 22:00  0.863823
880516 22:00  0.043451
880521 22:00  0.203474
880526 22:00  0.356072
880531 22:00  0.525389
880605 22:00  0.709530
880610 22:00  0.889761
880615 22:00  0.058072
880620 22:00  0.211873
880625 22:00  0.369028
880630 22:00  0.550599
880705 22:00  0.738626
880710 22:00  0.911168
880715 22:00  0.069783
880720 22:00  0.221681
880725 22:00  0.387086
880730 22:00  0.579085
returns: 0

$ moon -T 890503.0031 890503.1200 1h
890503 00:31  the moon is waning crescent (7% illuminated)
890503 01:31  the moon is waning crescent (7% illuminated)
890503 02:31  the moon is waning crescent (7% illuminated)
890503 03:31  the moon is waning crescent (6% illuminated)
890503 04:31  the moon is waning crescent (6% illuminated)
890503 05:31  the moon is waning crescent (6% illuminated)
890503 06:31  the moon is waning crescent (6% illuminated)
890503 07:31  the moon is waning crescent (5% illuminated)
890503 08:31  the moon is waning crescent (5% illuminated)
890503 09:31  the moon is waning crescent (5% illuminated)
890503 10:31  the moon is waning crescent (5% illuminated)
890503 11:31  the moon is waning crescent (5% illuminated)
returns: 0

$ moon -D 890513.113015 890513.1200 4 min
Sat May 13 11:30:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:34:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:38:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:42:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:46:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:50:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:54:15 1989  the moon is waxing gibbous (61% illuminated)
Sat May 13 11:58:15 1989  the moon is waxing gibbous (61% illuminated)
returns: 0

$ moon -nD 890523.113145 890523.1138 20 s
Tue May 23 11:31:45 1989  0.596302
Tue May 23 11:32:05 1989  0.596309
Tue May 23 11:32:25 1989  0.596317
Tue May 23 11:32:45 1989  0.596325
Tue May 23 11:33:05 1989  0.596332
Tue May 23 11:33:25 1989  0.596340
Tue May 23 11:33:45 1989  0.596348
Tue May 23 11:34:05 1989  0.596356
Tue May 23 11:34:25 1989  0.596363
Tue May 23 11:34:45 1989  0.596371
Tue May 23 11:35:05 1989  0.596379
Tue May 23 11:35:25 1989  0.596386
Tue May 23 11:35:45 1989  0.596394
Tue May 23 11:36:05 1989  0.596402
Tue May 23 11:36:25 1989  0.596410
Tue May 23 11:36:45 1989  0.596417
Tue May 23 11:37:05 1989  0.596425
Tue May 23 11:37:25 1989  0.596433
Tue May 23 11:37:45 1989  0.596441
returns: 0

$ moon next 890815 891001
890816 21:08  the moon is full (100% illuminated)
890915 05:39  the moon is full (100% illuminated)
returns: 0

$ moon any 890815 891001
890816 21:08  the moon is full (100% illuminated)
890823 12:49  the moon is last quarter (50% illuminated)
890830 23:37  the moon is new (0% illuminated)
890908 03:41  the moon is first quarter (50% illuminated)
890915 05:39  the moon is full (100% illuminated)
890921 20:34  the moon is last quarter (50% illuminated)
890929 15:34  the moon is new (0% illuminated)
returns: 0

$ TZ=MST7MDT moon 890815.1200
890815 12:00  the moon is waxing gibbous (97% illuminated)
returns: 0

$ TZ=UTC0    moon 890815.1200
890815 12:00  the moon is waxing gibbous (96% illuminated)
returns: 0
@EOF
if test "`wc -lwc <moon/test.out`" != '    545   3453  20865'
then
	echo ERROR: wc results of moon/test.out are `wc -lwc <moon/test.out` should be     545   3453  20865
fi

chmod 444 moon/test.out

echo x - moon/test.script
cat >moon/test.script <<'@EOF'

# TEST SCRIPT FOR moon(1).

# Purpose:  run moon a variety of ways, exercising permutations of options, and
# produce output to compare against a known good version.

# Usage:  <script> | diff test.out -

# Compile moon without -DDEBUG.  In cases where no date is specified, the exact
# output from moon changes, but the form should not.  These output chunks are
# marked "(OK if changes)" when they show up in diff output.

# Variants to combine:
#
# options:	combinations of -ntTuUp, including none
#		none, -d, -D
#		none, -x
#
# quarter:	none, new, first, full, last, next, any
#
# date:		none, start only, range with no increment, range with d/h/m/s
#		increment (as one or two arguments)
#
# There are too many possible permutations to test them all.  Instead, try only
# selected variations.


# INITIALIZE:
#
# Set run-strings to use for each command.  $run is generic; $run2 prefaces
# the first output line with a message that changes are OK.

	exec 2>&1			# for user's convenience.

	run=' echo "\n$ $x"			; eval $x; echo "returns: $?"'
	run2='echo "\n$ $x\n(OK if changes) \c"	; eval $x; echo "returns: $?"'

	TZ='MST7MDT'
	export TZ


# TEST ERRORS:
#
# These are based on Usage() and Error() calls in the source code, and appear
# in the same order.  Some errors are triggered several times in different
# ways.

	echo "=== expect errors ==="

	x='moon -?';				eval $run
	x='moon -x 0';				eval $run
	x='moon -dD';				eval $run
	x='moon -pd';				eval $run
	x='moon -pD';				eval $run
	x='moon new 890401 890402 1d';		eval $run
	x='moon 890402 890401';			eval $run
	x='moon -u';				eval $run
	x='moon -U new';			eval $run
	x='moon -U 890401';			eval $run
	x='moon 890431';			eval $run
	x='moon may 1';				eval $run
	# No way to trigger:  "failed trying to fill in default time values"
	x='moon 890401 890402 -1';		eval $run
	x='moon 890401 890402 0d';		eval $run
	x='moon 890401 890402 1';		eval $run
	x='moon 890401 890402 1d x';		eval $run
	x='moon 890401 890402 1x';		eval $run
	x='moon 890401 890402 1 x';		eval $run
	x='LINES=x moon -p';			eval $run
	x='LINES=2 moon -p';			eval $run
	x='COLUMNS=2 moon -p';			eval $run


# TEST SUCCESSES:

	echo "\n=== expect successes ==="

    # Simple options:

	x='moon';				eval $run2
	x='moon next';				eval $run2
	x='moon any';				eval $run2
	x='moon -n new';			eval $run2
	x='moon -t first';			eval $run2
	x='moon -T full';			eval $run2
	x='moon -u last';			eval $run2

	x='LINES=8   COLUMNS=100 moon -p first';	eval $run
	x='LINES=100 COLUMNS=20  moon -px0.4 last';	eval $run
	x='COLUMNS=10 moon -p first'			eval $run
	x='COLUMNS=10 moon -p last'			eval $run
	x='COLUMNS=11 moon -p first'			eval $run
	x='COLUMNS=11 moon -p last'			eval $run

    # Option combinations:

	x='moon -ntd new';			eval $run
	x='moon -tTD full';			eval $run2
	x='moon -tu new';			eval $run2
	x='moon -uD new';			eval $run2

    # Phases and/or times:

	x='moon -d any 890501';			eval $run
	x='moon -U next 890501';		eval $run2
	x='moon new 890530.0500 890530.0500';	eval $run	# no output.
	x='moon -U first 890530 890730';	eval $run2
	x='moon -uU last 890430 890630';	eval $run2
	x='moon -tuU any 890815 891001';	eval $run2

    # Increments:

	x='moon -n "may 1" "August 4" " 5 d"';			eval $run2
	x='moon -n "may 1, 1988 10:00pm" "August 4, 1988" 5d"';	eval $run
	x='moon -T 890503.0031 890503.1200 1h';			eval $run
	x='moon -D 890513.113015 890513.1200 4 min';		eval $run
	x='moon -nD 890523.113145 890523.1138 20 s';		eval $run

    # "next" versus "any":

	x='moon next 890815 891001';		eval $run
	x='moon any 890815 891001';		eval $run

    # Time zone:

	x='TZ=MST7MDT moon 890815.1200';	eval $run
	x='TZ=UTC0    moon 890815.1200';	eval $run
@EOF
if test "`wc -lwc <moon/test.script`" != '    127    588   3689'
then
	echo ERROR: wc results of moon/test.script are `wc -lwc <moon/test.script` should be     127    588   3689
fi

chmod 555 moon/test.script

chmod 750 moon

echo mkdir - parsedate
mkdir parsedate

echo x - parsedate/parsedate.h
cat >parsedate/parsedate.h <<'@EOF'
/* $Log:	/s/uclasrc/mail/date/RCS/parsedate.h,v $
 * Revision 1.1  84/09/01  15:01:38  wales
 * Initial revision
 * 
 * Copyright (c) 1984 by Richard B. Wales
 *
 */
#ifdef RCSIDENT
#define RCS_PARSEDATE_HDR "$Header: /s/uclasrc/mail/date/RCS/parsedate.h,v 1.1 84/09/01 15:01:38 wales UCLA $"
#endif RCSIDENT

/* Data structure returned by "parsedate".
 *
 * A value of NULL for "error" means that no syntax errors were detected
 * in the argument value.  A non-NULL value points to the byte position
 * within the argument string at which it was discovered that an error
 * existed.
 *
 * A value of -1 means that the field was never given a value, or that
 * the value supplied was invalid.  (A side effect of this convention is
 * that a time zone offset of -1 -- i.e., one minute west of GMT -- is
 * indistinguishable from an invalid or unspecified time zone offset.
 * Since the likelihood of "-0001" being a legitimate time zone is nil,
 * banning it is a small price to pay for the uniformity of using -1 as
 * a "missing/invalid" indication for all fields.)
 */
struct parsedate
    {	long unixtime;	/* UNIX internal representation of time */
	char *error;	/* NULL = OK; non-NULL = error */
	int year;	/* year (1600 on) */
	int month;	/* month (1-12) */
	int day;	/* day of month (1-31) */
	int hour;	/* hour (0-23) */
	int minute;	/* minute (0-59) */
	int second;	/* second (0-59) */
	int zone;	/* time zone offset in minutes -- "+" or "-" */
	int dst;	/* daylight savings time (0 = no, 1 = yes) */
	int weekday;	/* real day of week (0-6; 0 = Sunday) */
	int c_weekday;	/* claimed day of week (0-6; 0 = Sunday) */
    };

struct parsedate *parsedate();
@EOF
if test "`wc -lwc <parsedate/parsedate.h`" != '     42    296   1663'
then
	echo ERROR: wc results of parsedate/parsedate.h are `wc -lwc <parsedate/parsedate.h` should be      42    296   1663
fi

chmod 444 parsedate/parsedate.h

echo x - parsedate/Makefile
cat >parsedate/Makefile <<'@EOF'
# $Log:	/s/uclasrc/mail/date/RCS/Makefile,v $
# Revision 1.1  84/09/01  15:00:58  wales
# Initial revision
# 
# $Header: /s/uclasrc/mail/date/RCS/Makefile,v 1.1 84/09/01 15:00:58 wales UCLA $
#
# Makefile for "date" library routines
#
# Copyright (c) 1984 by Richard B. Wales

DEFS   =
CFLAGS = -O $(DEFS) -DRCSIDENT

all:		libdate.a

strip:		libdate.a

libdate.a:	datelex.o dateyacc.o parsedate.o
	rm -f libdate.a
	ar rc libdate.a datelex.o dateyacc.o parsedate.o

.c.o:
	cc -S $(CFLAGS) $*.c
	sed 's/_yy/_date_yy/g' $*.s | as -o $*.o
	rm -f $*.s

dateyacc.c dateyacc.h:	dateyacc.y
	yacc -d dateyacc.y
	mv y.tab.c dateyacc.c
	mv y.tab.h dateyacc.h

clean:
	rm -f *.o *.s\
	      dateyacc.c dateyacc.h y.tab.c y.tab.h y.output\
	      libdate.a

datelex.o:	parsedate.h dateyacc.h
dateyacc.o:	parsedate.h
parsedate.o:	parsedate.h
@EOF
if test "`wc -lwc <parsedate/Makefile`" != '     39    108    829'
then
	echo ERROR: wc results of parsedate/Makefile are `wc -lwc <parsedate/Makefile` should be      39    108    829
fi

chmod 444 parsedate/Makefile

echo x - parsedate/README
cat >parsedate/README <<'@EOF'
		    DATE MANIPULATION PACKAGE
			Richard B. Wales
	  UCLA Center for Experimental Computer Science

This is a software package for manipulating character strings repre-
senting dates.  It can do the following:

(1) Interpret an (almost) arbitrary date string and break it down into
    year, month, day, hour, minute, second, time zone, day of the week,
    and UNIX internal time (seconds since 1970).

(2) Compute the day of the week and UNIX internal time corresponding to
    a year/month/day/hour/minute/second/zone combination.

(3) Compute the year/month/day/hour/minute/second/zone/day-of-the-week
    combination corresponding to a UNIX internal time.

(4) Generate a character string corresponding to a year/month/day/hour/
    minute/second/zone combination.  Routines exist for creating strings
    in either RFC822 (ARPANET mail) or UUCP mail formats.

The date parser accepts a superset of RFC822, UUCP, USENET, and UNIX
("date" command) formats.  Numerous time zone abbreviations -- including
many used in various parts of the world but not acknowledged in RFC822
-- are understood.  The date parser is NOT suitable for applications
which require strict conformance with RFC822 and need to know whether a
given date string follows RFC822 exactly.  RFC822-format date strings
generated by this package, however, do conform strictly to RFC822; non-
RFC822 time zone abbreviations which might have been used on input are
replaced by numeric offsets on output.

The date parser was written using YACC.  However, it should be possible
to use these routines in programs which already use LEX and/or YACC for
other purposes, since all the external symbols beginning with "_yy" have
been renamed to begin with "_date_yy" instead.

This code was written and tested in a 4.1BSD environment.  Some mods may
be necessary to make it run on other UNIX systems; in particular, people
using AT&T System V and other systems which restrict the length of
external symbols may encounter some problems with long variable and rou-
tine names.

This package contains a copyright notice, as follows:

		Copyright (c) 1984 by Richard B. Wales

The author hereby grants permission to use or redistribute this package
freely and without charge, subject to the following restrictions:

(1) The copyright notice must be retained in all copies of the source.

(2) Any changes made to the source must be clearly documented (such as
    by #ifdef's or by use of a source-code control system such as RCS or
    SCCS), so that the original version of the source as distributed by
    the author can be reconstructed if necessary and distinguished from
    modifications made by others.

The author is interested in any comments, bug reports, etc., concerning
this package, and will try to help out with problems as time permits.
However, since this package is being made available without charge, the
author and his employer disclaim any responsibility, obligation, or com-
mitment to supply bug fixes or enhancements to this package, to adapt it
to run under any particular computer system, to supply consulting
assistance in its use, or to support it in any other manner whatsoever.
The author and his employer specifically and expressly disclaim any lia-
bility whatsoever for incidental or consequential damages which might
arise out of the use of this package.

    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
@EOF
if test "`wc -lwc <parsedate/README`" != '     73    532   3556'
then
	echo ERROR: wc results of parsedate/README are `wc -lwc <parsedate/README` should be      73    532   3556
fi

chmod 444 parsedate/README

echo x - parsedate/date.3
cat >parsedate/date.3 <<'@EOF'



     DDDDAAAATTTTEEEE((((3333))))                       ((((UUUUCCCCLLLLAAAA))))                      DDDDAAAATTTTEEEE((((3333))))



     NNNNAAAAMMMMEEEE
          parsedate - interpret character strings representing dates

     SSSSYYYYNNNNOOOOPPPPSSSSIIIISSSS
          #include "parsedate.h"

          char date;
          struct parsedate *pd;

          pd = parsedate (date);

          compute_unixtime (pd);

          break_down_unixtime (pd);

          date = mail_date_string (pd);

          date = uucp_date_string (pd);

     DDDDEEEESSSSCCCCRRRRIIIIPPPPTTTTIIIIOOOONNNN
          These routines manipulate character strings representing
          dates.  _p_a_r_s_e_d_a_t_e returns a pointer to a structure of the
          following form (described in the include-file _p_a_r_s_e_d_a_t_e._h):

          struct parsedate {
               long unixtime;      /* as returned by time(2) */
               char *error;        /* non-NULL = error */
               int year;           /* year (1600 on) */
               int month;          /* month (1-12) */
               int day;            /* day of month (1-31) */
               int hour;           /* hour (0-23) */
               int minute;         /* minute (0-59) */
               int second;         /* second (0-59) */
               int zone;           /* time zone offset */
               int dst;            /* daylight savings time */
               int weekday;        /* real day of week */
               int c_weekday;      /* claimed day of week */
          };

          Any field containing the value -1 (except for _e_r_r_o_r)
          indicates information which was either not supplied or was
          invalid.

          _u_n_i_x_t_i_m_e is the UNIX internal representation of the date
          (i.e., number of seconds since 1970).  _e_r_r_o_r is NNNNUUUULLLLLLLL if the
          date string did not contain a syntax error; otherwise, it
          points to the position in the date string where a syntax
          error was discovered.  _z_o_n_e indicates the time-zone offset
          in minutes from UTC.  A positive value denotes a time zone
          east of Greenwich; a negative value denotes a zone west of
          Greenwich.  _d_s_t is equal to 1 if the indicated time zone
          denotes ``daylight savings time'', or 0 if daylight savings



     Hewlett-Packard               - 1 -             (printed 3/14/85)






     DDDDAAAATTTTEEEE((((3333))))                       ((((UUUUCCCCLLLLAAAA))))                      DDDDAAAATTTTEEEE((((3333))))



          time is not indicated.

          _w_e_e_k_d_a_y and _c__w_e_e_k_d_a_y indicate a day of the week via a value
          in the range 0-6 (0 = Sunday, 6 = Saturday).  _c__w_e_e_k_d_a_y
          shows which day of the week was actually specified in the
          date string; _w_e_e_k_d_a_y is derived from _y_e_a_r, _m_o_n_t_h, and _d_a_y
          via a perpetual-calendar algorithm.

          _c_o_m_p_u_t_e__u_n_i_x_t_i_m_e takes a _s_t_r_u_c_t _p_a_r_s_e_d_a_t_e in which the _y_e_a_r,
          _m_o_n_t_h, _d_a_y, _h_o_u_r, _m_i_n_u_t_e, _s_e_c_o_n_d, and _z_o_n_e fields have been
          filled in, and fills in the _u_n_i_x_t_i_m_e and _w_e_e_k_d_a_y fields as
          appropriate.

          _b_r_e_a_k__d_o_w_n__u_n_i_x_t_i_m_e takes a _s_t_r_u_c_t _p_a_r_s_e_d_a_t_e in which the
          _u_n_i_x_t_i_m_e and _z_o_n_e fields have been filled in, and fills in
          the _y_e_a_r, _m_o_n_t_h, _d_a_y, _h_o_u_r, _m_i_n_u_t_e, _s_e_c_o_n_d, and _w_e_e_k_d_a_y
          fields as appropriate.

          _m_a_i_l__d_a_t_e__s_t_r_i_n_g returns a pointer to a RFC822 (ARPANET mail
          standard) format character string corresponding to a _s_t_r_u_c_t
          _p_a_r_s_e_d_a_t_e.  Similarly, _u_u_c_p__d_a_t_e__s_t_r_i_n_g returns a pointer to
          a UUCP-mail format character string corresponding to a
          _s_t_r_u_c_t _p_a_r_s_e_d_a_t_e.  In each case, the _y_e_a_r, _m_o_n_t_h, _d_a_y, _h_o_u_r,
          _m_i_n_u_t_e, _s_e_c_o_n_d, and _z_o_n_e fields should be set first.  If
          only the _u_n_i_x_t_i_m_e and _z_o_n_e values are initially avaiable,
          then _b_r_e_a_k__d_o_w_n__u_n_i_x_t_i_m_e should be called before trying to
          generate a date string.

     DDDDIIIIAAAAGGGGNNNNOOOOSSSSTTTTIIIICCCCSSSS
          If the character string supplied as an argument to _p_a_r_s_e_d_a_t_e
          contains a syntax error, the _e_r_r_o_r variables in the returned
          _s_t_r_u_c_t _p_a_r_s_e_d_a_t_e will be set to point to the place in the
          argument string where the error was discovered.

          Any field which is either unspecified or given an invalid
          value in a call to _p_a_r_s_e_d_a_t_e is set to -1.  _c_o_m_p_u_t_e__u_n_i_x_t_i_m_e
          and _b_r_e_a_k__d_o_w_n__u_n_i_x_t_i_m_e also set the appropriate fields to
          -1 if they are unable to compute the requested values.

          _m_a_i_l__d_a_t_e__s_t_r_i_n_g and _u_u_c_p__d_a_t_e__s_t_r_i_n_g return NNNNUUUULLLLLLLL if the
          information required to generate the date string is missing
          or invalid.

     BBBBUUUUGGGGSSSS
          The returned value from a call to _p_a_r_s_e_d_a_t_e,
          _m_a_i_l__d_a_t_e__s_t_r_i_n_g, or _u_u_c_p__d_a_t_e__s_t_r_i_n_g points to a static
          area whose contents will be overwritten by the next call to
          the same routine.

          A time-zone offset of ``-0001'' (i.e., one minute west of
          Greenwich) results in a _z_o_n_e value of -1, and is thus
          indistinguishable from an invalid or missing time zone.



     Hewlett-Packard               - 2 -             (printed 3/14/85)






     DDDDAAAATTTTEEEE((((3333))))                       ((((UUUUCCCCLLLLAAAA))))                      DDDDAAAATTTTEEEE((((3333))))



          Since this particular time-zone offset is not (and almost
          certainly never will be) used anywhere in the world, the
          fact that it cannot be distinguished from an invalid or
          missing value is probably unimportant.

     AAAAUUUUTTTTHHHHOOOORRRR
          Richard B. Wales
          UCLA Center for Experimental Computer Science















































     Hewlett-Packard               - 3 -             (printed 3/14/85)



@EOF
if test "`wc -lwc <parsedate/date.3`" != '    198    668   7184'
then
	echo ERROR: wc results of parsedate/date.3 are `wc -lwc <parsedate/date.3` should be     198    668   7184
fi

chmod 444 parsedate/date.3

echo x - parsedate/datelex.c
cat >parsedate/datelex.c <<'@EOF'
/*$Log:	/s/uclasrc/mail/date/RCS/datelex.c,v $
 * Revision 1.1  84/09/01  15:01:14  wales
 * Initial revision
 * 
 * Copyright (c) 1984 by Richard B. Wales
 *
 * Purpose:
 *
 *     Lexical analyzer for "parsedate" routine.  This lexer was orig-
 *     inally written in LEX, but rewriting it as an ad-hoc routine
 *     resulted in an enormous savings in space and a significant
 *     increase in speed.
 *
 * Usage:
 *
 *     Called as needed by the YACC parser ("dateyacc.c").  Not intended
 *     to be called from any other routine.
 *
 * Notes:
 *
 * Global contents:
 *
 *     int yylex ()
 *         Returns the token number (from the YACC grammar) of the next
 *         token in the input string pointed to by the global variable
 *         "yyinbuf".  The global variable "yylval" is set to the lexi-
 *         cal value (if any) of the token.  "yyinbuf" is set to point
 *         to the first character in the input string which is not a
 *         part of the token just recognized.
 *
 * Local contents:
 *
 *     struct wordtable *find_word (word) char *word;
 *         Returns a pointer to the entry in the "wordtable" array cor-
 *         responding to the string "word".  If "word" is not found, the
 *         returned value is NULL.
 */

/* ajs
 * ajs	Code added 850314 to allow NUM991231 and NUM99991231.
 * ajs	All added/changed lines contain "ajs" for easy searching.
 * ajs	*/

#ifdef RCSIDENT
static char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/datelex.c,v 1.1 84/09/01 15:01:14 wales UCLA $";
#endif RCSIDENT

#include <stdio.h>
#include "dateyacc.h"
#include "parsedate.h"

/* pointer to the input string */
char *yyinbuf;

/* "answer" structure */
struct parsedate yyans;

/* Binary-search word table.
 * Entries must be sorted in ascending order on "text" value, and the
 * total number of entries must be one less than a power of 2.  "Filler"
 * entries (with "token" values of -1) are inserted at the beginning and
 * end of the table to pad it as necessary.
 */
#define WORDTABLE_SIZE 127	/* MUST be one less than power of 2 */
#define MAX_WORD_LENGTH 20	/* used to weed out overly long words
				 * in "yylex".  Must be at least as long
				 * as the longest word in "wordtable",
				 * but may be longer.
				 */
struct wordtable
    {	char *text;
	int   token;
	int   lexval;
    } wordtable[WORDTABLE_SIZE] =
    {/* text            token           lexval */
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"",		-1,		0,
	"A",		STD_ZONE,	60,	/* UTC+1h */
	"ACSST",	DST_ZONE,	630,	/* Cent. Australia */
	"ACST",		STD_ZONE,	570,	/* Cent. Australia */
	"ADT",		DST_ZONE,	-180,	/* Atlantic (Canada) */
	"AESST",	DST_ZONE,	660,	/* E. Australia */
	"AEST",		STD_ZONE,	600,	/* E. Australia */
	"AM",		AMPM,		0,
	"APR",		MONTH_NAME,	4,
	"APRIL",	MONTH_NAME,	4,
	"AST",		STD_ZONE,	-240,	/* Atlantic (Canada) */
	"AT",		0,		0,	/* "at" (throwaway) */
	"AUG",		MONTH_NAME,	8,
	"AUGUST",	MONTH_NAME,	8,
	"AWSST",	DST_ZONE,	540,	/* W. Australia */
	"AWST",		STD_ZONE,	480,	/* W. Australia */
	"B",		STD_ZONE,	120,	/* UTC+2h */
	"BST",		DST_ZONE,	60,	/* Great Britain */
	"C",		STD_ZONE,	180,	/* UTC+3h */
	"CDT",		DST_ZONE,	-300,
	"CST",		STD_ZONE,	-360,
	"D",		STD_ZONE,	240,	/* UTC+4h */
	"DEC",		MONTH_NAME,	12,
	"DECEMBER",	MONTH_NAME,	12,
	"DST",		DST_SUFFIX,	0,
	"E",		STD_ZONE,	300,	/* UTC+5h */
	"EDT",		DST_ZONE,	-240,
	"EET",		STD_ZONE,	120,	/* Eastern Europe */
	"EETDST",	DST_ZONE,	180,	/* Eastern Europe */
	"EST",		STD_ZONE,	-300,
	"F",		STD_ZONE,	360,	/* UTC+6h */
	"FEB",		MONTH_NAME,	2,
	"FEBRUARY",	MONTH_NAME,	2,
	"FRI",		DAY_NAME,	5,
	"FRIDAY",	DAY_NAME,	5,
	"G",		STD_ZONE,	420,	/* UTC+7h */
	"GMT",		STD_ZONE,	0,
	"H",		STD_ZONE,	480,	/* UTC+8h */
	"HDT",		DST_ZONE,	-540,	/* Hawaii/Alaska */
	"HST",		STD_ZONE,	-600,	/* Hawaii/Alaska */
	"I",		STD_ZONE,	540,	/* UTC+9h */
	"IST",		STD_ZONE,	120,	/* Israel */
	"JAN",		MONTH_NAME,	1,
	"JANUARY",	MONTH_NAME,	1,
	"JUL",		MONTH_NAME,	7,
	"JULY",		MONTH_NAME,	7,
	"JUN",		MONTH_NAME,	6,
	"JUNE",		MONTH_NAME,	6,
	"K",		STD_ZONE,	600,	/* UTC+10h */
	"L",		STD_ZONE,	660,	/* UTC+11h */
	"M",		STD_ZONE,	720,	/* UTC+12h */
	"MAR",		MONTH_NAME,	3,
	"MARCH",	MONTH_NAME,	3,
	"MAY",		MONTH_NAME,	5,
	"MDT",		DST_ZONE,	-360,
	"MET",		STD_ZONE,	60,	/* Central Europe */
	"METDST",	DST_ZONE,	120,	/* Central Europe */
	"MON",		DAY_NAME,	1,
	"MONDAY",	DAY_NAME,	1,
	"MST",		STD_ZONE,	-420,
	"N",		STD_ZONE,	-60,	/* UTC-1h */
	"NDT",		DST_ZONE,	-150,	/* Nfld. (Canada) */
	"NOV",		MONTH_NAME,	11,
	"NOVEMBER",	MONTH_NAME,	11,
	"NST",		STD_ZONE,	-210,	/* Nfld. (Canada) */
	"O",		STD_ZONE,	-120,	/* UTC-2h */
	"OCT",		MONTH_NAME,	10,
	"OCTOBER",	MONTH_NAME,	10,
	"ON",		0,		0,	/* "on" (throwaway) */
	"P",		STD_ZONE,	-180,	/* UTC-3h */
	"PDT",		DST_ZONE,	-420,
	"PM",		AMPM,		12,
	"PST",		STD_ZONE,	-480,
	"Q",		STD_ZONE,	-240,	/* UTC-4h */
	"R",		STD_ZONE,	-300,	/* UTC-5h */
	"S",		STD_ZONE,	-360,	/* UTC-6h */
	"SAT",		DAY_NAME,	6,
	"SATURDAY",	DAY_NAME,	6,
	"SEP",		MONTH_NAME,	9,
	"SEPT",		MONTH_NAME,	9,
	"SEPTEMBER",	MONTH_NAME,	9,
	"SUN",		DAY_NAME,	0,
	"SUNDAY",	DAY_NAME,	0,
	"T",		STD_ZONE,	-420,	/* UTC-7h */
	"THU",		DAY_NAME,	4,
	"THUR",		DAY_NAME,	4,
	"THURS",	DAY_NAME,	4,
	"THURSDAY",	DAY_NAME,	4,
	"TUE",		DAY_NAME,	2,
	"TUES",		DAY_NAME,	2,
	"TUESDAY",	DAY_NAME,	2,
	"U",		STD_ZONE,	-480,	/* UTC-8h */
	"UT",		STD_ZONE,	0,
	"UTC",		STD_ZONE,	0,
	"V",		STD_ZONE,	-540,	/* UTC-9h */
	"W",		STD_ZONE,	-600,	/* UTC-10h */
	"WED",		DAY_NAME,	3,
	"WEDNESDAY",	DAY_NAME,	3,
	"WEDS",		DAY_NAME,	3,
	"WET",		STD_ZONE,	0,	/* Western Europe */
	"WETDST",	DST_ZONE,	60,	/* Western Europe */
	"X",		STD_ZONE,	-660,	/* UTC-11h */
	"Y",		STD_ZONE,	-720,	/* UTC-12h */
	"YDT",		DST_ZONE,	-480,	/* Yukon */
	"YST",		STD_ZONE,	-540,	/* Yukon */
	"Z",		STD_ZONE,	0,	/* UTC */
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
	"\177",		-1,		0,
    };
struct wordtable *find_word();

/* int yylex ()
 *     Return the next token for the YACC parser.
 */
int
yylex ()
{   static char buffer[MAX_WORD_LENGTH+1];
    register char *c, *d;
    register struct wordtable *wt;
    register int num, ndgts;

  restart:
    /* We will return here if an invalid input token is detected. */
    c = buffer; d = yyinbuf;

    /* Skip over blanks, tabs, commas, and parentheses. */
    do { *c = *d++; }
	while (*c == ' ' || *c == '\t' || *c == ','
	       || *c == '(' || *c == ')');

    /* A zero (null) byte signals the end of the input. */
    if (*c == 0)
    {	yyinbuf = --d;		/* stay put on the null */
	return 0;
    }

    /* Process a word (looking it up in "wordtable"). */
    if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z'))
    {	if (*c >= 'a' && *c <= 'z') *c += 'A' - 'a';
	while (c < buffer + MAX_WORD_LENGTH
	       && ((*d >= 'A' && *d <= 'Z')
		   || (*d >= 'a' && *d <= 'z')))
	{   *++c = *d++;
	    if (*c >= 'a' && *c <= 'z') *c += 'A' - 'a';
	}
	if ((*d >= 'A' && *d <= 'Z') || (*d >= 'a' && *d <= 'z'))
	{   /* Word is too long (over MAX_WORD_LENGTH characters). */
	    do { d++; } while ((*d >= 'A' && *d <= 'Z')
			       || (*d >= 'a' && *d <= 'z'));
	    yyinbuf = d;
	    goto error;
	}
	*++c = 0; yyinbuf = d;
	if ((wt = find_word (buffer)) == NULL) goto error;
	if (wt->token == 0) goto restart;	/* ignore this word */
	yylval.IntVal = wt->lexval;
	return wt->token;
    }

    /* Process a number. */
    if (*c >= '0' && *c <= '9')
    {	num = *c - '0'; ndgts = 1;
	for (ndgts = 1; ndgts < 8 && *d >= '0' && *d <= '9'; ndgts++)  /* ajs */
	    num = 10*num + (*d++ - '0');
	if (*d >= '0' && *d <= '9')
	{   /* Number is too long (over 8 digits). */		/* ajs */
	    do { d++; } while (*d >= '0' && *d <= '9');
	    yyinbuf = d;
	    goto error;
	}
	yyinbuf = d;
	yylval.IntVal = num;
	switch (ndgts)
	{   case 1:  return NUM9;
	    case 2:  if (num <= 23) return NUM23;
		     if (num <= 59) return NUM59;
		     /*otherwise*/  return NUM99;
	    case 3:
	    case 4:  if (num/100 <= 23 && num%100 <= 59) return NUM2359;
		     /*otherwise*/                       return NUM9999;
	    case 5:
	    case 6:  if (num/10000 <= 23
			 && (num%10000)/100 <= 59
			 && num%100 <= 59)
			 return NUM235959;
		     if ((((num % 10000) / 100) <= 12)	/* ajs */
		      &&  ((num % 100) <= 31))		/* ajs */
			 return NUM991231;		/* ajs */
		     goto error;
	    case 8:  if ((((num % 10000) / 100) <= 12)	/* ajs */
		      &&  ((num % 100) <= 31))		/* ajs */
			 return NUM99991231;		/* ajs */
		     goto error;			/* ajs */
	    default: goto error;
    }	}

    /* Pass back the following delimiter tokens verbatim.. */
    if (*c == '-' || *c == '+' || *c == '/' || *c == ':' || *c == '.')
    {	yyinbuf = d;
	return *c;
    }

  error:
    /* An unidentified character was found in the input. */
    yyinbuf = d;
    if (yyans.error == NULL) yyans.error = yyinbuf;
    goto restart;
}

/* struct wordtable *find_word (word) char *word;
 *     Look up a word in the "wordtable" array via a binary search.
 */
static
struct wordtable *
find_word (word)
    register char *word;
{   register int low, mid, high;
    register int comparison;

    low = -1;
    high = WORDTABLE_SIZE;
    while (low+1 < high)
    {	mid = (low + high) / 2;
	comparison = strcmp (wordtable[mid].text, word);
	if (comparison == 0) return wordtable+mid;
	if (comparison > 0)  high = mid;
	else                 low = mid;
    }
    return NULL;
}
@EOF
if test "`wc -lwc <parsedate/datelex.c`" != '    324   1582   9559'
then
	echo ERROR: wc results of parsedate/datelex.c are `wc -lwc <parsedate/datelex.c` should be     324   1582   9559
fi

chmod 444 parsedate/datelex.c

echo x - parsedate/dateyacc.y
cat >parsedate/dateyacc.y <<'@EOF'
/*$Log:	/s/uclasrc/mail/date/RCS/dateyacc.y,v $
 * Revision 1.1  84/09/01  15:01:22  wales
 * Initial revision
 * 
 * Copyright (c) 1984 by Richard B. Wales
 *
 * Purpose:
 *
 *     YACC parser for "parsedate" routine.
 *
 * Usage:
 *
 *     Called as needed by the "parsedate" routine in "parsedate.c".
 *     Not intended to be called from any other routine.
 *
 * Notes:
 *
 * Global contents:
 *
 *     int yyparse ()
 *         Parses the date string pointed to by the global variable
 *         "yyinbuf".  Sets the appropriate fields in the global data
 *         structure "yyans".  The returned value is 1 if there was a
 *         syntax error, 0 if there was no error.
 *
 * Local contents:
 *
 *     None.
 */

/* ajs
 * ajs	Code added on 850314 to allow	goal	  := year.date '.' time
 * ajs				and	year.date := [CC]YYMMDD (YY > 23)
 * ajs	All added lines contain "ajs" for easy searching.
 * ajs	*/

%{
#ifdef RCSIDENT
static char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/dateyacc.y,v 1.1 84/09/01 15:01:22 wales UCLA $";
#endif RCSIDENT

#include <stdio.h>
#include "parsedate.h"
struct parsedate yyans;

/* No error routine is needed here. */
#define yyerror(s)
%}

%union {
    int IntVal;
}

%token	DAY_NAME
%token	MONTH_NAME
%token	NUM9 NUM23 NUM59 NUM99 NUM2359 NUM9999 NUM235959
%token	NUM991231 NUM99991231				/* ajs */
%token	AMPM
%token	STD_ZONE DST_ZONE DST_SUFFIX

%type	<IntVal>	DAY_NAME
%type	<IntVal>	MONTH_NAME
%type	<IntVal>	NUM9 NUM23 NUM59 NUM99 NUM2359 NUM9999 NUM235959
%type	<IntVal>	NUM991231 NUM99991231		/* ajs */
%type	<IntVal>	AMPM
%type	<IntVal>	STD_ZONE DST_ZONE
%type	<IntVal>	num59 num zone.offset

%start	goal
%%

num59:
    NUM23
  | NUM59

num:
    NUM9
  | num59

goal:
    date
  | date dayname
  | date dayname time
  | date dayname time year
  | date dayname year
  | date dayname year time
  | date time
  | date time dayname
  | date time dayname year
  | date time year
  | date time year dayname
  | date.year
  | date.year dayname
  | date.year dayname time
  | date.year time
  | date.year time dayname
  | dayname date
  | dayname date time
  | dayname date time year
  | dayname date.year
  | dayname date.year time
  | dayname time date
  | dayname time date.year
  | dayname time year.date
  | dayname year.date
  | dayname year.date time
  | dayname year time date
  | time
  | time date
  | time date dayname
  | time date dayname year
  | time date.year
  | time date.year dayname
  | time dayname date
  | time dayname date.year
  | time dayname year.date
  | time year.date
  | time year.date dayname
  | time year dayname date
  | year.date
  | year.date dayname
  | year.date dayname time
  | year.date time
  | year.date time dayname
  | year dayname date
  | year dayname date time
  | year dayname time date
  | year time date
  | year time date dayname
  | year time dayname date
  | NUM2359
	{ yyans.hour   = $1 / 100;
	  yyans.minute = $1 % 100;
	  yyans.second = -1;		/* unspecified */
	}
  | dayname
  | yymmdd '.' time2359			/* ajs */
  | yymmdd '.' time			/* ajs */
  | yymmdd '.' time dayname		/* ajs */
  | error
	{ extern char *yyinbuf;
	  if (yyans.error == NULL) yyans.error = yyinbuf;
	}

dayname:
    DAY_NAME
	{ yyans.c_weekday = $1; }
  | DAY_NAME '.'
	{ yyans.c_weekday = $1; }

date.year:
    date year
  | hyphen.date '-' year
  | slash.date '/' year

year.date:
    year date
  /* | year '-' hyphen.date	(leads to parser conflict) */
  | year '/' slash.date
  | yymmdd					/* ajs */
						/* ajs */
yymmdd:						/* ajs */
    NUM991231					/* ajs */
	{ yyans.year  = ($1 / 10000) + 1900;	/* ajs */
	  yyans.month = ($1 % 10000) / 100;	/* ajs */
	  yyans.day   = ($1 % 100);		/* ajs */
	}					/* ajs */
/*| NUM235959	(leads to parser conflict) */	/* ajs */
  | NUM99991231					/* ajs */
	{ yyans.year  = ($1 / 10000);		/* ajs */
	  yyans.month = ($1 % 10000) / 100;	/* ajs */
	  yyans.day   = ($1 % 100);		/* ajs */
	}					/* ajs */

date:
    num month.name
	{ yyans.day = $1; }
  | month.name num
	{ yyans.day = $2; }
  | num num
	{ yyans.month = $1; yyans.day = $2; }

hyphen.date:
    num '-' month.name
	{ yyans.day = $1; }
  | month.name '-' num
	{ yyans.day = $3; }
  | num '-' num
	{ yyans.month = $1; yyans.day = $3; }

slash.date:
    num '/' month.name
	{ yyans.day = $1; }
  | month.name '/' num
	{ yyans.day = $3; }
  | num '/' num
	{ yyans.month = $1; yyans.day = $3; }

year:
    NUM99		/* precludes two-digit date before 1960 */
	{ yyans.year = 1900 + $1; }
  | NUM2359
	{ yyans.year = $1; }
  | NUM9999
	{ yyans.year = $1; }

month.name:
    MONTH_NAME
	{ yyans.month = $1; }
  | MONTH_NAME '.'
	{ yyans.month = $1; }

time:
    hour.alone
  | hour am.pm
  | hour zone
  | hour am.pm zone

hour:
    NUM2359
	{ yyans.hour   = $1 / 100;
	  yyans.minute = $1 % 100;
	  yyans.second = -1;		/* unspecified */
	}
  | hour.alone

hour.alone:
    NUM9 ':' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = -1;		/* unspecified */
	}
  | NUM9 '.' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = -1;		/* unspecified */
	}
  | NUM9 ':' num59 ':' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = $5;
	}
  | NUM9 '.' num59 '.' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = $5;
	}
  | NUM23 ':' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = -1;		/* unspecified */
	}
  | NUM23 '.' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = -1;		/* unspecified */
	}
  | NUM23 ':' num59 ':' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = $5;
	}
  | NUM23 '.' num59 '.' num59
	{ yyans.hour   = $1;
	  yyans.minute = $3;
	  yyans.second = $5;
	}
  | NUM2359 ':' num59
	{ yyans.hour   = $1 / 100;
	  yyans.minute = $1 % 100;
	  yyans.second = $3;
	}
  | NUM2359 '.' num59
	{ yyans.hour   = $1 / 100;
	  yyans.minute = $1 % 100;
	  yyans.second = $3;
	}
  | NUM235959
	{ yyans.hour   = $1 / 10000;
	  yyans.minute = ($1 % 10000) / 100;
	  yyans.second = $1 % 100;
	}

am.pm:
    AMPM
	{ if (yyans.hour < 1 || yyans.hour > 12)
	    yyans.hour = -1;		/* invalid */
	  else
	  { if (yyans.hour == 12) yyans.hour = 0;
	    yyans.hour += $1;		/* 0 for AM, 12 for PM */
	} }

zone:
    STD_ZONE
	{ yyans.zone = $1; yyans.dst = 0; }
  | STD_ZONE DST_SUFFIX
	{ yyans.zone = $1 + 60; yyans.dst = 1; }
  | '-' STD_ZONE
	{ yyans.zone = $2; yyans.dst = 0; }
  | '-' STD_ZONE DST_SUFFIX
	{ yyans.zone = $2 + 60; yyans.dst = 1; }
  | DST_ZONE
	{ yyans.zone = $1; yyans.dst = 1; }
  | '-' DST_ZONE
	{ yyans.zone = $2; yyans.dst = 1; }
  | '+' zone.offset
	{ yyans.zone = $2; yyans.dst = 0; }
  | '-' '+' zone.offset
	{ yyans.zone = $3; yyans.dst = 0; }
  | '-' zone.offset
	{ yyans.zone = - $2; yyans.dst = 0; }
  | '-' '-' zone.offset
	{ yyans.zone = - $3; yyans.dst = 0; }

zone.offset:
    NUM9
	{ $$ = 60 * $1; }
  | NUM9 ':' num59
	{ $$ = 60 * $1 + $3; }
  | NUM9 '.' num59
	{ $$ = 60 * $1 + $3; }
  | NUM23
	{ $$ = 60 * $1; }
  | NUM23 ':' num59
	{ $$ = 60 * $1 + $3; }
  | NUM23 '.' num59
	{ $$ = 60 * $1 + $3; }
  | NUM2359
	{ $$ = 60 * ($1 / 100) | ($1 % 100); }

time2359:				/* ajs */
    NUM2359				/* ajs */
	{ yyans.hour   = $1 / 100;	/* ajs */
	  yyans.minute = $1 % 100;	/* ajs */
	  yyans.second = -1;		/* ajs */
	}				/* ajs */

%%
@EOF
if test "`wc -lwc <parsedate/dateyacc.y`" != '    338   1293   7260'
then
	echo ERROR: wc results of parsedate/dateyacc.y are `wc -lwc <parsedate/dateyacc.y` should be     338   1293   7260
fi

chmod 444 parsedate/dateyacc.y

echo x - parsedate/parsedate.c
cat >parsedate/parsedate.c <<'@EOF'
/*LINTLIBRARY*/

/*$Log:	/s/uclasrc/mail/date/RCS/parsedate.c,v $
 * Revision 1.1  84/09/01  15:01:30  wales
 * Initial revision
 * 
 * Copyright (c) 1984 by Richard B. Wales
 *
 * Purpose:
 *
 *     Manipulate character strings representing dates.
 *
 * Usage:
 *
 *     #include <parsedate.h>
 *
 *     char date;
 *     struct parsedate *pd;
 *
 *     pd = parsedate (date);
 *
 *     compute_unixtime (pd);
 *
 *     break_down_unixtime (pd);
 *
 *     date = mail_date_string (pd);
 *
 *     date = uucp_date_string (pd);
 *
 * Notes:
 *
 *     The returned value from "parsedate", "mail_date_string", or
 *     "uucp_date_string" points to static data whose contents are
 *     overwritten by the next call to the same routine.
 *
 *     "compute_unixtime" is implicitly called by "parsedate".
 *
 * Global contents:
 *
 *     struct parsedate *parsedate (date) char *date;
 *         Parse a character string representing a date and time into
 *         individual values in a "struct parsedate" data structure.
 *    
 *     compute_unixtime (pd) struct parsedate *pd;
 *         Given a mostly filled-in "struct parsedate", compute the day
 *         of the week and the internal UNIX representation of the date.
 *    
 *     break_down_unixtime (pd) struct parsedate *pd;
 *         Compute the date and time corresponding to the "unixtime" and
 *         "zone" values in a "struct parsedate".
 *    
 *     char *mail_date_string (pd) struct parsedate *pd;
 *         Generate a character string representing a date and time in
 *         the RFC822 (ARPANET mail standard) format.
 *    
 *     char *uucp_date_string (pd) struct parsedate *pd;
 *         Generate a character string representing a date and time in
 *         the UUCP mail format.
 *
 * Local contents:
 *
 *     None.
 */

#include <stdio.h>
#include "parsedate.h"

#ifdef RCSIDENT
static char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/parsedate.c,v 1.1 84/09/01 15:01:30 wales UCLA $";
static char rcs_parsedate_hdr[] = RCS_PARSEDATE_HDR;
#endif RCSIDENT

/* Number of seconds in various time intervals. */
#define SEC_PER_MIN  60
#define SEC_PER_HOUR (60*SEC_PER_MIN)
#define SEC_PER_DAY  (24*SEC_PER_HOUR)
#define SEC_PER_YEAR (365*SEC_PER_DAY)

/* Number of days in each month. */
static int monthsize[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

/* Three-letter abbreviations of month and day names. */
static char monthname[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
static char dayname[]   = "SunMonTueWedThuFriSat";

/* struct parsedate *parsedate (date) char *date;
 *     Analyze a character string representing a date and time.  The
 *     returned value points to a data structure with the desired
 *     information.  (NOTE:  The returned value points to static data
 *     whose contents are overwritten by each call.)
 */
struct parsedate *
parsedate (date)
    register char *date;
{   register char *c;
    register int year_save;
    extern struct parsedate yyans;
    extern char *yyinbuf;
    extern char *sprintf();

    /* Initialize the returned-value structure. */
    yyans.unixtime  = -1;
    yyans.year      = -1;
    yyans.month     = -1;
    yyans.day       = -1;
    yyans.hour      = -1;
    yyans.minute    = -1;
    yyans.second    = -1;
    yyans.zone      = -1;
    yyans.dst       = -1;
    yyans.weekday   = -1;
    yyans.c_weekday = -1;
    yyans.error     =  NULL;

    /* Parse the argument string. */
    yyinbuf = date;
    if (yyparse () != 0 && yyans.error == NULL) yyans.error = yyinbuf;

    /* Validate the day of the month, compute/validate the day of the
     * week, and compute the internal UNIX form of the time.  See if
     * "compute_unixtime" found fault with the year or the day of the
     * month.  (Note that we have to remember the original "year" value
     * because it might legitimately have been -1 to begin with.)
     */
    year_save = yyans.year; compute_unixtime (&yyans);
    if (yyans.error == NULL
	&& (yyans.year != year_save
	    || (yyans.month > 0 && yyans.day < 0)
	    || (yyans.month < 0 && yyans.day > 0)))
	yyans.error = yyinbuf;

    return &yyans;
}

/* compute_unixtime (pd) struct parsedate *pd;
 *     Given a mostly filled-in "struct parsedate", compute the day of
 *     the week and the internal UNIX representation of the date.
 *
 *     A year before 1600 will be rejected and replaced with -1.  A
 *     date from 1600 on which falls outside the range representable in
 *     internal UNIX form will still have the correct day of the week
 *     computed.
 *
 *     The day of the week is always computed on the assumption that the
 *     Gregorian calendar is in use.  Days of the week for dates in the
 *     far future may turn out to be incorrect if any changes are made
 *     to the calendar between now and then.
 */
compute_unixtime (pd)
    register struct parsedate *pd;
{   register int weekday, n, l, a;

    /* Validate the year. */
    if (pd->year >= 0 && pd->year < 1600) pd->year = -1;

    /* Validate the day of the month.  Also calculate the number of days
     * in February (even if this is not February, we will need the num-
     * ber of days in February later on when computing the UNIX time).
     */
    if (pd->month > 0)
    {	if      (pd->year < 0)        monthsize[2] = 29;
	else if (pd->year %   4 != 0) monthsize[2] = 28;
	else if (pd->year % 100 != 0) monthsize[2] = 29;
	else if (pd->year % 400 != 0) monthsize[2] = 28;
	else                          monthsize[2] = 29;
	if (pd->day <= 0 || pd->day > monthsize[pd->month])
	    pd->day = -1;
    }

    /* Compute the day of the week.  The next several lines constitute a
     * perpetual-calendar formula.  Note, of course, that the "claimed"
     * day of the week (pd->c_weekday) is ignored here.
     */
    if (pd->year > 0 && pd->month > 0 && pd->day > 0)
    {	if (pd->month >= 3) n = pd->year / 100,
			    l = pd->year % 100;
	else                n = (pd->year-1) / 100,
			    l = (pd->year-1) % 100;
	a = (26 * ((pd->month+9)%12 + 1) - 2) / 10;
	weekday = (a+(l>>2)+(n>>2)+l-(n+n)+pd->day);
	while (weekday < 0) weekday += 7;
	pd->weekday = weekday % 7;
    }

    /* Figure out the internal UNIX form of the date. */
    if (pd->year >= 1969 && pd->year <= 2038
	&& pd->month > 0 && pd->day > 0
	&& pd->hour >= 0 && pd->minute >= 0
	&& pd->zone != -1 && pd->zone > -1440 && pd->zone < 1440)
    {	pd->unixtime =
	      SEC_PER_YEAR * (pd->year - 1970)
	    + SEC_PER_DAY  * ((pd->year - 1969) / 4)
	    /* month is taken care of later */
	    + SEC_PER_DAY  * (pd->day - 1)
	    + SEC_PER_HOUR * pd->hour
	    + SEC_PER_MIN  * pd->minute
	    /* seconds are taken care of later */
	    - SEC_PER_MIN  * pd->zone;
	if (pd->second >= 0)
	    pd->unixtime += pd->second;
	for (n = pd->month - 1; n > 0; n--)
	    pd->unixtime += SEC_PER_DAY * monthsize[n];
	if (pd->unixtime < 0) pd->unixtime = -1;
    }
    else pd->unixtime = -1;
}

/* break_down_unixtime (pd) struct parsedate *pd;
 *     Given the "unixtime" and "zone" fields of a "struct parsedate",
 *     compute the values of the "year", "month", "day", "hour", "min-
 *     ute", "second", and "weekday" fields.  The "dst" and "error"
 *     fields of the structure are not used or modified.
 */
break_down_unixtime (pd)
    register struct parsedate *pd;
{   register unsigned long timevalue;
    register int m, n;

    /* Validate the "unixtime" and "zone" fields. */
    if (pd->unixtime < 0
	|| pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
    {	/* Sorry, can't do it. */
	pd->year = -1; pd->month = -1; pd->day = -1;
	pd->hour = -1; pd->minute = -1; pd->second = -1;
	pd->weekday = -1;
	return;
    }

    /* Even though "pd->unixtime" must be non-negative, and thus cannot
     * indicate a time earlier than 1970, a negative "pd->zone" could
     * cause the local date to be Wednesday, 31 December 1969.  Such a
     * date requires special handling.
     *
     * A local date earlier than 31 December 1969 is impossible because
     * "pd->zone" must represent a time-zone shift of less than a day.
     */
    if (pd->zone < 0 && pd->unixtime + SEC_PER_MIN * pd->zone < 0)
    {	pd->year = 1969; pd->month = 12; pd->day = 31;
	pd->weekday = 3;    /* Wednesday */
	timevalue = pd->unixtime + SEC_PER_MIN * pd->zone + SEC_PER_DAY;
	/* Note:  0 <= timevalue < SEC_PER_DAY */
	pd->hour = timevalue / SEC_PER_HOUR;
	pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
	pd->second = timevalue % SEC_PER_MIN;
	return;
    }

    /* Handle the general case (local time is 1970 or later). */
    timevalue = pd->unixtime + SEC_PER_MIN * pd->zone;

    /* day of the week (1 January 1970 was a Thursday) . . . */
    pd->weekday = (timevalue/SEC_PER_DAY + 4 /* Thursday */) % 7;

    /* year (note that the only possible century year here is 2000,
     * a leap year -- hence no special tests for century years are
     * needed) . . .
     */
    for (m = 1970; ; m++)
    {	n = (m%4==0) ? SEC_PER_YEAR+SEC_PER_DAY : SEC_PER_YEAR;
	if (n > timevalue) break;
	timevalue -= n;
    }
    pd->year = m;
    monthsize[2] = (m%4==0) ? 29 : 28;

    /* month . . . */
    for (m = 1; ; m++)
    {	n = SEC_PER_DAY * monthsize[m];
	if (n > timevalue) break;
	timevalue -= n;
    }
    pd->month = m;

    /* day, hour, minute, and second . . . */
    pd->day    = (timevalue / SEC_PER_DAY) + 1;
    pd->hour   = (timevalue % SEC_PER_DAY) / SEC_PER_HOUR;
    pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
    pd->second = timevalue % SEC_PER_MIN;
}

/* char *mail_date_string (pd) struct parsedate *pd;
 *     Generate a character string representing a date and time in the
 *     RFC822 (ARPANET mail standard) format.  A value of NULL is re-
 *     turned if "pd" does not contain all necessary data fields.
 *     (NOTE:  The returned value points to static data whose contents
 *     are overwritten by each call.)
 */
char *
mail_date_string (pd)
    register struct parsedate *pd;
{   register char *c;
    static char answer[50];

    /* Check the day of the month and compute the day of the week. */
    compute_unixtime (pd);

    /* Make sure all required fields are present. */
    if (pd->year < 0 || pd->month < 0 || pd->day < 0
	|| pd->hour < 0 || pd->minute < 0
	|| pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
	return NULL;		/* impossible to generate string */

    /* Generate the answer string. */
    sprintf (answer,
	     "%.3s, %d %.3s %d %02d:%02d",
	     dayname + 3*pd->weekday,
	     pd->day, monthname + 3*(pd->month-1),
	     (pd->year >= 1960 && pd->year <= 1999)
		 ? pd->year - 1900 : pd->year,
	     pd->hour, pd->minute);
    c = answer + strlen (answer);
    if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
    *c++ = ' ';
    switch (pd->zone)
    {	/* NOTE:  Only zone abbreviations in RFC822 are used here. */
	case    0: strcpy (c, pd->dst ? "+0000" : "GMT");   break;
	case -240: strcpy (c, pd->dst ? "EDT"   : "-0400"); break;
	case -300: strcpy (c, pd->dst ? "CDT"   : "EST");   break;
	case -360: strcpy (c, pd->dst ? "MDT"   : "CST");   break;
	case -420: strcpy (c, pd->dst ? "PDT"   : "MST");   break;
	case -480: strcpy (c, pd->dst ? "-0800" : "PST");   break;
	default:
	    if (pd->zone >= 0)
		 sprintf (c, "+%02d%02d",  pd->zone/60,  pd->zone%60);
	    else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
    }

    return answer;
}

/* char *uucp_date_string (pd) struct parsedate *pd;
 *     Generate a character string representing a date and time in the
 *     UUCP mail format.  A value of NULL is returned if "pd" does not
 *     contain all necessary data fields.  (NOTE:  The returned value
 *     points to static data whose contents are overwritten by each
 *     call.)
 */
char *
uucp_date_string (pd)
    register struct parsedate *pd;
{   register char *c;
    static char answer[50];

    /* Check the day of the month and compute the day of the week. */
    compute_unixtime (pd);

    /* Make sure all required fields are present. */
    if (pd->year < 0 || pd->month < 0 || pd->day < 0
	|| pd->hour < 0 || pd->minute < 0
	|| pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
	return NULL;		/* impossible to generate string */

    /* Generate the answer string. */
    sprintf (answer,
	     "%.3s %.3s %d %02d:%02d",
	     dayname + 3*pd->weekday,
	     monthname + 3*(pd->month-1), pd->day,
	     pd->hour, pd->minute);
    c = answer + strlen (answer);
    if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
    switch (pd->zone)
    {	/* NOTE:  Only zone abbreviations in RFC822 are used here. */
	case    0: strcpy (c, pd->dst ? "+0000" : "-GMT");   break;
	case -240: strcpy (c, pd->dst ? "-EDT"  : "-0400"); break;
	case -300: strcpy (c, pd->dst ? "-CDT"  : "-EST");   break;
	case -360: strcpy (c, pd->dst ? "-MDT"  : "-CST");   break;
	case -420: strcpy (c, pd->dst ? "-PDT"  : "-MST");   break;
	case -480: strcpy (c, pd->dst ? "-0800" : "-PST");   break;
	default:
	    if (pd->zone >= 0)
		 sprintf (c, "+%02d%02d",  pd->zone/60,  pd->zone%60);
	    else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
    }
    c = answer + strlen (answer);
    sprintf (c, " %d", pd->year);

    return answer;
}
@EOF
if test "`wc -lwc <parsedate/parsedate.c`" != '    378   2040  13198'
then
	echo ERROR: wc results of parsedate/parsedate.c are `wc -lwc <parsedate/parsedate.c` should be     378   2040  13198
fi

chmod 444 parsedate/parsedate.c

chmod 750 parsedate

exit 0