[comp.os.minix] Bug in cal.c

ast@cs.vu.nl (Andy Tanenbaum) (05/31/87)

Here is a version of the calendar program cal.c.  It prints calendars for
any year or month on the VAX.  It doesn't do anything on MINIX.  Beats me
why.  If any calendar lovers feel inspired to figure out what's wrong and
fix it, please post your findings.    Maybe a problem in stdio?

Andy Tanenbaum (ast@cs.vu.nl)

------------------------ cal.c ------------------------------
/* cal - print a calendar		Author: Martin Minow */

#include "stdio.h"

#define do3months	domonth
#define	IO_SUCCESS	0		/* Unix definitions		*/
#define	IO_ERROR	1
#define	EOS	0

#define	ENTRY_SIZE	3		/* 3 bytes per value		*/
#define DAYS_PER_WEEK	7		/* Sunday, etc.			*/
#define	WEEKS_PER_MONTH	6		/* Max. weeks in a month	*/
#define	MONTHS_PER_LINE	3		/* Three months across		*/
#define	MONTH_SPACE	3		/* Between each month		*/

char *badarg = {"Bad argument\n"};
char *how = {"Usage: cal [month] year\n"};

/*
 * calendar() stuffs data into layout[],
 * output() copies from layout[] to outline[], (then trims blanks).
 */
char layout[MONTHS_PER_LINE][WEEKS_PER_MONTH][DAYS_PER_WEEK][ENTRY_SIZE];
char outline[(MONTHS_PER_LINE * DAYS_PER_WEEK * ENTRY_SIZE)
      + (MONTHS_PER_LINE * MONTH_SPACE)
      + 1];

char *weekday = " S  M Tu  W Th  F  S";
char *monthname[] = {
  "???",						/* No month 0	*/
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

main(argc, argv)
int argc;
char *argv[];
{
  register int month;
  register int year;

  register int arg1val;
  int arg1len;
  int arg2val;

  if (argc <= 1) {
  	usage(how);
  } else {
      arg1val = atoi(argv[1]);
      arg1len = strlen(argv[1]);
      if (argc == 2) {
	/*
	 * Only one argument, if small, it's a month.  If
	 * large, it's a year.  Note:
	 *	cal	0082	Year 0082
	 *	cal	82	Year 0082
	 */
	if (arg1len <= 2 && arg1val <= 12)
		do3months(year, arg1val);
	else
		doyear(arg1val);
      } else {
	/*
	 * Two arguments, allow 1980 12 or 12 1980
	 */
	arg2val = atoi(argv[2]);
	if (arg1len > 2)
	    do3months(arg1val, arg2val);
	else
	    do3months(arg2val, arg1val);
      }
  }
  exit(IO_SUCCESS);
}

doyear(year)
int year;
/*
 * Print the calendar for an entire year.
 */
{
  register int month;

  if (year < 1 || year > 9999) usage(badarg);
  if (year < 100)
	printf("\n\n\n                                 00%2d\n\n", year);
  else
	printf("\n\n\n%35d\n\n", year);
  for (month = 1; month <= 12; month += MONTHS_PER_LINE) {
      printf("%12s%23s%23s\n",
	    monthname[month],
	    monthname[month+1],
	    monthname[month+2]);
      printf("%s   %s   %s\n", weekday, weekday, weekday);
      calendar(year, month+0, 0);
      calendar(year, month+1, 1);
      calendar(year, month+2, 2);
      output(3);
#if MONTHS_PER_LINE != 3
      << error, the above won't work >>
#endif
  }
  printf("\n\n\n");
}

domonth(year, month)
int year;
int month;
/*
 * Do one specific month -- note: no longer used
 */
{
  if (year < 1 || year > 9999) usage(badarg);
  if (month <= 0 || month > 12) usage(badarg);
  printf("%9s%5d\n\n%s\n", monthname[month], year, weekday);
  calendar(year, month, 0);
  output(1);
  printf("\n\n");
}

output(nmonths)
int nmonths;		/* Number of months to do	*/
/*
 * Clean up and output the text.
 */
{
  register int week;
  register int month;
  register char *outp;
  int i;
  char tmpbuf[21], *p;

  for (week = 0; week < WEEKS_PER_MONTH; week++) {
      outp = outline;
      for (month = 0; month < nmonths; month++) {
	/*
	 * The -1 in the following removes
	 * the unwanted leading blank from
	 * the entry for Sunday.
	 */
	p = &layout[month][week][0][1];
	for (i=0; i<20; i++) tmpbuf[i] = *p++;
	tmpbuf[20] = 0;
	sprintf(outp, "%s   ", tmpbuf);
	outp += (DAYS_PER_WEEK * ENTRY_SIZE) + MONTH_SPACE - 1;
      }
      while (outp > outline && outp[-1] == ' ')	outp--;
      *outp = EOS;
      puts(outline);
  }
}

calendar(year, month, index)
int year;
int month;
int index;		/* Which of the three months		*/
/*
 * Actually build the calendar for this month.
 */
{
  register char *tp;
  int week;
  register int wday;
  register int today;

  setmonth(year, month);
  for (week = 0; week < WEEKS_PER_MONTH; week++) {
      for (wday = 0; wday < DAYS_PER_WEEK; wday++) {
	tp = &layout[index][week][wday][0];
	*tp++ = ' ';
	today = getdate(week, wday);
	if (today <= 0) {
	    *tp++ = ' ';
	    *tp++ = ' ';
	}
	else if (today < 10) {
	    *tp++ = ' ';
	    *tp   = (today + '0');
	}
	else {
	    *tp++ = (today / 10) + '0';
	    *tp   = (today % 10) + '0';
	}
      }
  }
}

usage(s)
char *s;
{
/* Fatal parameter error. */

  fprintf(stderr, "%s", s);
  exit(IO_ERROR);
}

/*
 * Calendar routines, intended for eventual porting to TeX
 *
 * date(year, month, week, wday)
 *	Returns the date on this week (0 is first, 5 last possible)
 *	and day of the week (Sunday == 0)
 *	Note: January is month 1.
 *
 * setmonth(year, month)
 *	Parameters are as above, sets getdate() for this month.
 *
 * int
 * getdate(week, wday)
 *	Parameters are as above, uses the data set by setmonth()
 */

/*
 * This structure is used to pass data between setmonth() and getdate().
 * It needs considerable expansion if the Julian->Gregorian change is
 * to be extended to other countries.
 */

static struct {
    int this_month;	/* month number used in 1752 checking	*/
    int feb;		/* Days in February for this month	*/
    int sept;		/* Days in September for this month	*/
    int days_in_month;	/* Number of days in this month		*/
    int dow_first;	/* Day of week of the 1st day in month	*/
} info;

static int day_month[] = {	/* 30 days hath September...		*/
  0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

int
date(year, month, week, wday)
int year;		/* Calendar date being computed		*/
int month;		/* January == 1				*/
int week;		/* Week in the month 0..5 inclusive	*/
int wday;		/* Weekday, Sunday == 0			*/
/*
 * Return the date of the month that fell on this week and weekday.
 * Return zero if it's out of range.
 */
{
  setmonth(year, month);
  return (getdate(week, wday));
}

setmonth(year, month)
int year;			/* Year to compute		*/
int month;			/* Month, January is month 1	*/
/*
 * Setup the parameters needed to compute this month
 * (stored in the info structure).
 */
{
  register int i;

  if (month < 1 || month > 12) {	/* Verify caller's parameters	*/
      info.days_in_month = 0;	/* Garbage flag			*/
      return;
  }
  info.this_month = month;	/* used in 1752	checking	*/
  info.dow_first = Jan1(year);	/* Day of January 1st for now	*/
  info.feb = 29;			/* Assume leap year		*/
  info.sept = 30;			/* Assume normal year		*/
  /*
   * Determine whether it's an ordinary year, a leap year
   * or the magical calendar switch year of 1752.
   */
  switch ((Jan1(year + 1) + 7 - info.dow_first) % 7) {
  case 1:				/* Not a leap year		*/
      info.feb = 28;
  case 2:				/* Ordinary leap year		*/
      break;

  default:			/* The magical moment arrives	*/
      info.sept = 19;		/* 19 days hath September	*/
      break;
  }
  info.days_in_month =
        (month == 2) ? info.feb
      : (month == 9) ? info.sept
      : day_month[month];
  for (i = 1; i < month; i++) {
      switch (i) {		/* Special months?		*/
      case 2:			/* February			*/
	info.dow_first += info.feb;
	break;

      case 9:
	info.dow_first += info.sept;
	break;

      default:
	info.dow_first += day_month[i];
	break;
      }
  }
  info.dow_first %= 7;		/* Now it's Sunday to Saturday	*/
}

int getdate(week, wday)
int week;
int wday;
{
  register int today;

  /*
   * Get a first guess at today's date and make sure it's in range.
   */
  today = (week * 7) + wday - info.dow_first + 1;
  if (today <= 0 || today > info.days_in_month)
      return (0);
  else if (info.sept == 19 && info.this_month == 9
	&& today >= 3)		/* The magical month?	*/
      return (today + 11);	/* If so, some dates changed	*/
  else				/* Otherwise,			*/
      return (today);		/* Return the date		*/
}

static int Jan1(year)
int year;
/*
 * Return day of the week for Jan 1 of the specified year.
 */
{
  register int day;

  day = year + 4 + ((year + 3) / 4);	/* Julian Calendar	*/
  if (year > 1800) {			/* If it's recent, do	*/
      day -= ((year - 1701) / 100);	/* Clavian correction	*/
      day += ((year - 1601) / 400);	/* Gregorian correction	*/
  }
  if (year > 1752)			/* Adjust for Gregorian	*/
      day += 3;				/* calendar		*/
  return (day % 7);
}