Dynamic Biorhythm Graphs
By Andrew Davison
Web Techniques, June 1997

Web Techniques grants permission to use these listings for private or 
commercial use provided that credit to Web Techniques and the author is 
maintained within the comments of the source. For questions, contact
editors@web-techniques.com.



Listing One

start = 0
bio(day, cycle) =  sin((real(day-1+start)/cycle)*2*pi) * 100
set xlabel "Biorhythm Chart for 7/1955"
set xtics 1,1,31
plot [x=1:31] \
  bio(x,23) title 'Physical', \
  bio(x,28) title 'Emotional', \
  bio(x,33) title 'Intellectual'
pause -1 'Hit <return>'



Listing Two

<html>
<head>
<title>Biorhythm Graphing</title>
<link rev="made" href="mailto:ad@cs.mu.oz.au">
</head>
<body background="chalk.jpg" text=#000000>
<H1 align=center>Biorhythm Graphing</H1>

<P>This biorhythms program was developed as an example
of how to generate graphs at run-time. Personally, I don't
believe anything that can be coded in a couple of pages
can offer much insight into our lives.</P>

<h2>Biorhythm Information</h2>
<P>A biorhythm is composed from three sine waves, which
supposedly control our <em>physical</em>, <em>emotional</em>,
and <em>intellectual</em> highs and lows. 
The physical wave repeats every 23 days and controls vitality,
etc. The emotional wave repeats every 28 days and controls
temperment, etc. The intellectual wave repeats every 33 days 
and controls thinking capacity, etc.
A wave value above 0 is good; one below is bad. A 0 value is
a <strong>critical</strong> day, where problems are more
likely to happen.</P>
<P>Other information on biorhythms can be gleaned from 
<a href="http://www.value.net/~esoteric/biorhythm/">Nancy
Laytos</a>, and  
<a href="http://www.qns.com/html/weborhythm/">Web-O-Rhythm</a></P>.
<h2>Calculate your Biorhythm</h2>
<P>Enter your date of birth and a month/year of interest in
the fields below, and a biorhythm graph for that month/year
will be generated when you click the <b>Make Biorhythm</b> button. 
All the fields can be cleared by clicking on the <b>Clear</b> button.</P>
<FORM METHOD="POST" ACTION="http://www.cs.mu.oz.au/cgi-bin/pb">
<P>Enter birthday in day/month/year format (e.g. 23/7/1962):
<INPUT TYPE="text" NAME="birth" SIZE="12" MAXLENGTH="12" VALUE=""><br>
Enter biorhythm month/year, in the form month/year (e.g. 7/1996):
<INPUT TYPE="text" NAME="bio" SIZE="10" MAXLENGTH="10" VALUE=""></P>
<P><INPUT TYPE="submit" VALUE="Make Biorhythm">
<INPUT TYPE="reset" VALUE="Clear"></P>
</FORM>

<hr>
<address>
Last updated: July, 1996<br>
Author: <a href="http://www.cs.mu.oz.au/~ad">Andrew Davison</a><br>
Email: <a href="mailto:ad@cs.mu.oz.au">ad@cs.mu.oz.au</a><br>
</address>
</body>
</html>



Listing Three



/* pb.c */
/* Andrew Davison, July 1996 (ad@cs.mu.oz.au)  */
/* A Web page containing a GIF is returned. The GIF contains 
   the user's three biorhythm curves for a specified month/year.
   The user supplies their birthday and month/year of interest
   via a form.
   The number of days since the user's birthday is
   calculated by converting the birthday and month/year of interest
   into their number of Julian days. The difference is the
   start day for the biorhythm equation.
   The gnuplot commands are in PLOT_CMDS, and this is modified
   and copied into TEMP_CMDS before being displayed. The two
   changes insert the start day and the month/year details.
   gnuplot saves its drawing into <PIC>.ppm, and this is
   converted into GIF format with ppmtogif.
   URL_PIC is the URL of PIC. 
   Tools used: gnuplot, ppmtogif
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>     /* for read(), lseek(), write(), close(), lockf() */
#include <fcntl.h>      /* for open() */

#define BKGROUND "http://www.cs.mu.oz.au/~ad/chalk.jpg"
#define MAX_ENTRIES 2           /* number of input fields */
#define MAXLEN 500              /* max length of a string */
#define NOVAL "$$no-value$$"    /* when no value is found */
#define COUNT_NM  "/home/staff/ad/www_public/code/biorhythm/count"
#define PLOT_CMDS "/home/staff/ad/www_public/code/biorhythm/bio.plt"
#define TEMP_CMDS "/home/staff/ad/www_public/code/biorhythm/tmp.plt"
#define PIC       "/home/staff/ad/www_public/code/biorhythm/pbio"
#define URL_PIC   "http://www.cs.mu.oz.au/~ad/code/biorhythm/pbio"
#define DELNO 4
#define LOCKFNM   "/home/staff/ad/www_public/code/biorhythm/lock##"

typedef struct {  /* structure for HTML name/value pair */
  char *name;
  char *val;
} entry; 
typedef struct date {
  int day;
  int month;    /* 1 to 12 */
  int year;     /* e.g. 1996 */
} Date;

Date start_date(entry entries[], int etnum, long int *start);
char *get_val(entry entries[], int etnum, char *field);
Date check_date(Date d);
long int toJulian(Date d);
int get_count();
void make_bpic(long int start, Date sd, int count);
void modify_plot(char *plotnm, char *tempnm, long int start, Date d);
/* Locking Utilities */
int my_lock(void);
void my_unlock(int fd);
/* Standard Form Functions */
void start_reply(char *title);
void cgi_errs();
int build_entries(entry entries[]);
char *find_value(entry entries[], int etnum, char *name);
/* Debugging Utilities */
int input_entries(entry entries[]);
void show_entries(entry entries[], int etnum);
/* CGI utilities written by Rob McCool */
char *makeword(char *line, char stop);
char *fmakeword(FILE *f, char stop, int *len);
void unescape_url(char *url);
char x2c(char *what);
void plustospace(char *str);

int main()
{
  entry entries[MAX_ENTRIES];
  int etnum = 0, count;
  Date sd;
  long int start;
  start_reply("Biorhythm Output");
  cgi_errs();
  etnum = build_entries(entries);
/*
  etnum = input_entries(entries);
  show_entries(entries, etnum);
*/
  sd = start_date(entries, etnum, &start);
  printf("<P>Number of days since your birth: %ld</P>\n", start);
  count = get_count();
  make_bpic(start, sd, count);
  printf("<P><img src=\"%s%d.gif\" alt=\"[No Graph]\"></P>\n", 
                                                 URL_PIC, count); 
  printf("</body></html>\n");
  return 1;
}

Date start_date(entry entries[], int etnum, long int *start)
/* Get the user's birthday (bd) and month/year of interest (sd)
   from the form details. Work out the number of days between
   bd and sd, and store it in start. sd is also returned.
*/
{
  char *val;
  Date bd, sd;
  long int bdjul, sdjul;
  val = get_val(entries, etnum, "birth");
  sscanf(val, "%d/%d/%d", &bd.day, &bd.month, &bd.year);
  bd = check_date(bd);
  printf("<P>Birthday: %d/%d/%d<br>\n", bd.day, bd.month, bd.year);
  bdjul = toJulian(bd);
  val =  get_val(entries, etnum, "bio");
  sscanf(val, "%d/%d", &sd.month, &sd.year);
  sd.day = 1;
  sd = check_date(sd);
  printf("Biorhythm month: %d/%d</P>\n", sd.month, sd.year);
  sdjul = toJulian(sd);
  if (sdjul >= bdjul)
    *start = sdjul - bdjul;
  else {
    printf("<P><b>Error</b>: your biorhythm month starts before your birthday;
adjusting it to be the birthday</P>\n");
    *start = 0;
    return bd;
  }
  return sd;
}

char *get_val(entry entries[], int etnum, char *field)
/* Terminate if no value has been entered */
{
  char *val;
  val = find_value(entries, etnum, field);
  if (strcmp(val, NOVAL) == 0) {
    printf("<P>Error: No value found in \"%s\" field</P>\n", field);
    printf("</body></html>\n");
    exit(1);
  }
  return val;
}

Date check_date(Date d)
/* Check the date for obvious mistakes */
{
  if (d.year < 0) {
    printf("<P><b>Error</b>: year cannot be less than 0; adjusting to 0</P>\n");
    d.year = 0;
  }
  if (d.year > 9000) {
    printf("<P><b>Error</b>: year cannot be more than 9000; adjusting to 9000</P>\n");
    d.year = 9000;
  }
  if (d.month < 1) {
    printf("<P><b>Error</b>: month cannot be less than 1; adjusting to 1</P>\n");
    d.month = 1;
  }
  if (d.month > 12) {
    printf("<P><b>Error</b>: month cannot be more than 12; adjusting to 12</P>\n");
    d.month = 12;
  }
  if (d.day < 1) {
    printf("<P><b>Error</b>: day cannot be less than 1; adjusting to 1</P>\n");
    d.day = 1;
  }
  if ((d.month == 1) || (d.month == 3) || (d.month == 5) ||
      (d.month == 7) || (d.month == 8) || (d.month == 10) ||
      (d.month == 12)) {
    if (d.day > 31) {
      printf("<P><b>Error</b>: day cannot be more than 31; adjusting to 31</P>\n");
      d.day = 31;
    }
  }
  else if ((d.month == 4) || (d.month == 6) || (d.month == 9) ||
           (d.month == 11)) {
     if (d.day > 30) {
       printf("<P><b>Error</b>: day cannot be more than 30; adjusting to 30</P>\n");
       d.day = 30;
     }
  }
  else if (d.month == 2) {
     /* february: don't bother with leap year checking */
     if (d.day > 29) {
       printf("<P><b>Error</b>: day cannot be more than 29; adjusting to 29</P>\n");
       d.day = 29;
     }
  }
  return d;
}

long int toJulian(Date d)
/* Calculates the Julian day for the given date.
   This code is taken from the function MdyToJulian() in
   Astrolog (v.5.10), available from ftp.magitech.com
   in the directory /pub/astrology/astrolog.
   It seems to be based on algorithms in Michael Erlewine's
   'Manual of Computer Programming for Astrologers',
   available from Matrix Software.
*/
{
  long im, jul;
  im = 12*((long)d.year+4800) + (long)d.month - 3;
  jul = (2*(im%12) + 7 + 365*im)/12;
  jul += (long)d.day + im/48 - 32083;
  if (jul > 2299171)                   /* Take care of dates in */
    jul += im/4800 - im/1200 + 38;     /* Gregorian calendar.   */
  return jul;
}

int get_count()
/* The count is extracted from COUNT_NM, and the value in
   the file is incremented. The use of lockf() means that
   lower-level UNIX file manipulation (e.g. open(), read())
   must be utilised.
*/
{
  char ln[MAXLEN];
  int fd, cnt, size;
  if ((fd = open(COUNT_NM, O_RDWR)) == -1) {   /* both read and write */
    printf("<P><b>Error</b>: Cannot open %s for changing</P>\n", COUNT_NM);
    printf("</body></html>\n");
    exit(1);
  }
  lockf(fd, F_LOCK, 0L);      /* (wait for) exclusive lock */
  size = read(fd, ln, MAXLEN);
  ln[size] = '\0';
  sscanf(ln, "%d\n", &cnt);
  lseek(fd, 0L, 0);           /* rewind to start of file */
  sprintf(ln, "%d\n", cnt+1);
  size = strlen(ln);
  write(fd, ln, size);
  lockf(fd, F_ULOCK, 0L);     /* unlock */
  close(fd);
  return cnt;
}

void make_bpic(long int start, Date sd, int count)
/* Housekeeping: remove an old biorhythm GIF.
   Creating a new biorhythm GIF involves making a new 
   version of the gnuplot commands in PLOT_CMDS by changing 
   start and the month/year of interest (sd), to create TEMP_CMDS. 
   TEMP_CMDS is evaluated by gnuplot to create PIC.
   PIC is in ".ppm" format, and so is converted into 
   a GIF with a unique name by using ppmtogif and count.
   Since TEMP_CMDS and PIC are not unique, use locking
   to stop any other invocations of pb from working on them
   at the same time: an 'exclusion' zone.
*/
{
  char cmd[MAXLEN];
  int lock_fd;
  /* Remove old biorhythm picture */
  sprintf(cmd, "%s%d.gif", PIC, count-DELNO);
  remove(cmd);
  lock_fd = my_lock();    /* start of 'exclusion' zone */
  modify_plot(PLOT_CMDS, TEMP_CMDS, start, sd);
  /* Plot the biorhythm */
  sprintf(cmd, "/usr/local/bin/gnuplot %s", TEMP_CMDS);
  system(cmd);
  /* Change the biorhythm into GIF form */
  sprintf(cmd, "/home/staff/ad/pbm/pbm-mundook/ppmtogif %s.ppm > %s%d.gif", 
                                    PIC, PIC, count);
  system(cmd);
  my_unlock(lock_fd);     /* end of 'exclusion' zone */
}

void modify_plot(char *plotnm, char *tempnm, long int start, Date d)
/* Open the template gnuplot commands file (plotnm) and modify it,
   saving the new version in tempnm. The changes are to replace '?'
   by start and '@' by d.
*/
{
  FILE *fp, *tfp;
  int c;
  if ((fp = fopen(plotnm, "r")) == NULL) {
    printf("<P><b>Error</b>: Cannot open %s for reading</P>\n", plotnm);
    printf("</body></html>\n");
    exit(1);
  }
  else if ((tfp = fopen(tempnm, "w")) == NULL) {
    printf("<P><b>Error</b>: Cannot open %s for writing</P>\n", tempnm);
    printf("</body></html>\n");
    exit(1);
  }
  else {
    while ((c = fgetc(fp)) != EOF) 
      if (c == '?')         /* marker for start digit */
        fprintf(tfp, "%ld", start);
      else if (c == '@')    /* marker for start date */
        fprintf(tfp, "%d/%d", d.month, d.year);
      else 
        fputc(c, tfp);
    fclose(fp);
    fclose(tfp);
  }
}

/* Locking Utilities */
int my_lock(void)
{
  int fd;
  if ((fd = open(LOCKFNM, O_RDWR)) == -1)
    printf("<P>Error: Cannot use locking</P>\n");
  else
    lockf(fd, F_LOCK, 0L);
  return fd;
}

void my_unlock(int fd)
{
  if (fd != -1) {
    lockf(fd, F_ULOCK, 0L);
    close(fd);
  }
}

/* Standard Form Functions */
void start_reply(char *title)
/* Output the HTML header, including the title and H1 container */
{
  printf("Content-type: text/html\n\n");
  printf("<html><head><title>%s</title></head>\n", title);
  printf("<body background=\"%s\">", BKGROUND);
  printf("<H1 align=center>%s</H1>\n", title);
  fflush(NULL);   /* force output to occur now */
}

void cgi_errs()
/* Check for errors in the REQUEST_METHOD and CONTENT_TYPE
   environment variables.
*/
{
  if(strcmp(getenv("REQUEST_METHOD"),"POST")) {
    printf("<P>This script should be referenced with a METHOD of POST.\n");
    printf("If you don't understand this, read ");
    printf("<A HREF=\"http://www.ncsa.uiuc.edu/SDG/Software/Mosaic/Docs
/fill-out-forms/overview.html\">forms overview</A>.</P>\n");
    printf("</body></html>");
    exit(1);
  }
  if(strcmp(getenv("CONTENT_TYPE"), "application/x-www-form-urlencoded")) {
    printf("<P>This script can only be used to decode form results.</P>\n");
    printf("</body></html>");
    exit(1);
  }
}

int build_entries(entry entries[])
/* Fill in the entries array with nameString=valueString 
   pairs from the form. 
*/
{
  int len, x;
  len = atoi(getenv("CONTENT_LENGTH"));
  for(x=0; len && (!feof(stdin)) && (x < MAX_ENTRIES); x++) {
    entries[x].val = fmakeword(stdin,'&',&len);
    plustospace(entries[x].val);
    unescape_url(entries[x].val);
    entries[x].name = makeword(entries[x].val,'=');
  }
  return x;  
}

char *find_value(entry entries[], int etnum, char *name)
/* Search through the entries array for a name string 
   which matches name and return its associated value 
   string. Otherwise return NOVAL.
*/
{
  int x;
  char *value;
  for(x=0; x < etnum; x++)
    if (strcmp(entries[x].name, name) == 0) {
      value = (char *)malloc(sizeof(char)*(strlen(entries[x].val)+1));
      strcpy(value, entries[x].val);
      break;
    }
  if ((x == etnum) || (value[0] == '\0')) {
    value = (char *)malloc(sizeof(char)*(strlen(NOVAL)+1));
    strcpy(value, NOVAL);
  }
  return value;
}

/* Debugging Utilities */
int input_entries(entry entries[])
/* Fill in the entries array with nameString=valueString pairs 
   entered by the user at the keyboard.
   Each pair should be entered in the format:
      nameString=valueString
   Terminate the input with <ctrl>d
*/
{
  int num = 0, namelen, linelen;
  char line[MAXLEN];
  printf("\nEnter name=value pairs:\n\n");
  while (gets(line) && (num < MAX_ENTRIES)) {
    linelen = strlen(line);
    namelen = strcspn(line, "=");
    entries[num].name =
      (char *)malloc(sizeof(char)*(namelen+1));
    strncpy(entries[num].name, line, namelen);
    entries[num].name[namelen] = '\0';
    entries[num].val =
      (char *)malloc(sizeof(char)*(linelen-namelen));
    strcpy(entries[num].val, &line[namelen+1]);
    num++;
  }
  return num;
}

void show_entries(entry entries[], int etnum)
/* Output the nameString=valueString pairs as an HTML 
   unnumbered list */
{
  int x;
  printf("<P>You submitted these nameString=valueString pairs:</P>\n");
  printf("<ul>\n");
  for(x=0; x < etnum; x++)
    printf("<li><code>%s = %s</code>\n", entries[x].name, entries[x].val);
  printf("</ul>\n");
  printf("</body></html>\n\n");
}

/* CGI Utilities by Rob McCool; 
   comments by Andrew Davison */
char *makeword(char *line, char stop) 
/* Builds a word by extracting characters from line up to the
   stopping character in stop (or until line is exhausted).
*/
{
  int x = 0, y = 0;
  char *word = (char *) malloc(sizeof(char) * (strlen(line) + 1));
  for(x=0; ((line[x]) && (line[x] != stop)); x++)
     word[x] = line[x];
  word[x] = '\0';
  if(line[x]) 
    ++x;
  while((line[y++] = line[x++]));
  return word;
}

char *fmakeword(FILE *f, char stop, int *len) 
/* Similar to makeword(): builds a word by extracting characters 
   from the file f up to the stopping character in stop (or
   until f is exhausted). The function is also supplied with
   the length of the string (len) left unread in f.
*/
{
  int wsize = 102400, ll = 0;
  char *word = (char *) malloc(sizeof(char) * (wsize + 1));
  while(1) {
    word[ll] = (char)fgetc(f);
    if(ll == wsize) {
       word[ll+1] = '\0';
       wsize += 102400;
       word = (char *)realloc(word,sizeof(char)*(wsize+1));
    }
    --(*len);
    if((word[ll] == stop) || (feof(f)) || (!(*len))) {
       if(word[ll] != stop) ll++;
       word[ll] = '\0';
       return word;
    }
    ++ll;
  }
}

void unescape_url(char *url) 
/* Convert the hexadecimal characters in url into ASCII.
   They are detected by starting with a '%'.
*/
{
  register int x, y;
  for(x=0,y=0; url[y]; ++x,++y) {
    if((url[x] = url[y]) == '%') {
      url[x] = x2c(&url[y+1]);
      y+=2;
    }
  }
  url[x] = '\0';
}

char x2c(char *what) 
/* Convert the hexadecimal (consisting of two characters) into
   a single ASCII character.
*/
{
  register char digit;
  digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
  digit *= 16;
  digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
  return(digit);
}

void plustospace(char *str) 
/* Replace '+'s by spaces in str */
{
  register int x;
  for(x=0; str[x]; x++) 
    if(str[x] == '+') str[x] = ' ';
}



Listing Four



# Plot the biorhythm curves between start and start+30
# Save output into the file pbio.ppm
start = ?
bio(day, cycle) =  sin((real(day-1+start)/cycle)*2*pi) * 100
set term pbm color
set output "/home/staff/ad/www_public/code/biorhythm/pbio.ppm"
set xlabel "Biorhythm Chart for @"
set xtics 1,1,31
plot [x=1:31] \
  bio(x,23) title 'Physical', \
  bio(x,28) title 'Emotional', \
  bio(x,33) title 'Intellectual'



Listing Five



/* simp-pb.c */
/* Andrew Davison, July 1996 (ad@cs.mu.oz.au)  */
/* Return a GIF containing 
   the user's three biorhythm curves for a specified month/year.
   The user supplies their birthday and month/year of interest
   via a form.
   The number of days since the user's birthday is
   calculated by converting the birthday and month/year of interest
   into their number of Julian days. The difference is the
   start day for the biorhythm equation.
   The gnuplot commands are in PLOT_CMDS, and this is modified
   and copied into TEMP_CMDS before being displayed. The two
   changes insert the start day and the month/year details.
   gnuplot saves its drawing into <PIC>.ppm, and this is
   converted into GIF format with ppmtogif.
   URL_PIC is the URL of PIC. 
   Tools used: gnuplot, ppmtogif
   This is a simplified version of pb.c which generates a GIF
   rather than a Web page containing a link to a GIF. 
   Only the functions that are different from those in pb.c 
   are listed here. The full version of simp-pb.c is available in:
      http://www.cs.mu.oz.au/~ad/code/biorhythm/simp-pb.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>     /* for read(), lseek(), write(), close(), lockf() */
#include <fcntl.h>      /* for open() */

#define BKGROUND "http://www.cs.mu.oz.au/~ad/chalk.jpg"
#define MAX_ENTRIES 2           /* number of input fields */
#define MAXLEN 500              /* max length of a string */
#define NOVAL "$$no-value$$"    /* when no value is found */
#define PLOT_CMDS "/home/staff/ad/www_public/code/biorhythm/bio.plt"
#define TEMP_CMDS "/home/staff/ad/www_public/code/biorhythm/tmp.plt"
#define PIC       "/home/staff/ad/www_public/code/biorhythm/pbio"
#define LOCKFNM   "/home/staff/ad/www_public/code/biorhythm/lock##"

typedef struct {  /* structure for HTML name/value pair */
  char *name;
  char *val;
} entry; 
typedef struct date {
  int day;
  int month;    /* 1 to 12 */
  int year;     /* e.g. 1996 */
} Date;

Date start_date(entry entries[], int etnum, long int *start);
char *get_val(entry entries[], int etnum, char *field);
Date check_date(Date d);
long int toJulian(Date d);
void make_bpic(long int start, Date sd);
void modify_plot(char *plotnm, char *tempnm, long int start, Date d);
/* Locking Utilities */
int my_lock(void);
void my_unlock(int fd);
/* Standard Form Functions */
void start_reply(char *title);
void cgi_errs();
int build_entries(entry entries[]);
char *find_value(entry entries[], int etnum, char *name);
/* Debugging Utilities */
int input_entries(entry entries[]);
void show_entries(entry entries[], int etnum);
/* CGI utilities written by Rob McCool */
char *makeword(char *line, char stop);
char *fmakeword(FILE *f, char stop, int *len);
void unescape_url(char *url);
char x2c(char *what);
void plustospace(char *str);

int main()
{
  entry entries[MAX_ENTRIES];
  int etnum = 0;
  Date sd;
  long int start;
  cgi_errs();
  etnum = build_entries(entries);
/*
  etnum = input_entries(entries);
  show_entries(entries, etnum);
*/
  sd = start_date(entries, etnum, &start);
  make_bpic(start, sd);
  return 1;
}

Date start_date(entry entries[], int etnum, long int *start)
/* Get the user's birthday (bd) and month/year of interest (sd)
   from the form details. Work out the number of days between
   bd and sd, and store it in start. sd is also returned.
*/
{
  char *val;
  Date bd, sd;
  long int bdjul, sdjul;
  val = get_val(entries, etnum, "birth");
  sscanf(val, "%d/%d/%d", &bd.day, &bd.month, &bd.year);
  bd = check_date(bd);
  bdjul = toJulian(bd);
  val =  get_val(entries, etnum, "bio");
  sscanf(val, "%d/%d", &sd.month, &sd.year);
  sd.day = 1;
  sd = check_date(sd);
  sdjul = toJulian(sd);
  if (sdjul >= bdjul)
    *start = sdjul - bdjul;
  else {
    *start = 0;
    return bd;
  }
  return sd;
}

char *get_val(entry entries[], int etnum, char *field)
/* Terminate if no value has been entered */
{
  char *val;
  val = find_value(entries, etnum, field);
  if (strcmp(val, NOVAL) == 0) {
    start_reply("Biorhythm Error");
    printf("<P>Error: No value found in \"%s\" field</P>\n", field);
    printf("</body></html>\n");
    exit(1);
  }
  return val;
}

Date check_date(Date d)
/* Check the date for obvious mistakes */
{
  if (d.year < 0)
    d.year = 0;
  else if (d.year > 9000)
    d.year = 9000;
  if (d.month < 1)
    d.month = 1;
  else if (d.month > 12)
    d.month = 12;
  if (d.day < 1) 
    d.day = 1;
  if ((d.month == 1) || (d.month == 3) || (d.month == 5) ||
      (d.month == 7) || (d.month == 8) || (d.month == 10) ||
      (d.month == 12)) {
    if (d.day > 31)
      d.day = 31;
  }
  else if ((d.month == 4) || (d.month == 6) || (d.month == 9) ||
           (d.month == 11)) {
     if (d.day > 30) 
       d.day = 30;
  }
  else if (d.month == 2) {
     /* february: don't bother with leap year checking */
     if (d.day > 29)
       d.day = 29;
  }
  return d;
}

long int toJulian(Date d)
{ /* see pb.c for definition */ }

void make_bpic(long int start, Date sd)
/* Housekeeping: remove an old biorhythm GIF.
   Creating a new biorhythm GIF involves making a new 
   version of the gnuplot commands in PLOT_CMDS by changing 
   start and the month/year of interest (sd), to create TEMP_CMDS. 
   TEMP_CMDS is evaluated by gnuplot to create PIC.
   PIC is in ".ppm" format, and so is converted into 
   a GIF sent to stdout. This means that it will be returned
   to the user. Prior to this a suitable "Content-type"
   header must be output.
   Since TEMP_CMDS and PIC are not unique, use locking
   to stop any other invocations of pb from working on them
   at the same time: an 'exclusion' zone.
*/
{
  char cmd[MAXLEN];
  int lock_fd;
  lock_fd = my_lock();    /* start of 'exclusion' zone */
  modify_plot(PLOT_CMDS, TEMP_CMDS, start, sd);
  /* Plot the biorhythm */
  sprintf(cmd, "/usr/local/bin/gnuplot %s", TEMP_CMDS);
  system(cmd);
  printf("Content-type: image/gif\n\n");
  fflush(NULL);   /* force output to occur now */
  /* Change the biorhythm into GIF form */
  sprintf(cmd, "/home/staff/ad/pbm/pbm-mundook/ppmtogif %s.ppm 2> /dev/null", 
                                    PIC);
  system(cmd);
  my_unlock(lock_fd);     /* end of 'exclusion' zone */
}

void modify_plot(char *plotnm, char *tempnm, long int start, Date d)
/* Open the template gnuplot commands file (plotnm) and modify it,
   saving the new version in tempnm. The changes are to replace '?'
   by start and '@' by d.
*/
{
  FILE *fp, *tfp;
  int c;
  if ((fp = fopen(plotnm, "r")) == NULL) {
    start_reply("Biorhythm Error");
    printf("<P><b>Error</b>: Cannot open %s for reading</P>\n", plotnm);
    printf("</body></html>\n");
    exit(1);
  }
  else if ((tfp = fopen(tempnm, "w")) == NULL) {
    start_reply("Biorhythm Error");
    printf("<P><b>Error</b>: Cannot open %s for writing</P>\n", tempnm);
    printf("</body></html>\n");
    exit(1);
  }
  else {
    while ((c = fgetc(fp)) != EOF) 
      if (c == '?')         /* marker for start digit */
        fprintf(tfp, "%ld", start);
      else if (c == '@')    /* marker for start date */
        fprintf(tfp, "%d/%d", d.month, d.year);
      else 
        fputc(c, tfp);
    fclose(fp);
    fclose(tfp);
  }
}

/* Locking Utilities */
int my_lock(void)
{ /* see pb.c for definition */ }

void my_unlock(int fd)
{ /* see pb.c for definition */ }

/* Standard Form Functions */
void start_reply(char *title)
{ /* see pb.c for definition */ }

void cgi_errs()
/* Check for errors in the REQUEST_METHOD and CONTENT_TYPE
   environment variables.
*/
{
  if(strcmp(getenv("REQUEST_METHOD"),"POST")) {
    start_reply("Biorhythm Error");
    printf("<P>This script should be referenced with a METHOD of POST.\n");
    printf("If you don't understand this, read ");
    printf("<A HREF=\"http://www.ncsa.uiuc.edu/SDG/Software/Mosaic/Docs
/fill-out-forms/overview.html\">forms overview</A>.</P>\n");
    printf("</body></html>");
    exit(1);
  }
  if(strcmp(getenv("CONTENT_TYPE"), "application/x-www-form-urlencoded")) {
    start_reply("Biorhythm Error");
    printf("<P>This script can only be used to decode form results.</P>\n");
    printf("</body></html>");
    exit(1);
  }
}

/* All the other Standard Form Functions are unchanged from
   pb.c. See the function prototypes at the top of this listing
   for their names.
*/