[alt.sources] Calendar program for non-PostScript sites

rogers@sud509.ed.ray.com (Andrew Rogers) (09/28/90)

I've gotten a few requests for this from people who do not have PostScript at
their sites and (consequently) can't use Pcal.  This program generates a
calendar which may be printed on any line printer using standard 132x66
fan-fold paper.  Unfortunately, it lacks Pcal's most important capability -
to import text from a date file - but it's useful nonetheless.  Have fun!

Andrew

-------------------------------- cut here --------------------------------
/*
 *	Calendar program - one month per page
 *
 *	Author: AW Rogers
 *
 *	Parameters:
 *
 *		calen		generate calendar for current month/year
 *
 *		calen yy	generate calendar for entire year yy
 *
 *		calen mm yy	generate calendar for month mm (1 = January),
 *					year yy (19yy if yy < 100)
 *
 *		calen mm yy n	as above, for n consecutive months
 *
 *	Options:
 *
 *		-b<N>		add N blank lines at top of each page
 *
 *		-f<FILE>	write output to file FILE (calen.lst if -f alone)
 *
 *		-l		left-justify dates within boxes (default)
 *
 *		-r		right-justify dates within boxes
 *
 *		-o<STR>		use characters in STR as overstrike sequence for
 *					printing large month/year (default: HIX)
 *		
 *		-t		print trailing dates (30, 31 in 23/30 and 24/31)
 *					in vacant box on first line
 *
 *	Output:
 *
 *		Full-page calendar for each of the specified months; written to
 *		stdout unless -f option used.
 *
 */
 
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <string.h>

#define FALSE	0
#define TRUE	1

#define JAN	1			/* significant months/years */
#define FEB	2
#define DEC	12
#define MINYR	1753
#define MAXYR	9999

#define SOLID	0			/* line styles (cf. box_line()) */
#define OPEN	1

#define LEFT	0			/* date justification within boxes */
#define RIGHT	1
#define DEFAULT_JUST	LEFT

#define TOP		0		/* trailing date position */
#define BOTTOM		1
#define DEFAULT_TRAIL	BOTTOM

#define TOP_ROW		0		/* top and bottom rows of calendar */
#define BOTTOM_ROW	5

#define OVERSTRIKE	"HIX"		/* overstrike sequence for heading */
#define MAX_OVERSTR	3

#define OUTFILE		"calen.lst"	/* default output file if -f option used */

#define NUM_BLANKS	0		/* default blank lines after <FF> */
#define NUM_MONTHS	1		/* default number of months */

#define MAXARGS		3		/* maximum non-flag command-line args */


#ifdef VMS		
#define EXIT_FAILURE	3
#define END_PATH	']'
#else
#define EXIT_FAILURE	1
#define END_PATH	'/'
#endif

#define is_leap(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))

typedef struct				/* information about a single month */
    {
    int 	mm;
    int		yy;
    char	*mmname;
    char	dates[6][7][3];
    } month_rec;

typedef month_rec *p_month;		/* pointer to above structure */

/* globals for defaultable command-line parameters, and their defaults */

int just = DEFAULT_JUST;		/* justification of dates */
int trail = DEFAULT_TRAIL;		/* format for 23/30, 24/31 */
int nblank = NUM_BLANKS;		/* blank lines after <FF> */
char *seq = OVERSTRIKE;			/* overstrike sequence for heading */
int nmonths = NUM_MONTHS;		/* number of months to print */

char *fname = "";			/* output file name */

main(argc, argv)
    int  argc;
    char *argv[];
{
month_rec mRec[3];			/* space for main and small calendars */
p_month prev = mRec, curr = mRec+1, next = mRec+2, temp;

/* Get and validate command-line parameters and flags */

get_params(argc, argv, curr);

/* Fill in calendars for previous and current month */

prev->mm = curr->mm == JAN ? DEC : curr->mm - 1;
prev->yy = curr->mm == JAN ? curr-> yy - 1 : curr->yy;

fill_calendar(prev);
fill_calendar(curr);

/*
 * Main loop: print each month of the calendar (with small calendars for the
 * previous and next months in the upper corners).  The current and next
 * months' calendars can be reused as the previous and current calendars for
 * the following month; only the 'next' calendar need be calculated each
 * time through the loop.
 */
 
while (nmonths-- > 0 && curr->yy <= MAXYR)
    {
    next->mm = curr->mm == DEC ? JAN : curr->mm + 1;
    next->yy = curr->mm == DEC ? curr->yy + 1 : curr->yy;
    fill_calendar(next);			/* fill in following month */
    
    print_calendar(prev, curr, next);
    
    temp = prev;				/* swap pointers to months */
    prev = curr;
    curr = next;
    next = temp;
    }

if (*fname)					/* report output file name */
    fprintf(stderr, "Output is in file %s\n", fname);
}


/*
 * Get and validate command-line parameters and flags.  If month/year not
 * specified on command line, generate calendar for current month/year.
 * Exit program if month or year out of range; forgive illegal flags.
 */
 
get_params(argc, argv, curr)
    int  argc;		/* argument count, vector passed in from main() */
    char *argv[];
    p_month curr;	/* current month record (fill in month/year) */
{
int  badopt = FALSE;		/* flag set if bad option  */
int  badpar = FALSE;		/* flag set if bad param   */
int  nargs = 0;			/* count of non-flag args  */
int  numargs[MAXARGS];		/* non-flag (numeric) args */
char *parg;			/* generic argument ptr    */
long tmp;			/* temp for system clock   */
struct tm *p_tm;		/* ptr to date/time struct */
char *progname, *p;		/* program name (argv[0))  */
extern int atoi();

/* Isolate root program name (for use in error messages) */

progname = **argv ? *argv : "calen";
if ((p = strrchr(progname, END_PATH)) != NULL)
    progname = ++p;
if ((p = strchr(progname, '.')) != NULL)
    *p = '\0';

/* Walk command-line argument list */

while (--argc)
    {
    parg = *++argv;
    if (*parg == '-')
        {
        switch (*++parg)
            {
        case 'b':
            nblank = atoi(++parg);
            break;
	case 'f':
	    fname = *++parg ? parg : OUTFILE;
	    if (freopen(fname, "w", stdout) == (FILE *) NULL)
		{
		fprintf(stderr, "%s: error opening output file %s\n",
			progname, fname);
		exit (EXIT_FAILURE);
		}
            break;
	case 'l':
            just = LEFT;
            break;
        case 'r':
            just = RIGHT;
            break;
        case 'o':
	    if (*++parg)
	        seq = parg;
	    break;
	case 't':
	    trail = TOP;
	    break;
	default:
	    fprintf(stderr, "%s: invalid flag: %s\n", progname, *argv);
	    badopt = TRUE;
	    break;
	    }
	}
    else			/* non-flag argument - add to list */
    	{
    	if (nargs < MAXARGS)
    	    numargs[nargs++] = atoi(parg);
	}
    }

/* Get and validate non-flag (numeric) parameters */

switch (nargs)
    {

case 0:		/* no arguments - print current month/year */
    time(&tmp);
    p_tm = localtime(&tmp);
    curr->mm = p_tm->tm_mon + 1;
    curr->yy = p_tm->tm_year;
    break;    	    

case 1:		/* one argument - print entire year */
    curr->mm = JAN;
    curr->yy = numargs[0];
    nmonths = 12;
    break;

default:	/* two or three arguments - print one or more months */
    curr->mm = numargs[0];
    curr->yy = numargs[1];
    nmonths = nargs > 2 ? numargs[2] : NUM_MONTHS;
    break;
    }

if (curr->yy > 0 && curr->yy < 100)	/* treat nn as 19nn */
    curr->yy += 1900;
    
if (nmonths < 1)			/* ensure at least one month */
    nmonths = 1;
    
if (curr->mm < JAN || curr->mm > DEC)	/* check range of month and year */
    {
    fprintf(stderr, "%s: month %d not in range %d .. %d\n", progname, 
		curr->mm, JAN, DEC);
    badpar = TRUE;
    }
    
if (curr->yy < MINYR || curr->yy > MAXYR)
    {
    fprintf(stderr, "%s: year %d not in range %d .. %d\n", progname, 
		curr->yy, MINYR, MAXYR);
    badpar = TRUE;
    }
       
if (badpar || badopt)
    usage(progname);

if (badpar)
    exit(EXIT_FAILURE);
}

/*
 *	Print message explaining correct usage of the command-line
 *	arguments and flags
 */

usage(prog)
    char *prog;
{
fprintf(stderr, "\nUsage:\n\n");
fprintf(stderr, "\t%s [-bN] [-fFILE] [-l | -r] [-oSTR] [-t]\n", prog);
fprintf(stderr, "\t\t[ [ [mm] yy ] | [mm yy n] ]\n\n");
fprintf(stderr, "\nValid flags are:\n\n");
fprintf(stderr, "\t-bN\t\tadd N blank lines after each <FF> (default: %d)\n\n",
	NUM_BLANKS);
fprintf(stderr, "\t-fFILE\t\twrite output to file FILE (%s if -f alone)\n\n",
	OUTFILE);
fprintf(stderr, "\t-l\t\tleft-justify dates within boxes");
fprintf(stderr, "%s\n\n", DEFAULT_JUST == LEFT ? " (default)" : "");
fprintf(stderr, "\t-r\t\tright-justify dates within boxes");
fprintf(stderr, "%s\n\n", DEFAULT_JUST == RIGHT ? " (default)" : "");
fprintf(stderr, "\t-oSTR\t\tuse characters in STR as overstrike sequence for\n");
fprintf(stderr, "\t\t\tprinting large month/year (default: %s)\n\n",
	OVERSTRIKE);
fprintf(stderr, "\t-t\t\tmove trailing 30 and 31 to vacant box on top line\n");
fprintf(stderr, "\n");
fprintf(stderr, "\t%s [opts]\t\tgenerate calendar for current month/year\n",
	prog);
fprintf(stderr, "\n");
fprintf(stderr, "\t%s [opts] yy\t\tgenerate calendar for entire year yy\n",
	prog);
fprintf(stderr, "\n");
fprintf(stderr, "\t%s [opts] mm yy\tgenerate calendar for month mm\n", prog);
fprintf(stderr, "\t\t\t\t(Jan = 1), year yy (19yy if yy < 100)\n");
fprintf(stderr, "\n");
fprintf(stderr, "\t%s [opts] mm yy n\tas above, for n consecutive months\n",
	prog);
fprintf(stderr, "\n");
}


/*
 *	Print the calendar for the current month, generating small calendars
 *	for the previous and following months in the upper corners and the
 *	month/year (in 5x9 dot-matrix characters) centered at the top.
 */

print_calendar(prev, curr, next)
    p_month prev;		/* previous month (upper-left corner)	*/
    p_month curr;		/* current month (main calendar)	*/
    p_month next;		/* following month (upper-right corner)	*/
{
static char *wkday[] =
    { 
    " Sunday  ", " Monday  ", " Tuesday ", "Wednesday", "Thursday",
    " Friday  ", "Saturday "
    };
          
int nchars, line, week, day;
char *blanks = "                     ";		/* 21 blanks for centering */
char *padding;					/* pointer into 'blanks'   */
char month_and_year[20];			/* work area		   */
char *ovr;					/* overstrike sequence	   */

/* Set up month and year heading and appropriate padding to center it */

nchars = strlen(curr->mmname);
padding = blanks + (3 * (nchars - 3));
sprintf(month_and_year, "%s%5d", curr->mmname, curr->yy);

/* Print top-of-form and leading blank lines, if any */

printf("\f\n");
for (line = 0; line < nblank; line++)
    printf("\n");

/* Print month and year in large letters, with small calendars on each side */
    
for (line = 0; line < 9; line++)
    {
    for (ovr = seq; ovr < seq + MAX_OVERSTR - 1 && *(ovr+1); ovr++)
        {
        printf("%20s%s", " ", padding);		/* overstruck lines first */
        header_line(month_and_year, line, *ovr);
        printf(" %s", padding);
        printf("\r");
	}  
    small_cal_line(prev, line);		/* calendars and non-overstruck line */
    printf("%s", padding);
    header_line(month_and_year, line, *ovr);
    printf(" %s", padding);
    small_cal_line(next, line);
    printf("\n");
    }

printf("\n");				/* print the weekday names */
box_line(1, SOLID);
box_line(1, OPEN);
printf("  ");
for (day = 0; day < 7; day++)
    printf("|%13.9s    ", wkday[day]);
printf("|\n");
box_line(1, OPEN);

for (week = TOP_ROW; week < BOTTOM_ROW - 1; week++)	/* first four weeks */
    {
    box_line(1, SOLID);
    date_line(curr, week, just);
    box_line(7, OPEN);
    }

box_line(1, SOLID);			/* fifth week */
date_line(curr, BOTTOM_ROW - 1, just);
box_line(2, OPEN);

divider_line(curr->dates[BOTTOM_ROW]);	/* divider for 23/30 and/or 24/31 */
box_line(3, OPEN);

date_line(curr, BOTTOM_ROW, !just);	/* sixth week (trailing 31 or 30 31) */
box_line(1, SOLID);

}



/*
 *	Fill in the month name and date fields of a calendar record according
 *	to its month and year fields.
 */

fill_calendar(month)
    p_month month;		/* record to be filled in */
{
typedef struct			/* local info about months of year */
    {
    char *name;			/* month name 				   */
    int offset[2];		/* offset of m/1 from 1/1 (non-leap, leap) */
    int length[2];		/* length of month (non-leap, leap) 	   */
    } month_info;

static month_info info[12] = {
    { "January",   {0, 0}, {31, 31} }, { "February",  {3, 3}, {28, 29} },
    { "March",     {3, 4}, {31, 31} }, { "April",     {6, 0}, {30, 30} },
    { "May",       {1, 2}, {31, 31} }, { "June",      {4, 5}, {30, 30} },
    { "July",      {6, 0}, {31, 31} }, { "August",    {2, 3}, {31, 31} },
    { "September", {5, 6}, {30, 30} }, { "October",   {0, 1}, {31, 31} },
    { "November",  {3, 4}, {30, 30} }, { "December",  {5, 6}, {31, 31} }
    } ;

int i, first, last, date = 0, y = month->yy, m = month->mm - 1;
int leap = is_leap(y);

/* Determine when month starts and ends */

first = (y + (y-1)/4 - (y-1)/100 + (y-1)/400 + info[m].offset[leap]) % 7; 
last = first + info[m].length[leap] - 1;

for (i = 0; i < 42; i++)		/* fill in 7x6 matrix of dates */
    if (i < first || i > last)
	month->dates[i/7][i%7][0] = '\0';
    else
	sprintf(month->dates[i/7][i%7], "%2d", ++date);

if (trail == TOP)	/* move trailing 30/31 to top row if requested */
    for (i = 0; month->dates[BOTTOM_ROW][i][0]; i++)
	{
	strcpy(month->dates[TOP_ROW][i], month->dates[BOTTOM_ROW][i]);
	month->dates[BOTTOM_ROW][i][0] = '\0';
	}

month->mmname = info[m].name;		/* fill in month name */
}


/*
 *	Print one line of a small calendar (for upper left and right corners);
 *	always prints exactly 20 characters.
 */

small_cal_line(month, line)
    p_month month;		/* information for month to print */
    int line;			/* line to print (0-8; see below) */
{
int day;
char tmp1[10], tmp2[30];

switch (line)
    {
case 0:				/* month and year (centered) */
    strcpy(tmp1, "      ");
    tmp1[(15 - strlen(month->mmname)) / 2] = '\0';
    sprintf(tmp2, "%s%s %4d      ", tmp1, month->mmname, month->yy);
    printf("%-20.20s", tmp2);
    break;
case 1:				/* blank line */
    printf("%20s", " ");
    break;
case 2:				/* weekdays */
    printf("Su Mo Tu We Th Fr Sa");
    break;
default:			/* line of calendar (3 = first) */
    for (day = 0; day < 6; day++)
	printf("%2s ", month->dates[line-3][day]);
    printf("%2s", month->dates[line-3][day]);
    break;
    }
}


/*
 *	Print n calendar box lines in selected style
 */

box_line(n, style)
    int n;			/* number of lines to print */
    int style;			/* SOLID or OPEN	    */
{
int day;
char *fmt = style == SOLID ? "+-----------------" :
			     "|                 " ;

for (; n > 0; n--)
    {
    printf("  ");
    for (day = 0; day < 7; day++)
	printf(fmt);
    printf("%c\n", *fmt);
    }
}


/*
 *	Print one week of dates, left- or right-justified
 */

date_line(month, week, just)
    p_month month;		/* pointer to month data 	 */
    int week;			/* week to print (0 = first) 	 */
    int just;			/* justification (LEFT or RIGHT) */
{
int day;
char *fmt = just == LEFT ? "| %-16s" : "|%16s " ;

printf("  ");
for (day = 0; day < 7; day++)
    printf(fmt, month->dates[week][day]);
printf("|\n");
}


/*
 *	Print the divider separating 23/30 and/or 24/31 as needed
 */

divider_line(last_row)
    char last_row[7][3];	/* row containing any trailing 30 and/or 31 */
{
int day;

printf("  ");
for (day = 0; day < 7; day++)
    printf(last_row[day][0] ? "|_________________" : "|                 ");
printf("|\n");
}


/*
 *	Print least-significant 6 bits of n (0 = ' '; 1 = other char)
 */

decode(n, c)
    int n;		/* number to decode (row of 5x9 character) */
    char c;		/* character to print for each 1 bit	   */
{
int msk = 1 << 5;

for (; msk; msk >>= 1)
    printf("%c", n & msk ? c : ' ');
}


/*
 *	Print one line of string in large (5x9) characters
 */

header_line(str, line, c)
    char *str;		/* string to print 	*/
    int line;		/* line (0 - 8)		*/
    char c;		/* output character	*/
{

/* 5x7 representations of A-Z, 0-9; 5x9 representation of a-z */

static char uppers[26][7] = {
    {14, 17, 17, 31, 17, 17, 17},  {30, 17, 17, 30, 17, 17, 30},  /* AB */
    {14, 17, 16, 16, 16, 17, 14},  {30, 17, 17, 17, 17, 17, 30},  /* CD */
    {31, 16, 16, 30, 16, 16, 31},  {31, 16, 16, 30, 16, 16, 16},  /* EF */
    {14, 17, 16, 23, 17, 17, 14},  {17, 17, 17, 31, 17, 17, 17},  /* GH */
    {31,  4,  4,  4,  4,  4, 31},  { 1,  1,  1,  1,  1, 17, 14},  /* IJ */
    {17, 18, 20, 24, 20, 18, 17},  {16, 16, 16, 16, 16, 16, 31},  /* KL */
    {17, 27, 21, 21, 17, 17, 17},  {17, 17, 25, 21, 19, 17, 17},  /* MN */
    {14, 17, 17, 17, 17, 17, 14},  {30, 17, 17, 30, 16, 16, 16},  /* OP */
    {14, 17, 17, 17, 21, 18, 13},  {30, 17, 17, 30, 20, 18, 17},  /* QR */
    {14, 17, 16, 14,  1, 17, 14},  {31,  4,  4,  4,  4,  4,  4},  /* ST */
    {17, 17, 17, 17, 17, 17, 14},  {17, 17, 17, 17, 17, 10,  4},  /* UV */
    {17, 17, 17, 21, 21, 21, 10},  {17, 17, 10,  4, 10, 17, 17},  /* WX */
    {17, 17, 17, 14,  4,  4,  4},  {31,  1,  2,  4,  8, 16, 31}   /* YZ */
    };

static char lowers[26][9] = {
    { 0,  0, 14,  1, 15, 17, 15,  0,  0},  {16, 16, 30, 17, 17, 17, 30,  0,  0},  /* ab */
    { 0,  0, 15, 16, 16, 16, 15,  0,  0},  { 1,  1, 15, 17, 17, 17, 15,  0,  0},  /* cd */
    { 0,  0, 14, 17, 31, 16, 14,  0,  0},  { 6,  9, 28,  8,  8,  8,  8,  0,  0},  /* ef */
    { 0,  0, 14, 17, 17, 17, 15,  1, 14},  {16, 16, 30, 17, 17, 17, 17,  0,  0},  /* gh */
    { 4,  0, 12,  4,  4,  4, 31,  0,  0},  { 1,  0,  3,  1,  1,  1,  1, 17, 14},  /* ij */
    {16, 16, 17, 18, 28, 18, 17,  0,  0},  {12,  4,  4,  4,  4,  4, 31,  0,  0},  /* kl */
    { 0,  0, 30, 21, 21, 21, 21,  0,  0},  { 0,  0, 30, 17, 17, 17, 17,  0,  0},  /* mn */
    { 0,  0, 14, 17, 17, 17, 14,  0,  0},  { 0,  0, 30, 17, 17, 17, 30, 16, 16},  /* op */
    { 0,  0, 15, 17, 17, 17, 15,  1,  1},  { 0,  0, 30, 17, 16, 16, 16,  0,  0},  /* qr */
    { 0,  0, 15, 16, 14,  1, 30,  0,  0},  { 8,  8, 30,  8,  8,  9,  6,  0,  0},  /* st */
    { 0,  0, 17, 17, 17, 17, 15,  0,  0},  { 0,  0, 17, 17, 17, 10,  4,  0,  0},  /* uv */
    { 0,  0, 17, 21, 21, 21, 10,  0,  0},  { 0,  0, 17, 10,  4, 10, 17,  0,  0},  /* wx */
    { 0,  0, 17, 17, 17, 17, 15,  1, 14},  { 0,  0, 31,  2,  4,  8, 31,  0,  0},  /* yz */
    };

static char digits[10][7] = {
    {14, 17, 17, 17, 17, 17, 14},  { 2,  6, 10,  2,  2,  2, 31},  /* 01 */
    {14, 17,  2,  4,  8, 16, 31},  {14, 17,  1, 14,  1, 17, 14},  /* 23 */
    { 2,  6, 10, 31,  2,  2,  2},  {31, 16, 16, 30,  1, 17, 14},  /* 45 */
    {14, 17, 16, 30, 17, 17, 14},  {31,  1,  2,  4,  8, 16, 16},  /* 67 */
    {14, 17, 17, 14, 17, 17, 14},  {14, 17, 17, 15,  1, 17, 14}   /* 89 */
    };

char ch;

/* convert each character of str to dot-matrix representation for line */

for ( ; ch = *str; str++)
    {
    if (isupper(ch))
	decode(line < 7 ? uppers[ch-'A'][line] : 0, c);
    else if (islower(ch))
	decode(lowers[ch-'a'][line], c);
    else if (isdigit(ch))
	decode(line < 7 ? digits[ch-'0'][line] : 0, c);
    else
	decode(0, c);
    }

}