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


/**
 * Given a width, return a function that takes a string, and
 * pads it at both ends to the given width
 * @param {number} width
 * @returns {function(string): string}
 */
const printCenter = width =>
    s => s.padStart(width / 2 + s.length / 2, ' ').padEnd(width);
 
/**
 * Given an locale string and options, return a function that takes a date
 * object, and retrurns the date formatted to the locale and options.
 * @param {string} locale
 * @param {DateTimeFormatOptions} options
 * @returns {function(Date): string}
 */
const localeName = (locale, options) => {
  const formatter = new Intl.DateTimeFormat(locale, options);
  return date => formatter.format(date);
};
 
/**
 * Increment the date by number.
 * @param {Date} date
 * @param {number} inc
 * @returns {Date}
 */
const addDay = (date, inc = 1) => {
  const res = new Date(date.valueOf());
  res.setDate(date.getDate() + inc);
  return res;
}
 
/**
 * Given a date, build a string of the week, and return it along with
 * the mutated date object.
 * @param {Date} date
 * @returns {[boolean, Date, string]}
 */
const makeWeek = date => {
  const month = date.getMonth();
  let [wdi, md, m] = [date.getUTCDay(), date.getDate(), date.getMonth()];
  const line = Array(7).fill('  ').map((e, i) => {
    if (i === wdi && m === month) {
      const result = (md + '').padStart(2, ' ');
      date = addDay(date);
      [wdi, md, m] = [date.getUTCDay(), date.getDate(), date.getMonth()];
      return result;
    } else {
      return e;
    }
  }).join(' ');
  return [month !== m, date, line];
}
 
/**
 * Print a nicely formatted calender for the given year in the given locale.
 * @param {number} year The required year of the calender
 * @param {string} locale The locale string. Defaults to US English.
 * @param {number} cols The number of columns for the months. Defaults to 3.
 * @param {number} coll_space The space between the columns. Defaults to 5.
 */
const cal = (year, locale = 'en-US', cols = 3, coll_space = 5) => {
  const MONTH_LINES = 9;  // Number of lines that make up a month.
  const MONTH_COL_WIDTH = 20;  // Character width of a month
  const COL_SPACE = ' '.padStart(coll_space);
  const FULL_WIDTH = MONTH_COL_WIDTH * cols + coll_space * (cols - 1);
 
  const collArr = Array(cols).fill('');
  const monthName = localeName(locale, {month: 'long'});
  const weekDayShort = localeName(locale, {weekday: 'short'});
  const monthCenter = printCenter(MONTH_COL_WIDTH);
  const pageCenter = printCenter(FULL_WIDTH);
 
  // Get the weekday in the given locale.
  const sun = new Date(Date.UTC(2017, 0, 1)); // A sunday
  const weekdays = Array(7).fill('').map((e, i) =>
      weekDayShort(addDay(sun, i)).padStart(2, ' ').substring(0, 2)).join(' ');
 
  // The start date.
  let date = new Date(Date.UTC(year, 0, 1, 0, 0, 0));
  let nextMonth = true;
  let line = '';
  const fullYear = date.getUTCFullYear();
 
  // The array into which each of the lines are populated.
  const accumulate = [];
 
  // Populate the month table heading and columns.
  const preAmble = date => {
    accumulate.push(monthCenter(' '))
    accumulate.push(monthCenter(monthName(date)));
    accumulate.push(weekdays);
  };
 
  // Accumulate the week lines for the year.
  while (date.getUTCFullYear() === fullYear) {
    if (nextMonth) {
      if (accumulate.length % MONTH_LINES !== 0) {
        accumulate.push(monthCenter(' '))
      }
      preAmble(date);
    }
    [nextMonth, date, line] = makeWeek(date);
    accumulate.push(line);
  }
 
  // Print the calendar.
  console.log(pageCenter(String.fromCodePoint(0x1F436)));
  console.log(pageCenter(`--- ${fullYear} ---`));
  accumulate.reduce((p, e, i) => {
    if (!p.includes(i)) {
      const indexes = collArr.map((e, ci) => i + ci * MONTH_LINES);
      console.log(indexes.map(e => accumulate[e]).join(COL_SPACE));
      p.push(...indexes);
    }
    return p;
  }, []);
};
 
cal(1969, 'en-US', 3);
 