program calendar;
{*************************************************************************
Program:  CALENDAR
Author:  Richard Conn
Date:  4 Feb 82

Description:
       CALENDAR is used to display a Calendar to the user.  The
Calendar may be that of a particular Month in a particular Year
or that of all Months in a Particular Year.
       The calendar displayed is the Gregorian Calendar.
       The Calendar display may be sent to the user's Console
(by default) or optionally to the user's LST: device or a disk file.

Usage:
               calendar [month] year [/o]
       where
               month may be one of january, february, ..., december
                       (optional and only first three letters are req'd)
               year may be any year after byear
               o may be one of the following --
                       p - send output to Printer
                       d - send output to Disk
                       (o is optional and defaults to Console if omitted)

Examples:
               CALENDAR JANUARY 1982 -- Calendar of Month of January of 1982
               CALENDAR JAN 1982 -- Same as Above
               CALENDAR 1982 -- Calendar of all months of 1982
               CALENDAR 1982 /P -- Same as Above but Output to Printer
               CALENDAR 1982 /D -- Same as Above but Output to Disk
               CALENDAR 1982 /P/D -- Same as Above but Output to Disk
                       (Disk has priority)
*****************************************************************************}

{***************************************************************************

       'version' is the Version Number of CALENDAR.
       'byear1' is the Base Year of CALENDAR.  This year MUST be a Leap
Year.  Since CALENDAR uses integer arithmetic to do its calculations,
the range of years that may be addressed by CALENDAR is from byear to
byear + 30,000 (approx).
       'bday1' is the Base Day of CALENDAR.  This is the number (1 to 7)
of the First Sunday in January of the Base Year.

****************************************************************************}
const
       version = 13;
       byear1 = 1804;  { Base Year for this program }
       bday1 = 1;      { Base Day for the Base Year }

{***********************************************
       Global Types and Variables
************************************************}
type
       strptr = ^string;
var
       ofile : text;
       filename : string[14];
       month1, year1, dow : integer;
       mposfnd, mpos, ypos : integer;
       mdays : array [1..12] of integer;
       month : array [1..12] of string[10];
       year : string;
       command : strptr;
       cmdline, yline : string;
       lyear : boolean;
       icount : integer;
       match, conout, diskout : boolean;
       byear, bday, bdow : integer;

{****************************************************
       External PASCAL/MT+ System Functions
*****************************************************}
external function @cmd : strptr;

{**************************************************************************
       Function:  day_count
               Computes the number of days since the beginning of the year.
               (Jan 1 = Day 0)
       Input Parameters:
               day: integer in range 1-31
               month: integer in range 1-12
               year: integer
               mdays[i, 1<=i<=12 ]: number of days in month i, i=1=January
                       (Global Parameter)
       Output Parameters:
               day_count: Number of days since 1st day of year (0=1st day)
***************************************************************************}
function day_count (day, month, year : integer) : integer;
var
       ndays, i : integer;
begin
       ndays := day - 1;  { Adjust for first day being day 0}
       if month <> 1 then for i:=1 to month-1 do ndays := ndays + mdays[i];
                               { Compute Number of Days since Year Start }
       day_count := ndays;
       lyear := false;  { Assume NOT Leap Year }
       if (year mod 4) <> 0 then exit;  { If not Leap Year, Done }
       if ((year mod 100) = 0) and ((year mod 400) <> 0) then exit;
                       { 2000, 2400, etc are Leap, other centurys not }
       lyear := true;  { Leap Year }
       if month < 3 then exit;  { If in Feb or Jan, Done }
       day_count := ndays + 1;  { Adjust for Leap Year }
end;

{*********************************************************************
       Function:  day_of_week
               Computes day of the week that a given date falls on.
       Input Parameters:
               day : integer in range 1-31
               month : integer in range 1-12
               year : integer
       Output Parameters:
               day_of_week : integer in range 1-7 (bday = Sunday)
**********************************************************************}
function day_of_week (day, month, year : integer) : integer;
var
       ndays, tyear : integer;
begin
       ndays := day_count (day, month, year);  { Compute Number of Days }
       ndays := ndays + 365*(year - byear) + ((year - byear + 3) div 4);
       tyear := (year div 100) * 100;  { Century below given year }
       if ((tyear mod 400) <> 0) and (byear < tyear) and (tyear < year) then
               ndays := ndays - 1;  { Adjust for NO Leap Year century }
       day_of_week := (ndays mod 7) + 1;
end;

{************************************************************************
       Function:  CLINE
               Print syntax of Command Line for Calendar Program.
       Input/Output Parameters:  None
*************************************************************************}
procedure cline;  { Print Syntax of Command Line }
begin
       writeln('       Calendar Command Line should be:');
       writeln('               calendar month year /o');
       writeln('       ', byear1, ' <= YEAR <= 30,000 (approx)');
       writeln('       Only first three characters of MONTH are meaningful');
       writeln('       /O may be one of --');
       writeln('               /P to send output to Printer');
       writeln('               /D to send output to Disk File');
       writeln;
       writeln('       Examples:');
       writeln('               CALENDAR JAN 1982');
       writeln('               CALENDAR DECEMBER 2000');
       writeln('               CALENDAR 1982 /D');
       writeln('               CALENDAR 1984 /P');
end;

{*************************************************************************
       Function:  NUMBER
               Converts the input string of digits to an integer.
       Input Parameter:
               value:  string of digits
       Output Parameter:
               number:  value of digit string; evaluation stops at
                       first non-digit character
**************************************************************************}
function number (valstr : string) : integer;
var
       idx, numb : integer;
       cont : boolean;
       digit : char;
       idigit : integer;
       val1 : string;
begin
       val1 := valstr; { Temp Variable }
       numb := 0;  { Initialize result }

       { Test for Empty Input String; if empty, return zero value }
       if length(val1) = 0 then begin
               number := numb;  { Pass out value }
               exit;
       end;

       { Extract each digit from string and convert into result }
       cont := true;
       idx := 1;
       while cont do begin
               digit := val1[idx];  { Get next digit }
               if (digit < '0') or (digit > '9') then idigit := 10 else
                       idigit := ord(digit) - ord('0');  { Convert to bin }
               if idigit = 10 then cont := false;
               if cont then numb := numb * 10 + idigit;  { Update Value }
               idx := idx + 1;  { Increment Char Pointer }
               if length (val1) < idx then cont := false;
       end;
       number := numb;  { Final Value }
end;

{************************************************************************
       Function:  CAL
               Prints one line of the calendar.
       Input Parameters:
               dow: Day of the Week to Start On
               day: Number of Day in Month
               month:  Month of Year
               lyear:  Leap Year (T/F)
       Output Parameter:
               cal:  Number of next Day in Month (0=done)
************************************************************************}
function cal (dow, day, month : integer) : integer;
var
       i : integer;
       monlen, nday, ndays : integer;
begin
       { If day is zero, print blank entry }
       if day=0 then begin
               for i:=1 to 7 do write(ofile, '   ');
               write(ofile, '  ');
               cal := 0;
               exit;
       end;

       { Determine number of days in month }
       monlen := mdays[month];
       { If month is Feb and it is a leap year, then add 1 }
       if (month=2) and lyear then monlen := monlen + 1;

       { If number < Sunday, set dow to 7+ }
       if dow < bday then dow := dow + 7;

       { If not Sunday, space over to proper starting column of month cal }
       if dow <> bday then for i:=1 to dow-bday do write(ofile, '   ');

       { Compute number of days in current line }
       ndays := 7 - (dow-bday);
       { If we exceed number of days in month, adjust to limit }
       if day+ndays > monlen then ndays := monlen-day+1;

       { We are in proper position, to print day entries in Calendar line }
       if ndays<>0 then for i:=1 to ndays do begin
               nday := day + i - 1;
               write(ofile, nday:2, ' ');
       end;
       { Fill out rest of line if end of calendar }
       if (day<>1) and (ndays<>7) then
               for i:=ndays+1 to 7 do write(ofile, '   ');

       { Write ending spaces }
       write(ofile, '  ');

       { Set return value to be day of month to start on or zero if done }
       if monlen < (ndays+day) then cal := 0 else cal := day + ndays;

end; { CAL }

{**********************************************************************
       Function:  DOMONTH
               Prints Calendar for Month 'month1' of Year 'year1'.
       Input Parameters:
               month1: month number (1 to 12)
               year1: year number (byear to 30,000)
       Output Parameters:
               - None -
***********************************************************************}
procedure domonth;
var
       day1 : integer;
begin
       { Determine what day of the week the first day of month falls on }
       day1 := day_of_week (1,month1,year1);  { Day of 1st Day of Month }

       { Write header for Calendar Month }
       writeln(ofile); writeln(ofile, 'Calendar for ',month[month1],' ',
               year1);
       writeln(ofile, 'Su Mo Tu We Th Fr Sa');

       { Print first line of Calendar }
       day1 := cal (day1, 1, month1); writeln(ofile);

       { Print rest of Calendar }
       while day1 <> 0 do begin
               day1 := cal (bday, day1, month1);
               writeln(ofile);
       end;

end; { DOMONTH }

{**************************************************************
       Function:  DOYEAR
               Prints Calendar for Year 'year1'.
       Input Parameters:
               year1: year number
       Output Parameters:
               - None -
**************************************************************}
procedure doyear;
var
       dayx : array [1..3] of integer;
       idx, mbase, group3, group4 : integer;

begin
       { Write Header for Calendar }
       writeln(ofile, '                       Calendar of Year ', year1);
       writeln(ofile);

       { Loop over Calendar as 4 rows of three months each }
       for group3 := 1 to 4 do begin
               { Compute Base Month Number }
               mbase := (group3-1) * 3 + 1;

               { Page if output to CON: and beginning 3rd group of months }
               if (group3 = 3) and conout then begin
                       write('Strike RETURN Key to Continue - ');
                       readln; writeln;
               end;

               { Print Heading of Each Month }
               writeln(ofile);
               for group4 := mbase to mbase+2 do
                       write(ofile, 'Calendar for ',month[group4], ' ');
               if ((group3 = 1) or (group3 = 3)) and conout then
                       writeln(ofile, year1) else writeln(ofile);
               for group4 := mbase to mbase+2 do begin
                       write(ofile, 'Su Mo Tu We Th Fr Sa   ');
                       idx := group4 mod 3; if idx=0 then idx := 3;
                       dayx[idx] := day_of_week(1,group4,year1);
               end;
               writeln(ofile);

               { Print first line of Calendar }
               dayx[1] := cal (dayx[1], 1, mbase);
               dayx[2] := cal (dayx[2], 1, mbase+1);
               dayx[3] := cal (dayx[3], 1, mbase+2);
               writeln(ofile);

               { Print rest of Calendar }
               repeat
                       dayx[1] := cal (bday, dayx[1], mbase);
                       dayx[2] := cal (bday, dayx[2], mbase+1);
                       dayx[3] := cal (bday, dayx[3], mbase+2);
                       writeln(ofile);
               until dayx[1]+dayx[2]+dayx[3] = 0;
               writeln(ofile);
       end;

end; { DOYEAR }

{*************************************************************************
       Function:  Initialize
               Initialize the command line pointer, the number of days
               in each month, and the names of the months.
       Input/Output Parameters:  None
**************************************************************************}
procedure initialize;
begin
       { Point to Command Line }
       command := @cmd;
       cmdline := command^;

       { Number of days in each month }
       mdays[1]  := 31; mdays[2]  := 28; mdays[3]  := 31;
       mdays[4]  := 30; mdays[5]  := 31; mdays[6]  := 30;
       mdays[7]  := 31; mdays[8]  := 31; mdays[9]  := 30;
       mdays[10] := 31; mdays[11] := 30; mdays[12] := 31;

       { Names of each month }
       month[1]  := 'JANUARY  '; month[2]  := 'FEBRUARY ';
       month[3]  := 'MARCH    '; month[4]  := 'APRIL    ';
       month[5]  := 'MAY      '; month[6]  := 'JUNE     ';
       month[7]  := 'JULY     '; month[8]  := 'AUGUST   ';
       month[9]  := 'SEPTEMBER'; month[10] := 'OCTOBER  ';
       month[11] := 'NOVEMBER '; month[12] := 'DECEMBER ';

end;  { Initialize }

{Mainline}
begin
       { Initialize Month Data and Command Line Pointer }
       initialize;

       { Print Banner }
       writeln('Calendar,  Version ',(version div 10),'.',(version mod 10));

       { Determine Output Direction }
       diskout := false;  { Assume no disk output }
       conout := false;   { Assume no console output }
       if pos ('/D',cmdline) <> 0 then begin
               diskout := true;
               write('Name of Disk Output File? '); readln(filename); end
       else if pos ('/P',cmdline) <> 0 then filename := 'LST:'
                else begin
                       filename := 'CON:'; conout := true; end;

       { Open Output File or Device }
       assign (ofile, filename);
       rewrite(ofile);
       if ioresult = 255 then begin
               writeln ('Fatal Error: Cannot Open ', filename, ' for Output');
               exit;
       end;
       writeln('Calendar Output File/Device is ',filename);

       { Determine which month was specified in command line }
       month1 := 0;  { Assume none for all months }
       match := false;  { No match found }
       for icount:=1 to 12 do begin
               mpos := pos (copy (month[icount],1,3), cmdline);
               if mpos <> 0 then begin
                       if match then begin
                               writeln('Error -- More than one month given');
                               exit;
                       end;
                       match := true;  { We have a match }
                       month1 := icount;
                       mposfnd := mpos;
               end;
       end;

       { Extract Year from command line }
       yline := copy (cmdline, mposfnd, length(cmdline)-mposfnd+1);
       ypos := pos (' ', yline);
       year := copy (yline, ypos, length(yline)-ypos+1);
       while (length(year) <> 0) and (year[1] = ' ') do
               year := copy (year, 2, length(year)-1);
       year1 := number(year);  { Convert Year String into Number }

       { If no year specified, give syntax of command }
       if year1 = 0 then begin
               cline;  { Print syntax of command line }
               exit;
       end;
       { If year specified is out of range, say so }
       if year1 < byear1 then begin
               write('Invalid Year Specification');
               writeln(' -- Year Specified was ',year1);
               writeln('Year MUST be such that ', byear1, ' <= Year');
               cline;  { Print syntax of command line }
               exit;
       end;

       { Determine Base Year from byear1 and Base Day from bday1 }
       byear := byear1;   bday := bday1;
       while year1 > byear+44 do begin
               bdow := day_of_week (1,1,byear+44);  { First day of leap year }
               byear := byear + 44;  { Set byear to next 11th leap year }
               if bdow <= bday then bday := bday - bdow + 1
                              else bday := 7 - (bdow - bday) + 1;
                                       { bday = 1st Sunday of Leap Year }
       end;

       { Do Calendar }
       if ?match then doyear else domonth;
       if diskout then close (ofile, icount);

end. {Mainline}