home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / compsrcs / misc / volume08 / moon < prev    next >
Encoding:
Internet Message Format  |  1991-08-27  |  138.8 KB

  1. From decwrl!pyramid!lll-winken!uunet!allbery Sun Aug 20 15:36:57 PDT 1989
  2. Article 1042 of comp.sources.misc:
  3. Path: decwrl!pyramid!lll-winken!uunet!allbery
  4. From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
  5. Newsgroups: comp.sources.misc
  6. Subject: v08i011: moon(1) -- another phase-of-the-moon-program
  7. Message-ID: <64306@uunet.UU.NET>
  8. Date: 20 Aug 89 00:53:40 GMT
  9. Sender: allbery@uunet.UU.NET
  10. Reply-To: ajs@hpfcajs.hp.com (Alan Silverstein)
  11. Lines: 4860
  12. Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
  13.  
  14. Posting-number: Volume 8, Issue 11
  15. Submitted-by: ajs@hpfcajs.hp.com (Alan Silverstein)
  16. Archive-name: moon
  17.  
  18. [Alan asked me if it was an imposition to submit this.  Oy veh.  ++bsa]
  19.  
  20. OK, here you go.  Note that the sharchive includes other stuff you
  21. need to build moon.c (parsedate), and stuff you might want to run
  22. the accuracy script (anod, stddev).  I'll let you decide if it's all
  23. worth shipping or not.
  24.  
  25. Major features:  clean source code; lots of options; includes drawing
  26. an ASCII picture (also can print phase as a number for feeding to a true
  27. graphics program); very accurate; manual entry; test scripts.
  28.  
  29. Alan Silverstein
  30.  
  31.  
  32. # This is a shell archive.  Remove anything before this line,
  33. # then unpack it by saving it in a file and typing "sh file".
  34. #
  35. # Wrapped by ajs at hpfcajs on Thu Aug 10 17:44:21 1989
  36. #
  37. # This archive contains:
  38. #    misc        moon        parsedate    
  39. #
  40. # Error checking via wc(1) will be performed.
  41.  
  42. echo mkdir - misc
  43. mkdir misc
  44.  
  45. echo x - misc/anod.1
  46. sed 's/^@//' >misc/anod.1 <<'@EOF'
  47. .TH ANOD 1 "Unsupported Utility, Version 4"
  48. .SH NAME
  49. anod \- add number of days to dates/values
  50. .SH SYNOPSIS
  51. .B anod
  52. [
  53. .B \-ipwWmM
  54. ] [
  55. .BI \-c \0date
  56. ] [
  57. .B \-C
  58. ] [
  59. .I files...
  60. ]
  61. .ad b
  62. .SH DESCRIPTION
  63. This program adds a ``number of days'' (or weeks or months) column to data
  64. whose first field is a date in the form ``YYMM[DD]''
  65. (or optionally including a time).
  66. It is useful for quick interactive inquiries of amounts of time between dates,
  67. accurately plotting data points gathered on non-periodic dates,
  68. and converting occurrence dates to interval sizes (e.g. for scattergramming).
  69. .PP
  70. The program reads the concatenation of named
  71. .I files
  72. (use ``\-'' for standard input),
  73. or reads standard input if no filenames are given,
  74. and writes results to standard output.
  75. .PP
  76. Input lines which are comments (first field starts with ``#'') or blank lines
  77. are passed through to standard output unaltered.
  78. The first field of each data line must be a four or six digit date
  79. (YYMM or YYMMDD) in the 20th century.
  80. If no day of month is present, ``01'' is assumed.
  81. .PP
  82. Output lines have one blank and a number appended after the first (date) field:
  83. the number of days before or after the date given on the first data line
  84. (always zero for the first data line).
  85. .PP
  86. Options are:
  87. .TP
  88. .B \-i
  89. Intervals:  On each data line,
  90. add (insert) the delta from the date on the previous data line,
  91. not from the first date.
  92. .TP
  93. .B \-p
  94. Higher precision:
  95. Each data line can contain a time after the date, in this form:
  96. YYMM[DD[.[HH[MM[SS]]]]].
  97. If any part of the time (hours, minutes, or seconds) is missing,
  98. ``00'' is assumed.
  99. This option is allowed when adding days or weeks only.
  100. When adding days, fractional days are printed when appropriate.
  101. .TP
  102. .B \-w
  103. Add the number of whole (seven-day) weeks, not days, since the first date.
  104. Fractions of weeks are ignored.
  105. .TP
  106. .B \-W
  107. Like
  108. .BR \-w ,
  109. but add exact (fractional) weeks, not whole weeks.
  110. .TP
  111. .B \-m
  112. Add the number of whole calendar months, not days, since the first date.
  113. A month is the amount of time between successive same days-of-month,
  114. e.g. between 850812 and 850912 is one month.
  115. Fractions of months are ignored.
  116. .TP
  117. .B \-M
  118. Like
  119. .BR \-m ,
  120. but add approximate (fractional) months, not whole months.
  121. The fractional part is the number of excess days divided by the average days
  122. per month (365.25 / 12).
  123. .TP
  124. .BI \-c \0date
  125. Convert the matching date,
  126. if it appears in the input data,
  127. to a dot (``.'') in output.
  128. In other words, emit a dot instead of repeating the date field as given,
  129. with the rest of the line the same (including the added number of days).
  130. .IP
  131. This is intended for use with
  132. .IR dataplot (1),
  133. which understands a dot to mean ``don't plot this label''.
  134. Give as many
  135. .B -c
  136. options as you need,
  137. one per date to convert.
  138. If
  139. .I date
  140. is of the form YYMM,
  141. DD is assumed to be 01,
  142. e.g. 8605 matches 860501.
  143. .TP
  144. .B \-C
  145. Convert all input dates to ``.'' in output.
  146. Giving any
  147. .B \-c
  148. options is redundant.
  149. The same results could be gained by piping the data through
  150. .IR sed (1)
  151. or
  152. .IR awk (1),
  153. but this is more convenient.
  154. .PP
  155. You can only give one of
  156. .BR \-w ,
  157. .BR \-W ,
  158. .BR \-m ,
  159. or
  160. .BR \-M .
  161. .PP
  162. The program doesn't worry about aligning its output,
  163. which normally is passed directly to another program, e.g.
  164. .IR dataplot (1).
  165. .SH EXAMPLES
  166. .TP
  167. anod data1 > data1.plot
  168. Add day intervals since the first date to dates in file ``data1''
  169. and save results in ``data1.plot''.
  170. .TP
  171. anod -im -c861214 -c 8701 < t
  172. Add number-of-months column to data in file ``t'',
  173. where each number of months is incremental from the previous date,
  174. and convert dates 861214 and 870101 to ``.'' in the output.
  175. .SH SEE ALSO
  176. bars(1), dataplot(1)
  177. .SH LIMITATIONS
  178. Dates input are restricted to format YYMM[DD].
  179. However, most ``rational'' date formats can be converted to/from this style
  180. with little trouble.
  181. .SH DIAGNOSTICS
  182. Prints a message standard error and exits
  183. if invoked wrong or it detects invalid-format data.
  184. @EOF
  185. if test "`wc -lwc <misc/anod.1`" != '    137    703   3917'
  186. then
  187.     echo ERROR: wc results of misc/anod.1 are `wc -lwc <misc/anod.1` should be     137    703   3917
  188. fi
  189.  
  190. chmod 444 misc/anod.1
  191.  
  192. echo x - misc/anod.c
  193. cat >misc/anod.c <<'@EOF'
  194. static char *version = "@(#) 4 880804";
  195. /*
  196.  * Add number of days (Julian days from start date) to a list of dates.
  197.  * See the manual entry (anod(1)) for details.
  198.  *
  199.  * Compile with -DDEBUG to test JulianDate().
  200.  */
  201.  
  202. #include <stdio.h>
  203. #include <string.h>
  204.  
  205.  
  206. /*********************************************************************
  207.  * MISCELLANEOUS GLOBAL VALUES:
  208.  */
  209.  
  210. #define    PROC                /* null; easy to find procs */
  211. #define    FALSE    0
  212. #define    TRUE    1
  213. #define    CHNULL    ('\0')
  214. #define    CPNULL    ((char *) NULL)
  215. #define    REG    register
  216.  
  217. char *usage[] = {
  218.     "usage: %s [-iwWmM] [-c date] [-C] [files...]",
  219.     "-i intervals: show each delta from previous date, not from first date",
  220.     "-p higher precision: input has times of days; add fractional days",
  221.     "-w add number of whole (seven-day) weeks, not days, since the first date",
  222.     "-W like -w, but add exact (fractional) weeks",
  223.     "-m add number of whole calendar months, not days, since the first date",
  224.     "-M like -m, but add approximate (fractional) months",
  225.     "-c convert matching date to \".\" in output",
  226.     "-C convert all dates to \".\" in output",
  227.     "First fields of non-comment input lines must be dates in the form",
  228.     "YYMM[DD].  If DD is missing, 01 is assumed.  If -p, dates can be",
  229.     "followed by times: .HH[MM[SS]]] (default .000000).  Number fields are",
  230.     "inserted after the date field.",
  231.     CPNULL,
  232. };
  233.  
  234. char    *defargv[] = { "-" };        /* default argument list    */
  235.  
  236. char    *myname;            /* how program was invoked    */
  237. int    iflag = FALSE;            /* -i (intervals) option    */
  238. int    pflag = FALSE;            /* -p (precision) option    */
  239. int    wflag = FALSE;            /* -w (weeks, whole) option    */
  240. int    Wflag = FALSE;            /* -W (weeks, exact) option    */
  241. int    mflag = FALSE;            /* -m (months, whole) option    */
  242. int    Mflag = FALSE;            /* -M (months, exact) option    */
  243. int    Cflag = FALSE;            /* -C (convert all dates) opt    */
  244.  
  245. #define    MAXCONV    500            /* maximum dates to convert    */
  246. #define    DOT    '.'            /* what to convert dates to    */
  247. double    convdate [MAXCONV];        /* Julian dates from -c options    */
  248. int    convcount = 0;            /* number of -c dates given    */
  249.  
  250. double    prevdate = 0;            /* Julian of previous date    */
  251.  
  252. int    prevyear, prevmonth, prevday;    /* parts  of previous date    */
  253. int    year,      month,     day;    /* parts  of current  date    */
  254. int    length;                /* length of current  date    */
  255.  
  256. #define    SHORTLEN  4            /* length of short date (YYMM)    */
  257. #define    DATELEN   6            /* length of normal date    */
  258. #define    HHLEN      2            /* length of hour only        */
  259. #define    HHMMLEN      4            /* length of hour plus minutes    */
  260. #define    HHMMSSLEN 6            /* length of complete time    */
  261.  
  262. /*
  263.  * Days in each month (February is special) and in year before each month:
  264.  *
  265.  *            0  Jan Feb Mar  Apr May Jun  Jul Aug Sep  Oct Nov Dec */
  266. int monthdays[] = { 0,  31, 28, 31,  30, 31, 30,  31, 31, 30,  31, 30, 31 };
  267. int priordays[] = { 0,   0, 31, 59,  90,120,151, 181,212,243, 273,304,334 };
  268.  
  269. #define    DAYSperMONTH (365.25 / 12)    /* for -M conversions        */
  270.  
  271. double    JulianDate();
  272. double    ParseTime();
  273.  
  274.  
  275. /************************************************************************
  276.  * M A I N
  277.  */
  278.  
  279. PROC main (argc, argv)
  280.     int    argc;
  281.     char    **argv;
  282. {
  283. extern    int    optind;            /* from getopt()    */
  284. extern    char    *optarg;        /* from getopt()    */
  285. REG    int    option;            /* option "letter"    */
  286.  
  287. REG    FILE    *filep;            /* open input file    */
  288.     char    *filename;        /* name to use        */
  289.  
  290. /*
  291.  * PARSE ARGUMENTS:
  292.  */
  293.  
  294.     myname = *argv;
  295.  
  296.     while ((option = getopt (argc, argv, "ipwWmMc:C")) != EOF)
  297.     {
  298.         switch (option)
  299.         {
  300.         case 'i':    iflag = TRUE;    break;
  301.         case 'p':    pflag = TRUE;    break;
  302.         case 'w':    wflag = TRUE;    break;
  303.         case 'W':    Wflag = TRUE;    break;
  304.         case 'm':    mflag = TRUE;    break;
  305.         case 'M':    Mflag = TRUE;    break;
  306.         case 'C':    Cflag = TRUE;    break;
  307.  
  308.         case 'c':    if (convcount >= MAXCONV)
  309.             {
  310.                 Error ("can't handle more than %d -c options",
  311.                     MAXCONV);
  312.             }
  313.  
  314.             if ((convdate [convcount++] = JulianDate (optarg)) < 0)
  315.             {
  316.                 Error ("invalid date with -c option: \"%s\"",
  317.                    optarg);
  318.             }
  319.  
  320.             break;
  321.  
  322.         default:    Usage();
  323.         }
  324.     }
  325.  
  326.     argc -= optind;            /* skip options    */
  327.     argv += optind;
  328.  
  329. /*
  330.  * MORE ARGUMENT CHECKS:
  331.  */
  332.  
  333.     if (wflag + Wflag + mflag + Mflag > TRUE)
  334.         Error ("you can only give one of -w, -W, -m, or -M");
  335.  
  336.     if (pflag && (mflag || Mflag))
  337.         Error ("-p is not allowed with -m or -M");
  338.         /* the math is too hard and result meaningless; see PrintLine() */
  339.  
  340. /*
  341.  * READ FROM LIST OF FILES OR STDIN:
  342.  */
  343.  
  344.     if (argc < 1)                /* no file names */
  345.     {
  346.         argc = 1;
  347.         argv = defargv;            /* use default */
  348.     }
  349.  
  350.     while (argc-- > 0)
  351.     {
  352.         if (strcmp (*argv, "-") == 0)    /* read stdin */
  353.         {
  354.         filename = "<stdin>";
  355.         filep     = stdin;
  356.         }
  357.         else if ((filep = fopen ((filename = *argv), "r")) == (FILE *) NULL)
  358.         Error ("can't open file \"%s\" to read it", filename);
  359.  
  360.         ReadFile (filename, filep);
  361.  
  362.         if (ferror (filep))
  363.         Error ("read failed from file \"%s\"", filename);
  364.  
  365.         if (filep != stdin)
  366.         fclose (filep);            /* assume it works */
  367.  
  368.         argv++;
  369.     }
  370.  
  371.     exit (0);
  372.  
  373. } /* main */
  374.  
  375.  
  376. /************************************************************************
  377.  * R E A D   F I L E
  378.  *
  379.  * Given a filename and stream pointer for reading, read lines from the file,
  380.  * skip comment or blank lines, expect dates as first fields of other lines,
  381.  * and insert number-of-days fields.  Error out if an invalid date appears in a
  382.  * data line.
  383.  */
  384.  
  385. PROC ReadFile (filename, filep)
  386.     char    *filename;
  387. REG    FILE    *filep;
  388. {
  389. REG    int    linenum = 0;        /* input line number    */
  390. #define    LINESIZE  500
  391.     char    line [LINESIZE];    /* read from file    */
  392. REG    char    *cp;            /* place in line    */
  393. REG    double    date;            /* Julian date        */
  394.  
  395. /*
  396.  * READ LINE, REMOVE NEWLINE, SKIP LEADING WHITESPACE:
  397.  */
  398.  
  399.     while ((cp = fgets (line, LINESIZE, filep)) != CPNULL)
  400.     {
  401.         linenum++;
  402.  
  403.         while ((*cp != '\n') && (*cp != CHNULL))
  404.         cp++;                /* look for end of line */
  405.  
  406.         *cp = CHNULL;
  407.         cp  = line;
  408.  
  409.         while ((*cp == ' ') || (*cp == '\t'))
  410.         cp++;                /* skip leading whitespace */
  411.  
  412. /*
  413.  * HANDLE COMMENT, BLANK, OR DATA LINE:
  414.  */
  415.  
  416.         if ((*cp == '#') || (*cp == CHNULL))
  417.         puts (line);
  418.         else
  419.         {
  420.         if ((date = JulianDate (cp)) < 0)
  421.         {
  422.             Error ("file \"%s\", line %d: invalid date:\n%s",
  423.                filename, linenum, line);
  424.         }
  425.  
  426.         PrintLine (line, cp, date);
  427.         }
  428.     } /* while */
  429.  
  430. } /* ReadFile */
  431.  
  432.  
  433. /************************************************************************
  434.  * J U L I A N   D A T E
  435.  *
  436.  * Given a string supposed to start with a date (format YYMM[DD], or if pflag,
  437.  * YYMM[DD[.[HH[MM[SS]]]]]), and global monthdays[], check for a valid date
  438.  * (which must be terminated by CHNULL, blank, or tab) and return its Julian
  439.  * equivalent (where date 000101 == day 1.0).  In case of invalid date, return
  440.  * a negative value.
  441.  * 
  442.  * As a side-effect, use and set globals year, month, day, and length, so
  443.  * they're remembered and available if needed by callers.  Only valid if
  444.  * successful.
  445.  */
  446.  
  447. PROC double JulianDate (datestr)
  448.     char    *datestr;        /* value to check */
  449. {
  450.     char    *endstr;        /* end of datestr */
  451.     long    strtol();
  452. REG    long    date = strtol (datestr, & endstr, 10);
  453.     int    havetime;        /* have time too?    */
  454.     double    time = 0.0;        /* fraction of day   */
  455.     int    leapyear;        /* is it a leapyear? */
  456.  
  457. /*
  458.  * CHECK FOR SHORT DATE (NO "DD"):
  459.  */
  460.  
  461.     if ((length = (endstr - datestr)) == SHORTLEN)
  462.         date = (date * 100) + 1;        /* assume missing DD == 01 */
  463.  
  464. /*
  465.  * CHECK FOR AND RETURN VALID DATE [AND TIME]:
  466.  *
  467.  * Hope to get through all checks to the "return" statement.
  468.  * Any failure drops out to below.
  469.  */
  470.  
  471.     havetime = pflag && (*endstr == '.');
  472.  
  473.     if (((length  == SHORTLEN)
  474.       || (length  == DATELEN))    /* OK length */
  475.      && ((*endstr == CHNULL)
  476.       || (*endstr == ' ')
  477.       || (*endstr == '\t')
  478.       || havetime))            /* OK end */
  479.     {
  480.         year  = date / 10000;
  481.         month = date / 100 % 100;
  482.         day      = date % 100;
  483.  
  484.         leapyear = (year > 0) && ((year % 4) == 0);
  485.  
  486.         if ((month >= 1) && (month <= 12) && (day >= 1)
  487.          && (day <= monthdays [month] + (leapyear && (month == 2)))
  488.          && ((! havetime)
  489.          || ((time = ParseTime (datestr + (++length))) >= 0)))
  490.         {
  491.         return ((year * 365)            /* previous years */
  492.             + (priordays [month])        /* before month      */
  493.             + day                /* in this month  */
  494.             + (year / 4)            /* prior leapdays */
  495.             - (leapyear && (month <= 2))    /* before Feb 29  */
  496.             + time);            /* return double  */
  497.         }
  498.     } /* if */
  499.  
  500. /*
  501.  * HANDLE ERROR:
  502.  */
  503.  
  504.     return (-1.0);
  505.  
  506. } /* JulianDate */
  507.  
  508.  
  509. /************************************************************************
  510.  * P A R S E   T I M E
  511.  *
  512.  * Given a string supposed to start with a time (format [HH[MM[SS]]]), check
  513.  * for a valid time (which must be terminated by CHNULL, blank, or tab) and
  514.  * return its value as a fraction of 24 hours.  In case of invalid time, return
  515.  * a negative value.
  516.  * 
  517.  * As a side-effect, add to global length the length of the time string.  Only
  518.  * valid if successful.
  519.  */
  520.  
  521. PROC double ParseTime (timestr)
  522.     char    *timestr;        /* value to check */
  523. {
  524.     char    *endstr;        /* end of datestr */
  525.     long    strtol();
  526. REG    long    time = strtol (timestr, & endstr, 10);
  527.     int    timelen;        /* length of time */
  528.     int    hour, minute, second;    /* parts of time  */
  529.  
  530. /*
  531.  * CHECK FOR SHORT TIME (NOT HHMMSS):
  532.  */
  533.  
  534.     if (timestr == endstr)            /* no time value */
  535.         return (0.0);
  536.  
  537.     if    ((timelen = (endstr - timestr)) == HHLEN)    time *= 10000;
  538.     else if    (timelen == HHMMLEN)                time *= 100;
  539.  
  540. /*
  541.  * CHECK FOR AND RETURN VALID TIME:
  542.  *
  543.  * Hope to get through all checks to the "return" statement.
  544.  * Any failure drops out to below.
  545.  */
  546.  
  547.     if (((timelen == HHLEN)
  548.       || (timelen == HHMMLEN)
  549.       || (timelen == HHMMSSLEN))        /* OK length */
  550.      && ((*endstr == CHNULL)
  551.       || (*endstr == ' ')
  552.       || (*endstr == '\t')))        /* OK end */
  553.     {
  554.         length += timelen;
  555.  
  556.         hour   = time / 10000;
  557.         minute = time / 100 % 100;
  558.         second = time % 100;
  559.  
  560.         if ((hour <= 23) && (minute <= 59) && (second <= 59))
  561.         return (((((second / 60.0) + minute) / 60) + hour) / 24);
  562.  
  563.     } /* if */
  564.  
  565. /*
  566.  * HANDLE ERROR:
  567.  */
  568.  
  569.     return (-1.0);
  570.  
  571. } /* ParseTime */
  572.  
  573.  
  574. /************************************************************************
  575.  * P R I N T   L I N E
  576.  *
  577.  * Given an input line, a pointer to a valid date string in the line, the
  578.  * Julian number for that date, and globals convcount, prevdate, prevyear,
  579.  * prevmonth, prevday, year, month, day, length, and monthdays[], print the
  580.  * line with the number-of-days (or weeks or months) inserted after the date.
  581.  * Also update prevdate (and optionally prevyear, prevmonth, and prevday) if
  582.  * this is the first data line (currently prevdate == 0.0) or if iflag is set.
  583.  */
  584.  
  585. PROC PrintLine (cp, datestr, date)
  586. REG    char    *cp;        /* place in line, initially start */
  587. REG    char    *datestr;    /* start of date in line      */
  588.     double    date;        /* Julian value of input date      */
  589. {
  590. REG    int    cflag = Cflag || (convcount && IsConv (date));
  591.  
  592. #ifdef DEBUG
  593.     printf ("%g\n", date);
  594.     return;
  595. #endif
  596.  
  597. /*
  598.  * PRINT INDENTATION PART:
  599.  */
  600.  
  601.     while (cp < datestr)
  602.     {
  603.         putchar (*cp);
  604.         cp++;        /* because putchar() is a macro */
  605.     }
  606.  
  607. /*
  608.  * PRINT DATE STRING, POSSIBLY CONVERTED TO DOT:
  609.  */
  610.  
  611.     if (cflag)
  612.     {
  613.         putchar (DOT);
  614.         cp += length;
  615.     }
  616.     else
  617.     {
  618.         for (datestr += length; cp < datestr; cp++)
  619.         putchar (*cp);
  620.     }
  621.  
  622.     /* now cp is at the next char after the date input */
  623.  
  624. /*
  625.  * PRINT INITIAL "ADDED" VALUE:
  626.  */
  627.  
  628.     if (prevdate == 0.0)            /* no previous date yet */
  629.         printf (" 0");
  630.  
  631. /*
  632.  * PRINT "ADDED" NUMBER OF MONTHS:
  633.  *
  634.  * This computation compares previous and current year, day, and month, rather
  635.  * than prevdate and date, in order to account for months varying in size.
  636.  */
  637.  
  638.     else if (mflag || Mflag)
  639.     {
  640.         long interval = ((year - prevyear) * 12) + (month - prevmonth);
  641.  
  642.         if (mflag)                /* whole months */
  643.         {
  644.         /* reduce for incomplete month: */
  645.  
  646.         if    ((interval > 0) && (prevday > day))    interval--;
  647.         else if    ((interval < 0) && (prevday < day))    interval++;
  648.  
  649.         printf (" %ld", interval);
  650.         }
  651.         else                /* fractional months */
  652.         {
  653.         /* not precise, but close enough: */
  654.  
  655.         printf (" %g", interval + ((day - prevday) / DAYSperMONTH));
  656.         }
  657.     }
  658.  
  659. /*
  660.  * PRINT "ADDED" NUMBER OF WEEKS OR DAYS:
  661.  */
  662.  
  663.     else
  664.     {
  665.         double interval = date - prevdate;
  666.  
  667.         if        (Wflag)    printf (" %g",  interval / 7);
  668.         else if (wflag)    printf (" %ld", ((long) interval) / 7);
  669.         else if (pflag)    printf (" %g",  interval);
  670.         else        printf (" %ld", (long) interval);
  671.     }
  672.  
  673. /*
  674.  * PRINT REST OF INPUT LINE; SAVE VALUES:
  675.  */
  676.  
  677.     puts (cp);
  678.  
  679.     if (iflag || (prevdate == 0.0))    /* always, or first input line */
  680.     {
  681.         prevdate = date;        /* save this date as previous */
  682.  
  683.         if (mflag || Mflag)        /* need to save them */
  684.         {
  685.         prevyear  = year;
  686.         prevmonth = month;
  687.         prevday      = day;
  688.         }
  689.     }
  690.  
  691. } /* PrintLine */
  692.  
  693.  
  694. /************************************************************************
  695.  * I S   C O N V
  696.  *
  697.  * Given a Julian date and globals convdate[] and convcount, return TRUE if
  698.  * the given date is in convdate[], FALSE otherwise.
  699.  */
  700.  
  701. PROC int IsConv (date)
  702. REG    double    date;
  703. {
  704. REG    int    index;
  705.  
  706.     for (index = 0; index < convcount; index++)
  707.         if (convdate [index] == date)
  708.         return (TRUE);
  709.  
  710.     return (FALSE);
  711.  
  712. } /* IsConv */
  713.  
  714.  
  715. /************************************************************************
  716.  * U S A G E
  717.  *
  718.  * Print usage messages (char *usage[]) to stderr and exit nonzero.
  719.  * Each message is followed by a newline.
  720.  */
  721.  
  722. PROC Usage()
  723. {
  724. REG    int    which = 0;        /* current line */
  725.  
  726.     while (usage [which] != CPNULL)
  727.     {
  728.         fprintf (stderr, usage [which++], myname);
  729.         putc ('\n', stderr);
  730.     }
  731.  
  732.     exit (1);
  733.  
  734. } /* Usage */
  735.  
  736.  
  737. /************************************************************************
  738.  * E R R O R
  739.  *
  740.  * Print an error message to stderr and exit nonzero.  Message is preceded
  741.  * by "<myname>: " using global char *myname, and followed by a newline.
  742.  */
  743.  
  744. /* VARARGS */
  745. PROC Error (message, arg1, arg2, arg3, arg4)
  746.     char    *message;
  747.     long    arg1, arg2, arg3, arg4;
  748. {
  749.     fprintf (stderr, "%s: ", myname);
  750.     fprintf (stderr, message, arg1, arg2, arg3, arg4);
  751.     putc ('\n', stderr);
  752.  
  753.     exit (1);
  754.  
  755. } /* Error */
  756. @EOF
  757. if test "`wc -lwc <misc/anod.c`" != '    562   2363  13942'
  758. then
  759.     echo ERROR: wc results of misc/anod.c are `wc -lwc <misc/anod.c` should be     562   2363  13942
  760. fi
  761.  
  762. chmod 444 misc/anod.c
  763.  
  764. echo x - misc/stddev
  765. cat >misc/stddev <<'@EOF'
  766. #! /bin/ksh
  767. # Compute standard deviation.
  768.  
  769. # Usage: <script> [files...]
  770.  
  771. # Reads standard input or the concatenation of files.  The first field of
  772. # each non-blank line is taken to be a number (no error checking).  The
  773. # script reports the number of numbers read, the average, and the standard
  774. # deviation.
  775.  
  776. # Standard deviation, easy computational form:
  777. #
  778. #    sqrt (( sum (Xi^2) - ((sum (Xi))^2 / N) ) / (N - 1))
  779. #
  780. # Empirical rule:  68% within 1 standard deviation; 95% within 2; all within 3.
  781.  
  782. cat "$@" |
  783.  
  784. awk '
  785.  
  786. (NF > 0) {            # non blank line.
  787.     count ++;
  788.     sum   += $1;
  789.     sumsq += $1 * $1;
  790. }
  791.  
  792. END {
  793.     if (count == 0)
  794.     {
  795.         print "'"$0"': no data lines";
  796.         exit;
  797.     }
  798.  
  799.     printf ("count:    %d\n", count);
  800.     printf ("average:  %f\n", sum / count);
  801.     printf ("std dev:  %f\n", \
  802.         sqrt ((sumsq - (sum * sum / count)) / (count - 1)));
  803. }'
  804. @EOF
  805. if test "`wc -lwc <misc/stddev`" != '     38    155    832'
  806. then
  807.     echo ERROR: wc results of misc/stddev are `wc -lwc <misc/stddev` should be      38    155    832
  808. fi
  809.  
  810. chmod 555 misc/stddev
  811.  
  812. chmod 750 misc
  813.  
  814. echo mkdir - moon
  815. mkdir moon
  816.  
  817. echo x - moon/Makefile
  818. cat >moon/Makefile <<'@EOF'
  819. # Makefile for phase-of-moon program.
  820. # Necessary for libm and libdate.
  821.  
  822. CC    = cc
  823. CFLAGS    = -Ovs
  824.  
  825. INCLUDE    = /usr/local/include
  826. LIB    = /usr/local/lib
  827.  
  828. all:    moon
  829.  
  830. moon:    moon.c $(INCLUDE)/parsedate.h $(LIB)/libdate.a
  831.     $(CC) $(CFLAGS)   -o moon moon.c -lm -ldate -I $(INCLUDE) -L $(LIB)
  832.  
  833. debug:    moon.c $(INCLUDE)/parsedate.h $(LIB)/libdate.a
  834.     $(CC) -gv -DDEBUG -o moon moon.c -lm -ldate -I $(INCLUDE) -L $(LIB)
  835.  
  836. lint:    moon.c
  837.     lint $(MATH) moon.c -lm -I $(INCLUDE)
  838.  
  839. clean:
  840.     rm -f moon *.o core
  841. @EOF
  842. if test "`wc -lwc <moon/Makefile`" != '     22     70    486'
  843. then
  844.     echo ERROR: wc results of moon/Makefile are `wc -lwc <moon/Makefile` should be      22     70    486
  845. fi
  846.  
  847. chmod 444 moon/Makefile
  848.  
  849. echo x - moon/README
  850. cat >moon/README <<'@EOF'
  851. # This directory contains the source, manual entry, and test scripts and files
  852. # for the phase of the moon program by Alan Silverstein.
  853.  
  854. # To build moon, you need libdate.a, which is created from the parsedate
  855. # sources available separately.  After making parsedate, copy libdate.a to
  856. # /usr/local/lib and parsedate.h to /usr/local/include (or anywhere you like,
  857. # but change moon/Makefile if not the above locations).
  858.  
  859. # I welcome and solicit your feedback on the design (including usability) and
  860. # implementation (including coding style) of this software.
  861.  
  862. Makefile    needed for libdate.a and libm.a
  863.  
  864. moon.c        source file
  865. moon.1        manual entry (tbl | nroff -man)
  866.  
  867. accuracy    script to check program accuracy against known lunar events
  868.         (needs anod(1) and stddev(1))
  869.  
  870. test.script    script to run moon a variety of ways, for testing
  871. test.out    expected output to diff against
  872. @EOF
  873. if test "`wc -lwc <moon/README`" != '     21    134    867'
  874. then
  875.     echo ERROR: wc results of moon/README are `wc -lwc <moon/README` should be      21    134    867
  876. fi
  877.  
  878. chmod 440 moon/README
  879.  
  880. echo x - moon/accuracy
  881. cat >moon/accuracy <<'@EOF'
  882.  
  883. # CHECK ACCURACY OF moon(1) AGAINST KNOWN LUNAR EVENT TIMES
  884.  
  885. # Usage:  <script> (ignores arguments)
  886.  
  887. # Compares known lunar events against moon(1) predictions.  Uses unsupported
  888. # anod(1) and stddev(1) commands.
  889.  
  890. # In the data below:
  891. #
  892. # Deltas between all events (days):
  893. #
  894. #    count:    36
  895. #    average:  7.363908
  896. #    std dev:  0.562316 (13.5 hours)
  897. #
  898. # Deltas between new moons (days):
  899. #
  900. #    count:    9
  901. #    average:  29.455633
  902. #    std dev:  0.111066 (2.67 hours)
  903.  
  904.  
  905. # INITIALIZE:
  906.  
  907.     start='890105'        # surrounds data points below.
  908.     end='890930'
  909.  
  910.     temp1="/tmp/mooncka$$"
  911.     temp2="/tmp/moonckb$$"
  912.     trap "rm -f $temp1 $temp2; exit" 0 1 2 3 15
  913.  
  914.  
  915. # DATA FROM SKY AND TELESCOPE MAGAZINE, UTC:
  916.  
  917.     cat > $temp1 <<-'!'
  918.         890107.1922 new
  919.         890114.1358 first
  920.         890121.2133 full
  921.         890130.0202 last
  922.         890206.0737 new
  923.         890212.2315 first
  924.         890220.1532 full
  925.         890228.2008 last
  926.         890307.1819 new
  927.         890314.1011 first
  928.         890322.0958 full
  929.         890330.1021 last
  930.         890406.0333 new
  931.         890412.2313 first
  932.         890421.0313 full
  933.         890428.2046 last
  934.         890505.1146 new
  935.         890512.1419 first
  936.         890520.1816 full
  937.         890528.0401 last
  938.         890603.1953 new
  939.         890611.0659 first
  940.         890619.0657 full
  941.         890626.0909 last
  942.         890703.0459 new
  943.         890711.0019 first
  944.         890718.1742 full
  945.         890725.1331 last
  946.         890801.1606 new
  947.         890809.1728 first
  948.         890817.0307 full
  949.         890823.1840 last
  950.         890831.0544 new
  951.         890908.0949 first
  952.         890915.1151 full
  953.         890922.0210 last
  954.         890929.2147 new
  955.     !
  956.  
  957.  
  958. # RUN moon AND MERGE DATA:
  959.  
  960.     TZ='UTC0' moon any $start $end    |
  961.     sed -e 's/ /./'    -e 's/://'    |  # convert time format to YYMMDD.HHMM.
  962.     paste $temp1 -            |
  963.  
  964.     # Data past here:  known-time phase moon-time the moon is phase ...
  965.     # where phase might be "at phase".
  966.  
  967.  
  968. # CHECK AND COMPARE DATA:
  969.  
  970.     awk '{
  971.         if ((phase = $7) == "at")
  972.         phase = $8;
  973.  
  974.         if ($2 != phase)
  975.         {
  976.         print "Mismatched phases:", $0;
  977.         exit;
  978.         }
  979.  
  980.         printf ("%s\n%s\t%s\n", $1, $3, phase);
  981.     }' |
  982.  
  983.     # Data past here:  known-time<newline>moon-time phase
  984.  
  985.     anod -ip |            # exact incremental numbers of days.
  986.  
  987.     # Ignore known-time lines; round to whole minutes since times are
  988.     # only to nearest minute anyway:
  989.  
  990.     awk > $temp2 \
  991.         '(NF > 2) { printf ("%+5.0f  %s  %s\n", ($2 * 24 * 60), $1, $3) }'
  992.  
  993.     # Data in file:  delta-minutes  moon-time  phase
  994.  
  995.  
  996. # PRINT RESULTS:
  997.  
  998.     echo \
  999. "Error (minutes) versus known times, moon predicted time (UTC), and phase:\n"
  1000.     cat $temp2
  1001.  
  1002.     echo
  1003.     stddev < $temp2
  1004. @EOF
  1005. if test "`wc -lwc <moon/accuracy`" != '    122    347   2365'
  1006. then
  1007.     echo ERROR: wc results of moon/accuracy are `wc -lwc <moon/accuracy` should be     122    347   2365
  1008. fi
  1009.  
  1010. chmod 554 moon/accuracy
  1011.  
  1012. echo x - moon/moon.1
  1013. cat >moon/moon.1 <<'@EOF'
  1014. .\" Matches program @(#) $Revision:  9, 890809 $
  1015. .\" Print with tbl | nroff -man
  1016. .TH MOON 1
  1017. .SH NAME
  1018. moon \- tell the phase of the moon
  1019. .SH SYNOPSIS
  1020. .B moon
  1021. .RB [ \-ntTuUp ]
  1022. .RB [ \-dD ]
  1023. [\fB\-x \fIxyratio\fR]
  1024. .RI [ quarter ]
  1025. .RI [ time
  1026. .RI [ endtime
  1027. .RI [ increment ]]]
  1028. .\" ==========
  1029. .SH DESCRIPTION
  1030. .\" ==========
  1031. This program prints information about the phase of the moon
  1032. at the current time, at a specified time, or during a range of specified times.
  1033. It can also print information about when moon phases (quarter moons) occur.
  1034. .\" ----------
  1035. .PP
  1036. By default,
  1037. .I moon
  1038. prints to standard output
  1039. a long one-line description of the current phase of the moon
  1040. at the current system clock time.
  1041. The description includes the date and time in the format
  1042. .SM "YYMMDD HH:MM"
  1043. (rounded to the nearest minute),
  1044. a description of the phase,
  1045. and the percentage of the moon which is currently illuminated
  1046. as seen from the earth.
  1047. .\" ----------
  1048. .PP
  1049. The phase is considered to be exactly at a quarter moon
  1050. (new, first, full, last)
  1051. if it is within 1% of that quarter.
  1052. .\" ==========
  1053. .SS Options
  1054. .\" ==========
  1055. The options are:
  1056. .\" ----------
  1057. .TP
  1058. .B \-n
  1059. (numeric)
  1060. Print the phase value as a number from 0 to 1,
  1061. where 0.0 means ``new'',
  1062. 0.5 means ``full'',
  1063. and 1.0 is the next new moon.
  1064. .\" ----------
  1065. .TP
  1066. .B \-t
  1067. (terse text)
  1068. Print the phase name only, not a longer string.
  1069. .\" ----------
  1070. .TP
  1071. .B \-T
  1072. (text)
  1073. Print a description of the moon's phase.
  1074. This option is the default.
  1075. .\" ----------
  1076. .TP
  1077. .B \-u
  1078. (until)
  1079. Tell how long it is from the current system clock time
  1080. since or until each specified lunar event,
  1081. in days, hours, and minutes, in fractional days,
  1082. and as a percentage of an average lunar month (29.5306 days).
  1083. .\" ----------
  1084. .TP
  1085. .B \-U
  1086. (until)
  1087. Tell how long it is from the start time to the first specified lunar event,
  1088. and from each event until the next one if a time range is specified.
  1089. .\" ----------
  1090. .TP
  1091. .B \-p
  1092. (picture)
  1093. Draw a picture of the moon's phase using
  1094. .SM ASCII
  1095. characters to represent
  1096. dark areas (blanks),
  1097. lit areas (``@''),
  1098. and
  1099. dark limbs (``('' or ``)'').
  1100. The size of the picture is determined by the
  1101. .SM LINES
  1102. and
  1103. .SM COLUMNS
  1104. environment variables, if present.
  1105. The defaults are 24 lines and 80 columns.
  1106. Only 80% of the lines are used,
  1107. plus one blank line is emitted before and after each picture.
  1108. Each picture is the largest approximate circle (see the
  1109. .B \-x
  1110. option) that fits in the rectangle formed by the specified lines and columns.
  1111. If the number of lines is the limiter,
  1112. the circle is centered in the available columns.
  1113. .\" ----------
  1114. .TP
  1115. .B \-d
  1116. Don't print the date and time with the
  1117. .B \-ntT
  1118. options.
  1119. Just print the phase information.
  1120. .\" ----------
  1121. .TP
  1122. .B \-D
  1123. Print a long form date and time with the
  1124. .B \-ntT
  1125. options using
  1126. .IR ctime (3).
  1127. This date and time format includes seconds,
  1128. but the program is not really that accurate (see
  1129. .I Accuracy
  1130. below).
  1131. .\" ----------
  1132. .TP
  1133. .BI \-x \0xyratio
  1134. Set the per-character
  1135. .SM X/Y
  1136. ratio to match that of the the display or font used,
  1137. so the picture printed with the
  1138. .B \-p
  1139. option is more circular.
  1140. The default
  1141. .I xyratio
  1142. is 0.5, which is correct if character cells
  1143. are half as many pixels wide as they are tall.
  1144. The larger the
  1145. .I xyratio
  1146. value, the more the picture is ``squashed'' horizontally
  1147. so it is represented as a circle when displayed.
  1148. To use this option,
  1149. determine your display's or font's character cell pixel ratio,
  1150. or experiment with values until you like what you see.
  1151. .\" ==========
  1152. .SS Arguments
  1153. .\" ==========
  1154. Various positional arguments are allowed.
  1155. .\" ----------
  1156. .TP 10
  1157. .I quarter
  1158. The first argument can be the name of a quarter moon
  1159. to select the next occurrence of that phase after the current or specified time.
  1160. .I Quarter
  1161. must be one of:
  1162. .BR new ,
  1163. .BR first ,
  1164. .BR full ,
  1165. .BR last ,
  1166. .BR next ,
  1167. .BR any .
  1168. The program prints when the specified quarter next occurs,
  1169. or prints all occurrences of that quarter in the specified time range
  1170. (see below).
  1171. Saying
  1172. .B next
  1173. or
  1174. .B any
  1175. specifies whatever quarter first occurs
  1176. after the current or specified starting time.
  1177. If you give a range of times,
  1178. .B next
  1179. shows only when the next quarter occurs during that range;
  1180. .B any
  1181. shows all quarters during that range.
  1182. .\" ----------
  1183. .TP
  1184. .I time
  1185. Set the time value to use or the initial time for a range.
  1186. .\" ----------
  1187. .TP
  1188. .I endtime
  1189. Specify the end time for a range of times.
  1190. .I Moon
  1191. prints phase or quarter information from
  1192. .I time
  1193. to
  1194. .IR endtime .
  1195. .\" ----------
  1196. .PP
  1197. Enter dates and times in almost any reasonable format,
  1198. as single quoted arguments if they contains blanks.
  1199. The year, month, and day default to the current system clock.
  1200. The hour, minute, and second default to zero (midnight).
  1201. The timezone defaults to the current value in the
  1202. .SM TZ
  1203. environment variable (assumed to be west of Greenwich),
  1204. including daylight savings time if it's in effect.
  1205. Beware of timezone confusion, such as that caused by daylight savings.
  1206. .\" ----------
  1207. .PP
  1208. To control the default timezone used for all input and output, set the
  1209. .SM TZ
  1210. environment variable.
  1211. For example:
  1212. .B "TZ=UTC0 moon"
  1213. .\" ----------
  1214. .TP 10
  1215. .I increment
  1216. Specify the step size for stepping through a range of times
  1217. starting with the initial
  1218. .IR time .
  1219. .I Increment
  1220. must end in one of:
  1221. .B d
  1222. (days),
  1223. .B h
  1224. (hours),
  1225. .B m
  1226. (minutes),
  1227. .B s
  1228. (seconds).
  1229. The default is
  1230. .B 1d
  1231. (step one day at a time).
  1232. .\" ----------
  1233. .PP
  1234. The options and arguments can be combined in numerous ways
  1235. to make complex requests.
  1236. .I Moon
  1237. complains if you specify a nonsensical combination.
  1238. .\" ==========
  1239. .SS Accuracy
  1240. .\" ==========
  1241. This program includes many real-world constant values,
  1242. some parameterized and some not,
  1243. with varying numbers of significant digits.
  1244. When run by the ``accuracy'' script which accompanies the sources,
  1245. .I moon
  1246. predicted 37 lunar events (nine cycles of new, first, full, last)
  1247. beginning with the new moon on 890107.
  1248. Comparing the predicted times (to the nearest minute, in
  1249. .SM UT)
  1250. with those published in Sky and Telescope Magazine
  1251. (also to the nearest minute), its accuracy was:
  1252. .\" ----------
  1253. .PP
  1254. .TS
  1255. center;
  1256. l l.
  1257. average error    2.16 minutes (too late)
  1258. standard deviation of errors    12.5 minutes
  1259. maximum errors    -17, +28 minutes
  1260. .TE
  1261. .\" ==========
  1262. .SS Portability
  1263. .\" ==========
  1264. The Epoch (time of known orbital positions) is 473299200 seconds (19841231.0
  1265. .SM GMT
  1266. in
  1267. .SM UNIX
  1268. system time).
  1269. To build a program for converting an Epoch date
  1270. to a particular system's equivalent clock time in seconds,
  1271. edit the GetEpoch() routine to change the date if needed,
  1272. then compile moon.c with
  1273. .SM "-DGET_EPOCH"
  1274. to produce a special-purpose conversion program, and run it.
  1275. Use the output to revise for your system the value of
  1276. .SM EPOCH
  1277. in the source code.
  1278. .\" ==========
  1279. .SS Debugging
  1280. .\" ==========
  1281. The sources include a
  1282. .B test.script
  1283. and a hand checked
  1284. .B test.out
  1285. file.
  1286. To test
  1287. .IR moon ,
  1288. run
  1289. .B test.script
  1290. and compare its output to
  1291. .BR test.out .
  1292. Some output lines vary with the system clock;
  1293. they are marked in
  1294. .B test.script
  1295. output.
  1296. .\" ----------
  1297. .PP
  1298. For extra output during NextQuarter() calculations, compile moon.c with
  1299. .SM -DDEBUG.
  1300. .\" ==========
  1301. .SH RETURN VALUE
  1302. .\" ==========
  1303. .I Moon
  1304. returns 0 if it succeeds or 1 if it detects and reports any errors.
  1305. .\" ==========
  1306. .SH DIAGNOSTICS
  1307. .\" ==========
  1308. .I Moon
  1309. prints an explanatory message to standard error
  1310. and exits if it detects any of about 14 invocation errors.
  1311. If it is invoked correctly, no other errors are expected.
  1312. .\" ==========
  1313. .SH EXAMPLES
  1314. .\" ==========
  1315. Print the current phase of the moon:
  1316. .IP
  1317. .B moon
  1318. .\" ----------
  1319. .PP
  1320. Print when the next quarter occurs, and draw a small picture of that phase:
  1321. .IP
  1322. .B "COLUMNS=20 moon \-Tp next"
  1323. .\" ----------
  1324. .PP
  1325. Print when the next new moon occurs (short form):
  1326. .IP
  1327. .B "moon \-t new"
  1328. .\" ----------
  1329. .PP
  1330. Print when the moon is next last quarter, and how long it is until then,
  1331. using long form dates:
  1332. .IP
  1333. .B "moon \-uD last"
  1334. .\" ----------
  1335. .PP
  1336. Print the current phase of the moon both numerically and tersely:
  1337. .IP
  1338. .B "moon \-nt"
  1339. .\" ----------
  1340. .PP
  1341. Print the next quarter of the moon after midnight May 1, 1989,
  1342. without including the date in the output.
  1343. .IP
  1344. .B "moon \-d any 890501"
  1345. .\" ----------
  1346. .PP
  1347. Print all first quarter moons in a two month period,
  1348. along with time intervals from the start time and between quarters:
  1349. .IP
  1350. .B "moon \-U first 890530 890730"
  1351. .\" ----------
  1352. .PP
  1353. Print the phase of the moon every 5 days, in Universal Time,
  1354. starting and ending at specified times:
  1355. .IP
  1356. .B "TZ=UT0 moon 'may 1, 1988 10:00pm' 'August 4, 1988' 5d"
  1357. .\" ==========
  1358. .SH AUTHOR
  1359. .\" ==========
  1360. .I Moon
  1361. was developed by
  1362. Alan Silverstein at Hewlett-Packard.
  1363. It is based on sources
  1364. ``stolen from ArchMach and converted from PL/I by Brian Hess;
  1365. extensively cleaned up by Rich $alz,''
  1366. with a higher-precision Phase() algorithm from
  1367. moon.c by Keith E. Brandt (1984), based on routines from
  1368. ``Practical Astronomy with Your Calculator'', by Duffett-Smith.
  1369. .\" ==========
  1370. .SH SEE ALSO
  1371. .\" ==========
  1372. sun(1), ctime(3), parsedate(3).
  1373. @EOF
  1374. if test "`wc -lwc <moon/moon.1`" != '    359   1558   8928'
  1375. then
  1376.     echo ERROR: wc results of moon/moon.1 are `wc -lwc <moon/moon.1` should be     359   1558   8928
  1377. fi
  1378.  
  1379. chmod 444 moon/moon.1
  1380.  
  1381. echo x - moon/moon.c
  1382. cat >moon/moon.c <<'@EOF'
  1383. static char * version =    "@(#) $Revision:  9, 890809 $";
  1384. /*
  1385.  * Tell the phase of the moon.
  1386.  *
  1387.  * See the manual entry, or run with "-?" for a usage summary.
  1388.  *
  1389.  * Uses the unsupported parsedate(3) and compute_unixtime(3) library calls for
  1390.  * date parsing.
  1391.  *
  1392.  * For extra output during NextQuarter() calculations, compile with -DDEBUG.
  1393.  *
  1394.  * This code has been carefully code-read, lint'd, and even BFA'd (97%+).
  1395.  *
  1396.  * Possible enhancements:
  1397.  * - allow case-insensitive quarter and time unit specification
  1398.  */
  1399.  
  1400. #include <sys/types.h>            /* for time_t            */
  1401. #include <stdio.h>
  1402. #include <ctype.h>            /* for isspace() and isdigit()    */
  1403. #include <string.h>            /* for strcmp() and strncpy()    */
  1404. #include <math.h>            /* for trig funcs and fmod()    */
  1405. #include <time.h>            /* for time conversion        */
  1406. #include <parsedate.h>            /* for time conversion        */
  1407.  
  1408. #define    CTIMELEN 26            /* length of ctime() return    */
  1409.  
  1410.  
  1411. /*********************************************************************
  1412.  * MISCELLANEOUS GLOBAL VALUES:
  1413.  */
  1414.  
  1415. #define    PROC                /* null; easy to find procs */
  1416. #define    FALSE    0
  1417. #define    TRUE    1
  1418. #define    CHNULL    ('\0')
  1419. #define    CPNULL    ((char *) NULL)
  1420. #define    REG    register
  1421.  
  1422. char *usage[] = {
  1423. "usage: %s [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]",
  1424. "",
  1425. "-n (numeric) print date, time, and phase as a number 0..1 (new..full..new)",
  1426. "-t (terse text) print only the date, time, and phase name",
  1427. "-T (text) print date, time, and a description of the moon's phase (default)",
  1428. "-u (until) tell how long from now until each specified event",
  1429. "-U (until) tell how long from time to quarter and/or each quarter to next",
  1430. "-p (picture) draw a picture of the moon's phase using characters",
  1431. "-d don't print the date and time with -ntT, just the phase information",
  1432. "-D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)",
  1433. "-x set display/font character X/Y ratio (for square picture; default:  0.5)",
  1434. "",
  1435. "quarter:    any of \"new first full last next any\"; if specified, tell when",
  1436. "            that quarter next occurs, or all occurrences in the time range",
  1437. "time:       value to use (default:  current system time)",
  1438. "endtime:    print phase or quarter information from time to endtime",
  1439. "increment:  amount to step through range of times; must end in one of:",
  1440. "            d(ays) h(ours) m(inutes) s(econds) (default:  1d)",
  1441. "",
  1442. "Enter date/time in almost any reasonable format, quoted if it contains",
  1443. "blanks.  Year, month, and day default to current; hour, minute, and second to",
  1444. "zero; timezone to current, including DST if in effect (assumed west of",
  1445. "Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 %s",
  1446. CPNULL,
  1447. };
  1448.  
  1449. char    *myname;            /* how program was invoked    */
  1450.  
  1451. int    nflag = FALSE;            /* -n (print number) option    */
  1452. int    tflag = FALSE;            /* -t (print terse text) option    */
  1453. int    Tflag = FALSE;            /* -T (print text) option    */
  1454. int    uflag = FALSE;            /* -u (print until) option    */
  1455. int    Uflag = FALSE;            /* -U (print until) option    */
  1456. int    pflag = FALSE;            /* -p (print picture) option    */
  1457.  
  1458. int    dflag = FALSE;            /* -d (skip date) option    */
  1459. int    Dflag = FALSE;            /* -D (long date) option    */
  1460.  
  1461. #define    SHORTDATEFORM  "%02d%02d%02d %02d:%02d  "    /* change to suit */
  1462.  
  1463. #define    USAGE    0            /* for Error() */
  1464. #define    NOUSAGE    1
  1465.  
  1466. #define    OKEXIT  0            /* for exit() */
  1467. #define    ERREXIT 1
  1468.  
  1469.  
  1470. /*********************************************************************
  1471.  * GLOBALS FOR TIME AND PHASE FIGURING:
  1472.  */
  1473.  
  1474. time_t    currtime;            /* current time, set once */
  1475.  
  1476. #define    SECperMIN    60            /* seconds per minute    */
  1477. #define    SECperHOUR  (SECperMIN  * 60)    /* seconds per hour    */
  1478. #define    SECperDAY   (SECperHOUR * 24)    /* seconds per day    */
  1479. #define    DAYSperMON   29.5306        /* approximate, varies    */
  1480. #define    SECperMON   (SECperDAY * DAYSperMON)
  1481. #define    DAYSperYEAR 365.2422;        /* exact as possible    */
  1482.  
  1483. typedef    double    phase_t;
  1484.  
  1485. #define    P_NEW       ((phase_t) 0.000)    /* lunar phase values; see Phase() */
  1486. #define    P_FIRST    ((phase_t) 0.250)
  1487. #define    P_FULL       ((phase_t) 0.500)
  1488. #define    P_LAST       ((phase_t) 0.750)
  1489. #define    P_NEXTNEW  ((phase_t) 1.000)    /* next new moon */
  1490.  
  1491. #define    P_MONTH         P_NEXTNEW        /* size of one lunar cycle (month) */
  1492. #define    P_PRECISION  ((phase_t) 0.01)    /* consider at quarter if within   */
  1493.  
  1494. #define    P_NONE       ((phase_t) 2.0)    /* magic value:  none specified    */
  1495. #define    P_NEXT       ((phase_t) 3.0)    /* magic value:  next quarter    */
  1496. #define    P_ANY       ((phase_t) 4.0)    /* magic value:  any  quarter    */
  1497.  
  1498. #define    TWOPI       (2 * M_PI)        /* hope compiler makes it a const */
  1499. #define    EPSILON       (0.00001)        /* a small real number          */
  1500.  
  1501.  
  1502. /*********************************************************************
  1503.  * GLOBALS FOR SELECTING QUARTER BY NAME:
  1504.  */
  1505.  
  1506. struct    {
  1507.     char    *name;        /* name of quarter      */
  1508.     phase_t    phase;        /* equivalent phase value */
  1509. } quarter [] = {
  1510.     { "new",   P_NEW   },
  1511.     { "first", P_FIRST },
  1512.     { "full",  P_FULL  },
  1513.     { "last",  P_LAST  },
  1514.     { "next",  P_NEXT  },
  1515.     { "any",   P_ANY   },
  1516.     { CPNULL,  P_NONE  },    /* marks end */
  1517. };
  1518.  
  1519.  
  1520. /*********************************************************************
  1521.  * GLOBALS FOR DRAWING PICTURE:
  1522.  *
  1523.  * For greatest accuracy, centercol and halfcols are kept as decimal numbers
  1524.  * and truncating to a whole column position happens as late as possible.
  1525.  */
  1526.  
  1527. double    xyratio = 0.5;            /* X/Y aspect ratio of display    */
  1528. int    lines;                /* picture height        */
  1529. double    halfcols;            /* 1/2 of picture width        */
  1530. double    centercol;            /* center column, base 0    */
  1531.  
  1532. #define    LINES         24        /* default lines on display    */
  1533. #define    COLUMNS         80        /* default columns on display    */
  1534. #define    PICTFRACT    0.8        /* fraction of lines to use    */
  1535.  
  1536. #define    LINESVAR    "LINES"        /* environment variable names    */
  1537. #define    COLUMNSVAR    "COLUMNS"
  1538.  
  1539. #define    CH_LEFTEDGE    '('        /* left edge of moon        */
  1540. #define    CH_RIGHTEDGE    ')'        /* right edge of moon        */
  1541. #define    CH_LIT        '@'        /* lit part of moon        */
  1542.  
  1543.  
  1544. phase_t    ParseQuarter();
  1545. time_t    ParseTime();
  1546. long    ParseIncrement();
  1547. time_t    NextQuarter();
  1548. phase_t    Phase();
  1549. double    Mod2Pi();
  1550. char *    DateString();
  1551. char *    PhaseString();
  1552.  
  1553.  
  1554. /************************************************************************
  1555.  * M A I N
  1556.  *
  1557.  * Parse options and arguments and print the requested information.
  1558.  */
  1559.  
  1560. PROC main (argc, argv)
  1561. REG    int    argc;
  1562. REG    char    **argv;
  1563. {
  1564. extern    int    optind;            /* from getopt()        */
  1565. extern    char    *optarg;        /* from getopt()        */
  1566. REG    int    option;            /* option "letter"        */
  1567.  
  1568. REG    time_t    basetime;        /* base for -U option        */
  1569. REG    time_t    attime;            /* time to use            */
  1570. REG    time_t    endtime;        /* end of time range        */
  1571.     int    have_endtime = FALSE;    /* flag:  was one specified?    */
  1572. REG    long    increment = SECperDAY;    /* step size in seconds        */
  1573.  
  1574.     phase_t    phase;            /* moon phase, P_NEW..P_NEXTNEW    */
  1575.     phase_t    wantphase = P_NONE;    /* quarter desired, if any    */
  1576.  
  1577.     double    atof();
  1578.     long    time();
  1579.  
  1580. #ifdef GET_EPOCH
  1581.     GetEpoch();            /* does not return */
  1582. #endif;
  1583.  
  1584. /*
  1585.  * PARSE OPTIONS:
  1586.  */
  1587.  
  1588.     myname = *argv;
  1589.  
  1590.     while ((option = getopt (argc, argv, "ntTuUpdDx:")) != EOF)
  1591.     {
  1592.         switch (option)
  1593.         {
  1594.         case 'n':    nflag = TRUE;    break;
  1595.         case 't':    tflag = TRUE;    break;
  1596.         case 'T':    Tflag = TRUE;    break;
  1597.         case 'u':    uflag = TRUE;    break;
  1598.         case 'U':    Uflag = TRUE;    break;
  1599.         case 'p':    pflag = TRUE;    break;
  1600.  
  1601.         case 'd':    dflag = TRUE;    break;
  1602.         case 'D':    Dflag = TRUE;    break;
  1603.  
  1604.         case 'x':    if ((xyratio = atof (optarg)) <= 0)
  1605.                 Error (NOUSAGE, "invalid X/Y ratio with -x option");
  1606.             break;
  1607.  
  1608.         default:    Usage();
  1609.         }
  1610.     }
  1611.  
  1612.     argc -= optind;
  1613.     argv += optind;
  1614.  
  1615. /*
  1616.  * MORE OPTION CHECKS:
  1617.  */
  1618.  
  1619.     if ((! nflag) && (! tflag) && (uflag || Uflag || (! pflag)))
  1620.         Tflag = TRUE;                /* use default */
  1621.  
  1622.     if (dflag && Dflag)
  1623.         Error (NOUSAGE, "only one of -d and -D is allowed");
  1624.  
  1625.     if ((dflag || Dflag) && ! (nflag || tflag || Tflag))
  1626.         Error (NOUSAGE, "-d and -D make no sense without one of -ntT");
  1627.  
  1628.     if (pflag)
  1629.         SetPictSize();
  1630.  
  1631. /*
  1632.  * PARSE QUARTER ARGUMENT:
  1633.  */
  1634.  
  1635.     if ((argc >= 1)                        /* have arg   */
  1636.      && ((wantphase = ParseQuarter (argv [0])) != P_NONE))    /* recognized */
  1637.     {
  1638.         argv++;            /* skip argument for quarter */
  1639.  
  1640.         if (--argc >= 3)
  1641.         {
  1642.         Error (NOUSAGE, "increment is not allowed with quarter \
  1643. specified too");
  1644.         }
  1645.     }
  1646.  
  1647. /*
  1648.  * PARSE TIME, ENDTIME, AND INCREMENT ARGUMENTS:
  1649.  */
  1650.  
  1651.     currtime = (time_t) time ((long *) 0);
  1652.  
  1653.     if (argc == 0)                    /* none given */
  1654.         attime = currtime;
  1655.     else
  1656.     {
  1657.         attime = ParseTime (argv [0]);    /* use given time */
  1658.  
  1659.         if (argc >= 2)
  1660.         {
  1661.         if ((endtime = ParseTime (argv [1])) < attime)
  1662.             Error (NOUSAGE, "endtime must be at or after start time");
  1663.  
  1664.         have_endtime = TRUE;
  1665.  
  1666.         if (argc >= 3)
  1667.             increment = ParseIncrement (argc - 2, argv [2], argv [3]);
  1668.         }
  1669.     }
  1670.  
  1671. /*
  1672.  * YET MORE OPTION CHECKS:
  1673.  */
  1674.  
  1675.     if (uflag && (wantphase == P_NONE) && (argc == 0))
  1676.     {
  1677.         Error (NOUSAGE, "-u makes no sense without a quarter and/or \
  1678. time specified");
  1679.     }
  1680.  
  1681.     if (Uflag && ((wantphase == P_NONE) || (argc == 0)))
  1682.     {
  1683.         Error (NOUSAGE, "-U makes no sense without a quarter and a \
  1684. time specified");
  1685.     }
  1686.  
  1687. /*
  1688.  * FIGURE INITIAL PHASE:
  1689.  */
  1690.  
  1691.     phase = Phase (attime);
  1692.  
  1693. /*
  1694.  * PRINT PHASE INFORMATION FOR ONE TIME ONLY:
  1695.  */
  1696.  
  1697.     if (! have_endtime)
  1698.     {
  1699.         basetime = attime;
  1700.  
  1701.         if (wantphase != P_NONE)    /* advance to specified phase */
  1702.         attime = NextQuarter (attime, & phase, & wantphase);
  1703.  
  1704.         PrintAll (basetime, attime, phase);
  1705.     }
  1706.  
  1707. /*
  1708.  * PRINT PHASE INFORMATION FOR A SERIES OF TIMES:
  1709.  */
  1710.  
  1711.     else if (wantphase == P_NONE)
  1712.     {
  1713.         while (TRUE)            /* until break */
  1714.         {
  1715.         PrintAll ((time_t) 0, attime, phase);    /* basetime is unused */
  1716.  
  1717.         if ((attime += increment) > endtime)
  1718.             break;
  1719.  
  1720.         phase = Phase (attime);
  1721.         }
  1722.     }
  1723.  
  1724. /*
  1725.  * PRINT PHASE INFORMATION FOR A SERIES OF PHASES:
  1726.  */
  1727.  
  1728.     else
  1729.     {
  1730.         basetime = attime;
  1731.  
  1732.         while ((attime = NextQuarter (attime, & phase, & wantphase))
  1733.            <= endtime)
  1734.         {
  1735.         PrintAll (basetime, attime, phase);
  1736.         basetime = attime;
  1737.         }
  1738.     }
  1739.  
  1740.     exit (OKEXIT);
  1741.  
  1742. } /* main */
  1743.  
  1744.  
  1745. /************************************************************************
  1746.  * S E T   P I C T   S I Z E
  1747.  *
  1748.  * Given global xyratio, set picture size and center (globals lines, halfcols,
  1749.  * centercol) based on screen size (default values or those from environment
  1750.  * variables), with various adjustments:
  1751.  *
  1752.  * - use a fraction (PICTFRACT) of the total display lines;
  1753.  * - scale the number of columns to match the number of lines using xyratio, or
  1754.  * - reduce lines to fit if the number of columns is insufficient (try to keep
  1755.  *   the picture "square").
  1756.  *
  1757.  * Error out if the result is too small (less than three lines or columns).
  1758.  */
  1759.  
  1760. PROC SetPictSize()
  1761. {
  1762. REG    double    columns;    /* number in picture     */
  1763.     double    trycols;    /* test value of columns */
  1764.     char    *cp;        /* temporary pointer     */
  1765.     char    *getenv();
  1766.  
  1767. /*
  1768.  * GET SCREEN SIZE:
  1769.  */
  1770.  
  1771.     lines   = ((cp = getenv (LINESVAR))   == CPNULL) ? LINES   : atoi (cp);
  1772.     columns = ((cp = getenv (COLUMNSVAR)) == CPNULL) ? COLUMNS : atoi (cp);
  1773.  
  1774. /*
  1775.  * MAKE ADJUSTMENTS:
  1776.  *
  1777.  * Divide the number of columns by two for a zero-based center value.  For
  1778.  * halfcols, subtract EPSILON from columns to avoid reaching the column after
  1779.  * the last one.
  1780.  */
  1781.  
  1782.     lines *= PICTFRACT;            /* truncates */
  1783.  
  1784.     centercol = columns / 2;        /* before more adjusting */
  1785.  
  1786.     if (columns >= (trycols = lines / xyratio))    /* typical case */
  1787.         columns = trycols;
  1788.     else
  1789.         lines = columns * xyratio;        /* shrink lines */
  1790.  
  1791.     halfcols = (columns - EPSILON) / 2;
  1792.  
  1793. /*
  1794.  * CHECK IF BIG ENOUGH:
  1795.  */
  1796.  
  1797.     if ((lines < 3) || (columns < 3))
  1798.     {
  1799.         Error (NOUSAGE, "too few lines or columns for picture; check %s \
  1800. and %s vars", LINESVAR, COLUMNSVAR);
  1801.     }
  1802.  
  1803. } /* SetPictSize */
  1804.  
  1805.  
  1806. /************************************************************************
  1807.  * P A R S E   Q U A R T E R
  1808.  *
  1809.  * Given a string alleged to match the string in one of the elements of global
  1810.  * struct quarter[], search for that element.  If found, return the phase value
  1811.  * in the element, else return P_NONE.
  1812.  */
  1813.  
  1814. PROC phase_t ParseQuarter (string)
  1815. REG    char    *string;
  1816. {
  1817. REG    int    index = 0;        /* in struct quarter[] */
  1818.  
  1819.     while (strcmp (string, quarter [index] . name))     /* no match    */
  1820.         if ((quarter [++index] . name) == CPNULL)     /* end of list    */
  1821.         return (P_NONE);
  1822.  
  1823.     return (quarter [index] . phase);
  1824.  
  1825. } /* ParseQuarter */
  1826.  
  1827.  
  1828. /************************************************************************
  1829.  * P A R S E   T I M E
  1830.  *
  1831.  * Given a string alleged to contain a date string, and global currtime, parse
  1832.  * the string and return the equivalent system clock time in seconds.  Fill in
  1833.  * default values for missing time elements using currtime.  Error out if
  1834.  * parsing fails.
  1835.  *
  1836.  * Due to a deficiency in tzset(3) (extern timezone is always positive),
  1837.  * timezones are assumed to be west of Greenwich.
  1838.  */
  1839.  
  1840. PROC time_t ParseTime (string)
  1841.     char    *string;
  1842. {
  1843. REG    struct    parsedate *pd;
  1844.  
  1845. /*
  1846.  * PARSE DATE STRING, CHECK FOR ERROR:
  1847.  */
  1848.  
  1849.     pd = parsedate (string);
  1850.  
  1851.     if ((pd -> error) != CPNULL)        /* some sort of failure */
  1852.     {
  1853.         Error (NOUSAGE, "invalid date specified (was it a single, quoted \
  1854. argument?):\n%s\n%*s^",
  1855.            string, (pd -> error) - string, "");  /* pointer under it */
  1856.     }
  1857.  
  1858. /*
  1859.  * MISSING TIME ELEMENTS; SUPPLY DEFAULTS:
  1860.  */
  1861.  
  1862.     if ((pd -> unixtime) < 0)
  1863.     {
  1864.         if (((pd -> year) < 0) || ((pd -> month) < 0) || ((pd -> day) < 0))
  1865.         {
  1866.         struct    tm *tm = localtime (& currtime);
  1867.  
  1868.         if ((pd -> year)  < 0)    (pd -> year)  = tm -> tm_year + 1900;
  1869.         if ((pd -> month) < 0)    (pd -> month) = tm -> tm_mon  + 1;
  1870.         if ((pd -> day)   < 0)    (pd -> day)   = tm -> tm_mday;
  1871.         }
  1872.  
  1873.         if ((pd -> hour)   < 0)    (pd -> hour)   = 0;
  1874.         if ((pd -> minute) < 0)    (pd -> minute) = 0;
  1875.         if ((pd -> second) < 0)    (pd -> second) = 0;
  1876.  
  1877.         if ((pd -> zone) == -1)
  1878.         {
  1879.         (void) tzset();        /* sets extern timezone and daylight */
  1880.  
  1881.         (pd -> zone) =        /* value in minutes, negative west */
  1882.             - ((timezone - (daylight ? SECperHOUR : 0)) / SECperMIN);
  1883.         }
  1884.  
  1885. /*
  1886.  * RECOMPUTE TIME USING DEFAULT ELEMENTS:
  1887.  */
  1888.  
  1889.         compute_unixtime (pd);
  1890.  
  1891.         if ((pd -> unixtime) < 0)        /* should never happen */
  1892.         Error (NOUSAGE, "cannot fill in default time values (reason \
  1893. unknown)");
  1894.  
  1895.     } /* if */
  1896.  
  1897.     return ((time_t) (pd -> unixtime));
  1898.  
  1899. } /* ParseTime */
  1900.  
  1901.  
  1902. /************************************************************************
  1903.  * P A R S E   I N C R E M E N T
  1904.  *
  1905.  * Given a string count and two strings alleged to contain an integer followed
  1906.  * by a time unit (d(ays), h(ours), m(inutes), or s(econds)), either both in
  1907.  * the first string or the number in the first string and the unit in the
  1908.  * second string, parse the string(s) and return the equivalent number of
  1909.  * seconds.  Ignore whitespace before and after the time value and before the
  1910.  * time unit.  Accept any time unit string which begins with a recognized
  1911.  * letter.  Error out if:
  1912.  *
  1913.  * - the number in the first string is invalid
  1914.  * - the unit does not appear or is invalid
  1915.  * - a unit appears in the first string and the string count is two or more
  1916.  * - the string count is three or more
  1917.  *
  1918.  * Recognizing the integer and time unit as two different arguments is an
  1919.  * undocumented usability feature.
  1920.  */
  1921.  
  1922. PROC long ParseIncrement (count, string1, string2)
  1923.     int    count;
  1924.     char    *string1, *string2;
  1925. {
  1926. REG    char    *cp = string1;    /* place in string */
  1927. REG    long    result;        /* to return       */
  1928.  
  1929.     long    atol();
  1930.  
  1931. /*
  1932.  * CONVERT, CHECK, AND SKIP NUMBER:
  1933.  */
  1934.  
  1935.     while (isspace (*cp))    cp++;
  1936.  
  1937.     if ((result = atol (cp)) <= 0)
  1938.         Error (USAGE, "invalid increment value");
  1939.  
  1940.     while (isdigit (*cp))    cp++;
  1941.     while (isspace (*cp))    cp++;
  1942.  
  1943. /*
  1944.  * LOOK FOR TIME UNIT:
  1945.  */
  1946.  
  1947.     if (*cp == CHNULL)        /* end of string1 */
  1948.     {
  1949.         count--;
  1950.         cp = string2;
  1951.  
  1952.         while (isspace (*cp))
  1953.         cp++;
  1954.     }
  1955.  
  1956.     if (count < 1)
  1957.         Error (USAGE, "missing time unit on increment value");
  1958.  
  1959.     if (count > 1)
  1960.         Error (USAGE, "too many arguments specified");
  1961.  
  1962. /*
  1963.  * CHECK TIME UNIT:
  1964.  */
  1965.  
  1966.     switch (*cp)
  1967.     {
  1968.     case 'd':   result *= SECperDAY;    break;
  1969.     case 'h':   result *= SECperHOUR;    break;
  1970.     case 'm':   result *= SECperMIN;    break;
  1971.     case 's':    /* do nothing */        break;
  1972.     default:    Error (USAGE, "invalid suffix on increment value");
  1973.     }
  1974.  
  1975.     return (result);
  1976.  
  1977. } /* ParseIncrement */
  1978.  
  1979.  
  1980. /************************************************************************
  1981.  * N E X T   Q U A R T E R
  1982.  *
  1983.  * Given a system clock time in seconds, a pointer to the moon phase at that
  1984.  * time (P_NEW..P_NEXTNEW), and a pointer to the wanted phase value (quarter
  1985.  * moon, which can be a special value P_NONE, P_NEXT, or P_ANY to mean any
  1986.  * quarter is acceptable), find and return the time of the next specified
  1987.  * quarter to the nearest second, and change the current phase to match the
  1988.  * phase of that event.  If *wantphasep is P_NEXT, set it to the next specific
  1989.  * value.
  1990.  *
  1991.  * Note:  If *wantphasep is not a special value, it can actually be any legal
  1992.  * value, not just quarter values.
  1993.  *
  1994.  * For lack of better understanding of the math involved in determining when
  1995.  * the quarter occurs, call Phase() repeatedly to approximation search to the
  1996.  * time which is closest to the event.  Always return the first time after the
  1997.  * initial time whose phase value is at or after the desired phase value.
  1998.  * Assume successive times produce monotonically increasing Phase() values
  1999.  * (except for rolling past P_NEXTNEW back to P_NEW).
  2000.  *
  2001.  * The approximation method is similar but not identical to Newtonian.  It does
  2002.  * not know or use the actual slope at each point in the time-phase curve, but
  2003.  * revises the slope based on each approximation.  This doesn't make a huge
  2004.  * difference, since the moon's orbit isn't very eccentric, but tests show it
  2005.  * saves about two calls to Phase() each time.
  2006.  */
  2007.  
  2008. PROC time_t NextQuarter (attime, phasep, wantphasep)
  2009. REG    time_t    attime;
  2010.     phase_t    *phasep;
  2011.     phase_t    *wantphasep;
  2012. {
  2013. REG    phase_t    prevphase;        /* previous approximation    */
  2014. REG    phase_t    phase      = *phasep;    /* current values        */
  2015. REG    phase_t    wantphase = *wantphasep;
  2016. REG    phase_t    wrapphase;        /* "wrapped" value, see below    */
  2017. REG    long    deltatime;        /* between two approximations    */
  2018. REG    double    invslope;        /* inverse of slope of curve    */
  2019.  
  2020. /*
  2021.  * IF ANY QUARTER IS ACCEPTABLE, SET NEXT PHASE TO FIND:
  2022.  */
  2023.  
  2024.     if ((wantphase == P_NONE)
  2025.      || (wantphase == P_NEXT)
  2026.      || (wantphase == P_ANY ))
  2027.     {
  2028.         wantphase = (phase < P_FIRST) ? P_FIRST :
  2029.             (phase < P_FULL ) ? P_FULL  :
  2030.             (phase < P_LAST ) ? P_LAST  :
  2031.                         P_NEW;    /* wrap around */
  2032.  
  2033.         if (*wantphasep == P_NEXT)
  2034.         *wantphasep = wantphase;    /* change caller's value */
  2035.     }
  2036.  
  2037. /*
  2038.  * COMPUTE FIRST APPROXIMATE TIME OF WANTED PHASE:
  2039.  *
  2040.  * The initial approximate time may be as much as one cycle ahead, in the case
  2041.  * where wantphase == phase now.
  2042.  *
  2043.  * Take care to "wrap" phase values around P_NEW.  If a phase value is late in
  2044.  * the cycle and the goal is P_NEW, use a value less than P_NEW.
  2045.  */
  2046.  
  2047.     attime += SECperMON *
  2048.           (wantphase - phase + ((wantphase <= phase) ? P_MONTH : 0));
  2049.  
  2050. #define    WRAP(phase) \
  2051.     (((wantphase == P_NEW) && (phase > P_LAST)) ? (phase - P_MONTH) : phase)
  2052.  
  2053.     phase      = Phase (attime);
  2054.     wrapphase = WRAP (phase);
  2055.     invslope  = SECperMON / P_MONTH;  /* initially over a whole month */
  2056.  
  2057. /*
  2058.  * SEARCH FOR TIME OF NEXT EVENT:
  2059.  */
  2060.  
  2061.     while (TRUE)            /* until break */
  2062.     {
  2063.         deltatime = invslope * (wantphase - wrapphase);    /* truncates */
  2064.  
  2065. #ifdef DEBUG
  2066.         printf ("wp: %.2f at: %d p: %12.9f dt: %8d is: %f\n",
  2067.             wantphase, attime, wrapphase, deltatime, invslope);
  2068. #endif
  2069.         if (deltatime == 0)        /* as close as we can get */
  2070.         break;
  2071.  
  2072.         prevphase = wrapphase;
  2073.         attime   += deltatime;        /* positive or negative */
  2074.         phase     = Phase (attime);
  2075.         wrapphase = WRAP (phase);
  2076.         invslope  = deltatime / (wrapphase - prevphase);
  2077.     }
  2078.  
  2079. /*
  2080.  * DECREMENT/INCREMENT TO EXACT TIME:
  2081.  *
  2082.  * For repeatability, find the first time on which phase >= wantphase.  The
  2083.  * following code is overkill in all but the rarest cases.  Usually the attime
  2084.  * already found is exactly right or just one second too low.
  2085.  */
  2086.  
  2087.     while (wrapphase > wantphase)
  2088.     {
  2089.         phase     = Phase (--attime);
  2090.         wrapphase = WRAP (phase);
  2091. #ifdef DEBUG
  2092.         printf ("wp: %.2f at: %d p: %12.9f (down)\n",
  2093.             wantphase, attime, wrapphase);
  2094. #endif
  2095.     }
  2096.  
  2097.     while (wrapphase < wantphase)
  2098.     {
  2099.         phase     = Phase (++attime);
  2100.         wrapphase = WRAP (phase);
  2101. #ifdef DEBUG
  2102.         printf ("wp: %.2f at: %d p: %12.9f (up)\n",
  2103.             wantphase, attime, wrapphase);
  2104. #endif
  2105.     }
  2106.  
  2107. /*
  2108.  * RETURN:
  2109.  */
  2110.  
  2111.     *phasep = phase;
  2112.     return (attime);
  2113.  
  2114. } /* NextQuarter */
  2115.  
  2116.  
  2117. /************************************************************************
  2118.  * P H A S E
  2119.  *
  2120.  * Given a system clock time in seconds, compute and return the phase of the
  2121.  * moon at that time, P_NEW (inclusive) .. P_FULL .. P_NEXTNEW (exclusive).
  2122.  *
  2123.  * Section numbers in comments refer to "Practical Astronomy with Your
  2124.  * Calculator".  Unfortunately, an earlier version of this program provided no
  2125.  * explanation of the calculations or variable names.  The descriptions of
  2126.  * variable names are guesses.
  2127.  *
  2128.  * It appears this code figures the geocentric longitudes of the sun and moon
  2129.  * at the given time, using their actual geocentric orbits (ellipses).  Simple
  2130.  * geometry shows that the difference in their longitudes is also the portion
  2131.  * of the moon which is lit as seen from earth, assuming the sun's rays are
  2132.  * parallel at both earth and moon, which is very nearly true since the moon's
  2133.  * orbital radius is only about 0.2% of the earth's.
  2134.  *
  2135.  * This code seems to assume the orbits are coplanar, which might introduce
  2136.  * some small error since they're not.  (The difference is about 18 degrees.)
  2137.  *
  2138.  * Some numbers expressed as manifest constants should be parameterized, but
  2139.  * I'm not sure what they mean.  Some of their accuracy is overstated.  They
  2140.  * used to be in degrees, to only 2-4 digits, but I converted them to radians
  2141.  * without rounding.
  2142.  */
  2143.  
  2144. PROC phase_t Phase (attime)
  2145.     time_t    attime;
  2146. {
  2147. #define    EPOCH     473299200    /* 19841231.0 GMT in UNIX system time    */
  2148.  
  2149. /* All the longitudes are values in radians at EPOCH: */
  2150.  
  2151. #define    EPSILONg 4.88013905    /* solar ecliptic longitude        */
  2152. #define    RHOg     4.9337037632    /* solar ecliptic longitude of perigee    */
  2153. #define    e     0.01671542    /* solar orbit eccentricity        */
  2154. #define    lzero     0.3185558719    /* lunar mean longitude            */
  2155. #define    Pzero     3.3670470432    /* lunar mean longitude of perigee    */
  2156. #define    Nzero     0.963504179    /* lunar mean longitude of node        */
  2157. #define    lPerDay     0.2299715042    /* lunar longitude change per day    */
  2158.  
  2159.     double    days;        /* since EPOCH                */
  2160.     double    N;        /* radians of Earth orbit since EPOCH    */
  2161.     double    Msol;        /* sun position in orbit versus perigee    */
  2162.     double    sinMsol;    /* sin (Msol)                */
  2163.     double    Ec;        /* eccentricity correction        */
  2164.     double    LambdaSol;    /* ecliptic longitude of sun        */
  2165.     double    l;        /* lunar mean longitude            */
  2166.     double    Mm;        /* moon position in orbit vs. perigee    */
  2167.      /*    double    Nm;        /* (not used)                */
  2168.     double    Ev;        /* based on angle between sun and moon?    */
  2169.     double    Ac;        /* correction factor?            */
  2170.     double    A3;        /* correction factor?            */
  2171.     double    Mmprime;    /* corrected Mm                */
  2172.     double    A4;        /* correction factor?            */
  2173.     double    lprime;        /* corrected lunar longitude        */
  2174.     double    V;        /* correction factor?            */
  2175.     double    ldprime;    /* recorrected lunar longitude        */
  2176.     double    D;        /* sun - moon - Earth angle        */
  2177.  
  2178. /*
  2179.  * CALCULATE SOLAR LONGITUDE:
  2180.  */
  2181.  
  2182.     days      = ((double) (attime - EPOCH)) / SECperDAY;
  2183.  
  2184.     N      = TWOPI * days / DAYSperYEAR;            /* sec 42 #3  */
  2185.     Msol      = EPSILONg - RHOg + N;            /* sec 42 #4  */
  2186.     sinMsol      = sin (Msol);
  2187.     Ec      = 2 * e * sinMsol;                /* sec 42 #5  */
  2188.     LambdaSol = Mod2Pi (EPSILONg + N + Ec);            /* sec 42 #6  */
  2189.  
  2190. /*
  2191.  * CALCULATE LUNAR LONGITUDE:
  2192.  */
  2193.  
  2194.     l      = Mod2Pi (lzero +    (lPerDay      * days));    /* sec 61 #4  */
  2195.     Mm      = Mod2Pi (l - Pzero -    (0.0019443683 * days));    /* sec 61 #5  */
  2196.      /*    Nm      = Mod2Pi (Nzero -    (0.0009242199 * days));    /* sec 61 #6  */
  2197.  
  2198.     Ev      = 0.0222337493 * sin (2 * (l - LambdaSol) - Mm);
  2199.                                 /* sec 61 #7  */
  2200.     Ac      = 0.0032428218 * sinMsol;            /* sec 61 #8  */
  2201.     A3      = 0.0064577182 * sinMsol;
  2202.     Mmprime      = Mm + Ev - Ac - A3;                /* sec 61 #9  */
  2203.  
  2204.     Ec      = 0.1097567753 * sin (Mmprime);        /* sec 61 #10 */
  2205.     A4      = 0.0037350046 * sin (2 * Mmprime);        /* sec 61 #11 */
  2206.     lprime      = l + Ev + Ec - Ac + A4;            /* sec 61 #12 */
  2207.     V      = 0.0114895025 * sin (2 * (lprime - LambdaSol));
  2208.                                 /* sec 61 #13 */
  2209.  
  2210.     ldprime      = lprime + V;                    /* sec 61 #14 */
  2211.  
  2212. /*
  2213.  * CALCULATE AND RETURN PHASE:
  2214.  *
  2215.  * The difference angle is lunar - solar longitude because longitudes increase
  2216.  * counter clockwise.  (I wish I could include a simple drawing.)
  2217.  */
  2218.  
  2219.     D = ldprime - LambdaSol;                /* sec 63 #2 */
  2220.  
  2221.     return ((phase_t) (Mod2Pi (D) / TWOPI));
  2222.  
  2223. } /* Phase */
  2224.  
  2225.  
  2226. /************************************************************************
  2227.  * M O D   2   P I
  2228.  *
  2229.  * Given an angle, adjust it to be in the range 0 <= angle < TWOPI.
  2230.  */
  2231.  
  2232. PROC double Mod2Pi (angle)
  2233. REG    double    angle;
  2234. {
  2235.     if (((angle < 0) || (angle >= TWOPI))        /* if needed    */
  2236.      && ((angle = fmod (angle, TWOPI)) < 0))    /* was negative    */
  2237.     {
  2238.         angle += TWOPI;                /* bring in range */
  2239.     }
  2240.  
  2241.     return (angle);
  2242.  
  2243. } /* Mod2Pi */
  2244.  
  2245.  
  2246. /************************************************************************
  2247.  * P R I N T   A L L
  2248.  *
  2249.  * Given base and target system clock times in seconds, a moon phase
  2250.  * (P_NEW..P_NEXTNEW) at the target time, and global currtime, print various
  2251.  * output lines, depending on global options, to represent the target time and
  2252.  * phase, and the time until the target time and phase (and/or from the
  2253.  * basetime to them, if basetime != currtime).
  2254.  */
  2255.  
  2256. PROC PrintAll (basetime, attime, phase)
  2257.     time_t    basetime;
  2258.     time_t    attime;
  2259.     phase_t    phase;
  2260. {
  2261.     int    Uflag2 = (Uflag && (basetime != currtime));
  2262.     phase_t    basephase;        /* at basetime */
  2263.  
  2264.     char    *datestring;        /* fast values */
  2265.     char    *phasestring;
  2266.  
  2267. /*
  2268.  * PRINT "UNTIL" INFORMATION:
  2269.  */
  2270.  
  2271.     if (uflag)
  2272.         PrintDiff (currtime, attime, TRUE, Uflag2);
  2273.  
  2274.     if (Uflag2)
  2275.     {
  2276.         PrintDiff (basetime, attime, FALSE, FALSE);
  2277.  
  2278.         basephase = Phase (basetime);
  2279.  
  2280.         PrintLong (DateString (basetime),
  2281.             PhaseString (basephase), basephase, " until");
  2282.     }
  2283.  
  2284. /*
  2285.  * PRINT PHASE INFORMATION:
  2286.  */
  2287.  
  2288.     if (nflag || tflag || Tflag)
  2289.         datestring = DateString (attime);
  2290.  
  2291.     if (tflag || Tflag)
  2292.         phasestring = PhaseString (phase);
  2293.  
  2294.     if (nflag)    printf ("%s%f\n", datestring, phase);
  2295.     if (tflag)    printf ("%s%s\n", datestring, phasestring);
  2296.     if (Tflag)    PrintLong (datestring, phasestring, phase, "");
  2297.     if (pflag)    PrintPicture (phase);
  2298.  
  2299. } /* PrintAll */
  2300.  
  2301.  
  2302. /************************************************************************
  2303.  * P R I N T   D I F F
  2304.  *
  2305.  * Given base and target system clock times in seconds, a flag whether to
  2306.  * compute time direction, and a flag whether to print a suffix, describe the
  2307.  * amount of time between the given times.  If dirflag is FALSE, assume the
  2308.  * time difference is positive and print "from"; otherwise, note and handle the
  2309.  * case where the base time is after the current time and print "since" or
  2310.  * "until" accordingly.  If sufflag is FALSE, print no suffix, else print
  2311.  * ", and".
  2312.  */
  2313.  
  2314. PROC PrintDiff (basetime, attime, dirflag, sufflag)
  2315.     time_t    basetime;
  2316.     time_t    attime;
  2317.     int    dirflag;
  2318.     int    sufflag;
  2319. {
  2320.     char    *desc;                /* trailing description      */
  2321. REG    time_t    delta = attime - basetime;    /* difference, in seconds */
  2322. REG    time_t    delta2;                /* modified copy      */
  2323.     int    days;                /* whole days          */
  2324.     int    hours;                /* whole hours          */
  2325.     int    minutes;            /* whole minutes      */
  2326.  
  2327.     if (! dirflag)
  2328.         desc = "from";
  2329.  
  2330.     else if (delta >= 0)
  2331.         desc = "until";
  2332.  
  2333.     else
  2334.     {
  2335.         desc  = "since";
  2336.         delta = -delta;
  2337.     }
  2338.  
  2339.     delta2     = delta  + (SECperMIN / 2);    /* round up  */
  2340.     days     = delta2 / SECperDAY;        /* truncates */
  2341.     delta2    -= days   * SECperDAY;
  2342.     hours     = delta2 / SECperHOUR;        /* truncates */
  2343.     delta2    -= hours  * SECperHOUR;
  2344.     minutes     = delta2 / SECperMIN;        /* truncates */
  2345.  
  2346.     printf ("it is %d day%s, %d hour%s, %d minute%s (%.3f days, %.0f%% \
  2347. of a lunar cycle) %s%s\n",
  2348.         days,     ((days    == 1) ? "" : "s"),
  2349.         hours,     ((hours   == 1) ? "" : "s"),
  2350.         minutes, ((minutes == 1) ? "" : "s"),
  2351.         ((double) delta) / SECperDAY,
  2352.         100.0 * delta / SECperMON,
  2353.         desc,
  2354.         sufflag ? ", and" : "");
  2355.  
  2356. } /* PrintDiff */
  2357.  
  2358.  
  2359. /************************************************************************
  2360.  * P R I N T   L O N G
  2361.  *
  2362.  * Given strings describing the date and time and phase, a moon phase
  2363.  * (P_NEW..P_NEXTNEW), and a suffix string, print a long one-line description of
  2364.  * the date, time, and phase.
  2365.  *
  2366.  * Percent illuminated = 50 * (1 - cos (phase * TWOPI)) because any great
  2367.  * circle on the moon has that percentage illuminated, and the moon can be seen
  2368.  * as a stack of great circles (assuming coplanar orbits).
  2369.  */
  2370.  
  2371. PROC PrintLong (datestring, phasestring, phase, suffix)
  2372.     char    *datestring;
  2373.     char    *phasestring;
  2374.     phase_t    phase;
  2375.     char    *suffix;
  2376. {
  2377.     printf ("%sthe moon is %s (%.0f%% illuminated)%s\n",
  2378.         datestring, phasestring,
  2379.         50 * (1 - cos (phase * TWOPI)),
  2380.         suffix);
  2381.  
  2382. } /* PrintLong */
  2383.  
  2384.  
  2385. /************************************************************************
  2386.  * P R I N T   P I C T U R E
  2387.  *
  2388.  * Given a moon phase (P_NEW..P_NEXTNEW) and globals lines, halfcols, and
  2389.  * centercol, "draw" a picture of the moon phase as an array of characters,
  2390.  * with one blank line before and after.  Print one line at a time, from the
  2391.  * top to the bottom.  Each line is a slice through a "circle" which represents
  2392.  * the visible face of the moon.
  2393.  *
  2394.  * This problem is surprisingly complex due to corner cases:
  2395.  * - new and full moons
  2396.  * - appropriate rounding of terminator X to column position
  2397.  *
  2398.  * The start and end X positions of the lit part (terminator to edge or edge to
  2399.  * terminator) are:
  2400.  *
  2401.  *    P_NEW  < phase < P_FULL:     cos (angle) .. 1     (left  side: 1 -> -1)
  2402.  *    P_FULL < phase < P_NEXTNEW:  -1 .. -cos (angle)   (right side: 1 -> -1)
  2403.  *
  2404.  * where angle = phase * TWOPI.
  2405.  */
  2406.  
  2407. PROC PrintPicture (phase)
  2408.     phase_t    phase;
  2409. {
  2410.     double    yperline = 2.0 / lines;    /* Y step per output line    */
  2411. REG    double    ypos;            /* Y position in drawing, -1..1    */
  2412.  
  2413. REG    double    edgexpos;        /* X pos of edge of moon, 0..1    */
  2414. REG    double    termxpos = cos (phase * TWOPI);
  2415.                     /* X pos of terminator,  -1..1    */
  2416.  
  2417.     int    waxing =  (phase <= P_FULL);    /* flag:  waxing moon?    */
  2418.  
  2419.     int    new    = ((phase <= P_NEW     + P_PRECISION)
  2420.                || (phase >= P_NEXTNEW - P_PRECISION));
  2421.  
  2422.     int    full   = ((phase >= P_FULL    - P_PRECISION)
  2423.                && (phase <= P_FULL    + P_PRECISION));
  2424.  
  2425. REG    int    currcol;        /* current column        */
  2426. REG    int    edgelcol, edgercol;    /* left and right edge columns    */
  2427. REG    int    litlcol,  litrcol;    /* left and right lit part cols    */
  2428.  
  2429. /*
  2430.  * X POS TO COLUMN NUMBER MACRO:
  2431.  *
  2432.  * Note that small negative numbers truncate to 0.
  2433.  */
  2434.  
  2435. #define    COLPOS(xpos, round) ((int) (centercol + ((xpos) * halfcols) + round))
  2436.  
  2437. /*
  2438.  * DRAW EACH LINE:
  2439.  */
  2440.  
  2441.     puts ("");
  2442.  
  2443.     for (ypos = 1.0 - (yperline / 2); ypos >= -1.0; ypos -= yperline)
  2444.     {
  2445.  
  2446. /*
  2447.  * FIGURE EDGE AND LIT-PART COLUMN NUMBERS:
  2448.  *
  2449.  * For edge columns, simply truncate to the whole column number if xpos is
  2450.  * anywhere in the column because the whole column (character cell) is used to
  2451.  * print one char.  However, handle the terminator column carefully to avoid
  2452.  * round-off error into an excess column.  Only mark a column as lit if more
  2453.  * than half of it is within the lit part, by adding (if waxing moon) 0.5 to or
  2454.  * subtracting (if waning) 0.5 from the X position before truncating.
  2455.  */
  2456.  
  2457.         edgexpos = sqrt (1.0 - (ypos * ypos));
  2458.  
  2459.         edgelcol = COLPOS (-edgexpos, 0);
  2460.         edgercol = COLPOS ( edgexpos, 0);
  2461.  
  2462.         if (new)
  2463.         {
  2464.         litrcol = -1;            /* no lit portion */
  2465.         }
  2466.         else if (full)
  2467.         {
  2468.         litlcol = edgelcol;
  2469.         litrcol = edgercol;
  2470.         }
  2471.         else if (waxing)
  2472.         {
  2473.         litrcol = edgercol;        /* right edge is lit */
  2474.  
  2475.         if ((litlcol = COLPOS (edgexpos * termxpos, 0.5)) > litrcol)
  2476.             litlcol = litrcol;        /* consistent single column */
  2477.         }
  2478.         else /* waning */
  2479.         {
  2480.         litlcol = edgelcol;        /* left edge is lit */
  2481.  
  2482.         if ((litrcol = COLPOS (edgexpos * (-termxpos), -0.5)) < litlcol)
  2483.             litrcol = litlcol;
  2484.         }
  2485.  
  2486. /*
  2487.  * PRINT A LINE OF CHARS:
  2488.  */
  2489.  
  2490.         for (currcol = 0; currcol <= edgercol; currcol++)
  2491.         {
  2492.         putchar (
  2493.             ((currcol <= litrcol) && (currcol >= litlcol)) ? CH_LIT :
  2494.             (currcol == edgelcol) ? CH_LEFTEDGE  :
  2495.             (currcol == edgercol) ? CH_RIGHTEDGE : ' ');
  2496.         }
  2497.  
  2498.         putchar ('\n');
  2499.  
  2500.     } /* for */
  2501.  
  2502.     puts ("");
  2503.  
  2504. } /* PrintPicture */
  2505.  
  2506.  
  2507. /************************************************************************
  2508.  * D A T E   S T R I N G
  2509.  *
  2510.  * Given a system clock time in seconds, return a pointer to a date/time string
  2511.  * representing the time as null (if global dflag), YYMMDD HH:MM (default,
  2512.  * rounded to nearest minute, in the local timezone), or in ctime(3) format (if
  2513.  * Dflag), in static memory which should not be altered by the caller.
  2514.  * Non-null return values are followed by two blanks.
  2515.  */
  2516.  
  2517. PROC char * DateString (attime)
  2518.     time_t    attime;
  2519. {
  2520. REG    struct    tm *tm;        /* for long form */
  2521. static    char    result [CTIMELEN + 1];
  2522.  
  2523.     if (dflag)
  2524.     {
  2525.         result [0] = CHNULL;
  2526.     }
  2527.     else if (Dflag)
  2528.     {
  2529.         strncpy (result, ctime (& attime), CTIMELEN - 2);
  2530.         result [CTIMELEN - 2] = result [CTIMELEN - 1] = ' ';
  2531.         result [CTIMELEN] = CHNULL;
  2532.     }
  2533.     else
  2534.     {
  2535.         attime += (SECperMIN / 2);        /* for rounding */
  2536.         tm        = localtime (& attime);
  2537.  
  2538.         sprintf (result, SHORTDATEFORM,
  2539.             tm -> tm_year, (tm -> tm_mon) + 1, tm -> tm_mday,
  2540.             tm -> tm_hour,  tm -> tm_min);
  2541.     }
  2542.  
  2543.     return (result);
  2544.  
  2545. } /* DateString */
  2546.  
  2547.  
  2548. /************************************************************************
  2549.  * P H A S E   S T R I N G
  2550.  *
  2551.  * Given a moon phase (P_NEW..P_NEXTNEW), return a minimum string which
  2552.  * describes that phase, in private memory (caller should not overwrite it).
  2553.  */
  2554.  
  2555. PROC char * PhaseString (phase)
  2556. REG    phase_t    phase;
  2557. {
  2558.     return ((phase < P_NEW       + P_PRECISION) ? "new"        :
  2559.         (phase < P_FIRST   - P_PRECISION) ? "waxing crescent"    :
  2560.         (phase < P_FIRST   + P_PRECISION) ? "first quarter"    :
  2561.         (phase < P_FULL       - P_PRECISION) ? "waxing gibbous"    :
  2562.         (phase < P_FULL       + P_PRECISION) ? "full"        :
  2563.         (phase < P_LAST       - P_PRECISION) ? "waning gibbous"    :
  2564.         (phase < P_LAST       + P_PRECISION) ? "last quarter"    :
  2565.         (phase < P_NEXTNEW - P_PRECISION) ? "waning crescent"    :
  2566.                             "new");
  2567.  
  2568. } /* PhaseString */
  2569.  
  2570.  
  2571. /************************************************************************
  2572.  * E R R O R
  2573.  *
  2574.  * Given a usage flag and a message string and arguments, print an error
  2575.  * message to stderr.  If usage flag is USAGE, call Usage(), else exit with
  2576.  * ERREXIT.  Message is preceded by "<myname>:  " using global char *myname,
  2577.  * and followed by a newline.
  2578.  */
  2579.  
  2580. /* VARARGS2 */
  2581. PROC Error (usageflag, message, arg1, arg2, arg3, arg4)
  2582.     int    usageflag;
  2583.     char    *message;
  2584.     long    arg1, arg2, arg3, arg4;
  2585. {
  2586.     fprintf (stderr, "%s:  ", myname);
  2587.     fprintf (stderr, message, arg1, arg2, arg3, arg4);
  2588.     putc ('\n', stderr);
  2589.  
  2590.     if (usageflag == USAGE)
  2591.         Usage();
  2592.  
  2593.     exit (ERREXIT);
  2594.  
  2595. } /* Error */
  2596.  
  2597.  
  2598. /************************************************************************
  2599.  * U S A G E
  2600.  *
  2601.  * Print usage messages (char *usage[]) to stderr and exit with ERREXIT.
  2602.  * Each message is followed by a newline.
  2603.  */
  2604.  
  2605. PROC Usage()
  2606. {
  2607. REG    int    which = 0;        /* current line */
  2608.  
  2609.     while (usage [which] != CPNULL)
  2610.     {
  2611.         fprintf (stderr, usage [which++], myname);
  2612.         putc ('\n', stderr);
  2613.     }
  2614.  
  2615.     exit (ERREXIT);
  2616.  
  2617. } /* Usage */
  2618.  
  2619.  
  2620. #ifdef GET_EPOCH
  2621.  
  2622. /************************************************************************
  2623.  * G E T   E P O C H
  2624.  *
  2625.  * Given a date (Epoch) hardwired in (see below), binary search to find the
  2626.  * equivalent system clock time in seconds, then exit.
  2627.  *
  2628.  * This could be accomplished using parsedate() on a string, but this method
  2629.  * is more portable (and was already written).
  2630.  */
  2631.  
  2632. PROC GetEpoch()
  2633. {
  2634.     struct    tm *tm;
  2635.     long    time();
  2636.  
  2637.     time_t    trytime    = (time_t) time ((long *) 0);
  2638. REG    time_t    range    = trytime;
  2639.  
  2640.     int    e_year    = 84;        /* time of Epoch */
  2641.     int    e_yday    = 365;
  2642.     int    e_hour    = 0;
  2643.     int    e_min    = 0;
  2644.     int    e_sec    = 0;
  2645.  
  2646.     int    d_year;            /* delta values */
  2647.     int    d_yday;
  2648.     int    d_hour;
  2649.     int    d_min;
  2650.     int    d_sec;
  2651.  
  2652. /*
  2653.  * TRY NEXT TIME:
  2654.  */
  2655.  
  2656.     while (TRUE)            /* until exit */
  2657.     {
  2658.         tm = gmtime (& trytime);
  2659.  
  2660.         d_year = e_year - (tm -> tm_year);
  2661.         d_yday = e_yday - (tm -> tm_yday);
  2662.         d_hour = e_hour - (tm -> tm_hour);
  2663.         d_min  = e_min  - (tm -> tm_min);
  2664.         d_sec  = e_sec  - (tm -> tm_sec);
  2665.  
  2666.         printf ("try: %9ld  delta: %3d %4d %3d %3d %3d  range: %9ld\n",
  2667.             trytime, d_year, d_yday, d_hour, d_min, d_sec, range);
  2668.  
  2669. /*
  2670.  * SEE IF DONE:
  2671.  */
  2672.  
  2673.         if ((d_year == 0) && (d_yday == 0)
  2674.          && (d_hour == 0) && (d_min  == 0) && (d_sec == 0))
  2675.         {
  2676.         printf ("in local time:  %s", ctime (& trytime));
  2677.         exit (OKEXIT);
  2678.         }
  2679.  
  2680. /*
  2681.  * ADJUST UP OR DOWN:
  2682.  */
  2683.  
  2684.         if (range > 1)
  2685.         range /= 2;
  2686.  
  2687.         if        (d_year > 0)    trytime += range;
  2688.         else if (d_year < 0)    trytime -= range;
  2689.         else if (d_yday > 0)    trytime += range;
  2690.         else if (d_yday < 0)    trytime -= range;
  2691.         else if (d_hour > 0)    trytime += range;
  2692.         else if (d_hour < 0)    trytime -= range;
  2693.         else if (d_min > 0)        trytime += range;
  2694.         else if (d_min < 0)        trytime -= range;
  2695.         else if (d_sec > 0)        trytime += range;
  2696.         else            trytime -= range;
  2697.  
  2698.     } /* while */
  2699.  
  2700. } /* GetEpoch */
  2701.  
  2702. #endif /* GET_EPOCH */
  2703. @EOF
  2704. if test "`wc -lwc <moon/moon.c`" != '   1320   6103  36836'
  2705. then
  2706.     echo ERROR: wc results of moon/moon.c are `wc -lwc <moon/moon.c` should be    1320   6103  36836
  2707. fi
  2708.  
  2709. chmod 444 moon/moon.c
  2710.  
  2711. echo x - moon/test.out
  2712. sed 's/^@//' >moon/test.out <<'@EOF'
  2713. === expect errors ===
  2714.  
  2715. $ moon -?
  2716. moon: illegal option -- ?
  2717. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2718.  
  2719. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2720. -t (terse text) print only the date, time, and phase name
  2721. -T (text) print date, time, and a description of the moon's phase (default)
  2722. -u (until) tell how long from now until each specified event
  2723. -U (until) tell how long from time to quarter and/or each quarter to next
  2724. -p (picture) draw a picture of the moon's phase using characters
  2725. -d don't print the date and time with -ntT, just the phase information
  2726. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2727. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2728.  
  2729. quarter:    any of "new first full last next any"; if specified, tell when
  2730.             that quarter next occurs, or all occurrences in the time range
  2731. time:       value to use (default:  current system time)
  2732. endtime:    print phase or quarter information from time to endtime
  2733. increment:  amount to step through range of times; must end in one of:
  2734.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2735.  
  2736. Enter date/time in almost any reasonable format, quoted if it contains
  2737. blanks.  Year, month, and day default to current; hour, minute, and second to
  2738. zero; timezone to current, including DST if in effect (assumed west of
  2739. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2740. returns: 1
  2741.  
  2742. $ moon -x 0
  2743. moon:  invalid X/Y ratio with -x option
  2744. returns: 1
  2745.  
  2746. $ moon -dD
  2747. moon:  only one of -d and -D is allowed
  2748. returns: 1
  2749.  
  2750. $ moon -pd
  2751. moon:  -d and -D make no sense without one of -ntT
  2752. returns: 1
  2753.  
  2754. $ moon -pD
  2755. moon:  -d and -D make no sense without one of -ntT
  2756. returns: 1
  2757.  
  2758. $ moon new 890401 890402 1d
  2759. moon:  increment is not allowed with quarter specified too
  2760. returns: 1
  2761.  
  2762. $ moon 890402 890401
  2763. moon:  endtime must be at or after start time
  2764. returns: 1
  2765.  
  2766. $ moon -u
  2767. moon:  -u makes no sense without a quarter and/or time specified
  2768. returns: 1
  2769.  
  2770. $ moon -U new
  2771. moon:  -U makes no sense without a quarter and a time specified
  2772. returns: 1
  2773.  
  2774. $ moon -U 890401
  2775. moon:  -U makes no sense without a quarter and a time specified
  2776. returns: 1
  2777.  
  2778. $ moon 890431
  2779. moon:  invalid date specified (was it a single, quoted argument?):
  2780. 890431
  2781.       ^
  2782. returns: 1
  2783.  
  2784. $ moon may 1
  2785. moon:  invalid date specified (was it a single, quoted argument?):
  2786. may
  2787.    ^
  2788. returns: 1
  2789.  
  2790. $ moon 890401 890402 -1
  2791. moon:  invalid increment value
  2792. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2793.  
  2794. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2795. -t (terse text) print only the date, time, and phase name
  2796. -T (text) print date, time, and a description of the moon's phase (default)
  2797. -u (until) tell how long from now until each specified event
  2798. -U (until) tell how long from time to quarter and/or each quarter to next
  2799. -p (picture) draw a picture of the moon's phase using characters
  2800. -d don't print the date and time with -ntT, just the phase information
  2801. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2802. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2803.  
  2804. quarter:    any of "new first full last next any"; if specified, tell when
  2805.             that quarter next occurs, or all occurrences in the time range
  2806. time:       value to use (default:  current system time)
  2807. endtime:    print phase or quarter information from time to endtime
  2808. increment:  amount to step through range of times; must end in one of:
  2809.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2810.  
  2811. Enter date/time in almost any reasonable format, quoted if it contains
  2812. blanks.  Year, month, and day default to current; hour, minute, and second to
  2813. zero; timezone to current, including DST if in effect (assumed west of
  2814. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2815. returns: 1
  2816.  
  2817. $ moon 890401 890402 0d
  2818. moon:  invalid increment value
  2819. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2820.  
  2821. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2822. -t (terse text) print only the date, time, and phase name
  2823. -T (text) print date, time, and a description of the moon's phase (default)
  2824. -u (until) tell how long from now until each specified event
  2825. -U (until) tell how long from time to quarter and/or each quarter to next
  2826. -p (picture) draw a picture of the moon's phase using characters
  2827. -d don't print the date and time with -ntT, just the phase information
  2828. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2829. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2830.  
  2831. quarter:    any of "new first full last next any"; if specified, tell when
  2832.             that quarter next occurs, or all occurrences in the time range
  2833. time:       value to use (default:  current system time)
  2834. endtime:    print phase or quarter information from time to endtime
  2835. increment:  amount to step through range of times; must end in one of:
  2836.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2837.  
  2838. Enter date/time in almost any reasonable format, quoted if it contains
  2839. blanks.  Year, month, and day default to current; hour, minute, and second to
  2840. zero; timezone to current, including DST if in effect (assumed west of
  2841. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2842. returns: 1
  2843.  
  2844. $ moon 890401 890402 1
  2845. moon:  missing time unit on increment value
  2846. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2847.  
  2848. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2849. -t (terse text) print only the date, time, and phase name
  2850. -T (text) print date, time, and a description of the moon's phase (default)
  2851. -u (until) tell how long from now until each specified event
  2852. -U (until) tell how long from time to quarter and/or each quarter to next
  2853. -p (picture) draw a picture of the moon's phase using characters
  2854. -d don't print the date and time with -ntT, just the phase information
  2855. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2856. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2857.  
  2858. quarter:    any of "new first full last next any"; if specified, tell when
  2859.             that quarter next occurs, or all occurrences in the time range
  2860. time:       value to use (default:  current system time)
  2861. endtime:    print phase or quarter information from time to endtime
  2862. increment:  amount to step through range of times; must end in one of:
  2863.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2864.  
  2865. Enter date/time in almost any reasonable format, quoted if it contains
  2866. blanks.  Year, month, and day default to current; hour, minute, and second to
  2867. zero; timezone to current, including DST if in effect (assumed west of
  2868. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2869. returns: 1
  2870.  
  2871. $ moon 890401 890402 1d x
  2872. moon:  too many arguments specified
  2873. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2874.  
  2875. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2876. -t (terse text) print only the date, time, and phase name
  2877. -T (text) print date, time, and a description of the moon's phase (default)
  2878. -u (until) tell how long from now until each specified event
  2879. -U (until) tell how long from time to quarter and/or each quarter to next
  2880. -p (picture) draw a picture of the moon's phase using characters
  2881. -d don't print the date and time with -ntT, just the phase information
  2882. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2883. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2884.  
  2885. quarter:    any of "new first full last next any"; if specified, tell when
  2886.             that quarter next occurs, or all occurrences in the time range
  2887. time:       value to use (default:  current system time)
  2888. endtime:    print phase or quarter information from time to endtime
  2889. increment:  amount to step through range of times; must end in one of:
  2890.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2891.  
  2892. Enter date/time in almost any reasonable format, quoted if it contains
  2893. blanks.  Year, month, and day default to current; hour, minute, and second to
  2894. zero; timezone to current, including DST if in effect (assumed west of
  2895. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2896. returns: 1
  2897.  
  2898. $ moon 890401 890402 1x
  2899. moon:  invalid suffix on increment value
  2900. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2901.  
  2902. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2903. -t (terse text) print only the date, time, and phase name
  2904. -T (text) print date, time, and a description of the moon's phase (default)
  2905. -u (until) tell how long from now until each specified event
  2906. -U (until) tell how long from time to quarter and/or each quarter to next
  2907. -p (picture) draw a picture of the moon's phase using characters
  2908. -d don't print the date and time with -ntT, just the phase information
  2909. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2910. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2911.  
  2912. quarter:    any of "new first full last next any"; if specified, tell when
  2913.             that quarter next occurs, or all occurrences in the time range
  2914. time:       value to use (default:  current system time)
  2915. endtime:    print phase or quarter information from time to endtime
  2916. increment:  amount to step through range of times; must end in one of:
  2917.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2918.  
  2919. Enter date/time in almost any reasonable format, quoted if it contains
  2920. blanks.  Year, month, and day default to current; hour, minute, and second to
  2921. zero; timezone to current, including DST if in effect (assumed west of
  2922. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2923. returns: 1
  2924.  
  2925. $ moon 890401 890402 1 x
  2926. moon:  invalid suffix on increment value
  2927. usage: moon [-ntTuUp] [-dD] [-x xyratio] [quarter] [time [endtime [increment]]]
  2928.  
  2929. -n (numeric) print date, time, and phase as a number 0..1 (new..full..new)
  2930. -t (terse text) print only the date, time, and phase name
  2931. -T (text) print date, time, and a description of the moon's phase (default)
  2932. -u (until) tell how long from now until each specified event
  2933. -U (until) tell how long from time to quarter and/or each quarter to next
  2934. -p (picture) draw a picture of the moon's phase using characters
  2935. -d don't print the date and time with -ntT, just the phase information
  2936. -D print long form date and time with -ntT, instead of default (YYMMDD HH:MM)
  2937. -x set display/font character X/Y ratio (for square picture; default:  0.5)
  2938.  
  2939. quarter:    any of "new first full last next any"; if specified, tell when
  2940.             that quarter next occurs, or all occurrences in the time range
  2941. time:       value to use (default:  current system time)
  2942. endtime:    print phase or quarter information from time to endtime
  2943. increment:  amount to step through range of times; must end in one of:
  2944.             d(ays) h(ours) m(inutes) s(econds) (default:  1d)
  2945.  
  2946. Enter date/time in almost any reasonable format, quoted if it contains
  2947. blanks.  Year, month, and day default to current; hour, minute, and second to
  2948. zero; timezone to current, including DST if in effect (assumed west of
  2949. Greenwich).  To control timezone, set TZ, for example:  TZ=UTC0 moon
  2950. returns: 1
  2951.  
  2952. $ LINES=x moon -p
  2953. moon:  too few lines or columns for picture; check LINES and COLUMNS vars
  2954. returns: 1
  2955.  
  2956. $ LINES=2 moon -p
  2957. moon:  too few lines or columns for picture; check LINES and COLUMNS vars
  2958. returns: 1
  2959.  
  2960. $ COLUMNS=2 moon -p
  2961. moon:  too few lines or columns for picture; check LINES and COLUMNS vars
  2962. returns: 1
  2963.  
  2964. === expect successes ===
  2965.  
  2966. $ moon
  2967. (OK if changes) 890613 11:20  the moon is waxing gibbous (72% illuminated)
  2968. returns: 0
  2969.  
  2970. $ moon next
  2971. (OK if changes) 890619 01:19  the moon is full (100% illuminated)
  2972. returns: 0
  2973.  
  2974. $ moon any
  2975. (OK if changes) 890619 01:19  the moon is full (100% illuminated)
  2976. returns: 0
  2977.  
  2978. $ moon -n new
  2979. (OK if changes) 890702 23:14  0.000000
  2980. returns: 0
  2981.  
  2982. $ moon -t first
  2983. (OK if changes) 890710 18:05  first quarter
  2984. returns: 0
  2985.  
  2986. $ moon -T full
  2987. (OK if changes) 890619 01:19  the moon is full (100% illuminated)
  2988. returns: 0
  2989.  
  2990. $ moon -u last
  2991. (OK if changes) it is 12 days, 15 hours, 44 minutes (12.655 days, 43% of a lunar cycle) until
  2992. 890626 03:04  the moon is last quarter (50% illuminated)
  2993. returns: 0
  2994.  
  2995. $ LINES=8   COLUMNS=100 moon -p first
  2996.  
  2997.                                               (   @@@@
  2998.                                             (     @@@@@@
  2999.                                             (     @@@@@@
  3000.                                             (     @@@@@@
  3001.                                             (     @@@@@@
  3002.                                               (   @@@@
  3003.  
  3004. returns: 0
  3005.  
  3006. $ LINES=100 COLUMNS=20  moon -px0.4 last
  3007.  
  3008.      @@@@@    )
  3009.   @@@@@@@@       )
  3010. @@@@@@@@@@@         )
  3011. @@@@@@@@@@@         )
  3012. @@@@@@@@@@@         )
  3013. @@@@@@@@@@@         )
  3014.   @@@@@@@@       )
  3015.      @@@@@    )
  3016.  
  3017. returns: 0
  3018.  
  3019. $ COLUMNS=10 moon -p first
  3020.  
  3021.   (  @@@
  3022. (    @@@@@
  3023. (    @@@@@
  3024. (    @@@@@
  3025.   (  @@@
  3026.  
  3027. returns: 0
  3028.  
  3029. $ COLUMNS=10 moon -p last
  3030.  
  3031.   @@@  )
  3032. @@@@@@    )
  3033. @@@@@@    )
  3034. @@@@@@    )
  3035.   @@@  )
  3036.  
  3037. returns: 0
  3038.  
  3039. $ COLUMNS=11 moon -p first
  3040.  
  3041.   (  @@@@
  3042. (    @@@@@@
  3043. (    @@@@@@
  3044. (    @@@@@@
  3045.   (  @@@@
  3046.  
  3047. returns: 0
  3048.  
  3049. $ COLUMNS=11 moon -p last
  3050.  
  3051.   @@@   )
  3052. @@@@@@     )
  3053. @@@@@@     )
  3054. @@@@@@     )
  3055.   @@@   )
  3056.  
  3057. returns: 0
  3058.  
  3059. $ moon -ntd new
  3060. 0.000000
  3061. new
  3062. returns: 0
  3063.  
  3064. $ moon -tTD full
  3065. (OK if changes) Mon Jun 19 01:18:57 1989  full
  3066. Mon Jun 19 01:18:57 1989  the moon is full (100% illuminated)
  3067. returns: 0
  3068.  
  3069. $ moon -tu new
  3070. (OK if changes) it is 19 days, 11 hours, 54 minutes (19.496 days, 66% of a lunar cycle) until
  3071. 890702 23:14  new
  3072. returns: 0
  3073.  
  3074. $ moon -uD new
  3075. (OK if changes) it is 19 days, 11 hours, 54 minutes (19.496 days, 66% of a lunar cycle) until
  3076. Sun Jul  2 23:13:31 1989  the moon is new (0% illuminated)
  3077. returns: 0
  3078.  
  3079. $ moon -d any 890501
  3080. the moon is new (0% illuminated)
  3081. returns: 0
  3082.  
  3083. $ moon -U next 890501
  3084. (OK if changes) it is 4 days, 5 hours, 42 minutes (4.237 days, 14% of a lunar cycle) from
  3085. 890501 00:00  the moon is waning crescent (24% illuminated) until
  3086. 890505 05:42  the moon is new (0% illuminated)
  3087. returns: 0
  3088.  
  3089. $ moon new 890530.0500 890530.0500
  3090. returns: 0
  3091.  
  3092. $ moon -U first 890530 890730
  3093. (OK if changes) it is 12 days, 0 hours, 59 minutes (12.041 days, 41% of a lunar cycle) from
  3094. 890530 00:00  the moon is waning crescent (27% illuminated) until
  3095. 890611 00:59  the moon is first quarter (50% illuminated)
  3096. it is 29 days, 17 hours, 6 minutes (29.712 days, 101% of a lunar cycle) from
  3097. 890611 00:59  the moon is first quarter (50% illuminated) until
  3098. 890710 18:05  the moon is first quarter (50% illuminated)
  3099. returns: 0
  3100.  
  3101. $ moon -uU last 890430 890630
  3102. (OK if changes) it is 16 days, 13 hours, 17 minutes (16.554 days, 56% of a lunar cycle) since, and
  3103. it is 27 days, 22 hours, 2 minutes (27.918 days, 95% of a lunar cycle) from
  3104. 890430 00:00  the moon is waning crescent (35% illuminated) until
  3105. 890527 22:02  the moon is last quarter (50% illuminated)
  3106. it is 12 days, 15 hours, 44 minutes (12.655 days, 43% of a lunar cycle) until, and
  3107. it is 29 days, 5 hours, 1 minute (29.209 days, 99% of a lunar cycle) from
  3108. 890527 22:02  the moon is last quarter (50% illuminated) until
  3109. 890626 03:04  the moon is last quarter (50% illuminated)
  3110. returns: 0
  3111.  
  3112. $ moon -tuU any 890815 891001
  3113. (OK if changes) it is 64 days, 9 hours, 48 minutes (64.408 days, 218% of a lunar cycle) until, and
  3114. it is 1 day, 21 hours, 8 minutes (1.881 days, 6% of a lunar cycle) from
  3115. 890815 00:00  the moon is waxing gibbous (95% illuminated) until
  3116. 890816 21:08  full
  3117. it is 71 days, 1 hour, 29 minutes (71.062 days, 241% of a lunar cycle) until, and
  3118. it is 6 days, 15 hours, 41 minutes (6.654 days, 23% of a lunar cycle) from
  3119. 890816 21:08  the moon is full (100% illuminated) until
  3120. 890823 12:49  last quarter
  3121. it is 78 days, 12 hours, 17 minutes (78.512 days, 266% of a lunar cycle) until, and
  3122. it is 7 days, 10 hours, 48 minutes (7.450 days, 25% of a lunar cycle) from
  3123. 890823 12:49  the moon is last quarter (50% illuminated) until
  3124. 890830 23:37  new
  3125. it is 86 days, 16 hours, 22 minutes (86.682 days, 294% of a lunar cycle) until, and
  3126. it is 8 days, 4 hours, 4 minutes (8.170 days, 28% of a lunar cycle) from
  3127. 890830 23:37  the moon is new (0% illuminated) until
  3128. 890908 03:41  first quarter
  3129. it is 93 days, 18 hours, 19 minutes (93.763 days, 318% of a lunar cycle) until, and
  3130. it is 7 days, 1 hour, 57 minutes (7.082 days, 24% of a lunar cycle) from
  3131. 890908 03:41  the moon is first quarter (50% illuminated) until
  3132. 890915 05:39  full
  3133. it is 100 days, 9 hours, 14 minutes (100.385 days, 340% of a lunar cycle) until, and
  3134. it is 6 days, 14 hours, 55 minutes (6.622 days, 22% of a lunar cycle) from
  3135. 890915 05:39  the moon is full (100% illuminated) until
  3136. 890921 20:34  last quarter
  3137. it is 108 days, 4 hours, 14 minutes (108.177 days, 366% of a lunar cycle) until, and
  3138. it is 7 days, 19 hours, 0 minutes (7.792 days, 26% of a lunar cycle) from
  3139. 890921 20:34  the moon is last quarter (50% illuminated) until
  3140. 890929 15:34  new
  3141. returns: 0
  3142.  
  3143. $ moon -n "may 1" "August 4" " 5 d"
  3144. (OK if changes) 890501 00:00  0.836004
  3145. 890506 00:00  0.029493
  3146. 890511 00:00  0.206809
  3147. 890516 00:00  0.360946
  3148. 890521 00:00  0.515352
  3149. 890526 00:00  0.682113
  3150. 890531 00:00  0.863981
  3151. 890605 00:00  0.051936
  3152. 890610 00:00  0.218086
  3153. 890615 00:00  0.370084
  3154. 890620 00:00  0.531955
  3155. 890625 00:00  0.708963
  3156. 890630 00:00  0.892533
  3157. 890705 00:00  0.069861
  3158. 890710 00:00  0.227254
  3159. 890715 00:00  0.381261
  3160. 890720 00:00  0.554169
  3161. 890725 00:00  0.738638
  3162. 890730 00:00  0.917511
  3163. 890804 00:00  0.083191
  3164. returns: 0
  3165.  
  3166. $ moon -n "may 1, 1988 10:00pm" "August 4, 1988" 5d"
  3167. 880501 22:00  0.506121
  3168. 880506 22:00  0.681008
  3169. 880511 22:00  0.863823
  3170. 880516 22:00  0.043451
  3171. 880521 22:00  0.203474
  3172. 880526 22:00  0.356072
  3173. 880531 22:00  0.525389
  3174. 880605 22:00  0.709530
  3175. 880610 22:00  0.889761
  3176. 880615 22:00  0.058072
  3177. 880620 22:00  0.211873
  3178. 880625 22:00  0.369028
  3179. 880630 22:00  0.550599
  3180. 880705 22:00  0.738626
  3181. 880710 22:00  0.911168
  3182. 880715 22:00  0.069783
  3183. 880720 22:00  0.221681
  3184. 880725 22:00  0.387086
  3185. 880730 22:00  0.579085
  3186. returns: 0
  3187.  
  3188. $ moon -T 890503.0031 890503.1200 1h
  3189. 890503 00:31  the moon is waning crescent (7% illuminated)
  3190. 890503 01:31  the moon is waning crescent (7% illuminated)
  3191. 890503 02:31  the moon is waning crescent (7% illuminated)
  3192. 890503 03:31  the moon is waning crescent (6% illuminated)
  3193. 890503 04:31  the moon is waning crescent (6% illuminated)
  3194. 890503 05:31  the moon is waning crescent (6% illuminated)
  3195. 890503 06:31  the moon is waning crescent (6% illuminated)
  3196. 890503 07:31  the moon is waning crescent (5% illuminated)
  3197. 890503 08:31  the moon is waning crescent (5% illuminated)
  3198. 890503 09:31  the moon is waning crescent (5% illuminated)
  3199. 890503 10:31  the moon is waning crescent (5% illuminated)
  3200. 890503 11:31  the moon is waning crescent (5% illuminated)
  3201. returns: 0
  3202.  
  3203. $ moon -D 890513.113015 890513.1200 4 min
  3204. Sat May 13 11:30:15 1989  the moon is waxing gibbous (61% illuminated)
  3205. Sat May 13 11:34:15 1989  the moon is waxing gibbous (61% illuminated)
  3206. Sat May 13 11:38:15 1989  the moon is waxing gibbous (61% illuminated)
  3207. Sat May 13 11:42:15 1989  the moon is waxing gibbous (61% illuminated)
  3208. Sat May 13 11:46:15 1989  the moon is waxing gibbous (61% illuminated)
  3209. Sat May 13 11:50:15 1989  the moon is waxing gibbous (61% illuminated)
  3210. Sat May 13 11:54:15 1989  the moon is waxing gibbous (61% illuminated)
  3211. Sat May 13 11:58:15 1989  the moon is waxing gibbous (61% illuminated)
  3212. returns: 0
  3213.  
  3214. $ moon -nD 890523.113145 890523.1138 20 s
  3215. Tue May 23 11:31:45 1989  0.596302
  3216. Tue May 23 11:32:05 1989  0.596309
  3217. Tue May 23 11:32:25 1989  0.596317
  3218. Tue May 23 11:32:45 1989  0.596325
  3219. Tue May 23 11:33:05 1989  0.596332
  3220. Tue May 23 11:33:25 1989  0.596340
  3221. Tue May 23 11:33:45 1989  0.596348
  3222. Tue May 23 11:34:05 1989  0.596356
  3223. Tue May 23 11:34:25 1989  0.596363
  3224. Tue May 23 11:34:45 1989  0.596371
  3225. Tue May 23 11:35:05 1989  0.596379
  3226. Tue May 23 11:35:25 1989  0.596386
  3227. Tue May 23 11:35:45 1989  0.596394
  3228. Tue May 23 11:36:05 1989  0.596402
  3229. Tue May 23 11:36:25 1989  0.596410
  3230. Tue May 23 11:36:45 1989  0.596417
  3231. Tue May 23 11:37:05 1989  0.596425
  3232. Tue May 23 11:37:25 1989  0.596433
  3233. Tue May 23 11:37:45 1989  0.596441
  3234. returns: 0
  3235.  
  3236. $ moon next 890815 891001
  3237. 890816 21:08  the moon is full (100% illuminated)
  3238. 890915 05:39  the moon is full (100% illuminated)
  3239. returns: 0
  3240.  
  3241. $ moon any 890815 891001
  3242. 890816 21:08  the moon is full (100% illuminated)
  3243. 890823 12:49  the moon is last quarter (50% illuminated)
  3244. 890830 23:37  the moon is new (0% illuminated)
  3245. 890908 03:41  the moon is first quarter (50% illuminated)
  3246. 890915 05:39  the moon is full (100% illuminated)
  3247. 890921 20:34  the moon is last quarter (50% illuminated)
  3248. 890929 15:34  the moon is new (0% illuminated)
  3249. returns: 0
  3250.  
  3251. $ TZ=MST7MDT moon 890815.1200
  3252. 890815 12:00  the moon is waxing gibbous (97% illuminated)
  3253. returns: 0
  3254.  
  3255. $ TZ=UTC0    moon 890815.1200
  3256. 890815 12:00  the moon is waxing gibbous (96% illuminated)
  3257. returns: 0
  3258. @EOF
  3259. if test "`wc -lwc <moon/test.out`" != '    545   3453  20865'
  3260. then
  3261.     echo ERROR: wc results of moon/test.out are `wc -lwc <moon/test.out` should be     545   3453  20865
  3262. fi
  3263.  
  3264. chmod 444 moon/test.out
  3265.  
  3266. echo x - moon/test.script
  3267. cat >moon/test.script <<'@EOF'
  3268.  
  3269. # TEST SCRIPT FOR moon(1).
  3270.  
  3271. # Purpose:  run moon a variety of ways, exercising permutations of options, and
  3272. # produce output to compare against a known good version.
  3273.  
  3274. # Usage:  <script> | diff test.out -
  3275.  
  3276. # Compile moon without -DDEBUG.  In cases where no date is specified, the exact
  3277. # output from moon changes, but the form should not.  These output chunks are
  3278. # marked "(OK if changes)" when they show up in diff output.
  3279.  
  3280. # Variants to combine:
  3281. #
  3282. # options:    combinations of -ntTuUp, including none
  3283. #        none, -d, -D
  3284. #        none, -x
  3285. #
  3286. # quarter:    none, new, first, full, last, next, any
  3287. #
  3288. # date:        none, start only, range with no increment, range with d/h/m/s
  3289. #        increment (as one or two arguments)
  3290. #
  3291. # There are too many possible permutations to test them all.  Instead, try only
  3292. # selected variations.
  3293.  
  3294.  
  3295. # INITIALIZE:
  3296. #
  3297. # Set run-strings to use for each command.  $run is generic; $run2 prefaces
  3298. # the first output line with a message that changes are OK.
  3299.  
  3300.     exec 2>&1            # for user's convenience.
  3301.  
  3302.     run=' echo "\n$ $x"            ; eval $x; echo "returns: $?"'
  3303.     run2='echo "\n$ $x\n(OK if changes) \c"    ; eval $x; echo "returns: $?"'
  3304.  
  3305.     TZ='MST7MDT'
  3306.     export TZ
  3307.  
  3308.  
  3309. # TEST ERRORS:
  3310. #
  3311. # These are based on Usage() and Error() calls in the source code, and appear
  3312. # in the same order.  Some errors are triggered several times in different
  3313. # ways.
  3314.  
  3315.     echo "=== expect errors ==="
  3316.  
  3317.     x='moon -?';                eval $run
  3318.     x='moon -x 0';                eval $run
  3319.     x='moon -dD';                eval $run
  3320.     x='moon -pd';                eval $run
  3321.     x='moon -pD';                eval $run
  3322.     x='moon new 890401 890402 1d';        eval $run
  3323.     x='moon 890402 890401';            eval $run
  3324.     x='moon -u';                eval $run
  3325.     x='moon -U new';            eval $run
  3326.     x='moon -U 890401';            eval $run
  3327.     x='moon 890431';            eval $run
  3328.     x='moon may 1';                eval $run
  3329.     # No way to trigger:  "failed trying to fill in default time values"
  3330.     x='moon 890401 890402 -1';        eval $run
  3331.     x='moon 890401 890402 0d';        eval $run
  3332.     x='moon 890401 890402 1';        eval $run
  3333.     x='moon 890401 890402 1d x';        eval $run
  3334.     x='moon 890401 890402 1x';        eval $run
  3335.     x='moon 890401 890402 1 x';        eval $run
  3336.     x='LINES=x moon -p';            eval $run
  3337.     x='LINES=2 moon -p';            eval $run
  3338.     x='COLUMNS=2 moon -p';            eval $run
  3339.  
  3340.  
  3341. # TEST SUCCESSES:
  3342.  
  3343.     echo "\n=== expect successes ==="
  3344.  
  3345.     # Simple options:
  3346.  
  3347.     x='moon';                eval $run2
  3348.     x='moon next';                eval $run2
  3349.     x='moon any';                eval $run2
  3350.     x='moon -n new';            eval $run2
  3351.     x='moon -t first';            eval $run2
  3352.     x='moon -T full';            eval $run2
  3353.     x='moon -u last';            eval $run2
  3354.  
  3355.     x='LINES=8   COLUMNS=100 moon -p first';    eval $run
  3356.     x='LINES=100 COLUMNS=20  moon -px0.4 last';    eval $run
  3357.     x='COLUMNS=10 moon -p first'            eval $run
  3358.     x='COLUMNS=10 moon -p last'            eval $run
  3359.     x='COLUMNS=11 moon -p first'            eval $run
  3360.     x='COLUMNS=11 moon -p last'            eval $run
  3361.  
  3362.     # Option combinations:
  3363.  
  3364.     x='moon -ntd new';            eval $run
  3365.     x='moon -tTD full';            eval $run2
  3366.     x='moon -tu new';            eval $run2
  3367.     x='moon -uD new';            eval $run2
  3368.  
  3369.     # Phases and/or times:
  3370.  
  3371.     x='moon -d any 890501';            eval $run
  3372.     x='moon -U next 890501';        eval $run2
  3373.     x='moon new 890530.0500 890530.0500';    eval $run    # no output.
  3374.     x='moon -U first 890530 890730';    eval $run2
  3375.     x='moon -uU last 890430 890630';    eval $run2
  3376.     x='moon -tuU any 890815 891001';    eval $run2
  3377.  
  3378.     # Increments:
  3379.  
  3380.     x='moon -n "may 1" "August 4" " 5 d"';            eval $run2
  3381.     x='moon -n "may 1, 1988 10:00pm" "August 4, 1988" 5d"';    eval $run
  3382.     x='moon -T 890503.0031 890503.1200 1h';            eval $run
  3383.     x='moon -D 890513.113015 890513.1200 4 min';        eval $run
  3384.     x='moon -nD 890523.113145 890523.1138 20 s';        eval $run
  3385.  
  3386.     # "next" versus "any":
  3387.  
  3388.     x='moon next 890815 891001';        eval $run
  3389.     x='moon any 890815 891001';        eval $run
  3390.  
  3391.     # Time zone:
  3392.  
  3393.     x='TZ=MST7MDT moon 890815.1200';    eval $run
  3394.     x='TZ=UTC0    moon 890815.1200';    eval $run
  3395. @EOF
  3396. if test "`wc -lwc <moon/test.script`" != '    127    588   3689'
  3397. then
  3398.     echo ERROR: wc results of moon/test.script are `wc -lwc <moon/test.script` should be     127    588   3689
  3399. fi
  3400.  
  3401. chmod 555 moon/test.script
  3402.  
  3403. chmod 750 moon
  3404.  
  3405. echo mkdir - parsedate
  3406. mkdir parsedate
  3407.  
  3408. echo x - parsedate/parsedate.h
  3409. cat >parsedate/parsedate.h <<'@EOF'
  3410. /* $Log:    /s/uclasrc/mail/date/RCS/parsedate.h,v $
  3411.  * Revision 1.1  84/09/01  15:01:38  wales
  3412.  * Initial revision
  3413.  * 
  3414.  * Copyright (c) 1984 by Richard B. Wales
  3415.  *
  3416.  */
  3417. #ifdef RCSIDENT
  3418. #define RCS_PARSEDATE_HDR "$Header: /s/uclasrc/mail/date/RCS/parsedate.h,v 1.1 84/09/01 15:01:38 wales UCLA $"
  3419. #endif RCSIDENT
  3420.  
  3421. /* Data structure returned by "parsedate".
  3422.  *
  3423.  * A value of NULL for "error" means that no syntax errors were detected
  3424.  * in the argument value.  A non-NULL value points to the byte position
  3425.  * within the argument string at which it was discovered that an error
  3426.  * existed.
  3427.  *
  3428.  * A value of -1 means that the field was never given a value, or that
  3429.  * the value supplied was invalid.  (A side effect of this convention is
  3430.  * that a time zone offset of -1 -- i.e., one minute west of GMT -- is
  3431.  * indistinguishable from an invalid or unspecified time zone offset.
  3432.  * Since the likelihood of "-0001" being a legitimate time zone is nil,
  3433.  * banning it is a small price to pay for the uniformity of using -1 as
  3434.  * a "missing/invalid" indication for all fields.)
  3435.  */
  3436. struct parsedate
  3437.     {    long unixtime;    /* UNIX internal representation of time */
  3438.     char *error;    /* NULL = OK; non-NULL = error */
  3439.     int year;    /* year (1600 on) */
  3440.     int month;    /* month (1-12) */
  3441.     int day;    /* day of month (1-31) */
  3442.     int hour;    /* hour (0-23) */
  3443.     int minute;    /* minute (0-59) */
  3444.     int second;    /* second (0-59) */
  3445.     int zone;    /* time zone offset in minutes -- "+" or "-" */
  3446.     int dst;    /* daylight savings time (0 = no, 1 = yes) */
  3447.     int weekday;    /* real day of week (0-6; 0 = Sunday) */
  3448.     int c_weekday;    /* claimed day of week (0-6; 0 = Sunday) */
  3449.     };
  3450.  
  3451. struct parsedate *parsedate();
  3452. @EOF
  3453. if test "`wc -lwc <parsedate/parsedate.h`" != '     42    296   1663'
  3454. then
  3455.     echo ERROR: wc results of parsedate/parsedate.h are `wc -lwc <parsedate/parsedate.h` should be      42    296   1663
  3456. fi
  3457.  
  3458. chmod 444 parsedate/parsedate.h
  3459.  
  3460. echo x - parsedate/Makefile
  3461. cat >parsedate/Makefile <<'@EOF'
  3462. # $Log:    /s/uclasrc/mail/date/RCS/Makefile,v $
  3463. # Revision 1.1  84/09/01  15:00:58  wales
  3464. # Initial revision
  3465. # $Header: /s/uclasrc/mail/date/RCS/Makefile,v 1.1 84/09/01 15:00:58 wales UCLA $
  3466. #
  3467. # Makefile for "date" library routines
  3468. #
  3469. # Copyright (c) 1984 by Richard B. Wales
  3470.  
  3471. DEFS   =
  3472. CFLAGS = -O $(DEFS) -DRCSIDENT
  3473.  
  3474. all:        libdate.a
  3475.  
  3476. strip:        libdate.a
  3477.  
  3478. libdate.a:    datelex.o dateyacc.o parsedate.o
  3479.     rm -f libdate.a
  3480.     ar rc libdate.a datelex.o dateyacc.o parsedate.o
  3481.  
  3482. .c.o:
  3483.     cc -S $(CFLAGS) $*.c
  3484.     sed 's/_yy/_date_yy/g' $*.s | as -o $*.o
  3485.     rm -f $*.s
  3486.  
  3487. dateyacc.c dateyacc.h:    dateyacc.y
  3488.     yacc -d dateyacc.y
  3489.     mv y.tab.c dateyacc.c
  3490.     mv y.tab.h dateyacc.h
  3491.  
  3492. clean:
  3493.     rm -f *.o *.s\
  3494.           dateyacc.c dateyacc.h y.tab.c y.tab.h y.output\
  3495.           libdate.a
  3496.  
  3497. datelex.o:    parsedate.h dateyacc.h
  3498. dateyacc.o:    parsedate.h
  3499. parsedate.o:    parsedate.h
  3500. @EOF
  3501. if test "`wc -lwc <parsedate/Makefile`" != '     39    108    829'
  3502. then
  3503.     echo ERROR: wc results of parsedate/Makefile are `wc -lwc <parsedate/Makefile` should be      39    108    829
  3504. fi
  3505.  
  3506. chmod 444 parsedate/Makefile
  3507.  
  3508. echo x - parsedate/README
  3509. cat >parsedate/README <<'@EOF'
  3510.             DATE MANIPULATION PACKAGE
  3511.             Richard B. Wales
  3512.       UCLA Center for Experimental Computer Science
  3513.  
  3514. This is a software package for manipulating character strings repre-
  3515. senting dates.  It can do the following:
  3516.  
  3517. (1) Interpret an (almost) arbitrary date string and break it down into
  3518.     year, month, day, hour, minute, second, time zone, day of the week,
  3519.     and UNIX internal time (seconds since 1970).
  3520.  
  3521. (2) Compute the day of the week and UNIX internal time corresponding to
  3522.     a year/month/day/hour/minute/second/zone combination.
  3523.  
  3524. (3) Compute the year/month/day/hour/minute/second/zone/day-of-the-week
  3525.     combination corresponding to a UNIX internal time.
  3526.  
  3527. (4) Generate a character string corresponding to a year/month/day/hour/
  3528.     minute/second/zone combination.  Routines exist for creating strings
  3529.     in either RFC822 (ARPANET mail) or UUCP mail formats.
  3530.  
  3531. The date parser accepts a superset of RFC822, UUCP, USENET, and UNIX
  3532. ("date" command) formats.  Numerous time zone abbreviations -- including
  3533. many used in various parts of the world but not acknowledged in RFC822
  3534. -- are understood.  The date parser is NOT suitable for applications
  3535. which require strict conformance with RFC822 and need to know whether a
  3536. given date string follows RFC822 exactly.  RFC822-format date strings
  3537. generated by this package, however, do conform strictly to RFC822; non-
  3538. RFC822 time zone abbreviations which might have been used on input are
  3539. replaced by numeric offsets on output.
  3540.  
  3541. The date parser was written using YACC.  However, it should be possible
  3542. to use these routines in programs which already use LEX and/or YACC for
  3543. other purposes, since all the external symbols beginning with "_yy" have
  3544. been renamed to begin with "_date_yy" instead.
  3545.  
  3546. This code was written and tested in a 4.1BSD environment.  Some mods may
  3547. be necessary to make it run on other UNIX systems; in particular, people
  3548. using AT&T System V and other systems which restrict the length of
  3549. external symbols may encounter some problems with long variable and rou-
  3550. tine names.
  3551.  
  3552. This package contains a copyright notice, as follows:
  3553.  
  3554.         Copyright (c) 1984 by Richard B. Wales
  3555.  
  3556. The author hereby grants permission to use or redistribute this package
  3557. freely and without charge, subject to the following restrictions:
  3558.  
  3559. (1) The copyright notice must be retained in all copies of the source.
  3560.  
  3561. (2) Any changes made to the source must be clearly documented (such as
  3562.     by #ifdef's or by use of a source-code control system such as RCS or
  3563.     SCCS), so that the original version of the source as distributed by
  3564.     the author can be reconstructed if necessary and distinguished from
  3565.     modifications made by others.
  3566.  
  3567. The author is interested in any comments, bug reports, etc., concerning
  3568. this package, and will try to help out with problems as time permits.
  3569. However, since this package is being made available without charge, the
  3570. author and his employer disclaim any responsibility, obligation, or com-
  3571. mitment to supply bug fixes or enhancements to this package, to adapt it
  3572. to run under any particular computer system, to supply consulting
  3573. assistance in its use, or to support it in any other manner whatsoever.
  3574. The author and his employer specifically and expressly disclaim any lia-
  3575. bility whatsoever for incidental or consequential damages which might
  3576. arise out of the use of this package.
  3577.  
  3578.     Rich Wales
  3579.     UCLA Computer Science Department
  3580.     3531 Boelter Hall // Los Angeles, CA 90024 // (213) 825-5683
  3581.     ARPA:  wales@UCLA-LOCUS.ARPA
  3582.     UUCP:  ...!{cepu,ihnp4,trwspp,ucbvax}!ucla-cs!wales
  3583. @EOF
  3584. if test "`wc -lwc <parsedate/README`" != '     73    532   3556'
  3585. then
  3586.     echo ERROR: wc results of parsedate/README are `wc -lwc <parsedate/README` should be      73    532   3556
  3587. fi
  3588.  
  3589. chmod 444 parsedate/README
  3590.  
  3591. echo x - parsedate/date.3
  3592. cat >parsedate/date.3 <<'@EOF'
  3593.  
  3594.  
  3595.  
  3596.      DDDDAAAATTTTEEEE((((3333))))                       ((((UUUUCCCCLLLLAAAA))))                      DDDDAAAATTTTEEEE((((3333))))
  3597.  
  3598.  
  3599.  
  3600.      NNNNAAAAMMMMEEEE
  3601.           parsedate - interpret character strings representing dates
  3602.  
  3603.      SSSSYYYYNNNNOOOOPPPPSSSSIIIISSSS
  3604.           #include "parsedate.h"
  3605.  
  3606.           char date;
  3607.           struct parsedate *pd;
  3608.  
  3609.           pd = parsedate (date);
  3610.  
  3611.           compute_unixtime (pd);
  3612.  
  3613.           break_down_unixtime (pd);
  3614.  
  3615.           date = mail_date_string (pd);
  3616.  
  3617.           date = uucp_date_string (pd);
  3618.  
  3619.      DDDDEEEESSSSCCCCRRRRIIIIPPPPTTTTIIIIOOOONNNN
  3620.           These routines manipulate character strings representing
  3621.           dates.  _p_a_r_s_e_d_a_t_e returns a pointer to a structure of the
  3622.           following form (described in the include-file _p_a_r_s_e_d_a_t_e._h):
  3623.  
  3624.           struct parsedate {
  3625.                long unixtime;      /* as returned by time(2) */
  3626.                char *error;        /* non-NULL = error */
  3627.                int year;           /* year (1600 on) */
  3628.                int month;          /* month (1-12) */
  3629.                int day;            /* day of month (1-31) */
  3630.                int hour;           /* hour (0-23) */
  3631.                int minute;         /* minute (0-59) */
  3632.                int second;         /* second (0-59) */
  3633.                int zone;           /* time zone offset */
  3634.                int dst;            /* daylight savings time */
  3635.                int weekday;        /* real day of week */
  3636.                int c_weekday;      /* claimed day of week */
  3637.           };
  3638.  
  3639.           Any field containing the value -1 (except for _e_r_r_o_r)
  3640.           indicates information which was either not supplied or was
  3641.           invalid.
  3642.  
  3643.           _u_n_i_x_t_i_m_e is the UNIX internal representation of the date
  3644.           (i.e., number of seconds since 1970).  _e_r_r_o_r is NNNNUUUULLLLLLLL if the
  3645.           date string did not contain a syntax error; otherwise, it
  3646.           points to the position in the date string where a syntax
  3647.           error was discovered.  _z_o_n_e indicates the time-zone offset
  3648.           in minutes from UTC.  A positive value denotes a time zone
  3649.           east of Greenwich; a negative value denotes a zone west of
  3650.           Greenwich.  _d_s_t is equal to 1 if the indicated time zone
  3651.           denotes ``daylight savings time'', or 0 if daylight savings
  3652.  
  3653.  
  3654.  
  3655.      Hewlett-Packard               - 1 -             (printed 3/14/85)
  3656.  
  3657.  
  3658.  
  3659.  
  3660.  
  3661.  
  3662.      DDDDAAAATTTTEEEE((((3333))))                       ((((UUUUCCCCLLLLAAAA))))                      DDDDAAAATTTTEEEE((((3333))))
  3663.  
  3664.  
  3665.  
  3666.           time is not indicated.
  3667.  
  3668.           _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
  3669.           in the range 0-6 (0 = Sunday, 6 = Saturday).  _c__w_e_e_k_d_a_y
  3670.           shows which day of the week was actually specified in the
  3671.           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
  3672.           via a perpetual-calendar algorithm.
  3673.  
  3674.           _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,
  3675.           _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
  3676.           filled in, and fills in the _u_n_i_x_t_i_m_e and _w_e_e_k_d_a_y fields as
  3677.           appropriate.
  3678.  
  3679.           _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
  3680.           _u_n_i_x_t_i_m_e and _z_o_n_e fields have been filled in, and fills in
  3681.           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
  3682.           fields as appropriate.
  3683.  
  3684.           _m_a_i_l__d_a_t_e__s_t_r_i_n_g returns a pointer to a RFC822 (ARPANET mail
  3685.           standard) format character string corresponding to a _s_t_r_u_c_t
  3686.           _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
  3687.           a UUCP-mail format character string corresponding to a
  3688.           _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,
  3689.           _m_i_n_u_t_e, _s_e_c_o_n_d, and _z_o_n_e fields should be set first.  If
  3690.           only the _u_n_i_x_t_i_m_e and _z_o_n_e values are initially avaiable,
  3691.           then _b_r_e_a_k__d_o_w_n__u_n_i_x_t_i_m_e should be called before trying to
  3692.           generate a date string.
  3693.  
  3694.      DDDDIIIIAAAAGGGGNNNNOOOOSSSSTTTTIIIICCCCSSSS
  3695.           If the character string supplied as an argument to _p_a_r_s_e_d_a_t_e
  3696.           contains a syntax error, the _e_r_r_o_r variables in the returned
  3697.           _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
  3698.           argument string where the error was discovered.
  3699.  
  3700.           Any field which is either unspecified or given an invalid
  3701.           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
  3702.           and _b_r_e_a_k__d_o_w_n__u_n_i_x_t_i_m_e also set the appropriate fields to
  3703.           -1 if they are unable to compute the requested values.
  3704.  
  3705.           _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
  3706.           information required to generate the date string is missing
  3707.           or invalid.
  3708.  
  3709.      BBBBUUUUGGGGSSSS
  3710.           The returned value from a call to _p_a_r_s_e_d_a_t_e,
  3711.           _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
  3712.           area whose contents will be overwritten by the next call to
  3713.           the same routine.
  3714.  
  3715.           A time-zone offset of ``-0001'' (i.e., one minute west of
  3716.           Greenwich) results in a _z_o_n_e value of -1, and is thus
  3717.           indistinguishable from an invalid or missing time zone.
  3718.  
  3719.  
  3720.  
  3721.      Hewlett-Packard               - 2 -             (printed 3/14/85)
  3722.  
  3723.  
  3724.  
  3725.  
  3726.  
  3727.  
  3728.      DDDDAAAATTTTEEEE((((3333))))                       ((((UUUUCCCCLLLLAAAA))))                      DDDDAAAATTTTEEEE((((3333))))
  3729.  
  3730.  
  3731.  
  3732.           Since this particular time-zone offset is not (and almost
  3733.           certainly never will be) used anywhere in the world, the
  3734.           fact that it cannot be distinguished from an invalid or
  3735.           missing value is probably unimportant.
  3736.  
  3737.      AAAAUUUUTTTTHHHHOOOORRRR
  3738.           Richard B. Wales
  3739.           UCLA Center for Experimental Computer Science
  3740.  
  3741.  
  3742.  
  3743.  
  3744.  
  3745.  
  3746.  
  3747.  
  3748.  
  3749.  
  3750.  
  3751.  
  3752.  
  3753.  
  3754.  
  3755.  
  3756.  
  3757.  
  3758.  
  3759.  
  3760.  
  3761.  
  3762.  
  3763.  
  3764.  
  3765.  
  3766.  
  3767.  
  3768.  
  3769.  
  3770.  
  3771.  
  3772.  
  3773.  
  3774.  
  3775.  
  3776.  
  3777.  
  3778.  
  3779.  
  3780.  
  3781.  
  3782.  
  3783.  
  3784.  
  3785.  
  3786.  
  3787.      Hewlett-Packard               - 3 -             (printed 3/14/85)
  3788.  
  3789.  
  3790.  
  3791. @EOF
  3792. if test "`wc -lwc <parsedate/date.3`" != '    198    668   7184'
  3793. then
  3794.     echo ERROR: wc results of parsedate/date.3 are `wc -lwc <parsedate/date.3` should be     198    668   7184
  3795. fi
  3796.  
  3797. chmod 444 parsedate/date.3
  3798.  
  3799. echo x - parsedate/datelex.c
  3800. cat >parsedate/datelex.c <<'@EOF'
  3801. /*$Log:    /s/uclasrc/mail/date/RCS/datelex.c,v $
  3802.  * Revision 1.1  84/09/01  15:01:14  wales
  3803.  * Initial revision
  3804.  * 
  3805.  * Copyright (c) 1984 by Richard B. Wales
  3806.  *
  3807.  * Purpose:
  3808.  *
  3809.  *     Lexical analyzer for "parsedate" routine.  This lexer was orig-
  3810.  *     inally written in LEX, but rewriting it as an ad-hoc routine
  3811.  *     resulted in an enormous savings in space and a significant
  3812.  *     increase in speed.
  3813.  *
  3814.  * Usage:
  3815.  *
  3816.  *     Called as needed by the YACC parser ("dateyacc.c").  Not intended
  3817.  *     to be called from any other routine.
  3818.  *
  3819.  * Notes:
  3820.  *
  3821.  * Global contents:
  3822.  *
  3823.  *     int yylex ()
  3824.  *         Returns the token number (from the YACC grammar) of the next
  3825.  *         token in the input string pointed to by the global variable
  3826.  *         "yyinbuf".  The global variable "yylval" is set to the lexi-
  3827.  *         cal value (if any) of the token.  "yyinbuf" is set to point
  3828.  *         to the first character in the input string which is not a
  3829.  *         part of the token just recognized.
  3830.  *
  3831.  * Local contents:
  3832.  *
  3833.  *     struct wordtable *find_word (word) char *word;
  3834.  *         Returns a pointer to the entry in the "wordtable" array cor-
  3835.  *         responding to the string "word".  If "word" is not found, the
  3836.  *         returned value is NULL.
  3837.  */
  3838.  
  3839. /* ajs
  3840.  * ajs    Code added 850314 to allow NUM991231 and NUM99991231.
  3841.  * ajs    All added/changed lines contain "ajs" for easy searching.
  3842.  * ajs    */
  3843.  
  3844. #ifdef RCSIDENT
  3845. static char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/datelex.c,v 1.1 84/09/01 15:01:14 wales UCLA $";
  3846. #endif RCSIDENT
  3847.  
  3848. #include <stdio.h>
  3849. #include "dateyacc.h"
  3850. #include "parsedate.h"
  3851.  
  3852. /* pointer to the input string */
  3853. char *yyinbuf;
  3854.  
  3855. /* "answer" structure */
  3856. struct parsedate yyans;
  3857.  
  3858. /* Binary-search word table.
  3859.  * Entries must be sorted in ascending order on "text" value, and the
  3860.  * total number of entries must be one less than a power of 2.  "Filler"
  3861.  * entries (with "token" values of -1) are inserted at the beginning and
  3862.  * end of the table to pad it as necessary.
  3863.  */
  3864. #define WORDTABLE_SIZE 127    /* MUST be one less than power of 2 */
  3865. #define MAX_WORD_LENGTH 20    /* used to weed out overly long words
  3866.                  * in "yylex".  Must be at least as long
  3867.                  * as the longest word in "wordtable",
  3868.                  * but may be longer.
  3869.                  */
  3870. struct wordtable
  3871.     {    char *text;
  3872.     int   token;
  3873.     int   lexval;
  3874.     } wordtable[WORDTABLE_SIZE] =
  3875.     {/* text            token           lexval */
  3876.     "",        -1,        0,
  3877.     "",        -1,        0,
  3878.     "",        -1,        0,
  3879.     "",        -1,        0,
  3880.     "",        -1,        0,
  3881.     "",        -1,        0,
  3882.     "",        -1,        0,
  3883.     "",        -1,        0,
  3884.     "",        -1,        0,
  3885.     "",        -1,        0,
  3886.     "",        -1,        0,
  3887.     "A",        STD_ZONE,    60,    /* UTC+1h */
  3888.     "ACSST",    DST_ZONE,    630,    /* Cent. Australia */
  3889.     "ACST",        STD_ZONE,    570,    /* Cent. Australia */
  3890.     "ADT",        DST_ZONE,    -180,    /* Atlantic (Canada) */
  3891.     "AESST",    DST_ZONE,    660,    /* E. Australia */
  3892.     "AEST",        STD_ZONE,    600,    /* E. Australia */
  3893.     "AM",        AMPM,        0,
  3894.     "APR",        MONTH_NAME,    4,
  3895.     "APRIL",    MONTH_NAME,    4,
  3896.     "AST",        STD_ZONE,    -240,    /* Atlantic (Canada) */
  3897.     "AT",        0,        0,    /* "at" (throwaway) */
  3898.     "AUG",        MONTH_NAME,    8,
  3899.     "AUGUST",    MONTH_NAME,    8,
  3900.     "AWSST",    DST_ZONE,    540,    /* W. Australia */
  3901.     "AWST",        STD_ZONE,    480,    /* W. Australia */
  3902.     "B",        STD_ZONE,    120,    /* UTC+2h */
  3903.     "BST",        DST_ZONE,    60,    /* Great Britain */
  3904.     "C",        STD_ZONE,    180,    /* UTC+3h */
  3905.     "CDT",        DST_ZONE,    -300,
  3906.     "CST",        STD_ZONE,    -360,
  3907.     "D",        STD_ZONE,    240,    /* UTC+4h */
  3908.     "DEC",        MONTH_NAME,    12,
  3909.     "DECEMBER",    MONTH_NAME,    12,
  3910.     "DST",        DST_SUFFIX,    0,
  3911.     "E",        STD_ZONE,    300,    /* UTC+5h */
  3912.     "EDT",        DST_ZONE,    -240,
  3913.     "EET",        STD_ZONE,    120,    /* Eastern Europe */
  3914.     "EETDST",    DST_ZONE,    180,    /* Eastern Europe */
  3915.     "EST",        STD_ZONE,    -300,
  3916.     "F",        STD_ZONE,    360,    /* UTC+6h */
  3917.     "FEB",        MONTH_NAME,    2,
  3918.     "FEBRUARY",    MONTH_NAME,    2,
  3919.     "FRI",        DAY_NAME,    5,
  3920.     "FRIDAY",    DAY_NAME,    5,
  3921.     "G",        STD_ZONE,    420,    /* UTC+7h */
  3922.     "GMT",        STD_ZONE,    0,
  3923.     "H",        STD_ZONE,    480,    /* UTC+8h */
  3924.     "HDT",        DST_ZONE,    -540,    /* Hawaii/Alaska */
  3925.     "HST",        STD_ZONE,    -600,    /* Hawaii/Alaska */
  3926.     "I",        STD_ZONE,    540,    /* UTC+9h */
  3927.     "IST",        STD_ZONE,    120,    /* Israel */
  3928.     "JAN",        MONTH_NAME,    1,
  3929.     "JANUARY",    MONTH_NAME,    1,
  3930.     "JUL",        MONTH_NAME,    7,
  3931.     "JULY",        MONTH_NAME,    7,
  3932.     "JUN",        MONTH_NAME,    6,
  3933.     "JUNE",        MONTH_NAME,    6,
  3934.     "K",        STD_ZONE,    600,    /* UTC+10h */
  3935.     "L",        STD_ZONE,    660,    /* UTC+11h */
  3936.     "M",        STD_ZONE,    720,    /* UTC+12h */
  3937.     "MAR",        MONTH_NAME,    3,
  3938.     "MARCH",    MONTH_NAME,    3,
  3939.     "MAY",        MONTH_NAME,    5,
  3940.     "MDT",        DST_ZONE,    -360,
  3941.     "MET",        STD_ZONE,    60,    /* Central Europe */
  3942.     "METDST",    DST_ZONE,    120,    /* Central Europe */
  3943.     "MON",        DAY_NAME,    1,
  3944.     "MONDAY",    DAY_NAME,    1,
  3945.     "MST",        STD_ZONE,    -420,
  3946.     "N",        STD_ZONE,    -60,    /* UTC-1h */
  3947.     "NDT",        DST_ZONE,    -150,    /* Nfld. (Canada) */
  3948.     "NOV",        MONTH_NAME,    11,
  3949.     "NOVEMBER",    MONTH_NAME,    11,
  3950.     "NST",        STD_ZONE,    -210,    /* Nfld. (Canada) */
  3951.     "O",        STD_ZONE,    -120,    /* UTC-2h */
  3952.     "OCT",        MONTH_NAME,    10,
  3953.     "OCTOBER",    MONTH_NAME,    10,
  3954.     "ON",        0,        0,    /* "on" (throwaway) */
  3955.     "P",        STD_ZONE,    -180,    /* UTC-3h */
  3956.     "PDT",        DST_ZONE,    -420,
  3957.     "PM",        AMPM,        12,
  3958.     "PST",        STD_ZONE,    -480,
  3959.     "Q",        STD_ZONE,    -240,    /* UTC-4h */
  3960.     "R",        STD_ZONE,    -300,    /* UTC-5h */
  3961.     "S",        STD_ZONE,    -360,    /* UTC-6h */
  3962.     "SAT",        DAY_NAME,    6,
  3963.     "SATURDAY",    DAY_NAME,    6,
  3964.     "SEP",        MONTH_NAME,    9,
  3965.     "SEPT",        MONTH_NAME,    9,
  3966.     "SEPTEMBER",    MONTH_NAME,    9,
  3967.     "SUN",        DAY_NAME,    0,
  3968.     "SUNDAY",    DAY_NAME,    0,
  3969.     "T",        STD_ZONE,    -420,    /* UTC-7h */
  3970.     "THU",        DAY_NAME,    4,
  3971.     "THUR",        DAY_NAME,    4,
  3972.     "THURS",    DAY_NAME,    4,
  3973.     "THURSDAY",    DAY_NAME,    4,
  3974.     "TUE",        DAY_NAME,    2,
  3975.     "TUES",        DAY_NAME,    2,
  3976.     "TUESDAY",    DAY_NAME,    2,
  3977.     "U",        STD_ZONE,    -480,    /* UTC-8h */
  3978.     "UT",        STD_ZONE,    0,
  3979.     "UTC",        STD_ZONE,    0,
  3980.     "V",        STD_ZONE,    -540,    /* UTC-9h */
  3981.     "W",        STD_ZONE,    -600,    /* UTC-10h */
  3982.     "WED",        DAY_NAME,    3,
  3983.     "WEDNESDAY",    DAY_NAME,    3,
  3984.     "WEDS",        DAY_NAME,    3,
  3985.     "WET",        STD_ZONE,    0,    /* Western Europe */
  3986.     "WETDST",    DST_ZONE,    60,    /* Western Europe */
  3987.     "X",        STD_ZONE,    -660,    /* UTC-11h */
  3988.     "Y",        STD_ZONE,    -720,    /* UTC-12h */
  3989.     "YDT",        DST_ZONE,    -480,    /* Yukon */
  3990.     "YST",        STD_ZONE,    -540,    /* Yukon */
  3991.     "Z",        STD_ZONE,    0,    /* UTC */
  3992.     "\177",        -1,        0,
  3993.     "\177",        -1,        0,
  3994.     "\177",        -1,        0,
  3995.     "\177",        -1,        0,
  3996.     "\177",        -1,        0,
  3997.     "\177",        -1,        0,
  3998.     "\177",        -1,        0,
  3999.     "\177",        -1,        0,
  4000.     "\177",        -1,        0,
  4001.     "\177",        -1,        0,
  4002.     "\177",        -1,        0,
  4003.     };
  4004. struct wordtable *find_word();
  4005.  
  4006. /* int yylex ()
  4007.  *     Return the next token for the YACC parser.
  4008.  */
  4009. int
  4010. yylex ()
  4011. {   static char buffer[MAX_WORD_LENGTH+1];
  4012.     register char *c, *d;
  4013.     register struct wordtable *wt;
  4014.     register int num, ndgts;
  4015.  
  4016.   restart:
  4017.     /* We will return here if an invalid input token is detected. */
  4018.     c = buffer; d = yyinbuf;
  4019.  
  4020.     /* Skip over blanks, tabs, commas, and parentheses. */
  4021.     do { *c = *d++; }
  4022.     while (*c == ' ' || *c == '\t' || *c == ','
  4023.            || *c == '(' || *c == ')');
  4024.  
  4025.     /* A zero (null) byte signals the end of the input. */
  4026.     if (*c == 0)
  4027.     {    yyinbuf = --d;        /* stay put on the null */
  4028.     return 0;
  4029.     }
  4030.  
  4031.     /* Process a word (looking it up in "wordtable"). */
  4032.     if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z'))
  4033.     {    if (*c >= 'a' && *c <= 'z') *c += 'A' - 'a';
  4034.     while (c < buffer + MAX_WORD_LENGTH
  4035.            && ((*d >= 'A' && *d <= 'Z')
  4036.            || (*d >= 'a' && *d <= 'z')))
  4037.     {   *++c = *d++;
  4038.         if (*c >= 'a' && *c <= 'z') *c += 'A' - 'a';
  4039.     }
  4040.     if ((*d >= 'A' && *d <= 'Z') || (*d >= 'a' && *d <= 'z'))
  4041.     {   /* Word is too long (over MAX_WORD_LENGTH characters). */
  4042.         do { d++; } while ((*d >= 'A' && *d <= 'Z')
  4043.                    || (*d >= 'a' && *d <= 'z'));
  4044.         yyinbuf = d;
  4045.         goto error;
  4046.     }
  4047.     *++c = 0; yyinbuf = d;
  4048.     if ((wt = find_word (buffer)) == NULL) goto error;
  4049.     if (wt->token == 0) goto restart;    /* ignore this word */
  4050.     yylval.IntVal = wt->lexval;
  4051.     return wt->token;
  4052.     }
  4053.  
  4054.     /* Process a number. */
  4055.     if (*c >= '0' && *c <= '9')
  4056.     {    num = *c - '0'; ndgts = 1;
  4057.     for (ndgts = 1; ndgts < 8 && *d >= '0' && *d <= '9'; ndgts++)  /* ajs */
  4058.         num = 10*num + (*d++ - '0');
  4059.     if (*d >= '0' && *d <= '9')
  4060.     {   /* Number is too long (over 8 digits). */        /* ajs */
  4061.         do { d++; } while (*d >= '0' && *d <= '9');
  4062.         yyinbuf = d;
  4063.         goto error;
  4064.     }
  4065.     yyinbuf = d;
  4066.     yylval.IntVal = num;
  4067.     switch (ndgts)
  4068.     {   case 1:  return NUM9;
  4069.         case 2:  if (num <= 23) return NUM23;
  4070.              if (num <= 59) return NUM59;
  4071.              /*otherwise*/  return NUM99;
  4072.         case 3:
  4073.         case 4:  if (num/100 <= 23 && num%100 <= 59) return NUM2359;
  4074.              /*otherwise*/                       return NUM9999;
  4075.         case 5:
  4076.         case 6:  if (num/10000 <= 23
  4077.              && (num%10000)/100 <= 59
  4078.              && num%100 <= 59)
  4079.              return NUM235959;
  4080.              if ((((num % 10000) / 100) <= 12)    /* ajs */
  4081.               &&  ((num % 100) <= 31))        /* ajs */
  4082.              return NUM991231;        /* ajs */
  4083.              goto error;
  4084.         case 8:  if ((((num % 10000) / 100) <= 12)    /* ajs */
  4085.               &&  ((num % 100) <= 31))        /* ajs */
  4086.              return NUM99991231;        /* ajs */
  4087.              goto error;            /* ajs */
  4088.         default: goto error;
  4089.     }    }
  4090.  
  4091.     /* Pass back the following delimiter tokens verbatim.. */
  4092.     if (*c == '-' || *c == '+' || *c == '/' || *c == ':' || *c == '.')
  4093.     {    yyinbuf = d;
  4094.     return *c;
  4095.     }
  4096.  
  4097.   error:
  4098.     /* An unidentified character was found in the input. */
  4099.     yyinbuf = d;
  4100.     if (yyans.error == NULL) yyans.error = yyinbuf;
  4101.     goto restart;
  4102. }
  4103.  
  4104. /* struct wordtable *find_word (word) char *word;
  4105.  *     Look up a word in the "wordtable" array via a binary search.
  4106.  */
  4107. static
  4108. struct wordtable *
  4109. find_word (word)
  4110.     register char *word;
  4111. {   register int low, mid, high;
  4112.     register int comparison;
  4113.  
  4114.     low = -1;
  4115.     high = WORDTABLE_SIZE;
  4116.     while (low+1 < high)
  4117.     {    mid = (low + high) / 2;
  4118.     comparison = strcmp (wordtable[mid].text, word);
  4119.     if (comparison == 0) return wordtable+mid;
  4120.     if (comparison > 0)  high = mid;
  4121.     else                 low = mid;
  4122.     }
  4123.     return NULL;
  4124. }
  4125. @EOF
  4126. if test "`wc -lwc <parsedate/datelex.c`" != '    324   1582   9559'
  4127. then
  4128.     echo ERROR: wc results of parsedate/datelex.c are `wc -lwc <parsedate/datelex.c` should be     324   1582   9559
  4129. fi
  4130.  
  4131. chmod 444 parsedate/datelex.c
  4132.  
  4133. echo x - parsedate/dateyacc.y
  4134. cat >parsedate/dateyacc.y <<'@EOF'
  4135. /*$Log:    /s/uclasrc/mail/date/RCS/dateyacc.y,v $
  4136.  * Revision 1.1  84/09/01  15:01:22  wales
  4137.  * Initial revision
  4138.  * 
  4139.  * Copyright (c) 1984 by Richard B. Wales
  4140.  *
  4141.  * Purpose:
  4142.  *
  4143.  *     YACC parser for "parsedate" routine.
  4144.  *
  4145.  * Usage:
  4146.  *
  4147.  *     Called as needed by the "parsedate" routine in "parsedate.c".
  4148.  *     Not intended to be called from any other routine.
  4149.  *
  4150.  * Notes:
  4151.  *
  4152.  * Global contents:
  4153.  *
  4154.  *     int yyparse ()
  4155.  *         Parses the date string pointed to by the global variable
  4156.  *         "yyinbuf".  Sets the appropriate fields in the global data
  4157.  *         structure "yyans".  The returned value is 1 if there was a
  4158.  *         syntax error, 0 if there was no error.
  4159.  *
  4160.  * Local contents:
  4161.  *
  4162.  *     None.
  4163.  */
  4164.  
  4165. /* ajs
  4166.  * ajs    Code added on 850314 to allow    goal      := year.date '.' time
  4167.  * ajs                and    year.date := [CC]YYMMDD (YY > 23)
  4168.  * ajs    All added lines contain "ajs" for easy searching.
  4169.  * ajs    */
  4170.  
  4171. %{
  4172. #ifdef RCSIDENT
  4173. static char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/dateyacc.y,v 1.1 84/09/01 15:01:22 wales UCLA $";
  4174. #endif RCSIDENT
  4175.  
  4176. #include <stdio.h>
  4177. #include "parsedate.h"
  4178. struct parsedate yyans;
  4179.  
  4180. /* No error routine is needed here. */
  4181. #define yyerror(s)
  4182. %}
  4183.  
  4184. %union {
  4185.     int IntVal;
  4186. }
  4187.  
  4188. %token    DAY_NAME
  4189. %token    MONTH_NAME
  4190. %token    NUM9 NUM23 NUM59 NUM99 NUM2359 NUM9999 NUM235959
  4191. %token    NUM991231 NUM99991231                /* ajs */
  4192. %token    AMPM
  4193. %token    STD_ZONE DST_ZONE DST_SUFFIX
  4194.  
  4195. %type    <IntVal>    DAY_NAME
  4196. %type    <IntVal>    MONTH_NAME
  4197. %type    <IntVal>    NUM9 NUM23 NUM59 NUM99 NUM2359 NUM9999 NUM235959
  4198. %type    <IntVal>    NUM991231 NUM99991231        /* ajs */
  4199. %type    <IntVal>    AMPM
  4200. %type    <IntVal>    STD_ZONE DST_ZONE
  4201. %type    <IntVal>    num59 num zone.offset
  4202.  
  4203. %start    goal
  4204. %%
  4205.  
  4206. num59:
  4207.     NUM23
  4208.   | NUM59
  4209.  
  4210. num:
  4211.     NUM9
  4212.   | num59
  4213.  
  4214. goal:
  4215.     date
  4216.   | date dayname
  4217.   | date dayname time
  4218.   | date dayname time year
  4219.   | date dayname year
  4220.   | date dayname year time
  4221.   | date time
  4222.   | date time dayname
  4223.   | date time dayname year
  4224.   | date time year
  4225.   | date time year dayname
  4226.   | date.year
  4227.   | date.year dayname
  4228.   | date.year dayname time
  4229.   | date.year time
  4230.   | date.year time dayname
  4231.   | dayname date
  4232.   | dayname date time
  4233.   | dayname date time year
  4234.   | dayname date.year
  4235.   | dayname date.year time
  4236.   | dayname time date
  4237.   | dayname time date.year
  4238.   | dayname time year.date
  4239.   | dayname year.date
  4240.   | dayname year.date time
  4241.   | dayname year time date
  4242.   | time
  4243.   | time date
  4244.   | time date dayname
  4245.   | time date dayname year
  4246.   | time date.year
  4247.   | time date.year dayname
  4248.   | time dayname date
  4249.   | time dayname date.year
  4250.   | time dayname year.date
  4251.   | time year.date
  4252.   | time year.date dayname
  4253.   | time year dayname date
  4254.   | year.date
  4255.   | year.date dayname
  4256.   | year.date dayname time
  4257.   | year.date time
  4258.   | year.date time dayname
  4259.   | year dayname date
  4260.   | year dayname date time
  4261.   | year dayname time date
  4262.   | year time date
  4263.   | year time date dayname
  4264.   | year time dayname date
  4265.   | NUM2359
  4266.     { yyans.hour   = $1 / 100;
  4267.       yyans.minute = $1 % 100;
  4268.       yyans.second = -1;        /* unspecified */
  4269.     }
  4270.   | dayname
  4271.   | yymmdd '.' time2359            /* ajs */
  4272.   | yymmdd '.' time            /* ajs */
  4273.   | yymmdd '.' time dayname        /* ajs */
  4274.   | error
  4275.     { extern char *yyinbuf;
  4276.       if (yyans.error == NULL) yyans.error = yyinbuf;
  4277.     }
  4278.  
  4279. dayname:
  4280.     DAY_NAME
  4281.     { yyans.c_weekday = $1; }
  4282.   | DAY_NAME '.'
  4283.     { yyans.c_weekday = $1; }
  4284.  
  4285. date.year:
  4286.     date year
  4287.   | hyphen.date '-' year
  4288.   | slash.date '/' year
  4289.  
  4290. year.date:
  4291.     year date
  4292.   /* | year '-' hyphen.date    (leads to parser conflict) */
  4293.   | year '/' slash.date
  4294.   | yymmdd                    /* ajs */
  4295.                         /* ajs */
  4296. yymmdd:                        /* ajs */
  4297.     NUM991231                    /* ajs */
  4298.     { yyans.year  = ($1 / 10000) + 1900;    /* ajs */
  4299.       yyans.month = ($1 % 10000) / 100;    /* ajs */
  4300.       yyans.day   = ($1 % 100);        /* ajs */
  4301.     }                    /* ajs */
  4302. /*| NUM235959    (leads to parser conflict) */    /* ajs */
  4303.   | NUM99991231                    /* ajs */
  4304.     { yyans.year  = ($1 / 10000);        /* ajs */
  4305.       yyans.month = ($1 % 10000) / 100;    /* ajs */
  4306.       yyans.day   = ($1 % 100);        /* ajs */
  4307.     }                    /* ajs */
  4308.  
  4309. date:
  4310.     num month.name
  4311.     { yyans.day = $1; }
  4312.   | month.name num
  4313.     { yyans.day = $2; }
  4314.   | num num
  4315.     { yyans.month = $1; yyans.day = $2; }
  4316.  
  4317. hyphen.date:
  4318.     num '-' month.name
  4319.     { yyans.day = $1; }
  4320.   | month.name '-' num
  4321.     { yyans.day = $3; }
  4322.   | num '-' num
  4323.     { yyans.month = $1; yyans.day = $3; }
  4324.  
  4325. slash.date:
  4326.     num '/' month.name
  4327.     { yyans.day = $1; }
  4328.   | month.name '/' num
  4329.     { yyans.day = $3; }
  4330.   | num '/' num
  4331.     { yyans.month = $1; yyans.day = $3; }
  4332.  
  4333. year:
  4334.     NUM99        /* precludes two-digit date before 1960 */
  4335.     { yyans.year = 1900 + $1; }
  4336.   | NUM2359
  4337.     { yyans.year = $1; }
  4338.   | NUM9999
  4339.     { yyans.year = $1; }
  4340.  
  4341. month.name:
  4342.     MONTH_NAME
  4343.     { yyans.month = $1; }
  4344.   | MONTH_NAME '.'
  4345.     { yyans.month = $1; }
  4346.  
  4347. time:
  4348.     hour.alone
  4349.   | hour am.pm
  4350.   | hour zone
  4351.   | hour am.pm zone
  4352.  
  4353. hour:
  4354.     NUM2359
  4355.     { yyans.hour   = $1 / 100;
  4356.       yyans.minute = $1 % 100;
  4357.       yyans.second = -1;        /* unspecified */
  4358.     }
  4359.   | hour.alone
  4360.  
  4361. hour.alone:
  4362.     NUM9 ':' num59
  4363.     { yyans.hour   = $1;
  4364.       yyans.minute = $3;
  4365.       yyans.second = -1;        /* unspecified */
  4366.     }
  4367.   | NUM9 '.' num59
  4368.     { yyans.hour   = $1;
  4369.       yyans.minute = $3;
  4370.       yyans.second = -1;        /* unspecified */
  4371.     }
  4372.   | NUM9 ':' num59 ':' num59
  4373.     { yyans.hour   = $1;
  4374.       yyans.minute = $3;
  4375.       yyans.second = $5;
  4376.     }
  4377.   | NUM9 '.' num59 '.' num59
  4378.     { yyans.hour   = $1;
  4379.       yyans.minute = $3;
  4380.       yyans.second = $5;
  4381.     }
  4382.   | NUM23 ':' num59
  4383.     { yyans.hour   = $1;
  4384.       yyans.minute = $3;
  4385.       yyans.second = -1;        /* unspecified */
  4386.     }
  4387.   | NUM23 '.' num59
  4388.     { yyans.hour   = $1;
  4389.       yyans.minute = $3;
  4390.       yyans.second = -1;        /* unspecified */
  4391.     }
  4392.   | NUM23 ':' num59 ':' num59
  4393.     { yyans.hour   = $1;
  4394.       yyans.minute = $3;
  4395.       yyans.second = $5;
  4396.     }
  4397.   | NUM23 '.' num59 '.' num59
  4398.     { yyans.hour   = $1;
  4399.       yyans.minute = $3;
  4400.       yyans.second = $5;
  4401.     }
  4402.   | NUM2359 ':' num59
  4403.     { yyans.hour   = $1 / 100;
  4404.       yyans.minute = $1 % 100;
  4405.       yyans.second = $3;
  4406.     }
  4407.   | NUM2359 '.' num59
  4408.     { yyans.hour   = $1 / 100;
  4409.       yyans.minute = $1 % 100;
  4410.       yyans.second = $3;
  4411.     }
  4412.   | NUM235959
  4413.     { yyans.hour   = $1 / 10000;
  4414.       yyans.minute = ($1 % 10000) / 100;
  4415.       yyans.second = $1 % 100;
  4416.     }
  4417.  
  4418. am.pm:
  4419.     AMPM
  4420.     { if (yyans.hour < 1 || yyans.hour > 12)
  4421.         yyans.hour = -1;        /* invalid */
  4422.       else
  4423.       { if (yyans.hour == 12) yyans.hour = 0;
  4424.         yyans.hour += $1;        /* 0 for AM, 12 for PM */
  4425.     } }
  4426.  
  4427. zone:
  4428.     STD_ZONE
  4429.     { yyans.zone = $1; yyans.dst = 0; }
  4430.   | STD_ZONE DST_SUFFIX
  4431.     { yyans.zone = $1 + 60; yyans.dst = 1; }
  4432.   | '-' STD_ZONE
  4433.     { yyans.zone = $2; yyans.dst = 0; }
  4434.   | '-' STD_ZONE DST_SUFFIX
  4435.     { yyans.zone = $2 + 60; yyans.dst = 1; }
  4436.   | DST_ZONE
  4437.     { yyans.zone = $1; yyans.dst = 1; }
  4438.   | '-' DST_ZONE
  4439.     { yyans.zone = $2; yyans.dst = 1; }
  4440.   | '+' zone.offset
  4441.     { yyans.zone = $2; yyans.dst = 0; }
  4442.   | '-' '+' zone.offset
  4443.     { yyans.zone = $3; yyans.dst = 0; }
  4444.   | '-' zone.offset
  4445.     { yyans.zone = - $2; yyans.dst = 0; }
  4446.   | '-' '-' zone.offset
  4447.     { yyans.zone = - $3; yyans.dst = 0; }
  4448.  
  4449. zone.offset:
  4450.     NUM9
  4451.     { $$ = 60 * $1; }
  4452.   | NUM9 ':' num59
  4453.     { $$ = 60 * $1 + $3; }
  4454.   | NUM9 '.' num59
  4455.     { $$ = 60 * $1 + $3; }
  4456.   | NUM23
  4457.     { $$ = 60 * $1; }
  4458.   | NUM23 ':' num59
  4459.     { $$ = 60 * $1 + $3; }
  4460.   | NUM23 '.' num59
  4461.     { $$ = 60 * $1 + $3; }
  4462.   | NUM2359
  4463.     { $$ = 60 * ($1 / 100) | ($1 % 100); }
  4464.  
  4465. time2359:                /* ajs */
  4466.     NUM2359                /* ajs */
  4467.     { yyans.hour   = $1 / 100;    /* ajs */
  4468.       yyans.minute = $1 % 100;    /* ajs */
  4469.       yyans.second = -1;        /* ajs */
  4470.     }                /* ajs */
  4471.  
  4472. %%
  4473. @EOF
  4474. if test "`wc -lwc <parsedate/dateyacc.y`" != '    338   1293   7260'
  4475. then
  4476.     echo ERROR: wc results of parsedate/dateyacc.y are `wc -lwc <parsedate/dateyacc.y` should be     338   1293   7260
  4477. fi
  4478.  
  4479. chmod 444 parsedate/dateyacc.y
  4480.  
  4481. echo x - parsedate/parsedate.c
  4482. cat >parsedate/parsedate.c <<'@EOF'
  4483. /*LINTLIBRARY*/
  4484.  
  4485. /*$Log:    /s/uclasrc/mail/date/RCS/parsedate.c,v $
  4486.  * Revision 1.1  84/09/01  15:01:30  wales
  4487.  * Initial revision
  4488.  * 
  4489.  * Copyright (c) 1984 by Richard B. Wales
  4490.  *
  4491.  * Purpose:
  4492.  *
  4493.  *     Manipulate character strings representing dates.
  4494.  *
  4495.  * Usage:
  4496.  *
  4497.  *     #include <parsedate.h>
  4498.  *
  4499.  *     char date;
  4500.  *     struct parsedate *pd;
  4501.  *
  4502.  *     pd = parsedate (date);
  4503.  *
  4504.  *     compute_unixtime (pd);
  4505.  *
  4506.  *     break_down_unixtime (pd);
  4507.  *
  4508.  *     date = mail_date_string (pd);
  4509.  *
  4510.  *     date = uucp_date_string (pd);
  4511.  *
  4512.  * Notes:
  4513.  *
  4514.  *     The returned value from "parsedate", "mail_date_string", or
  4515.  *     "uucp_date_string" points to static data whose contents are
  4516.  *     overwritten by the next call to the same routine.
  4517.  *
  4518.  *     "compute_unixtime" is implicitly called by "parsedate".
  4519.  *
  4520.  * Global contents:
  4521.  *
  4522.  *     struct parsedate *parsedate (date) char *date;
  4523.  *         Parse a character string representing a date and time into
  4524.  *         individual values in a "struct parsedate" data structure.
  4525.  *    
  4526.  *     compute_unixtime (pd) struct parsedate *pd;
  4527.  *         Given a mostly filled-in "struct parsedate", compute the day
  4528.  *         of the week and the internal UNIX representation of the date.
  4529.  *    
  4530.  *     break_down_unixtime (pd) struct parsedate *pd;
  4531.  *         Compute the date and time corresponding to the "unixtime" and
  4532.  *         "zone" values in a "struct parsedate".
  4533.  *    
  4534.  *     char *mail_date_string (pd) struct parsedate *pd;
  4535.  *         Generate a character string representing a date and time in
  4536.  *         the RFC822 (ARPANET mail standard) format.
  4537.  *    
  4538.  *     char *uucp_date_string (pd) struct parsedate *pd;
  4539.  *         Generate a character string representing a date and time in
  4540.  *         the UUCP mail format.
  4541.  *
  4542.  * Local contents:
  4543.  *
  4544.  *     None.
  4545.  */
  4546.  
  4547. #include <stdio.h>
  4548. #include "parsedate.h"
  4549.  
  4550. #ifdef RCSIDENT
  4551. static char rcsident[] = "$Header: /s/uclasrc/mail/date/RCS/parsedate.c,v 1.1 84/09/01 15:01:30 wales UCLA $";
  4552. static char rcs_parsedate_hdr[] = RCS_PARSEDATE_HDR;
  4553. #endif RCSIDENT
  4554.  
  4555. /* Number of seconds in various time intervals. */
  4556. #define SEC_PER_MIN  60
  4557. #define SEC_PER_HOUR (60*SEC_PER_MIN)
  4558. #define SEC_PER_DAY  (24*SEC_PER_HOUR)
  4559. #define SEC_PER_YEAR (365*SEC_PER_DAY)
  4560.  
  4561. /* Number of days in each month. */
  4562. static int monthsize[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
  4563.  
  4564. /* Three-letter abbreviations of month and day names. */
  4565. static char monthname[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  4566. static char dayname[]   = "SunMonTueWedThuFriSat";
  4567.  
  4568. /* struct parsedate *parsedate (date) char *date;
  4569.  *     Analyze a character string representing a date and time.  The
  4570.  *     returned value points to a data structure with the desired
  4571.  *     information.  (NOTE:  The returned value points to static data
  4572.  *     whose contents are overwritten by each call.)
  4573.  */
  4574. struct parsedate *
  4575. parsedate (date)
  4576.     register char *date;
  4577. {   register char *c;
  4578.     register int year_save;
  4579.     extern struct parsedate yyans;
  4580.     extern char *yyinbuf;
  4581.     extern char *sprintf();
  4582.  
  4583.     /* Initialize the returned-value structure. */
  4584.     yyans.unixtime  = -1;
  4585.     yyans.year      = -1;
  4586.     yyans.month     = -1;
  4587.     yyans.day       = -1;
  4588.     yyans.hour      = -1;
  4589.     yyans.minute    = -1;
  4590.     yyans.second    = -1;
  4591.     yyans.zone      = -1;
  4592.     yyans.dst       = -1;
  4593.     yyans.weekday   = -1;
  4594.     yyans.c_weekday = -1;
  4595.     yyans.error     =  NULL;
  4596.  
  4597.     /* Parse the argument string. */
  4598.     yyinbuf = date;
  4599.     if (yyparse () != 0 && yyans.error == NULL) yyans.error = yyinbuf;
  4600.  
  4601.     /* Validate the day of the month, compute/validate the day of the
  4602.      * week, and compute the internal UNIX form of the time.  See if
  4603.      * "compute_unixtime" found fault with the year or the day of the
  4604.      * month.  (Note that we have to remember the original "year" value
  4605.      * because it might legitimately have been -1 to begin with.)
  4606.      */
  4607.     year_save = yyans.year; compute_unixtime (&yyans);
  4608.     if (yyans.error == NULL
  4609.     && (yyans.year != year_save
  4610.         || (yyans.month > 0 && yyans.day < 0)
  4611.         || (yyans.month < 0 && yyans.day > 0)))
  4612.     yyans.error = yyinbuf;
  4613.  
  4614.     return &yyans;
  4615. }
  4616.  
  4617. /* compute_unixtime (pd) struct parsedate *pd;
  4618.  *     Given a mostly filled-in "struct parsedate", compute the day of
  4619.  *     the week and the internal UNIX representation of the date.
  4620.  *
  4621.  *     A year before 1600 will be rejected and replaced with -1.  A
  4622.  *     date from 1600 on which falls outside the range representable in
  4623.  *     internal UNIX form will still have the correct day of the week
  4624.  *     computed.
  4625.  *
  4626.  *     The day of the week is always computed on the assumption that the
  4627.  *     Gregorian calendar is in use.  Days of the week for dates in the
  4628.  *     far future may turn out to be incorrect if any changes are made
  4629.  *     to the calendar between now and then.
  4630.  */
  4631. compute_unixtime (pd)
  4632.     register struct parsedate *pd;
  4633. {   register int weekday, n, l, a;
  4634.  
  4635.     /* Validate the year. */
  4636.     if (pd->year >= 0 && pd->year < 1600) pd->year = -1;
  4637.  
  4638.     /* Validate the day of the month.  Also calculate the number of days
  4639.      * in February (even if this is not February, we will need the num-
  4640.      * ber of days in February later on when computing the UNIX time).
  4641.      */
  4642.     if (pd->month > 0)
  4643.     {    if      (pd->year < 0)        monthsize[2] = 29;
  4644.     else if (pd->year %   4 != 0) monthsize[2] = 28;
  4645.     else if (pd->year % 100 != 0) monthsize[2] = 29;
  4646.     else if (pd->year % 400 != 0) monthsize[2] = 28;
  4647.     else                          monthsize[2] = 29;
  4648.     if (pd->day <= 0 || pd->day > monthsize[pd->month])
  4649.         pd->day = -1;
  4650.     }
  4651.  
  4652.     /* Compute the day of the week.  The next several lines constitute a
  4653.      * perpetual-calendar formula.  Note, of course, that the "claimed"
  4654.      * day of the week (pd->c_weekday) is ignored here.
  4655.      */
  4656.     if (pd->year > 0 && pd->month > 0 && pd->day > 0)
  4657.     {    if (pd->month >= 3) n = pd->year / 100,
  4658.                 l = pd->year % 100;
  4659.     else                n = (pd->year-1) / 100,
  4660.                 l = (pd->year-1) % 100;
  4661.     a = (26 * ((pd->month+9)%12 + 1) - 2) / 10;
  4662.     weekday = (a+(l>>2)+(n>>2)+l-(n+n)+pd->day);
  4663.     while (weekday < 0) weekday += 7;
  4664.     pd->weekday = weekday % 7;
  4665.     }
  4666.  
  4667.     /* Figure out the internal UNIX form of the date. */
  4668.     if (pd->year >= 1969 && pd->year <= 2038
  4669.     && pd->month > 0 && pd->day > 0
  4670.     && pd->hour >= 0 && pd->minute >= 0
  4671.     && pd->zone != -1 && pd->zone > -1440 && pd->zone < 1440)
  4672.     {    pd->unixtime =
  4673.           SEC_PER_YEAR * (pd->year - 1970)
  4674.         + SEC_PER_DAY  * ((pd->year - 1969) / 4)
  4675.         /* month is taken care of later */
  4676.         + SEC_PER_DAY  * (pd->day - 1)
  4677.         + SEC_PER_HOUR * pd->hour
  4678.         + SEC_PER_MIN  * pd->minute
  4679.         /* seconds are taken care of later */
  4680.         - SEC_PER_MIN  * pd->zone;
  4681.     if (pd->second >= 0)
  4682.         pd->unixtime += pd->second;
  4683.     for (n = pd->month - 1; n > 0; n--)
  4684.         pd->unixtime += SEC_PER_DAY * monthsize[n];
  4685.     if (pd->unixtime < 0) pd->unixtime = -1;
  4686.     }
  4687.     else pd->unixtime = -1;
  4688. }
  4689.  
  4690. /* break_down_unixtime (pd) struct parsedate *pd;
  4691.  *     Given the "unixtime" and "zone" fields of a "struct parsedate",
  4692.  *     compute the values of the "year", "month", "day", "hour", "min-
  4693.  *     ute", "second", and "weekday" fields.  The "dst" and "error"
  4694.  *     fields of the structure are not used or modified.
  4695.  */
  4696. break_down_unixtime (pd)
  4697.     register struct parsedate *pd;
  4698. {   register unsigned long timevalue;
  4699.     register int m, n;
  4700.  
  4701.     /* Validate the "unixtime" and "zone" fields. */
  4702.     if (pd->unixtime < 0
  4703.     || pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
  4704.     {    /* Sorry, can't do it. */
  4705.     pd->year = -1; pd->month = -1; pd->day = -1;
  4706.     pd->hour = -1; pd->minute = -1; pd->second = -1;
  4707.     pd->weekday = -1;
  4708.     return;
  4709.     }
  4710.  
  4711.     /* Even though "pd->unixtime" must be non-negative, and thus cannot
  4712.      * indicate a time earlier than 1970, a negative "pd->zone" could
  4713.      * cause the local date to be Wednesday, 31 December 1969.  Such a
  4714.      * date requires special handling.
  4715.      *
  4716.      * A local date earlier than 31 December 1969 is impossible because
  4717.      * "pd->zone" must represent a time-zone shift of less than a day.
  4718.      */
  4719.     if (pd->zone < 0 && pd->unixtime + SEC_PER_MIN * pd->zone < 0)
  4720.     {    pd->year = 1969; pd->month = 12; pd->day = 31;
  4721.     pd->weekday = 3;    /* Wednesday */
  4722.     timevalue = pd->unixtime + SEC_PER_MIN * pd->zone + SEC_PER_DAY;
  4723.     /* Note:  0 <= timevalue < SEC_PER_DAY */
  4724.     pd->hour = timevalue / SEC_PER_HOUR;
  4725.     pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
  4726.     pd->second = timevalue % SEC_PER_MIN;
  4727.     return;
  4728.     }
  4729.  
  4730.     /* Handle the general case (local time is 1970 or later). */
  4731.     timevalue = pd->unixtime + SEC_PER_MIN * pd->zone;
  4732.  
  4733.     /* day of the week (1 January 1970 was a Thursday) . . . */
  4734.     pd->weekday = (timevalue/SEC_PER_DAY + 4 /* Thursday */) % 7;
  4735.  
  4736.     /* year (note that the only possible century year here is 2000,
  4737.      * a leap year -- hence no special tests for century years are
  4738.      * needed) . . .
  4739.      */
  4740.     for (m = 1970; ; m++)
  4741.     {    n = (m%4==0) ? SEC_PER_YEAR+SEC_PER_DAY : SEC_PER_YEAR;
  4742.     if (n > timevalue) break;
  4743.     timevalue -= n;
  4744.     }
  4745.     pd->year = m;
  4746.     monthsize[2] = (m%4==0) ? 29 : 28;
  4747.  
  4748.     /* month . . . */
  4749.     for (m = 1; ; m++)
  4750.     {    n = SEC_PER_DAY * monthsize[m];
  4751.     if (n > timevalue) break;
  4752.     timevalue -= n;
  4753.     }
  4754.     pd->month = m;
  4755.  
  4756.     /* day, hour, minute, and second . . . */
  4757.     pd->day    = (timevalue / SEC_PER_DAY) + 1;
  4758.     pd->hour   = (timevalue % SEC_PER_DAY) / SEC_PER_HOUR;
  4759.     pd->minute = (timevalue % SEC_PER_HOUR) / SEC_PER_MIN;
  4760.     pd->second = timevalue % SEC_PER_MIN;
  4761. }
  4762.  
  4763. /* char *mail_date_string (pd) struct parsedate *pd;
  4764.  *     Generate a character string representing a date and time in the
  4765.  *     RFC822 (ARPANET mail standard) format.  A value of NULL is re-
  4766.  *     turned if "pd" does not contain all necessary data fields.
  4767.  *     (NOTE:  The returned value points to static data whose contents
  4768.  *     are overwritten by each call.)
  4769.  */
  4770. char *
  4771. mail_date_string (pd)
  4772.     register struct parsedate *pd;
  4773. {   register char *c;
  4774.     static char answer[50];
  4775.  
  4776.     /* Check the day of the month and compute the day of the week. */
  4777.     compute_unixtime (pd);
  4778.  
  4779.     /* Make sure all required fields are present. */
  4780.     if (pd->year < 0 || pd->month < 0 || pd->day < 0
  4781.     || pd->hour < 0 || pd->minute < 0
  4782.     || pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
  4783.     return NULL;        /* impossible to generate string */
  4784.  
  4785.     /* Generate the answer string. */
  4786.     sprintf (answer,
  4787.          "%.3s, %d %.3s %d %02d:%02d",
  4788.          dayname + 3*pd->weekday,
  4789.          pd->day, monthname + 3*(pd->month-1),
  4790.          (pd->year >= 1960 && pd->year <= 1999)
  4791.          ? pd->year - 1900 : pd->year,
  4792.          pd->hour, pd->minute);
  4793.     c = answer + strlen (answer);
  4794.     if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
  4795.     *c++ = ' ';
  4796.     switch (pd->zone)
  4797.     {    /* NOTE:  Only zone abbreviations in RFC822 are used here. */
  4798.     case    0: strcpy (c, pd->dst ? "+0000" : "GMT");   break;
  4799.     case -240: strcpy (c, pd->dst ? "EDT"   : "-0400"); break;
  4800.     case -300: strcpy (c, pd->dst ? "CDT"   : "EST");   break;
  4801.     case -360: strcpy (c, pd->dst ? "MDT"   : "CST");   break;
  4802.     case -420: strcpy (c, pd->dst ? "PDT"   : "MST");   break;
  4803.     case -480: strcpy (c, pd->dst ? "-0800" : "PST");   break;
  4804.     default:
  4805.         if (pd->zone >= 0)
  4806.          sprintf (c, "+%02d%02d",  pd->zone/60,  pd->zone%60);
  4807.         else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
  4808.     }
  4809.  
  4810.     return answer;
  4811. }
  4812.  
  4813. /* char *uucp_date_string (pd) struct parsedate *pd;
  4814.  *     Generate a character string representing a date and time in the
  4815.  *     UUCP mail format.  A value of NULL is returned if "pd" does not
  4816.  *     contain all necessary data fields.  (NOTE:  The returned value
  4817.  *     points to static data whose contents are overwritten by each
  4818.  *     call.)
  4819.  */
  4820. char *
  4821. uucp_date_string (pd)
  4822.     register struct parsedate *pd;
  4823. {   register char *c;
  4824.     static char answer[50];
  4825.  
  4826.     /* Check the day of the month and compute the day of the week. */
  4827.     compute_unixtime (pd);
  4828.  
  4829.     /* Make sure all required fields are present. */
  4830.     if (pd->year < 0 || pd->month < 0 || pd->day < 0
  4831.     || pd->hour < 0 || pd->minute < 0
  4832.     || pd->zone == -1 || pd->zone <= -1440 || pd->zone >= 1440)
  4833.     return NULL;        /* impossible to generate string */
  4834.  
  4835.     /* Generate the answer string. */
  4836.     sprintf (answer,
  4837.          "%.3s %.3s %d %02d:%02d",
  4838.          dayname + 3*pd->weekday,
  4839.          monthname + 3*(pd->month-1), pd->day,
  4840.          pd->hour, pd->minute);
  4841.     c = answer + strlen (answer);
  4842.     if (pd->second >= 0) sprintf (c, ":%02d", pd->second), c += 3;
  4843.     switch (pd->zone)
  4844.     {    /* NOTE:  Only zone abbreviations in RFC822 are used here. */
  4845.     case    0: strcpy (c, pd->dst ? "+0000" : "-GMT");   break;
  4846.     case -240: strcpy (c, pd->dst ? "-EDT"  : "-0400"); break;
  4847.     case -300: strcpy (c, pd->dst ? "-CDT"  : "-EST");   break;
  4848.     case -360: strcpy (c, pd->dst ? "-MDT"  : "-CST");   break;
  4849.     case -420: strcpy (c, pd->dst ? "-PDT"  : "-MST");   break;
  4850.     case -480: strcpy (c, pd->dst ? "-0800" : "-PST");   break;
  4851.     default:
  4852.         if (pd->zone >= 0)
  4853.          sprintf (c, "+%02d%02d",  pd->zone/60,  pd->zone%60);
  4854.         else sprintf (c, "-%02d%02d", -pd->zone/60, -pd->zone%60);
  4855.     }
  4856.     c = answer + strlen (answer);
  4857.     sprintf (c, " %d", pd->year);
  4858.  
  4859.     return answer;
  4860. }
  4861. @EOF
  4862. if test "`wc -lwc <parsedate/parsedate.c`" != '    378   2040  13198'
  4863. then
  4864.     echo ERROR: wc results of parsedate/parsedate.c are `wc -lwc <parsedate/parsedate.c` should be     378   2040  13198
  4865. fi
  4866.  
  4867. chmod 444 parsedate/parsedate.c
  4868.  
  4869. chmod 750 parsedate
  4870.  
  4871. exit 0
  4872.  
  4873.  
  4874.