Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!wasatch!cs.utexas.edu!uunet!allbery
From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
Newsgroups: comp.sources.misc
Subject: v08i011: moon(1) -- another phase-of-the-moon-program
Message-ID: <64306@uunet.UU.NET>
Date: 20 Aug 89 00:53:40 GMT
Sender: allbery@uunet.UU.NET
Reply-To: ajs@hpfcajs.hp.com (Alan Silverstein)
Lines: 4859
Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
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.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
#include
/*********************************************************************
* 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 = "";
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 ": " 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/stddev <<'@EOF'
#! /bin/ksh
# Compute standard deviation.
# Usage: