Create a routine that will generate a text calendar for any year.
Test the calendar by generating a calendar for the year 1969, on a device of the time.
Choose one of the following devices:

 A line printer with a width of 132 characters.
 An IBM 3278 model 4 terminal (80×43 display with accented characters). Target formatting the months of the year to fit nicely across the 80 character width screen. Restrict number of lines in test output to 43.


(Ideally, the program will generate well-formatted calendars for any page width from 20 characters up.)

Kudos (κῦδος) for routines that also transition from Julian to Gregorian calendar.

This task is inspired by Real Programmers Don't Use PASCAL by Ed Post, Datamation, volume 29 number 7, July 1983.

THE REAL PROGRAMMER'S NATURAL HABITAT
"Taped to the wall is a line-printer Snoopy calender for the year 1969."

For further Kudos see task CALENDAR, where all code is to be in UPPERCASE.

For economy of size, do not actually include Snoopy generation in either the code or the output, instead just output a place-holder.



Related task

   Five weekends


#!/usr/local/bin/a68g --script #
 
PROC print calendar = (INT year, page width)VOID: (
 
  []STRING month names = (
      "January","February","March","April","May","June",
      "July","August","September","October","November","December"),
    weekday names = ("Su","Mo","Tu","We","Th","Fr","Sa");
  FORMAT weekday fmt = $g,n(UPB weekday names - LWB weekday names)(" "g)$;
 
  # Juggle the calendar format to fit the printer/screen width #
  INT day width = UPB weekday names[1], day gap=1;
  INT month width = (day width+day gap) * UPB weekday names-1;
  INT month heading lines = 2;
  INT month lines = (31 OVER UPB weekday names+month heading lines+2); # +2 for head/tail weeks #
  INT year cols = (page width+1) OVER (month width+1);
  INT year rows = (UPB month names-1)OVER year cols + 1;
  INT month gap = (page width - year cols*month width + 1)OVER year cols;
  INT year width = year cols*(month width+month gap)-month gap;
  INT year lines = year rows*month lines;
 
  MODE MONTHBOX = [month lines, month width]CHAR;
  MODE YEARBOX  = [year  lines, year  width]CHAR;
 
  INT week start = 1; # Sunday #
 
  PROC days in month = (INT year, month)INT:
    CASE month IN 31,
      IF year MOD 4 EQ 0 AND year MOD 100 NE 0 OR year MOD 400 EQ 0 THEN 29 ELSE 28 FI,
      31, 30, 31, 30, 31, 31, 30, 31, 30, 31
    ESAC;
 
  PROC day of week = (INT year, month, day)INT: (
    # Day of the week by Zeller’s Congruence algorithm from 1887 #
    INT y := year, m := month, d := day, c;
    IF m <= 2 THEN m +:= 12; y -:= 1 FI;
    c := y OVER 100;
    y %*:= 100;
    (d - 1 + ((m + 1) * 26) OVER 10 + y + y OVER 4 + c OVER 4 - 2 * c) MOD 7
  );
 
  MODE SIMPLEOUT = UNION(STRING, []STRING, INT);
 
  PROC cputf = (REF[]CHAR out, FORMAT fmt, SIMPLEOUT argv)VOID:(
    FILE f; STRING s; associate(f,s);
    putf(f, (fmt, argv));
    out[:UPB s]:=s;
    close(f)
  );
 
  PROC month repr = (INT year, month)MONTHBOX:(
    MONTHBOX month box; FOR line TO UPB month box DO month box[line,]:=" "* 2 UPB month box OD;
    STRING month name = month names[month];
 
  # center the title #
    cputf(month box[1,(month width - UPB month name ) OVER 2+1:], $g$, month name);
    cputf(month box[2,], weekday fmt, weekday names);
 
    INT first day := day of week(year, month, 1);
    FOR day TO days in month(year, month) DO
      INT line = (day+first day-week start) OVER UPB weekday names + month heading lines + 1;
      INT char =((day+first day-week start) MOD UPB weekday names)*(day width+day gap) + 1;
      cputf(month box[line,char:char+day width-1],$g(-day width)$, day)
    OD;
    month box
  );
 
  PROC year repr = (INT year)YEARBOX:(
    YEARBOX year box;
    FOR line TO UPB year box DO year box[line,]:=" "* 2 UPB year box OD;
    FOR month row FROM 0 TO year rows-1 DO
      FOR month col FROM 0 TO year cols-1 DO
        INT month = month row * year cols + month col + 1;
        IF month > UPB month names THEN
          done
        ELSE
          INT month col width = month width+month gap;
          year box[
            month row*month lines+1 : (month row+1)*month lines,
            month col*month col width+1 : (month col+1)*month col width-month gap
          ] := month repr(year, month)
        FI
      OD
    OD;
    done: year box
  );
 
  INT center = (year cols*(month width+month gap) - month gap - 1) OVER 2;
  INT indent = (page width - year width) OVER 2;
 
  printf((
    $n(indent + center - 9)k  g l$, "[Insert Snoopy here]",
    $n(indent + center - 1)k 4d l$, year, $l$,
    $n(indent)k n(year width)(g)  l$, year repr(year)
  ))
);
 
main: (
CO inspired by http://www.ee.ryerson.ca/~elf/hack/realmen.html
           Real Programmers Don't Use PASCAL - Ed Post
            Datamation, volume 29 number 7, July 1983
              THE REAL PROGRAMMER'S NATURAL HABITAT
"Taped to the wall is a line-printer Snoopy calender for the year 1969."
CO
  INT mankind stepped on the moon = 1969,
      line printer width = 80; # as at 1969! #
  print calendar(mankind stepped on the moon, line printer width)
)