home *** CD-ROM | disk | FTP | other *** search
- From decwrl!pyramid!lll-winken!uunet!allbery Sun Aug 20 15:36:57 PDT 1989
- Article 1042 of comp.sources.misc:
- Path: decwrl!pyramid!lll-winken!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: 4860
- 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.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
-
-
-