home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume8 / moon < prev    next >
Text File  |  1989-08-19  |  142KB  |  4,866 lines

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