[net.sources] PD cal program

chad@anasazi.UUCP (Chad R. Larson) (05/17/86)

The Public Domain version of CAL(1) posted by Martin Minow had a
couple of bugs.  The first was the (possibly naive) belief that no one
would request month or year zero, or at least the belief that there
was protection against that possibility.  The second (and more
important?) was that every month except one in the year 1752 (can you
guess which one?) was garbage.  Below are fixes to those bugs.  Tested
on our MegaFrame (SysVr2), should work on all.

____________________________(cut)________________________________

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by chad on Fri May 16 20:54:50 MST 1986
# Contents:  cal.c cal.man
 
echo x - cal.c
sed 's/^@//' > "cal.c" <<'@//E*O*F cal.c//'
/*
 * 			C A L E N D A R
 *
 * Usage:
 *	cal	MM		If small, it's a month, if large, a year.
 * or
 *	cal	YYYY MM		year/month
 * or
 *	cal	MM YYYY	
 */

#include <stdio.h>
#include <time.h>
#ifdef	decus
int	$$narg = 1;			/* Don't prompt			*/
#endif
#ifdef vms
#include		ssdef
#define	IO_ERROR	SS$_ABORT
#define	IO_SUCCESS	SS$_NORMAL
#endif
#ifndef	IO_ERROR
#define	IO_SUCCESS	0		/* Unix definitions		*/
#define	IO_ERROR	1
#endif
#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		*/

/*
 * 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 = "Su Mo Tu We Th Fr Sa";
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;
	int		tvec[2];
	struct tm	*tm;

	time(&tvec[0]);
	tm = localtime(&tvec[0]);
	year = tm->tm_year + 1900;
	month = tm->tm_mon + 1;
	if (argc <= 1) {
	    /*
	     * No arguments mean do last, this, and next month
	     */
	    do3months(year, month);
	}
	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:
		 *	calend	0082	Year '82
		 *	calend	82	Year 1982
		 */
		if (arg1len <= 2 && arg1val <= 12)
		    do3months(year, arg1val);
		else {
		    if (arg1len <= 2 && arg1val > 0 && arg1val <= 99)
			arg1val += 1900;
		    doyear(arg1val);
		}
	    }
	    else {
		/*
		 * Two arguments, allow YYYY MM or MM YYYY (but not
		 * MM YY or YY MM).
		 */
		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("year", year);
	printf("\n\n\n%35d\n\n", year);
	for (month = 1; month <= 12; month += MONTHS_PER_LINE) {
	    printf("\n\n%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");
}

do3months(thisyear, thismonth)
int		thisyear;
register int	thismonth;
/*
 * Do last month, this month, and next month.
 */
{
	int		lastmonth;
	int		lastyear;
	int		nextmonth;
	int		nextyear;

/*
 * Range check year and month.  Avoid wraps that would
 * cause embarrassment.
 */
	if (thisyear < 2 || thisyear > 9998)
	    usage("year", thisyear);
	if (thismonth <= 0 || thismonth > 12)
	    usage("month", thismonth);

	lastyear = nextyear = thisyear;
	if ((lastmonth = thismonth - 1) == 0) {
	    lastmonth = 12;
	    lastyear--;
	}
	if ((nextmonth = thismonth + 1) == 13) {
	    nextmonth = 1;
	    nextyear++;
	}
	printf("%9s%5d%18s%5d%18s%5d\n",
		monthname[lastmonth], lastyear,
		monthname[thismonth], thisyear,
		monthname[nextmonth], nextyear);
	printf("%s   %s   %s\n", weekday, weekday, weekday);
	calendar(lastyear, lastmonth, 0);
	calendar(thisyear, thismonth, 1);
	calendar(nextyear, nextmonth, 2);
	output(3);
#if MONTHS_PER_LINE != 3
	<< error, the above won't work >>
#endif
	printf("\n\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;

	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.
		 */
		sprintf(outp, "%.*s%*s",
		    DAYS_PER_WEEK * ENTRY_SIZE - 1,
		    &layout[month][week][0][1],
		    MONTH_SPACE, "");
		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(what, value)
char		*what;
int		value;
/*
 * Fatal parameter error
 */
{
	fprintf(stderr, "Calendar parameter error: bad %s: %d\n",
	    what, value);
	fprintf(stderr, "Usage: \"calend month\" or \"calend year month\"\n");
	fprintf(stderr, "Year and month are integers.\n");
	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		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.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 == info.days_in_month && info.sept == 19 
		 && 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);
}

@//E*O*F cal.c//
chmod u=rw,g=rw,o=rw cal.c
 
echo x - cal.man
sed 's/^@//' > "cal.man" <<'@//E*O*F cal.man//'




       CAL(1)		local				CAL(1)



       USAGE

        	cal [year] [month]
		cal [month] [year]

       DESCRIPTION

        	When invoked without arguments, calend prints a
        	calendar for the preceeding, current, and next
        	months of the current year.

        	If a month is given (a value from 1 through 12),
        	it prints the three months centered on the requested
        	month.  For example,

        	    calend 12

        	Prints November and December for this year, and
        	January for next year.

        	If a year is given, it prints a calendar for the
        	entire year:

        	    calend 1985

        	If both values are given, it prints the three months
        	centered on the indicated date:

        	    calend 1752 9
        	    calend 9 1752

        	Note that a three or four digit number will always be taken as
        	a year.  A one or two digit number will be either a month
        	or a year in the 20th century:

        	    calend 78		(equals calend 1978)
        	    calend 0078		(early common era)

       BUGS

        	The change from the Julian to Gregorian calendars follows
        	usage in England and her colonies.  Options should be provided
        	to process the change for other countries (and localities).
        	This is, however, a fairly complex task with little payback.

        	The year didn't always start in January.

       REFERENCES

        	Enclycopaedia Brittanica, 13th edition, Vol. 4, p. 987 et. seq.

       AUTHOR

        	Martin Minow


        			        -1-
@//E*O*F cal.man//
chmod u=rw,g=rw,o=rw cal.man
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
    398   1478   9180 cal.c
     65    207   1573 cal.man
    463   1685  10753 total
!!!
wc  cal.c cal.man | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0

----------------------(uncut)----------------------------------
-- 
"I read the news today...oh, boy!"
	-John Lennon
_____________________________________________________________________
UUCP:    {mot,terak}!anasazi!chad               Voice: Hey, Chad!
Ma Bell: (602) 870-3330                         ICBM:  N33deg,33min
Surface: International Anasazi, Inc.                   W112deg,03min
         7500 North Dreamy Draw Drive
         Suit 120
         Phoenix, AZ 85020