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


import java.util.{ Calendar, GregorianCalendar }
import language.postfixOps
import collection.mutable.ListBuffer
 
object CalendarPrint extends App {
  val locd = java.util.Locale.getDefault()
  val cal = new GregorianCalendar
  val monthsMax = cal.getMaximum(Calendar.MONTH)
 
  def JDKweekDaysToISO(dn: Int) = {
    val nday = dn - Calendar.MONDAY
    if (nday < 0) (dn + Calendar.THURSDAY) else nday
  }
 
  def daysInMonth(year: Int, monthMinusOne: Int): Int = {
    cal.set(year, monthMinusOne, 1)
    cal.getActualMaximum(Calendar.DAY_OF_MONTH)
  }
 
  def namesOfMonths() = {
    def f1(i: Int): String = {
      cal.set(2013, i, 1)
      cal.getDisplayName(Calendar.MONTH, Calendar.LONG, locd)
    }
    (0 to monthsMax) map f1
  }
 
  def offsets(year: Int): List[Int] = {
    val months = cal.getMaximum(Calendar.MONTH)
    def get1stDayOfWeek(i: Int) = {
      cal.set(year, i, 1)
      cal.get(Calendar.DAY_OF_WEEK)
    }
    (0 to monthsMax).toList map get1stDayOfWeek map { i => JDKweekDaysToISO(i) }
  }
 
  def headerNameOfDays() = {
    val mdow = cal.getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.SHORT, locd) // map days of week
    val it = mdow.keySet.iterator
    val keySet = new ListBuffer[String]
    while (it.hasNext) keySet += it.next
    (keySet.map { key => (JDKweekDaysToISO(mdow.get(key)), key.take(2))
    }.sortWith(_._1 < _._1) map (_._2)).mkString(" ")
  }
 
  def getGregCal(year: Int) = {
    {
 
      def dayOfMonth(month: Int) = new Iterator[(Int, Int)] {
        cal.set(year, month, 1)
        var ldom = 0
 
        def next() = {
          val res = (cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH))
          ldom = res._2
          cal.roll(Calendar.DAY_OF_MONTH, true)
          res
        }
 
        def hasNext() = (cal.get(Calendar.DAY_OF_MONTH) > ldom)
      }
      var ret: List[(Int, Int)] = Nil
      for (i <- 0 to monthsMax) ret = ret ++ (dayOfMonth(i).toSeq)
      (ret, offsets(year))
    }
  }
 
  def printCalendar(calendar: (List[(Int, Int)], List[Int]),
                    headerList: List[String] = Nil,
                    printerWidth: Int = 80) = {
    val mw = 20 // month width
    val gwf = 2 // gap width fixed
    val gwm = 2 // gap width minimum
    val fgw = true
 
    val arr = Array.ofDim[String](6, 7)
 
    def limits(printerWidth: Int): (Int, Int, Int, Int, Int) = {
      val pw = if (printerWidth < 20) 20 else if (printerWidth > 300) 300 else printerWidth
 
      // months side by side, gaps sum minimum
      val (msbs, gsm) = {
        val x1 = {
          val c = if (pw / mw > 12) 12 else pw / mw
          val a = (c - 1)
          if (c * mw + a * gwm <= pw) c else a
        } match {
          case 5                      => 4
          case a if (a > 6 && a < 12) => 6
          case other                  => other
        }
        (x1, (x1 - 1) * gwm)
      }
 
      def getFGW(msbs: Int, gsm: Int) = { val r = (pw - msbs * mw - gsm) / 2; (r, gwf, r) } // fixed gap width
      def getVGW(msbs: Int, gsm: Int) = pw - msbs * mw - gsm match { // variable gap width
        case a if (a < 2 * gwm) => (a / 2, gwm, a / 2)
        case b                  => { val x = (b + gsm) / (msbs + 1); (x, x, x) }
      }
 
      // left margin, gap width, right margin
      val (lm, gw, rm) = if (fgw) getFGW(msbs, gsm) else getVGW(msbs, gsm)
      (pw, msbs, lm, gw, rm)
    } // def limits(
 
    val (pw, msbs, lm, gw, rm) = limits(printerWidth)
    val monthsList = (0 to monthsMax).map { m => calendar._1.filter { _._1 == m } }
    val nom = namesOfMonths()
    val hnod = headerNameOfDays
 
    def fsplit(list: List[(Int, Int)]): List[String] = {
      def fap(p: Int) = (p / 7, p % 7)
      for (i <- 0 until 6) for (j <- 0 until 7) arr(i)(j) = "  "
      for (i <- 0 until list.size)
        arr(fap(i + calendar._2(list(i)._1))._1)(fap(i + calendar._2(list(i)._1))._2) =
          f"${(list(i)._2)}%2d"
      arr.toList.map(_.foldRight("")(_ + " " + _))
    }
    val monthsRows = monthsList.map(fsplit)
 
    def center(s: String, l: Int): String = {
      (if (s.size >= l) s
      else " " * ((l - s.size) / 2) + s + " " * ((l - s.size) / 2) + " ").substring(0, l)
    }
 
    val maxMonths = monthsMax + 1
 
    val rowblocks = (1 to maxMonths / msbs).map { i =>
      (0 to 5).map { j =>
        val lb = new ListBuffer[String]
        val k = (i - 1) * msbs
        (k to k + msbs - 1).map { l => lb += monthsRows(l)(j) }
        lb
      }
    }
 
    val mheaders = (1 to maxMonths / msbs).
      map { i => (0 to msbs - 1).map { j => center(nom(j + (i - 1) * msbs), 20) } }
    val dowheaders = (1 to maxMonths / msbs).
      map { i => (0 to msbs - 1).map { j => center(hnod, 20) } }
 
    headerList.foreach(xs => println(center(xs + '\n', pw)))
    (1 to 12 / msbs).foreach { i =>
      println(" " * lm + mheaders(i - 1).foldRight("")(_ + " " * gw + _))
      println(" " * lm + dowheaders(i - 1).foldRight("")(_ + " " * gw + _))
      rowblocks(i - 1).foreach { xs => println(" " * lm + xs.foldRight("")(_ + " " * (gw - 1) + _)) }
      println
    }
  } //  def printCal(
 
  printCalendar(getGregCal(1969), List("[Snoopy Picture]", "1969"))
  printCalendar(getGregCal(1582), List("[Snoopy Picture]", "1582"), printerWidth = 132)
}