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


require 'date'
 
# Creates a calendar of _year_. Returns this calendar as a multi-line
# string fit to _columns_.
def cal(year, columns)
 
  # Start at January 1.
  #
  # Date::ENGLAND marks the switch from Julian calendar to Gregorian
  # calendar at 1752 September 14. This removes September 3 to 13 from
  # year 1752. (By fortune, it keeps January 1.)
  #
  date = Date.new(year, 1, 1, Date::ENGLAND)
 
  # Collect calendars of all 12 months.
  months = (1..12).collect do |month|
    rows = [Date::MONTHNAMES[month].center(20), "Su Mo Tu We Th Fr Sa"]
 
    # Make array of 42 days, starting with Sunday.
    days = []
    date.wday.times { days.push "  " }
    while date.month == month
      days.push("%2d" % date.mday)
      date += 1
    end
    (42 - days.length).times { days.push "  " }
 
    days.each_slice(7) { |week| rows.push(week.join " ") }
    next rows
  end
 
  # Calculate months per row (mpr).
  #  1. Divide columns by 22 columns per month, rounded down. (Pretend
  #     to have 2 extra columns; last month uses only 20 columns.)
  #  2. Decrease mpr if 12 months would fit in the same months per
  #     column (mpc). For example, if we can fit 5 mpr and 3 mpc, then
  #     we use 4 mpr and 3 mpc.
  mpr = (columns + 2).div 22
  mpr = 12.div((12 + mpr - 1).div mpr)
 
  # Use 20 columns per month + 2 spaces between months.
  width = mpr * 22 - 2
 
  # Join months into calendar.
  rows = ["[Snoopy]".center(width), "#{year}".center(width)]
  months.each_slice(mpr) do |slice|
    slice[0].each_index do |i|
      rows.push(slice.map {|a| a[i]}.join "  ")
    end
  end
  return rows.join("\n")
end
 
 
ARGV.length == 1 or abort "usage: #{$0} year"
 
# Guess width of terminal.
#  1. Obey environment variable COLUMNS.
#  2. Try to require 'io/console' from Ruby 1.9.3.
#  3. Try to run `tput co`.
#  4. Assume 80 columns.
columns = begin Integer(ENV["COLUMNS"] || "")
          rescue
            begin require 'io/console'; IO.console.winsize[1]
            rescue LoadError
              begin Integer(`tput cols`)
              rescue
                80; end; end; end
 
puts cal(Integer(ARGV[0]), columns)