Jump to content

Calendar

From Rosetta Code
Task
Calendar
You are encouraged to solve this task according to the task description, using any language you may know.

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



360 Assembly

Translation of: Free Basic

This program uses no external functions but two ASSIST macros (XDECO, XPRNT) to keep the code as short as possible.

*        calendar                  08/06/2016
CALENDAR CSECT
         USING  CALENDAR,R13       base register
         B      72(R15)            skip savearea
         DC     17F'0'             savearea
         STM    R14,R12,12(R13)    prolog
         ST     R13,4(R15)         "
         ST     R15,8(R13)         " 
         LR     R13,R15            "
         L      R4,YEAR            year
         SRDA   R4,32              .
         D      R4,=F'4'           year//4
         LTR    R4,R4              if year//4=0
         BNZ    LYNOT
         L      R4,YEAR            year
         SRDA   R4,32              .
         D      R4,=F'100'         year//100
         LTR    R4,R4              if year//100=0
         BNZ    LY 
         L      R4,YEAR            year
         SRDA   R4,32              .
         D      R4,=F'400'         if year//400
         LTR    R4,R4              if year//400=0
         BNZ    LYNOT
LY       MVC    ML+2,=H'29'        ml(2)=29  leapyear
LYNOT    SR     R10,R10            ltd1=0
         LA     R6,1               i=1
LOOPI1   C      R6,=F'31'          do i=1 to 31
         BH     ELOOPI1
         XDECO  R6,XDEC            edit i
         LA     R14,TD1            td1
         AR     R14,R10            td1+ltd1
         MVC    0(3,R14),XDEC+9    sub(td1,ltd1+1,3)=pic(i,3)
         LA     R10,3(R10)         ltd1+3
         LA     R6,1(R6)           i=i+1
         B      LOOPI1
ELOOPI1  LA     R6,1               i=1
LOOPI2   C      R6,=F'12'          do i=1 to 12
         BH     ELOOPI2
         ST     R6,M               m=i
         MVC    D,=F'1'            d=1
         MVC    YY,YEAR            yy=year
         L      R4,M               m
         C      R4,=F'3'           if m<3
         BNL    GE3
         L      R2,M               m
         LA     R2,12(R2)          m+12
         ST     R2,M               m=m+12
         L      R2,YY              yy
         BCTR   R2,0               yy-1
         ST     R2,YY              yy=yy-1
GE3      L      R2,YY              yy
         LR     R1,R2              yy
         SRA    R1,2               yy/4
         AR     R2,R1              yy+(yy/4)         
         L      R4,YY              yy
         SRDA   R4,32              .
         D      R4,=F'100'         yy/100      
         SR     R2,R5              yy+(yy/4)-(yy/100)
         L      R4,YY              yy
         SRDA   R4,32              .
         D      R4,=F'400'         yy/400                          
         AR     R2,R5              yy+(yy/4)-(yy/100)+(yy/400)  
         A      R2,D               r2=yy+(yy/4)-(yy/100)+(yy/400)+d
         LA     R5,153             153 
         M      R4,M               153*m            
         LA     R5,8(R5)           153*m+8                                     
         D      R4,=F'5'           (153*m+8)/5               
         AR     R5,R2              ((153*m+8)/5+r2  
         LA     R4,0               .
         D      R4,=F'7'           r4=mod(r5,7)  0=sun 1=mon ... 6=sat                  
         LTR    R4,R4              if j=0
         BNZ    JNE0
         LA     R4,7               j=7
JNE0     BCTR   R4,0               j-1
         MH     R4,=H'3'           j*3
         LR     R10,R4             j1=j*3
         LR     R1,R6              i
         SLA    R1,1               *2
         LH     R11,ML-2(R1)       ml(i)
         MH     R11,=H'3'          j2=ml(i)*3
         MVC    TD2,BLANK          td2=' '
         LA     R4,TD1             @td1
         LR     R5,R11             j2
         LA     R2,TD2             @td2
         AR     R2,R10             @td2+j1
         LR     R3,R5              j2
         MVCL   R2,R4              sub(td2,j1+1,j2)=sub(td1,1,j2)
         LR     R1,R6              i
         MH     R1,=H'144'         *144
         LA     R14,DA-144(R1)     @da(i)
         MVC    0(144,R14),TD2     da(i)=td2
         LA     R6,1(R6)           i=i+1
         B      LOOPI2
ELOOPI2  L      R1,YEAR            year
         XDECO  R1,PG+23           edit year
         XPRNT  PG,35              print year
         MVC    WDLINE,BLANK       wdline=' '
         LA     R10,1              lwdline=1
         LA     R8,1               k=1 
LOOPK3   C      R8,=F'3'           do k=1 to 3
         BH     ELOOPK3
         LA     R4,WDLINE          @wdline
         AR     R4,R10             +lwdline
         MVC    0(20,R4),WDNA      sub(wdline,lwdline+1,20)=wdna
         LA     R10,20(R10)        lwdline=lwdline+20
         C      R8,=F'3'           if k<3
         BNL    ITERK3
         LA     R10,2(R10)         lwdline=lwdline+2
ITERK3   LA     R8,1(R8)           k=k+1
         B      LOOPK3
ELOOPK3  LA     R6,1               i=1
LOOPI4   C      R6,=F'12'          do i=1 to 12 by 3
         BH     ELOOPI4
         MVC    MOLINE,BLANK       moline=' '
         LA     R10,6              lmoline=6
         LR     R8,R6              k=i
LOOPK4   LA     R2,2(R6)           i+2
         CR     R8,R2              do k=i to i+2
         BH     ELOOPK4
         LR     R1,R8              k
         MH     R1,=H'10'          *10
         LA     R3,MO-10(R1)       mo(k)
         LA     R4,MOLINE          @moline
         AR     R4,R10             +lmoline
         MVC    0(10,R4),0(R3)     sub(moline,lmoline+1,10)=mo(k)
         LA     R10,22(R10)        lmoline=lmoline+22
         LA     R8,1(R8)           k=k+1
         B      LOOPK4
ELOOPK4  XPRNT  MOLINE,L'MOLINE    print months
         XPRNT  WDLINE,L'WDLINE    print days of week
         LA     R7,1               j=1
LOOPJ4   C      R7,=F'106'         do j=1 to 106 by 21
         BH     ELOOPJ4
         MVC    PG,BLANK           clear buffer
         LA     R9,PG              pgi=0
         LR     R8,R6              k=i
LOOPK5   LA     R2,2(R6)           i+2
         CR     R8,R2              do k=i to i+2
         BH     ELOOPK5
         LR     R1,R8              k
         MH     R1,=H'144'         *144
         LA     R4,DA-144(R1)      da(k)
         BCTR   R4,0               -1
         AR     R4,R7              +j
         MVC    0(21,R9),0(R4)     substr(da(k),j,21)
         LA     R9,22(R9)          pgi=pgi+22
         LA     R8,1(R8)           k=k+1
         B      LOOPK5
ELOOPK5  XPRNT  PG,L'PG            print buffer
         LA     R7,21(R7)          j=j+21
         B      LOOPJ4
ELOOPJ4  LA     R6,3(R6)           i=i+3
         B      LOOPI4
ELOOPI4  L      R13,4(0,R13)       epilog 
         LM     R14,R12,12(R13)    "
         XR     R15,R15            "
         BR     R14                exit
         LTORG
YEAR     DC     F'1969'            <==
MO       DC     CL10' January  ',CL10' February ',CL10'  March   '
         DC     CL10'  April   ',CL10'   May    ',CL10'   June   '
         DC     CL10'   July   ',CL10'  August  ',CL10'September '
         DC     CL10' October  ',CL10' November ',CL10' December '
ML       DC     H'31',H'28',H'31',H'30',H'31',H'30'
         DC     H'31',H'31',H'30',H'31',H'30',H'31'
WDNA     DC     CL20'Mo Tu We Th Fr Sa Su'
M        DS     F
D        DS     F
YY       DS     F
TD1      DS     CL93
TD2      DS     CL144
MOLINE   DS     CL66
WDLINE   DS     CL66
PG       DC     CL66' '
XDEC     DS     CL12
BLANK    DC     CL144' '
DA       DS     12CL144
         YREG
         END    CALENDAR
Output:
                               2016
       January               February               March
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su
              1  2  3   1  2  3  4  5  6  7      1  2  3  4  5  6
  4  5  6  7  8  9 10   8  9 10 11 12 13 14   7  8  9 10 11 12 13
 11 12 13 14 15 16 17  15 16 17 18 19 20 21  14 15 16 17 18 19 20
 18 19 20 21 22 23 24  22 23 24 25 26 27 28  21 22 23 24 25 26 27
 25 26 27 28 29 30 31  29                    28 29 30 31

        April                  May                   June
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su
              1  2  3                     1         1  2  3  4  5
  4  5  6  7  8  9 10   2  3  4  5  6  7  8   6  7  8  9 10 11 12
 11 12 13 14 15 16 17   9 10 11 12 13 14 15  13 14 15 16 17 18 19
 18 19 20 21 22 23 24  16 17 18 19 20 21 22  20 21 22 23 24 25 26
 25 26 27 28 29 30     23 24 25 26 27 28 29  27 28 29 30
                       30 31
         July                 August              September
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su
              1  2  3   1  2  3  4  5  6  7            1  2  3  4
  4  5  6  7  8  9 10   8  9 10 11 12 13 14   5  6  7  8  9 10 11
 11 12 13 14 15 16 17  15 16 17 18 19 20 21  12 13 14 15 16 17 18
 18 19 20 21 22 23 24  22 23 24 25 26 27 28  19 20 21 22 23 24 25
 25 26 27 28 29 30 31  29 30 31              26 27 28 29 30

       October               November              December
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su
                 1  2      1  2  3  4  5  6            1  2  3  4
  3  4  5  6  7  8  9   7  8  9 10 11 12 13   5  6  7  8  9 10 11
 10 11 12 13 14 15 16  14 15 16 17 18 19 20  12 13 14 15 16 17 18
 17 18 19 20 21 22 23  21 22 23 24 25 26 27  19 20 21 22 23 24 25
 24 25 26 27 28 29 30  28 29 30              26 27 28 29 30 31
 31

Ada

We first specify a "Printable_Calendar" package (printable_calendar.ads). One can easily override the default names for weekdays and months, see the Calendar_-_for_"real"_programmers#Ada task.

with Ada.Calendar.Formatting;

package Printable_Calendar is

   subtype String20 is String(1 .. 20);
   type Month_Rep_Type is array (Ada.Calendar.Month_Number) of String20;

   type Description is record
      Weekday_Rep: String20;
      Month_Rep: Month_Rep_Type;
   end record;
   -- for internationalization, you only need to define a new description

   Default_Description: constant Description :=
     (Weekday_Rep =>
       "Mo Tu We Th Fr Sa So",
      Month_Rep   =>
      ("      January       ", "      February      ", "       March        ",
       "       April        ", "        May         ", "        June        ",
       "        July        ", "       August       ", "      September     ",
       "       October      ", "      November      ", "      December      "));

   type Calendar (<>) is tagged private;

   -- Initialize a calendar for devices with 80- or 132-characters per row
   function Init_80(Des: Description := Default_Description) return Calendar;
   function Init_132(Des: Description := Default_Description) return Calendar;

   -- the following procedures output to standard IO; override if neccessary
   procedure New_Line(Cal: Calendar);
   procedure Put_String(Cal: Calendar; S: String);

   -- the following procedures do the real stuff
   procedure Print_Line_Centered(Cal: Calendar'Class; Line: String);
   procedure Print(Cal: Calendar'Class;
                   Year:  Ada.Calendar.Year_Number;
                   Year_String: String); -- this is the main Thing

private
      type Calendar is tagged record
      Columns, Rows, Space_Between_Columns: Positive;
      Left_Space: Natural;

      Weekday_Rep: String20;
      Month_Rep: Month_Rep_Type;
   end record;

end Printable_Calendar;

We continue with the implementation (printable_calendar.ads):

with Ada.Text_IO;

package body Printable_Calendar is

   use Ada.Calendar;
   package F renames Ada.Calendar.Formatting;

   function Days_Per_Month(Year: Year_Number; Month: Month_Number)
                          return Day_Number is
   begin
      case Month is
         when 1 | 3 | 5 | 7 | 8 | 10 | 12 => return 31;
         when 4 | 6 | 9 | 11              => return 30;
         when 2 =>
            if Year mod 4 /= 0 then
               return 28;
            elsif Year mod 100 /= 0 then
               return 29;
            elsif Year mod 400 /= 0 then
               return 28;
            else
               return 29;
            end if;
      end case;
   end Days_Per_Month;

   type Full_Month_Rep is array (1 .. 6) of String20;
   function Generate_Printable_Month (Y: Ada.Calendar.Year_Number;
                                      M: Ada.Calendar.Month_Number)
                                     return Full_Month_Rep is

      X: Full_Month_Rep := (others => "                    ");
      -- If X=Generate_Printable_Month(2011, 01), the result could be
      --  "       January      ", -- Month_Rep(01)
      --  "Mo Tu We Th Fr Sa Su"  -- Weekday_Rep
      --  "                1  2"  -- X(1)
      --  " 3  4  5  6  7  8  9"  -- X(2)
      --  "10 11 12 13 14 15 16"  -- X(3)
      --  "17 18 19 20 21 22 23"  -- X(4)
      --  "24 25 26 27 28 29 30"  -- X(5)
      --  "31                  "  -- X(6)

      Row:       Integer range 1 .. 6  := 1;
      Day_Index: constant array(F.Day_Name) of Positive
        := (1, 4, 7, 10, 13, 16, 19);
   begin
      for I in 1 .. Days_Per_Month(Y, M) loop
         declare
           Weekday: constant F.Day_Name    := F.Day_Of_Week(F.Time_Of(Y, M, I));
           Pos: constant Positive          := Day_Index(Weekday);
           Cleartext_Name: constant String := Day_Number'Image(I);
           L: constant Positive            := Cleartext_Name'Last;
         begin
            X(Row)(Pos .. Pos+1) := Cleartext_Name(L-1 .. L);
            if F."="(Weekday, F.Sunday) then
               Row := Row + 1;
            end if;
         end;
      end loop;
      return X;
   end Generate_Printable_Month;


   procedure Print(Cal: Calendar'class;
                   Year:  Ada.Calendar.Year_Number;
                   Year_String: String) is

      The_Month: Month_Number := Month_Number'First;

      procedure Write_Space(Length: Natural) is
      begin
         for I in 1 .. Length loop
            Cal.Put_String(" ");
         end loop;
      end Write_Space;

      Year_Rep: array(Month_Number) of Full_Month_Rep;

   begin
      -- print the year
      Cal.Print_Line_Centered(Year_String);

      -- generate a printable form for all the months
      for Month in Month_Number loop
         Year_Rep(Month) := Generate_Printable_Month(Year, Month);
      end loop;

      begin
         while True loop

            -- new line
            Cal.New_Line;

            -- write month names
            Write_Space(Cal.Left_Space);
            for Month in The_Month .. The_Month+Cal.Columns-2 loop
               Cal.Put_String(Cal.Month_Rep(Month));
               Write_Space(Cal.Space_Between_Columns);
            end loop;
            Cal.Put_String(Cal.Month_Rep(The_Month+Cal.Columns-1));
            Cal.New_Line;

            -- write "Mo Tu .. So" - or whatever is defined by Weekday_Rep
            Write_Space(Cal.Left_Space);
            for Month in The_Month .. The_Month+Cal.Columns-2 loop
               Cal.Put_String(Cal.Weekday_Rep);
               Write_Space(Cal.Space_Between_Columns);
            end loop;
            Cal.Put_String(Cal.Weekday_Rep);
            Cal.New_Line;

            -- write the dates
            for I in 1 .. 6 loop
               Write_Space(Cal.Left_Space);
               for Month in The_Month .. The_Month+Cal.Columns-2 loop
                  Cal.Put_String(Year_Rep(Month)(I));
                  Write_Space(Cal.Space_Between_Columns);
               end loop;
            Cal.Put_String(Year_Rep(The_Month+Cal.Columns-1)(I));
            Cal.New_Line;
            end loop;

            The_Month := The_Month + Cal.Columns;
            -- this will eventually raise Constraint_Error to terminate the loop
         end loop;
      exception
         when Constraint_Error => null;
      end;
   end Print;

   procedure New_Line(Cal: Calendar) is
   begin
      Ada.Text_IO.New_Line;
   end New_Line;

   procedure Put_String(Cal: Calendar; S: String) is
   begin
      Ada.Text_IO.Put(S);
   end Put_String;

   procedure Print_Line_Centered(Cal: Calendar'Class; Line: String) is
      Width : constant Positive := Cal.Columns*20
        + (Cal.Columns-1)*Cal.Space_Between_Columns
        + Cal.Left_Space;
   begin
      if Line'Length    >= Width-1 then
         Cal.Put_String(Line);
         Cal.New_Line;
      else
         Print_Line_Centered(Cal, " " & Line & " ");
      end if;
   end Print_Line_Centered;

   function Init_80(Des: Description := Default_Description) return Calendar is
      X: Calendar:=
        (Columns => 3, Rows => 4, Space_Between_Columns => 4,
         Left_Space => 1,
         Weekday_Rep =>  Des.Weekday_Rep,
         Month_Rep   =>  Des.Month_Rep
        );
   begin
      return X;
   end Init_80;

   function Init_132(Des: Description := Default_Description) return Calendar is
      X: Calendar:=
        (Columns => 6, Rows => 2, Space_Between_Columns => 2,
         Left_Space => 1,
         Weekday_Rep =>  Des.Weekday_Rep,
         Month_Rep   =>  Des.Month_Rep
        );
   begin
      return X;
   end Init_132;

end Printable_Calendar;

Now, the main program is really simple:

with Printable_Calendar;

procedure Cal is

   C: Printable_Calendar.Calendar := Printable_Calendar.Init_80;

begin
   C.Print_Line_Centered("[reserved for Snoopy]");
   C.New_Line;
   C.Print(1969, "Nineteen-Sixty-Nine");
end Cal;

Here is the output:

                        [reserved for Snoopy]                        

                         Nineteen-Sixty-Nine                         

       January                 February                 March        
 Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So
        1  2  3  4  5                    1  2                    1  2
  6  7  8  9 10 11 12     3  4  5  6  7  8  9     3  4  5  6  7  8  9
 13 14 15 16 17 18 19    10 11 12 13 14 15 16    10 11 12 13 14 15 16
 20 21 22 23 24 25 26    17 18 19 20 21 22 23    17 18 19 20 21 22 23
 27 28 29 30 31          24 25 26 27 28          24 25 26 27 28 29 30
                                                 31                  

        April                    May                     June        
 Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So
     1  2  3  4  5  6              1  2  3  4                       1
  7  8  9 10 11 12 13     5  6  7  8  9 10 11     2  3  4  5  6  7  8
 14 15 16 17 18 19 20    12 13 14 15 16 17 18     9 10 11 12 13 14 15
 21 22 23 24 25 26 27    19 20 21 22 23 24 25    16 17 18 19 20 21 22
 28 29 30                26 27 28 29 30 31       23 24 25 26 27 28 29
                                                 30                  

         July                   August                 September     
 Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So
     1  2  3  4  5  6                 1  2  3     1  2  3  4  5  6  7
  7  8  9 10 11 12 13     4  5  6  7  8  9 10     8  9 10 11 12 13 14
 14 15 16 17 18 19 20    11 12 13 14 15 16 17    15 16 17 18 19 20 21
 21 22 23 24 25 26 27    18 19 20 21 22 23 24    22 23 24 25 26 27 28
 28 29 30 31             25 26 27 28 29 30 31    29 30               
                                                                     

        October                November                December      
 Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So    Mo Tu We Th Fr Sa So
        1  2  3  4  5                    1  2     1  2  3  4  5  6  7
  6  7  8  9 10 11 12     3  4  5  6  7  8  9     8  9 10 11 12 13 14
 13 14 15 16 17 18 19    10 11 12 13 14 15 16    15 16 17 18 19 20 21
 20 21 22 23 24 25 26    17 18 19 20 21 22 23    22 23 24 25 26 27 28
 27 28 29 30 31          24 25 26 27 28 29 30    29 30 31  


To get a 132-character-wide output, you just have to replace "Init_80" by "Init_132" in the main program.

ALGOL 68

Works with: ALGOL 68 version Revision 1 - no extensions to language used
Works with: ALGOL 68G version Any - tested with release 1.18.0-9h.tiny.
#!/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)
)

Output:

                             [Insert Snoopy here]
                                     1969

        January                    February                    March        
  Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
            1  2  3  4                          1                          1
   5  6  7  8  9 10 11        2  3  4  5  6  7  8        2  3  4  5  6  7  8
  12 13 14 15 16 17 18        9 10 11 12 13 14 15        9 10 11 12 13 14 15
  19 20 21 22 23 24 25       16 17 18 19 20 21 22       16 17 18 19 20 21 22
  26 27 28 29 30 31          23 24 25 26 27 28          23 24 25 26 27 28 29
                                                        30 31               
         April                       May                        June        
  Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
         1  2  3  4  5                    1  2  3        1  2  3  4  5  6  7
   6  7  8  9 10 11 12        4  5  6  7  8  9 10        8  9 10 11 12 13 14
  13 14 15 16 17 18 19       11 12 13 14 15 16 17       15 16 17 18 19 20 21
  20 21 22 23 24 25 26       18 19 20 21 22 23 24       22 23 24 25 26 27 28
  27 28 29 30                25 26 27 28 29 30 31       29 30               
                                                                            
          July                      August                   September      
  Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
         1  2  3  4  5                       1  2           1  2  3  4  5  6
   6  7  8  9 10 11 12        3  4  5  6  7  8  9        7  8  9 10 11 12 13
  13 14 15 16 17 18 19       10 11 12 13 14 15 16       14 15 16 17 18 19 20
  20 21 22 23 24 25 26       17 18 19 20 21 22 23       21 22 23 24 25 26 27
  27 28 29 30 31             24 25 26 27 28 29 30       28 29 30            
                             31                                             
        October                    November                   December      
  Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
            1  2  3  4                          1           1  2  3  4  5  6
   5  6  7  8  9 10 11        2  3  4  5  6  7  8        7  8  9 10 11 12 13
  12 13 14 15 16 17 18        9 10 11 12 13 14 15       14 15 16 17 18 19 20
  19 20 21 22 23 24 25       16 17 18 19 20 21 22       21 22 23 24 25 26 27
  26 27 28 29 30 31          23 24 25 26 27 28 29       28 29 30 31         
                             30                                             

ALGOL W

Translation of: Simula

which is a

Translation of: C

This is pretty much the same as the Simula version.
The main differences are that the "for" loop counter in Algol W is a separate variable that only exists during execution of the loop whereas in Simula the loop counter must be declared outside the loop and has its value changed as the loop executes - hence some of the "for" loops have been replaced by "while" loops.
In Algol W, the condition of a "while" loop can be a block and the boolean "and" operator short circuits, which allow us to avoid the goto statements.
Also, the Algol W string type is fixed length, unlike the Simula text type.

BEGIN
    INTEGER WIDTH, YEAR;
    INTEGER COLS, LEAD, GAP;
    STRING(2) ARRAY WDAYS (0::6);
    RECORD MONTH ( STRING(9) MNAME; INTEGER DAYS, START_WDAY, AT_POS );
    REFERENCE(MONTH) ARRAY MONTHS(0::11);
    WIDTH := 80; YEAR := 1969;

    BEGIN
        WDAYS(0) := "Su"; WDAYS(1) := "Mo"; WDAYS(2) := "Tu";
        WDAYS(3) := "We"; WDAYS(4) := "Th"; WDAYS(5) := "Fr"; WDAYS(6) := "Sa";
        MONTHS( 0) := MONTH(" January",  31, 0, 0 );
        MONTHS( 1) := MONTH(" February", 28, 0, 0 );
        MONTHS( 2) := MONTH("  March",   31, 0, 0 );
        MONTHS( 3) := MONTH("  April",   30, 0, 0 );
        MONTHS( 4) := MONTH("   May",    31, 0, 0 );
        MONTHS( 5) := MONTH("   June",   30, 0, 0 );
        MONTHS( 6) := MONTH("   July",   31, 0, 0 );
        MONTHS( 7) := MONTH("  August",  31, 0, 0 );
        MONTHS( 8) := MONTH("September", 30, 0, 0 );
        MONTHS( 9) := MONTH(" October",  31, 0, 0 );
        MONTHS(10) := MONTH(" November", 30, 0, 0 );
        MONTHS(11) := MONTH(" December", 31, 0, 0 )
    END;
 
    BEGIN
 
        PROCEDURE SPACE(INTEGER VALUE N);
        BEGIN
            WHILE N > 0 DO BEGIN
                WRITEON(" "); N := N-1;
            END
        END SPACE;
 
        PROCEDURE INIT_MONTHS;
        BEGIN
            INTEGER I;
 
            IF YEAR REM 4 = 0 AND YEAR REM 100 NOT = 0 OR YEAR REM 400 = 0 THEN
                DAYS(MONTHS(1)) := 29;
 
            YEAR := YEAR-1;
            START_WDAY(MONTHS(0))
                := (YEAR * 365 + YEAR DIV 4 - YEAR DIV 100 + YEAR DIV 400 + 1) REM 7;
 
            FOR I := 1 STEP 1 UNTIL 12-1 DO
                START_WDAY(MONTHS(I)) :=
                    (START_WDAY(MONTHS(I-1)) + DAYS(MONTHS(I-1))) REM 7;
 
            COLS := (WIDTH + 2) DIV 22;
            WHILE 12 REM COLS NOT = 0 DO
                COLS := COLS-1;
            GAP := IF COLS - 1 NOT = 0 THEN (WIDTH - 20 * COLS) DIV (COLS - 1) ELSE 0;
            IF GAP > 4 THEN
                GAP := 4;
            LEAD := (WIDTH - (20 + GAP) * COLS + GAP + 1) DIV 2;
            YEAR := YEAR+1
        END INIT_MONTHS;
 
        PROCEDURE PRINT_ROW(INTEGER VALUE ROW);
        BEGIN
            INTEGER C, I, FROM, UP_TO;
            INTEGER PROCEDURE PREINCREMENT(INTEGER VALUE RESULT I);
            BEGIN I := I+1; I
            END PREINCREMENT;
            INTEGER PROCEDURE POSTINCREMENT(INTEGER VALUE RESULT I);
            BEGIN INTEGER PREV_VALUE;
                PREV_VALUE := I; I := I+1; PREV_VALUE
            END POSTINCREMENT;
            FROM := ROW * COLS;
            UP_TO := FROM + COLS;
            SPACE(LEAD);
            FOR C := FROM STEP 1 UNTIL UP_TO-1 DO BEGIN
                I := 9 % LENGTH OF MNAME(MONTHS(C)) % ;
                SPACE((20 - I) DIV 2);
                WRITEON(MNAME(MONTHS(C)));
                SPACE(20 - I - (20 - I) DIV 2 + (IF C = UP_TO - 1 THEN 0 ELSE GAP));
            END;
            WRITE();
 
            SPACE(LEAD);
            FOR C := FROM STEP 1 UNTIL UP_TO-1 DO BEGIN
                FOR I := 0 STEP 1 UNTIL 7-1 DO BEGIN
                    WRITEON(WDAYS(I)); IF I NOT = 6 THEN WRITEON(" ")
                END;
                IF C < UP_TO - 1 THEN
                    SPACE(GAP)
                ELSE
                    WRITE();
            END;
            WHILE BEGIN
                C := FROM;
                WHILE C < UP_TO AND AT_POS(MONTHS(C)) >= DAYS(MONTHS(C)) DO
                    C := C + 1;

                C NOT = UP_TO
            END DO BEGIN
 
                SPACE(LEAD);
                C := FROM;
                WHILE C < UP_TO DO BEGIN
                    I := 0;
                    WHILE I < START_WDAY(MONTHS(C)) DO BEGIN
                        I := I + 1;
                        SPACE(3)
                    END;
                    WHILE POSTINCREMENT(I) < 7 AND AT_POS(MONTHS(C)) < DAYS(MONTHS(C)) DO BEGIN
                        WRITEON(I_W := 2, S_W := 0, PREINCREMENT(AT_POS(MONTHS(C))));
                        IF I < 7 OR C < UP_TO - 1 THEN
                            SPACE(1)
                    END;
                    WHILE POSTINCREMENT(I) <= 7 AND C < UP_TO-1 DO
                        SPACE(3);
                    IF C < UP_TO - 1 THEN
                        SPACE(GAP - 1);
                    START_WDAY(MONTHS(C)) := 0;
                    C := C + 1
                END;
                WRITE();
            END;
            WRITE()
        END PRINT_ROW;
 
        PROCEDURE PRINT_YEAR;
        BEGIN
            INTEGER ROW, STRLEN, Y;
            STRLEN := 1;
            Y      := YEAR;
            WHILE Y > 9 DO BEGIN Y := Y DIV 10; STRLEN := STRLEN + 1 END;
            SPACE((WIDTH - STRLEN) DIV 2);
            WRITEON(I_W := 1, YEAR);
            WRITE(); WRITE();
            WHILE ROW * COLS < 12 DO BEGIN
                PRINT_ROW(ROW);
                ROW := ROW+1
            END
        END PRINT_YEAR;

        INIT_MONTHS;
        PRINT_YEAR
    END
END.
Output:

                                      1969  

            January                 February                 March        
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                1  2  3  4                       1                       1
       5  6  7  8  9 10 11     2  3  4  5  6  7  8     2  3  4  5  6  7  8
      12 13 14 15 16 17 18     9 10 11 12 13 14 15     9 10 11 12 13 14 15
      19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22
      26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29
                                                      30 31 

             April                    May                     June        
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
             1  2  3  4  5                 1  2  3     1  2  3  4  5  6  7
       6  7  8  9 10 11 12     4  5  6  7  8  9 10     8  9 10 11 12 13 14
      13 14 15 16 17 18 19    11 12 13 14 15 16 17    15 16 17 18 19 20 21
      20 21 22 23 24 25 26    18 19 20 21 22 23 24    22 23 24 25 26 27 28
      27 28 29 30             25 26 27 28 29 30 31    29 30 

              July                   August                September      
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
             1  2  3  4  5                    1  2        1  2  3  4  5  6
       6  7  8  9 10 11 12     3  4  5  6  7  8  9     7  8  9 10 11 12 13
      13 14 15 16 17 18 19    10 11 12 13 14 15 16    14 15 16 17 18 19 20
      20 21 22 23 24 25 26    17 18 19 20 21 22 23    21 22 23 24 25 26 27
      27 28 29 30 31          24 25 26 27 28 29 30    28 29 30 
                              31                      

            October                 November                December      
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                1  2  3  4                       1        1  2  3  4  5  6
       5  6  7  8  9 10 11     2  3  4  5  6  7  8     7  8  9 10 11 12 13
      12 13 14 15 16 17 18     9 10 11 12 13 14 15    14 15 16 17 18 19 20
      19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27
      26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31 
                              30                      


Amazing Hopper

Hopper tiene una función que genera calendarios en español, y con un formato diferente al pedido.

El calendario generado en un array 2D como el que se muestra a continuación:

Línea de código:

  Let ( calendario := Calendar(1,año,12) )
  [ 1:8, 1:7 ] Cget 'calendario', Print this table
ENE,   ,   ,   ,   ,   ,1969
Dom,Lun,Mar,Mie,Jue,Vie,Sab
   ,   ,   ,  1,  2,  3,  4
  5,  6,  7,  8,  9, 10, 11
 12, 13, 14, 15, 16, 17, 18
 19, 20, 21, 22, 23, 24, 25
 26, 27, 28, 29, 30, 31,   
   ,   ,   ,   ,   ,   ,   

Son generados tantos calendarios como se pida, y de diferentes formas (lineales o radiales). El código a continuación debe reformatear el calendario antes de imprimirlo.

#include <jambo.h>

Main
   Set stack 15
   año=0
   Get arg numeric '2', Move to 'año'
   
   Set '4,1,1,1' Init 'fila, columna, contador columna, contador mes)
   meses={}
   Let list ( meses := "Enero","Febrero","Marzo","Abril","Mayo",\
                       "Junio","Julio","Agosto","Septiembre","Octubre",\
                       "Noviembre","Diciembre" )
   calendario=0
   Let ( calendario := Calendar(1,año,12) )
   
   Cls
   Link gosub 'Cambia lenguaje de los meses, Imprime año'
   
   Tok sep ("")
   Gosub while ( i=1, Less(i,97), Dibuja calendario )
End

Subrutines

Define 'Dibuja calendario'
   Locate (fila, columna)  Just center (23, [ contador mes++ ] Cget 'meses') 
                           Print it
   ++fila 
   Loc row (fila--)        [ {i}Plus(1):{i}Plus(7), 1:7 ] Cget 'calendario'
                           Print this table
   ++contador columna
   columna += 25
   When( Equals (contador columna, 4) ) { 
       Set '1', Copy to 'contador columna', 
                Move to 'columna'
       fila+=9
   }
   i+=8
Return

Define 'Imprime año'
   Locate (2,35),  Print (año)
Return

Define 'Cambia lenguaje de los meses'
   Let ( calendario := Tran (" Do","Dom",calendario) )
   Let ( calendario := Tran (" Lu","Lun",calendario) )
   Let ( calendario := Tran (" Ma","Mar",calendario) )
   Let ( calendario := Tran (" Mi","Mie",calendario) )
   Let ( calendario := Tran (" Ju","Jue",calendario) )
   Let ( calendario := Tran (" Vi","Vie",calendario) )
   Let ( calendario := Tran (" Sa","Sab",calendario) )
Return
Output:
$ hopper jm/calendar.jambo 1969

                                  1969

         Enero                   Febrero                   Marzo         
 Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa
           1  2  3  4                        1                        1
  5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
 12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
 19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
 26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                   30 31               

         Abril                    Mayo                     Junio         
 Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa
        1  2  3  4  5                  1  2  3      1  2  3  4  5  6  7
  6  7  8  9 10 11 12      4  5  6  7  8  9 10      8  9 10 11 12 13 14
 13 14 15 16 17 18 19     11 12 13 14 15 16 17     15 16 17 18 19 20 21
 20 21 22 23 24 25 26     18 19 20 21 22 23 24     22 23 24 25 26 27 28
 27 28 29 30              25 26 27 28 29 30 31     29 30               
                                                                       

         Julio                   Agosto                 Septiembre       
 Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa
        1  2  3  4  5                     1  2         1  2  3  4  5  6
  6  7  8  9 10 11 12      3  4  5  6  7  8  9      7  8  9 10 11 12 13
 13 14 15 16 17 18 19     10 11 12 13 14 15 16     14 15 16 17 18 19 20
 20 21 22 23 24 25 26     17 18 19 20 21 22 23     21 22 23 24 25 26 27
 27 28 29 30 31           24 25 26 27 28 29 30     28 29 30            
                          31                                           

        Octubre                 Noviembre                Diciembre       
 Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa     Do Lu Ma Mi Ju Vi Sa
           1  2  3  4                        1         1  2  3  4  5  6
  5  6  7  8  9 10 11      2  3  4  5  6  7  8      7  8  9 10 11 12 13
 12 13 14 15 16 17 18      9 10 11 12 13 14 15     14 15 16 17 18 19 20
 19 20 21 22 23 24 25     16 17 18 19 20 21 22     21 22 23 24 25 26 27
 26 27 28 29 30 31        23 24 25 26 27 28 29     28 29 30 31         
                          30        

AutoHotkey

Calendar(Yr){
	LastDay := [], Day := []
	Titles =
	(ltrim
	______January_________________February_________________March_______
	_______April____________________May____________________June________
	________July___________________August_________________September_____
	______October_________________November________________December______
	)
	StringSplit, title, titles, `n
	Res := "________________________________" Yr "`r`n"

	loop 4 {										; 4 Vertical Sections
		Day[1]:=Yr SubStr("0" A_Index*3 -2, -1) 01
		Day[2]:=Yr SubStr("0" A_Index*3 -1, -1) 01
		Day[3]:=Yr SubStr("0" A_Index*3   , -1) 01
		Res .= "`r`n" title%A_Index% "`r`nSu Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa"
		loop , 6 {									; 6 Weeks max per month
			Week := A_Index, Res .= "`r`n"
			loop, 21 {								; 3 weeks times 7 days
				Mon := Ceil(A_Index/7), ThisWD := Mod(A_Index-1,7)+1
				FormatTime, WD, % Day[Mon], WDay
				FormatTime, dd, % Day[Mon], dd
				if (WD>ThisWD) {
					Res .= "__ "
					continue
				}
				dd := ((Week>3) && dd <10) ? "__" : dd, Res .= dd " ", LastDay[Mon] := Day[Mon], Day[Mon] +=1, Days
				Res .= ((wd=7) && A_Index < 21) ? "___" : ""	
				FormatTime, dd, % Day[Mon], dd
			}
		}
		Res .= "`r`n"
	}
	StringReplace, Res, Res,_,%A_Space%, all
	Res:=RegExReplace(Res,"`am)(^|\s)\K0", " ")
	return res
}

Examples:

Gui, font,s8, COURIER
Gui, add, edit, vYr w40 r1 Limit4 Number, 1969
Gui, add, edit, vEdit2 w580 r38
Gui, Add, Button, Default Hidden gSubmit
Gui, show

Submit:
Gui, Submit, NoHide
GuiControl,, Edit2, % Calendar(Yr)
return

GuiEscape:
GuiClose:
ExitApp
return

Outputs:

                                1969

       January                February                  March       
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
          1  2  3  4                       1                       1 
 5  6  7  8  9 10 11     2  3  4  5  6  7  8     2  3  4  5  6  7  8 
12 13 14 15 16 17 18     9 10 11 12 13 14 15     9 10 11 12 13 14 15 
19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22 
26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29 
                                                30 31                

        April                    May                    June        
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
       1  2  3  4  5                 1  2  3     1  2  3  4  5  6  7 
 6  7  8  9 10 11 12     4  5  6  7  8  9 10     8  9 10 11 12 13 14 
13 14 15 16 17 18 19    11 12 13 14 15 16 17    15 16 17 18 19 20 21 
20 21 22 23 24 25 26    18 19 20 21 22 23 24    22 23 24 25 26 27 28 
27 28 29 30             25 26 27 28 29 30 31    29 30                
                                                                     

        July                   August                 September     
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
       1  2  3  4  5                    1  2        1  2  3  4  5  6 
 6  7  8  9 10 11 12     3  4  5  6  7  8  9     7  8  9 10 11 12 13 
13 14 15 16 17 18 19    10 11 12 13 14 15 16    14 15 16 17 18 19 20 
20 21 22 23 24 25 26    17 18 19 20 21 22 23    21 22 23 24 25 26 27 
27 28 29 30 31          24 25 26 27 28 29 30    28 29 30             
                        31                                           

       October                November                December      
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
          1  2  3  4                       1        1  2  3  4  5  6 
 5  6  7  8  9 10 11     2  3  4  5  6  7  8     7  8  9 10 11 12 13 
12 13 14 15 16 17 18     9 10 11 12 13 14 15    14 15 16 17 18 19 20 
19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27 
26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31          
                        30                                           

AutoIt

#include <Date.au3>

; Set the count of characters in each line, minimum is 20 - one month.
Global $iPrintSize = 132

; Set the count of months, you want to print side by side. With "0" it calculates automatically.
; The number will corrected, if it not allowed to print in an rectangle.
; If your print size is to small for given count, it will set back to automatically calculation.
Global $iSideBySide = 3

; Set the count of spaces between months.
Global $iSpace = 4

_CreateCalendar( 1969 )


Func _CreateCalendar($_iYear)
	Local $aMon[12] = ['       January      ', '      February      ', '        March       ', _
	                   '        April       ', '         May        ', '        June        ', _
	                   '        July        ', '       August       ', '      September     ', _
	                   '       October      ', '      November      ', '      December      ']
	Local $sHead = 'Mo Tu We Th Fr Sa Su'
	Local $aDaysInMonth[12] = [31,28,31,30,31,30,31,31,30,31,30,31]
	If _DateIsLeapYear($_iYear) Then $aDaysInMonth[1] = 29

	; == assign date in weekday table for the whole year
	Local $aAllDaysInMonth[6][7][12]   ; [ lines ][ weekdays ][ months ]
	Local $iDay = 1, $iShift
	For $i = 1 To 12
		$iShift = _DateToDayOfWeekISO($_iYear, $i, 1) -1
		For $j = 0 To 5
			For $k = $iShift To 6
				$aAllDaysInMonth[$j][$k][$i-1] = $iDay
				$iDay += 1
				If $iDay > $aDaysInMonth[$i-1] Then ExitLoop(2)
			Next
			$iShift = 0
		Next
		$iDay = 1
	Next

	; == check given side by side count, calculate if needed
	If $iSideBySide > 0 Then
		If $iPrintSize < ($iSideBySide *(20 +$iSpace) -$iSpace) Then $iSideBySide = 0
	EndIf
	Switch $iSideBySide
		Case 0
			$iSideBySide = Int($iPrintSize /(20 +$iSpace))
			If $iPrintSize < 20 Then Return _PrintLine('Escape: Size Error')
			If $iPrintSize < (20 +$iSpace) Then $iSideBySide = 1
		Case 5
			$iSideBySide = 4
		Case 7 To 11
			$iSideBySide = 6
	EndSwitch

	; == create space string
	Local $sSpace = ''
	For $i = 1 To $iSpace
		$sSpace &= ' '
	Next

	; == print header
	_PrintLine(@LF)
	_PrintLine('[ here is Snoopy ]', @LF)
	_PrintLine(StringRegExpReplace($_iYear, '(\d)(\d)(\d)(\d)', '$1 $2 $3 $4'), @LF)

	; == create data for each line, in dependence to count of months in one line
	Local $sLine, $iRight, $sTmp1, $sTmp2
	For $n = 0 To 12 /$iSideBySide -1
		$sTmp1 = ''
		$sTmp2 = ''
		For $z = 0 To $iSideBySide -1
			$sTmp1 &= $aMon[$iSideBySide *$n+$z] & $sSpace
			$sTmp2 &= $sHead & $sSpace
		Next
		_PrintLine(StringTrimRight($sTmp1, $iSpace))
		_PrintLine(StringTrimRight($sTmp2, $iSpace))
		For $j = 0 To 5
			$sLine = ''
			For $i = 1 To $iSideBySide
				For $k = 0 To 6
					$iRight = 3
					If $k = 0 Then $iRight = 2
					$sLine &= StringRight('   ' & $aAllDaysInMonth[$j][$k][$iSideBySide*$n+$i-1], $iRight)
				Next
				If $i < $iSideBySide Then $sLine &= $sSpace
			Next
			_PrintLine($sLine)
		Next
	Next
EndFunc  ;==>_CreateCalendar

Func _PrintLine($_sLine, $_sLF='')
	Local $iLen = StringLen($_sLine)
	Local $sSpace = '', $sLeft = ''
	For $i = 1 To $iPrintSize-1
		$sSpace &= ' '
	Next
	If $iLen < $iPrintSize Then $sLeft = StringLeft($sSpace, Int(($iPrintSize-$iLen)/2))
	ConsoleWrite($sLeft & $_sLine & $_sLF & @LF)
EndFunc  ;==>_PrintLine

Output

                                                                

                                                         [ here is Snoopy ]

                                                              1 9 6 9

                                       January                February                  March       
                                Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
                                       1  2  3  4  5                    1  2                    1  2
                                 6  7  8  9 10 11 12     3  4  5  6  7  8  9     3  4  5  6  7  8  9
                                13 14 15 16 17 18 19    10 11 12 13 14 15 16    10 11 12 13 14 15 16
                                20 21 22 23 24 25 26    17 18 19 20 21 22 23    17 18 19 20 21 22 23
                                27 28 29 30 31          24 25 26 27 28          24 25 26 27 28 29 30
                                                                                31                  
                                        April                    May                    June        
                                Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
                                    1  2  3  4  5  6              1  2  3  4                       1
                                 7  8  9 10 11 12 13     5  6  7  8  9 10 11     2  3  4  5  6  7  8
                                14 15 16 17 18 19 20    12 13 14 15 16 17 18     9 10 11 12 13 14 15
                                21 22 23 24 25 26 27    19 20 21 22 23 24 25    16 17 18 19 20 21 22
                                28 29 30                26 27 28 29 30 31       23 24 25 26 27 28 29
                                                                                30                  
                                        July                   August                 September     
                                Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
                                    1  2  3  4  5  6                 1  2  3     1  2  3  4  5  6  7
                                 7  8  9 10 11 12 13     4  5  6  7  8  9 10     8  9 10 11 12 13 14
                                14 15 16 17 18 19 20    11 12 13 14 15 16 17    15 16 17 18 19 20 21
                                21 22 23 24 25 26 27    18 19 20 21 22 23 24    22 23 24 25 26 27 28
                                28 29 30 31             25 26 27 28 29 30 31    29 30               
                                                                                                    
                                       October                November                December      
                                Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
                                       1  2  3  4  5                    1  2     1  2  3  4  5  6  7
                                 6  7  8  9 10 11 12     3  4  5  6  7  8  9     8  9 10 11 12 13 14
                                13 14 15 16 17 18 19    10 11 12 13 14 15 16    15 16 17 18 19 20 21
                                20 21 22 23 24 25 26    17 18 19 20 21 22 23    22 23 24 25 26 27 28
                                27 28 29 30 31          24 25 26 27 28 29 30    29 30 31            
                                                                                                                                                 

--BugFix (talk) 00:40, 15 November 2013 (UTC)

AWK

Works with Gnu awk version 3.1.5 and with BusyBox v1.20.0.git awk
To change the output width, change the value assigned to variable pagewide

#!/bin/gawk -f
BEGIN{
  wkdays = "Su Mo Tu We Th Fr Sa"
  pagewide = 80
  blank=" "
  for (i=1; i<pagewide; i++) blank = blank " "
  #  month name accessed as substr(month,num*10,10) 
  #      where num is number of month, 1-12
  month= "           January  February    March     April  "
  month=    month "   May      June       July     August  "
  month=    month " September October   November  December "
  #  table of days per month accessed as substr(days,2*month,2)
  days =" 312831303130313130313031"
  line1 = ""
  line2 = ""
  line3 = ""
  line4 = ""
  line5 = ""
  line6 = ""
  line7 = ""
  line8 = ""
# print " year: " year " starts on: " dow(year) 
  }
function center(text,   half) {
  half = (pagewide - length(text))/2
  return substr(blank,1,half) text substr(blank,1,half)
  }
function min(a,b) {
  if (a < b) return a
  else return b
  }
function makewk (fst,lst,day,  i,wstring ){
  wstring=""
  for (i=1;i<day;i++) wstring=wstring "   "
  for (i=fst;i<=lst;i++) wstring=wstring sprintf("%2d ",i)
  return substr(wstring "                     ",1,20)
  }
function dow (year, y){
  y=year
  y= (y*365+int(y/4) - int(y/100) + int(y/400) +1) %7
# leap year adjustment
  leap = 0
  if (year % 4 == 0)   leap = 1
  if (year % 100 == 0) leap = 0
  if (year % 400 == 0) leap = 1
  y = y - leap
  if (y==-1) y=6
  if (y==0) y=7
  return (y)
  }
function prmonth (nmonth, newdow,monsize  ){
  line1 = line1  "     " (substr(month,10*nmonth,10)) "       "
  line2 = line2  (wkdays) "  "
  line3 = line3  (makewk(1,8-newdow,newdow)) "  "
  line4 = line4  (makewk(9-newdow,15-newdow,1)) "  "
  line5 = line5  (makewk(16-newdow,22-newdow,1)) "  "
  line6 = line6  (makewk(23-newdow,29-newdow,1)) "  "
  line7 = line7  (makewk(30-newdow,min(monsize,36-newdow),1)) "  "
  line8 = line8  (makewk(37-newdow,monsize,1)) "  "
if (length(line3) + 22 > pagewide) {
  print center(line1)
  print center(line2)
  print center(line3)
  print center(line4)
  print center(line5)
  print center(line6)
  print center(line7)
  print center(line8)
  line1 = ""
  line2 = ""
  line3 = ""
  line4 = ""
  line5 = ""
  line6 = ""
  line7 = ""
  line8 = ""
  }
}
/q/{
  exit }
{ 
  monsize=substr(days,2*1,2)
  newdow=dow($1)
  print center("[ picture of Snoopy goes here ]")
  print center(sprintf("%d",$1)  )
  # January - December
  for (i=1; i<13; i++) {
  prmonth(i,newdow,monsize)
  newdow=(monsize+newdow) %7
  if (newdow == 0) newdow = 7
  monsize=substr(days,2+2*i,2)
  if (leap == 1 && monsize == 28) monsize = 29
  }
 }

Output:

                        [ picture of Snoopy goes here ]                        
                                      1969                                      
              January              February                March                
       Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa         
                 1  2  3  4                     1                     1         
        5  6  7  8  9 10 11   2  3  4  5  6  7  8   2  3  4  5  6  7  8         
       12 13 14 15 16 17 18   9 10 11 12 13 14 15   9 10 11 12 13 14 15         
       19 20 21 22 23 24 25  16 17 18 19 20 21 22  16 17 18 19 20 21 22         
       26 27 28 29 30 31     23 24 25 26 27 28     23 24 25 26 27 28 29         
                                                   30 31                        
               April                 May                  June                  
       Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa         
              1  2  3  4  5               1  2  3   1  2  3  4  5  6  7         
        6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14         
       13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21         
       20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28         
       27 28 29 30           25 26 27 28 29 30 31  29 30                        
                                                                                
               July                 August               September              
       Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa         
              1  2  3  4  5                  1  2      1  2  3  4  5  6         
        6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13         
       13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20         
       20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27         
       27 28 29 30 31        24 25 26 27 28 29 30  28 29 30                     
                             31                                                 
             October               November              December               
       Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa         
                 1  2  3  4                     1      1  2  3  4  5  6         
        5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13         
       12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20         
       19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27         
       26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31                  
                             30                                                 

BASIC

BaCon

Choosing 132 character output.

DECLARE month$[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }
DECLARE month[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
year$ = "1969"
' Leap year
INCR month[1], IIF(MOD(VAL(year$), 4) = 0 OR MOD(VAL(year$), 100) = 0 AND MOD(VAL(year$), 400) <> 0, 1, 0)
PRINT ALIGN$("[SNOOPY HERE]", 132, 2)
PRINT ALIGN$(year$, 132, 2)
FOR nr = 0 TO 11
    row = 3
    GOTOXY 1+(nr %6)*22, row+(nr/6)*9
    PRINT ALIGN$(month$[nr], 21, 2);
    INCR row
    GOTOXY 1+(nr %6)*22, row+(nr/6)*9
    PRINT ALIGN$("Mo Tu We Th Fr Sa Su", 21, 2);
    INCR row
    ' Each day
    FOR day = 1 TO month[nr]
        ' Zeller
        J = VAL(LEFT$(year$, 2))
        K = VAL(MID$(year$, 3, 2))
        m = nr+1
        IF nr < 2 THEN
            INCR m, 12
            DECR K
        END IF
        h = (day + ((m+1)*26)/10 + K + (K/4) + (J/4) + 5*J)
        daynr = MOD(h, 7) - 2
        IF daynr < 0 THEN INCR daynr, 7
        IF daynr = 0 AND day > 1 THEN INCR row
        GOTOXY 1+(nr %6)*22+daynr*3, row+(nr/6)*9
        PRINT day;
    NEXT
NEXT
Output:
                                                           [SNOOPY HERE]                                                            
                                                                1969                                                                
       January              February                March                 April                  May                  June         
Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su 
      1  2  3  4  5                  1  2                  1  2      1  2  3  4  5  6            1  2  3  4                     1
6  7  8  9  10 11 12  3  4  5  6  7  8  9   3  4  5  6  7  8  9   7  8  9  10 11 12 13  5  6  7  8  9  10 11  2  3  4  5  6  7  8
13 14 15 16 17 18 19  10 11 12 13 14 15 16  10 11 12 13 14 15 16  14 15 16 17 18 19 20  12 13 14 15 16 17 18  9  10 11 12 13 14 15
20 21 22 23 24 25 26  17 18 19 20 21 22 23  17 18 19 20 21 22 23  21 22 23 24 25 26 27  19 20 21 22 23 24 25  16 17 18 19 20 21 22
27 28 29 30 31        24 25 26 27 28        24 25 26 27 28 29 30  28 29 30              26 27 28 29 30 31     23 24 25 26 27 28 29
                                            31                                                                30

        July                 August               September              October              November              December       
Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su 
   1  2  3  4  5  6               1  2  3   1  2  3  4  5  6  7         1  2  3  4  5                  1  2   1  2  3  4  5  6  7
7  8  9  10 11 12 13  4  5  6  7  8  9  10  8  9  10 11 12 13 14  6  7  8  9  10 11 12  3  4  5  6  7  8  9   8  9  10 11 12 13 14
14 15 16 17 18 19 20  11 12 13 14 15 16 17  15 16 17 18 19 20 21  13 14 15 16 17 18 19  10 11 12 13 14 15 16  15 16 17 18 19 20 21
21 22 23 24 25 26 27  18 19 20 21 22 23 24  22 23 24 25 26 27 28  20 21 22 23 24 25 26  17 18 19 20 21 22 23  22 23 24 25 26 27 28
28 29 30 31           25 26 27 28 29 30 31  29 30                 27 28 29 30 31        24 25 26 27 28 29 30  29 30 31

Batch File

::Calender Task from Rosetta Code Wiki
::Batch File Implementation

@echo off
setlocal enabledelayedexpansion

	%== Set a valid year [will not be validated] ==%
set y=1969

	%== Set the variables for months (feb_l=the normal 28 days) ==%
set jan_l=31&set apr_l=30
set mar_l=31&set jun_l=30
set may_l=31&set sep_l=30
set jul_l=31&set nov_l=30
set aug_l=31&set feb_l=28
set oct_l=31
set dec_l=31

	%== Compute day for first day of the year ==%
set /a d=(y/4+y)-(y/100-y/400)

	%== Check if that year is a leap year ==%
set /a "op1=y%%4","op2=y%%100","op3=y%%400"
if not "%op1%"=="0" (goto :no_leap)
if not "%op2%"=="0" (goto :yes_leap)
if not "%op3%"=="0" (goto :no_leap)
	:yes_leap
		%== Ooops... Leap year. Change feb_l to 29. ==%
		set feb_l=29
		set/a d-=1
	:no_leap

	%== Compute weekday of the first day... ==%
set /a d%%=7

	%== Generate everything that's inside the calendar ==%
for %%a in (jan feb mar apr may jun jul aug sep oct nov dec) do (
	set %%a=
	set chars_added=0
	for /l %%b in (1,1,!d!) do (set "%%a=!%%a!   "&set /a chars_added+=3)
	for /l %%c in (1,1,!%%a_l!) do (
		if %%c lss 10 (set "%%a=!%%a! %%c ") else (set "%%a=!%%a!%%c ")
		set /a chars_added+=3
	)
	for /l %%d in (!chars_added!,1,124) do set "%%a=!%%a! "
	set /a d=^(d+%%a_l^)%%7
)

	%== Display the calendar ==%
cls
echo.
echo.                              [SNOOPY]
echo. 
echo. YEAR = %y%
echo.
echo.       January                February                March
echo. Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa 
echo. %jan:~0,20%   %feb:~0,20%   %mar:~0,20%
echo. %jan:~21,20%   %feb:~21,20%   %mar:~21,20%
echo. %jan:~42,20%   %feb:~42,20%   %mar:~42,20%
echo. %jan:~63,20%   %feb:~63,20%   %mar:~63,20%
echo. %jan:~84,20%   %feb:~84,20%   %mar:~84,20%
echo. %jan:~105%   %feb:~105%   %mar:~105%
echo.
echo.        April                   May                    June
echo. Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa 
echo. %apr:~0,20%   %may:~0,20%   %jun:~0,20%
echo. %apr:~21,20%   %may:~21,20%   %jun:~21,20%
echo. %apr:~42,20%   %may:~42,20%   %jun:~42,20%
echo. %apr:~63,20%   %may:~63,20%   %jun:~63,20%
echo. %apr:~84,20%   %may:~84,20%   %jun:~84,20%
echo. %apr:~105%   %may:~105%   %jun:~105%
echo.
echo.         July                  August                September
echo. Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa 
echo. %jul:~0,20%   %aug:~0,20%   %sep:~0,20%
echo. %jul:~21,20%   %aug:~21,20%   %sep:~21,20%
echo. %jul:~42,20%   %aug:~42,20%   %sep:~42,20%
echo. %jul:~63,20%   %aug:~63,20%   %sep:~63,20%
echo. %jul:~84,20%   %aug:~84,20%   %sep:~84,20%
echo. %jul:~105%   %aug:~105%   %sep:~105%
echo.
echo.       October                November               December
echo. Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa 
echo. %oct:~0,20%   %nov:~0,20%   %dec:~0,20%
echo. %oct:~21,20%   %nov:~21,20%   %dec:~21,20%
echo. %oct:~42,20%   %nov:~42,20%   %dec:~42,20%
echo. %oct:~63,20%   %nov:~63,20%   %dec:~63,20%
echo. %oct:~84,20%   %nov:~84,20%   %dec:~84,20%
echo. %oct:~105%   %nov:~105%   %dec:~105%
echo.
pause
endlocal
Output:

                              [SNOOPY]

 YEAR = 1969

       January                February                March
 Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
           1  2  3  4                      1                      1
  5  6  7  8  9 10 11    2  3  4  5  6  7  8    2  3  4  5  6  7  8
 12 13 14 15 16 17 18    9 10 11 12 13 14 15    9 10 11 12 13 14 15
 19 20 21 22 23 24 25   16 17 18 19 20 21 22   16 17 18 19 20 21 22
 26 27 28 29 30 31      23 24 25 26 27 28      23 24 25 26 27 28 29
                                               30 31

        April                   May                    June
 Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
        1  2  3  4  5                1  2  3    1  2  3  4  5  6  7
  6  7  8  9 10 11 12    4  5  6  7  8  9 10    8  9 10 11 12 13 14
 13 14 15 16 17 18 19   11 12 13 14 15 16 17   15 16 17 18 19 20 21
 20 21 22 23 24 25 26   18 19 20 21 22 23 24   22 23 24 25 26 27 28
 27 28 29 30            25 26 27 28 29 30 31   29 30


         July                  August                September
 Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
        1  2  3  4  5                   1  2       1  2  3  4  5  6
  6  7  8  9 10 11 12    3  4  5  6  7  8  9    7  8  9 10 11 12 13
 13 14 15 16 17 18 19   10 11 12 13 14 15 16   14 15 16 17 18 19 20
 20 21 22 23 24 25 26   17 18 19 20 21 22 23   21 22 23 24 25 26 27
 27 28 29 30 31         24 25 26 27 28 29 30   28 29 30
                        31

       October                November               December
 Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
           1  2  3  4                      1       1  2  3  4  5  6
  5  6  7  8  9 10 11    2  3  4  5  6  7  8    7  8  9 10 11 12 13
 12 13 14 15 16 17 18    9 10 11 12 13 14 15   14 15 16 17 18 19 20
 19 20 21 22 23 24 25   16 17 18 19 20 21 22   21 22 23 24 25 26 27
 26 27 28 29 30 31      23 24 25 26 27 28 29   28 29 30 31
                        30

Press any key to continue . . .

BBC BASIC

The day and month names are in the language for which the PC is configured.

      INSTALL @lib$+"DATELIB"
      VDU 23,22,640;570;8,15,16,128
      
      year% = 1969
      PRINT TAB(38); year%
      DIM dom%(2), mjd%(2), dim%(2)
      
      FOR day% = 1 TO 7
        days$ += LEFT$(FN_date$(FN_mjd(day%, 1, 1905), "ddd"), 2) + " "
      NEXT
      
      FOR month% = 1 TO 10 STEP 3
        PRINT
        FOR col% = 0 TO 2
          mjd%(col%) = FN_mjd(1, month% + col%, year%)
          month$ = FN_date$(mjd%(col%), "MMMM")
          PRINT TAB(col%*24 + 16 - LEN(month$)/2) month$;
        NEXT
        FOR col% = 0 TO 2
          PRINT TAB(col%*24 + 6) days$;
          dim%(col%) = FN_dim(month% + col%, year%)
        NEXT
        dom%() = 1
        col% = 0
        REPEAT
          dow% = FN_dow(mjd%(col%))
          IF dom%(col%)<=dim%(col%) THEN
            PRINT TAB(col%*24 + dow%*3 + 6); dom%(col%);
            dom%(col%) += 1
            mjd%(col%) += 1
          ENDIF
          IF dow%=6 OR dom%(col%)>dim%(col%) col% = (col% + 1) MOD 3
        UNTIL dom%(0)>dim%(0) AND dom%(1)>dim%(1) AND dom%(2)>dim%(2)
        PRINT
      NEXT

Output:

                                      1969

            January                 February                 March
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
               1  2  3  4                       1                       1
      5  6  7  8  9  10 11    2  3  4  5  6  7  8     2  3  4  5  6  7  8
      12 13 14 15 16 17 18    9  10 11 12 13 14 15    9  10 11 12 13 14 15
      19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22
      26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29
                                                      30 31

             April                    May                     June
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
            1  2  3  4  5                 1  2  3     1  2  3  4  5  6  7
      6  7  8  9  10 11 12    4  5  6  7  8  9  10    8  9  10 11 12 13 14
      13 14 15 16 17 18 19    11 12 13 14 15 16 17    15 16 17 18 19 20 21
      20 21 22 23 24 25 26    18 19 20 21 22 23 24    22 23 24 25 26 27 28
      27 28 29 30             25 26 27 28 29 30 31    29 30

              July                   August                September
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
            1  2  3  4  5                    1  2        1  2  3  4  5  6
      6  7  8  9  10 11 12    3  4  5  6  7  8  9     7  8  9  10 11 12 13
      13 14 15 16 17 18 19    10 11 12 13 14 15 16    14 15 16 17 18 19 20
      20 21 22 23 24 25 26    17 18 19 20 21 22 23    21 22 23 24 25 26 27
      27 28 29 30 31          24 25 26 27 28 29 30    28 29 30
                              31

            October                 November                December
      Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
               1  2  3  4                       1        1  2  3  4  5  6
      5  6  7  8  9  10 11    2  3  4  5  6  7  8     7  8  9  10 11 12 13
      12 13 14 15 16 17 18    9  10 11 12 13 14 15    14 15 16 17 18 19 20
      19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27
      26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31
                              30

Befunge

This is quite closely based on the C sample, although the centering calculation has been adjusted to fix an off-by-one error, and the months have been made a constant height, regardless of how much space they require, to try and produce a more balanced layout.

The year is read from stdin, and the width is specified by the first value on the stack (set to 80 - "P" - in the current implementation).

"P"00p&>:::4%!\"d"%*\45*:*%!+!!65*+31p:1-:::"I"5**\4/+\"d"/-\45*:*/+1+7%:0v
J!F?M!A M!J J!A!S O!N D!SaFrThWeTuMoSuvp01:_1#!-#%:#\>#+6<v-2g1+1g01p1p01:<
 January  February  March    April    >:45**00g\-\1-:v:<<6>+7%:10g2+:38*\`|
   May      June     July    August v02-1:+4*-4\`\4:/_$:^^:/*2+92+2:g00$$$<
September October  November December>p:45*+10g*\--2/00g4-2/>:#,1#*-#8\#4_$v
>20g>:#,1#*-#8\#4_$6"$S" v. .vp040\$_4#!8#\*#-,#1>#:<+5g03g01:,,:+55<0.p03<
^_v#:-1$_v#!:,*84,g1+1,< < ^ >::4%9*40g:8`!#v_$$$1+v^+g02_#v$#,$#+5<^_v#`\<
-#8\#4_$7>1-:2*64*+:1g>^ ^   ^,g+2/4\+p04+1:<>1#\-#<:66+\^ >>$10g30g>:#,1#*
> > $$55+,6>>40p:10g30g>:#,1#*-#8\#4_$>\:2*:1+1g2-50p1g640g-7*1+\-7v  v@,<6
2:+g01$_55+,^ > > > > #^>#g>#0>#2_v v*2!!\%+55:\/+55:**`0\!`g05:::\<  >2-^^
->:> >#^>#<>#<^#!:-1g04$$ < < < < < >4+8*+\:!!2*4+8*+,,48*,1+\1-:>#^_$$1+\1

C

With arbitrary display width (>= 20 though) and auto spacing.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int width = 80, year = 1969;
int cols, lead, gap;

const char *wdays[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
struct months {
	const char *name;
	int days, start_wday, at;
} months[12] = {
	{ "January",	31, 0, 0 },
	{ "February",	28, 0, 0 },
	{ "March",	31, 0, 0 },
	{ "April",	30, 0, 0 },
	{ "May",	31, 0, 0 },
	{ "June",	30, 0, 0 },
	{ "July",	31, 0, 0 },
	{ "August",	31, 0, 0 },
	{ "September",	30, 0, 0 },
	{ "October",	31, 0, 0 },
	{ "November",	30, 0, 0 },
	{ "December",	31, 0, 0 }
};

void space(int n) { while (n-- > 0) putchar(' '); }

void init_months()
{
	int i;

	if ((!(year % 4) && (year % 100)) || !(year % 400))
		months[1].days = 29;

	year--;
	months[0].start_wday
		= (year * 365 + year/4 - year/100 + year/400 + 1) % 7;

	for (i = 1; i < 12; i++)
		months[i].start_wday =
			(months[i-1].start_wday + months[i-1].days) % 7;

	cols = (width + 2) / 22;
	while (12 % cols) cols--;
	gap = cols - 1 ? (width - 20 * cols) / (cols - 1) : 0;
	if (gap > 4) gap = 4;
	lead = (width - (20 + gap) * cols + gap + 1) / 2;
        year++;
}

void print_row(int row)
{
	int c, i, from = row * cols, to = from + cols;
	space(lead);
	for (c = from; c < to; c++) {
		i = strlen(months[c].name);
		space((20 - i)/2);
		printf("%s", months[c].name);
		space(20 - i - (20 - i)/2 + ((c == to - 1) ? 0 : gap));
	}
	putchar('\n');

	space(lead);
	for (c = from; c < to; c++) {
		for (i = 0; i < 7; i++)
			printf("%s%s", wdays[i], i == 6 ? "" : " ");
		if (c < to - 1) space(gap);
		else putchar('\n');
	}

	while (1) {
		for (c = from; c < to; c++)
			if (months[c].at < months[c].days) break;
		if (c == to) break;

		space(lead);
		for (c = from; c < to; c++) {
			for (i = 0; i < months[c].start_wday; i++) space(3);
			while(i++ < 7 && months[c].at < months[c].days) {
				printf("%2d", ++months[c].at);
				if (i < 7 || c < to - 1) putchar(' ');
			}
			while (i++ <= 7 && c < to - 1) space(3);
			if (c < to - 1) space(gap - 1);
			months[c].start_wday = 0;
		}
		putchar('\n');
	}
	putchar('\n');
}

void print_year()
{
	int row;
	char buf[32];
	sprintf(buf, "%d", year);
	space((width - strlen(buf)) / 2);
	printf("%s\n\n", buf);
	for (row = 0; row * cols < 12; row++)
		print_row(row);
}

int main(int c, char **v)
{
	int i, year_set = 0;
	for (i = 1; i < c; i++) {
		if (!strcmp(v[i], "-w")) {
			if (++i == c || (width = atoi(v[i])) < 20)
				goto bail;
		} else if (!year_set) {
			if (!sscanf(v[i], "%d", &year) || year <= 0)
				year = 1969;
			year_set = 1;
		} else
			goto bail;
	}

	init_months();
	print_year();
	return 0;

bail:	fprintf(stderr, "bad args\nUsage: %s year [-w width (>= 20)]\n", v[0]);
	exit(1);
}
Library: Gadget
Translation of: Amazing Hopper
#include <gadget/gadget.h>
LIB_GADGET_START

void draw_calendar( RDS(char*,calendario), int year );
void print_calendar( RDS(char*,calendario) );

Main
   
   Assert( Arg_count ==2, fail_arg );
   Get_arg_int( year, 1 );

   ACTUAL_LANG_DATE = EN; /* text in english */
   
   New array calendario as string;
   /* get full year: 
      112 = code for months after initial month.
      1 = initial month
      year = well... */
   calendario = Calendar(calendario, 112, 1, year);
   
   draw_calendar( SDS(calendario), year );
   
   Free str array calendario;
   
   Exception( fail_arg ){
       Msg_yellow("Modo de uso:\n  ./calendar <nYear>");
   }

End

void draw_calendar( RDS( char*, calendario), int year )
{
   int fila=4, columna=1, cnt_columna=1, cnt_mes=0, i=0;
   
   Cls;
   At 2,35;  Print "%d", year;

   while ( cnt_mes < 12 )
   {
      String month_name;
      Stack { 
          Store ( month_name, Pad_c( Capital( Get_monthname(cnt_mes++) ),' ',23) );
      } Stack_off;
      
      At fila, columna;  Print "%s", month_name;

      Atrow ++fila; 
      
      Range for calendario [ i+1: 1: i+8, 0:1: Cols(calendario) ];
      print_calendar( SDS(calendario) );
      --fila;
      
      ++cnt_columna;
      columna += 25;
      When( cnt_columna == 4 ) { 
          cnt_columna = columna = 1;
          fila+=9;
      }
      i+=8;
      Free secure month_name;
   }
   Prnl;
}


void print_calendar( RDS(char*,calendario) )
{
   int i,j;
   int row = SCREEN_ROW;

   Iterup( row, calendario, i)
   {
       Iterup( col, calendario, j)
       {
            Print "%*s", 3, $calendario[i,j];
       }
       Atrow ++row;
   }
}
Output:
                                  1969

        January                 February                   March         
 Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
           1  2  3  4                        1                        1
  5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
 12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
 19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
 26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                   30 31               

         April                     May                     June          
 Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
        1  2  3  4  5                  1  2  3      1  2  3  4  5  6  7
  6  7  8  9 10 11 12      4  5  6  7  8  9 10      8  9 10 11 12 13 14
 13 14 15 16 17 18 19     11 12 13 14 15 16 17     15 16 17 18 19 20 21
 20 21 22 23 24 25 26     18 19 20 21 22 23 24     22 23 24 25 26 27 28
 27 28 29 30              25 26 27 28 29 30 31     29 30               
                                                                       

         July                    August                  September       
 Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
        1  2  3  4  5                     1  2         1  2  3  4  5  6
  6  7  8  9 10 11 12      3  4  5  6  7  8  9      7  8  9 10 11 12 13
 13 14 15 16 17 18 19     10 11 12 13 14 15 16     14 15 16 17 18 19 20
 20 21 22 23 24 25 26     17 18 19 20 21 22 23     21 22 23 24 25 26 27
 27 28 29 30 31           24 25 26 27 28 29 30     28 29 30            
                          31                                           

        October                 November                 December        
 Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
           1  2  3  4                        1         1  2  3  4  5  6
  5  6  7  8  9 10 11      2  3  4  5  6  7  8      7  8  9 10 11 12 13
 12 13 14 15 16 17 18      9 10 11 12 13 14 15     14 15 16 17 18 19 20
 19 20 21 22 23 24 25     16 17 18 19 20 21 22     21 22 23 24 25 26 27
 26 27 28 29 30 31        23 24 25 26 27 28 29     28 29 30 31         
                          30                                           

C#

An attempt to abuse the DateTime class for all static information. In the event that the number of days and months changes, so long as the DateTime class is updated accordingly, this should still print properly. It also abuses iterators to allow for a concise month printing method, but with the ability to still print x months per line.

using System;
using System.Collections.Generic; 
using System.Linq;
using System.Text;

namespace CalendarStuff
{
 
    class Program
    {
        static void Main(string[] args)
        {
            Console.WindowHeight = 46;
            Console.Write(buildMonths(new DateTime(1969, 1, 1)));
            Console.Read(); 
        }
        private static string buildMonths(DateTime date)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(center("[Snoop]", 24 * 3));
            sb.AppendLine();
            sb.AppendLine(center(date.Year.ToString(), 24 * 3));

            List<DateTime> dts = new List<DateTime>();
            while (true)
            {
                dts.Add(date);
                if (date.Year != ((date = date.AddMonths(1)).Year))
                {
                    break;
                }
            }
            var jd = dts.Select(a => buildMonth(a).GetEnumerator()).ToArray();
            
            int sCur=0;
            while (sCur<dts.Count)
            {
                sb.AppendLine();
                int curMonth=0; 
                var j = jd.Where(a => curMonth++ >= sCur && curMonth - 1 < sCur + 3).ToArray(); //grab the next 3
                sCur += j.Length; 
                bool breakOut = false;
                while (!breakOut)
                {
                    int inj = 1;
                    foreach (var cd in j)
                    {
                        if (cd.MoveNext())
                        {
                            sb.Append((cd.Current.Length == 21 ? cd.Current : cd.Current.PadRight(21, ' ')) + "     ");
                        }
                        else
                        {
                            sb.Append("".PadRight(21, ' ') + "     ");
                            breakOut = true;
                        }
                        if (inj++ % 3 == 0) sb.AppendLine();
                    }
                }

            }
            return sb.ToString();
        }


        private static IEnumerable<string> buildMonth(DateTime date)
        {
            yield return center(date.ToString("MMMM"),7*3);
            var j = DateTime.DaysInMonth(date.Year, date.Month);
            yield return Enum.GetNames(typeof(DayOfWeek)).Aggregate("", (current, result) => current + (result.Substring(0, 2).ToUpper() + " "));
            string cur = "";
            int total = 0;

            foreach (var day in Enumerable.Range(-((int)date.DayOfWeek),j + (int)date.DayOfWeek))
            {
                cur += (day < 0 ? "  " : ((day < 9 ? " " : "") + (day + 1))) +" ";
                if (total++ > 0 && (total ) % 7 == 0)
                {
                    yield return cur;
                    cur = "";
                }
            }
            yield return cur;
        }
        private static string center(string s, int len)
        {
            return (s.PadLeft((len - s.Length) / 2 + s.Length, ' ').PadRight((len), ' '));
        } 
    }
}

C++

#include <windows.h>
#include <iostream>

//--------------------------------------------------------------------------------------------------
using namespace std;


//--------------------------------------------------------------------------------------------------
class calender
{
public:
    void drawCalender( int y )
    {
	year = y;
	for( int i = 0; i < 12; i++ )
	    firstdays[i] = getfirstday( i );

	isleapyear();
	build();
    }

private:
    void isleapyear()
    {
	isleap = false;

	if( !( year % 4 ) )
	{
	    if( year % 100 ) isleap = true;
	    else if( !( year % 400 ) ) isleap = true;
	}
    }

    int getfirstday( int m )
    {
	int y = year;

	int f = y + 1 + 3 * m - 1;
	m++;
	if( m < 3 ) y--;
	else f -= int( .4 * m + 2.3 );

	f += int( y / 4 ) - int( ( y / 100 + 1 ) * 0.75 );
	f %= 7;

	return f;
    }

    void build()
    {
	int days[] = { 31, isleap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	int lc = 0, lco = 0, ystr = 7, start = 2, fd = 0, m = 0;
	HANDLE h = GetStdHandle( STD_OUTPUT_HANDLE );
	COORD pos = { 0, ystr };
	draw();

	for( int i = 0; i < 4; i++ )
	{
	    for( int j = 0; j < 3; j++ )
	    {
		int d = firstdays[fd++], dm = days[m++];
		pos.X = d * 3 + start;
		SetConsoleCursorPosition( h, pos );

		for( int dd = 0; dd < dm; dd++ )
		{
		    if( dd < 9 ) cout << 0 << dd + 1 << " ";
		    else cout << dd + 1 << " ";

		    pos.X += 3;
		    if( pos.X - start > 20 )
		    {
			pos.X = start; pos.Y++;
			SetConsoleCursorPosition( h, pos );
		    }
		}

		start += 23;
		pos.X = start; pos.Y = ystr;
		SetConsoleCursorPosition( h, pos );
	    }
	    ystr += 9; start = 2;
	    pos.Y = ystr;
	}
    }

    void draw()
    {
	system( "cls" );
	cout << "+--------------------------------------------------------------------+" << endl;
	cout << "|                              [SNOOPY]                              |" << endl;
	cout << "|                                                                    |" << endl;
	cout << "|                             == " << year << " ==                             |" << endl;
	cout << "+----------------------+----------------------+----------------------+" << endl;
	cout << "|       JANUARY        |       FEBRUARY       |         MARCH        |" << endl;
	cout << "| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "+----------------------+----------------------+----------------------+" << endl;
	cout << "|        APRIL         |          MAY         |         JUNE         |" << endl;
	cout << "| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "+----------------------+----------------------+----------------------+" << endl;
	cout << "|         JULY         |        AUGUST        |       SEPTEMBER      |" << endl;
	cout << "| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "+----------------------+----------------------+----------------------+" << endl;
	cout << "|        OCTOBER       |       NOVEMBER       |       DECEMBER       |" << endl;
	cout << "| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "|                      |                      |                      |" << endl;
	cout << "+----------------------+----------------------+----------------------+" << endl;
    }

    int firstdays[12], year;
    bool isleap;
};
//--------------------------------------------------------------------------------------------------
int main( int argc, char* argv[] )
{
    int y;
    calender cal;

    while( true )
    {
	system( "cls" );
	cout << "Enter the year( yyyy ) --- ( 0 to quit ): "; 
	cin >> y;
	if( !y ) return 0;

	cal.drawCalender( y );
	cout << endl << endl << endl << endl << endl << endl << endl << endl;

	system( "pause" );
    }
    return 0;
}
//--------------------------------------------------------------------------------------------------

Output:

+--------------------------------------------------------------------+
|                              [SNOOPY]                              |
|                                                                    |
|                             == 1969 ==                             |
+----------------------+----------------------+----------------------+
|       JANUARY        |       FEBRUARY       |         MARCH        |
| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |
|          01 02 03 04 |                   01 |                   01 |
| 05 06 07 08 09 10 11 | 02 03 04 05 06 07 08 | 02 03 04 05 06 07 08 |
| 12 13 14 15 16 17 18 | 09 10 11 12 13 14 15 | 09 10 11 12 13 14 15 |
| 19 20 21 22 23 24 25 | 16 17 18 19 20 21 22 | 16 17 18 19 20 21 22 |
| 26 27 28 29 30 31    | 23 24 25 26 27 28    | 23 24 25 26 27 28 29 |
|                      |                      | 30 31                |
+----------------------+----------------------+----------------------+
|        APRIL         |          MAY         |         JUNE         |
| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |
|       01 02 03 04 05 |             01 02 03 | 01 02 03 04 05 06 07 |
| 06 07 08 09 10 11 12 | 04 05 06 07 08 09 10 | 08 09 10 11 12 13 14 |
| 13 14 15 16 17 18 19 | 11 12 13 14 15 16 17 | 15 16 17 18 19 20 21 |
| 20 21 22 23 24 25 26 | 18 19 20 21 22 23 24 | 22 23 24 25 26 27 28 |
| 27 28 29 30          | 25 26 27 28 29 30 31 | 29 30                |
|                      |                      |                      |
+----------------------+----------------------+----------------------+
|         JULY         |        AUGUST        |       SEPTEMBER      |
| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |
|       01 02 03 04 05 |                01 02 |    01 02 03 04 05 06 |
| 06 07 08 09 10 11 12 | 03 04 05 06 07 08 09 | 07 08 09 10 11 12 13 |
| 13 14 15 16 17 18 19 | 10 11 12 13 14 15 16 | 14 15 16 17 18 19 20 |
| 20 21 22 23 24 25 26 | 17 18 19 20 21 22 23 | 21 22 23 24 25 26 27 |
| 27 28 29 30 31       | 24 25 26 27 28 29 30 | 28 29 30             |
|                      | 31                   |                      |
+----------------------+----------------------+----------------------+
|        OCTOBER       |       NOVEMBER       |       DECEMBER       |
| SU MO TU WE TH FR SA | SU MO TU WE TH FR SA | SU MO TU WE TH FR SA |
|          01 02 03 04 |                   01 |    01 02 03 04 05 06 |
| 05 06 07 08 09 10 11 | 02 03 04 05 06 07 08 | 07 08 09 10 11 12 13 |
| 12 13 14 15 16 17 18 | 09 10 11 12 13 14 15 | 14 15 16 17 18 19 20 |
| 19 20 21 22 23 24 25 | 16 17 18 19 20 21 22 | 21 22 23 24 25 26 27 |
| 26 27 28 29 30 31    | 23 24 25 26 27 28 29 | 28 29 30 31          |
|                      | 30                   |                      |
+----------------------+----------------------+----------------------+

Clojure

Translation of: Common Lisp

Java interop version.

Does not use any external dependencies to demonstrate the java interop.
Requires "join" from "clojure.string".
Written by Kyuvi.

(require '[clojure.string :only [join] :refer [join]])

(def day-row "Su Mo Tu We Th Fr Sa")

(def col-width (count day-row))

(defn month-to-word 
  "Translate a month from 0 to 11 into its word representation."
  [month]
  ((vec (.getMonths (new java.text.DateFormatSymbols))) month))

(defn month [date]
  (.get date (java.util.Calendar/MONTH))) 

(defn total-days-in-month [date]
  (.getActualMaximum date (java.util.Calendar/DAY_OF_MONTH)))

(defn first-weekday [date]
  (.get date (java.util.Calendar/DAY_OF_WEEK)))
         
(defn normal-date-string
  "Returns a formatted list of strings of the days of the month."
  [date]		 
  (map #(join " " %)
    (partition 7
      (concat
        (repeat (dec (first-weekday date)) "  ")
        (map #(format "%2s" %)
               (range 1 (inc (total-days-in-month date))))
        (repeat (- 42 (total-days-in-month date)
                      (dec (first-weekday date)) ) "  ")))))
         
		 
(defn oct-1582-string
  "Returns a formatted list of strings of the days of the month of October 1582."
  [date]
  (map #(join " " %)
    (partition 7
      (concat
         (repeat (dec (first-weekday date)) "  ")
         (map #(format "%2s" %)
                 (concat (range 1 5) 
                         (range 15 (inc (total-days-in-month date)))))
         (repeat (- 42 
                    (count (concat (range 1 5) 
		                   (range 15
                                          (inc (total-days-in-month date)))))
                    (dec (first-weekday date)) ) "  ")))))


(defn center-string 
  "Returns a string that is WIDTH long with STRING centered in it."
  [string width]
  (let [pad (- width (count string))
        lpad (quot pad 2) 
        rpad (- pad (quot pad 2))]
    (if (<= pad 0)
    string
    (str (apply str (repeat lpad " "))  ; remove vector
          string 
         (apply str (repeat rpad " "))))))


(defn calc-columns 
  "Calculates the number of columns given the width in CHARACTERS and the 
   MARGIN SIZE."
  [characters margin-size]
  (loop [cols 0 excess  characters ]
    (if (>= excess  col-width)
        (recur (inc cols) (- excess (+ margin-size col-width)))
         cols)))

(defn month-vector 
  "Returns a vector with the month name, day-row and days
   formatted for printing."
  [date]
  (vec (concat 
         (vector (center-string (month-to-word (month date)) col-width))
         (vector day-row)
 		 (if (and (= 1582 (.get date (java.util.Calendar/YEAR)))
		 		  (= 9 (month date)))
		     (oct-1582-string date)
                     (normal-date-string date)))))

(defn year-vector [date]
  "Returns a 2d vector of all the months in the year of DATE."
  (loop [m [] c (month date)]
    (if (= c 11 )
        (conj m (month-vector date)) 
        (recur (conj m (month-vector date))
               (do (.add date (java.util.Calendar/MONTH ) 1)
                   (month date))))))

(defn print-months
  "Prints the months to standard output with NCOLS and MARGIN."
  [ v ncols margin]
  (doseq [r (range (Math/ceil (/ 12 ncols)))]
    (do (doseq [i (range 8)]
          (do (doseq [c (range (* r ncols) (* (+ r 1) ncols))
		                :while (< c 12)]
              (printf (str (apply str (repeat margin " ")) "%s")
		      (get-in v [c i])))
          (println)))
        (println))))

(defn print-cal
  "(print-cal [year [width [margin]]])
   Prints out the calendar for a given YEAR with WIDTH characters wide and
   with MARGIN spaces between months."
  ([] 
     (print-cal 1969 80 2))
  ([year] 
     (print-cal year 80 2))
  ([year width]
   (print-cal year width 2))
  ([year width margin]
     (assert (>= width (count day-row)) "Width should be more than 20.")
     (assert (> margin 0) "Margin needs to be more than 0.")
     (let [date (new java.util.GregorianCalendar year 0 1)
           column-count (calc-columns width margin)
           total-size (+ (* column-count (count day-row))
                         (* (dec column-count) margin))]
       (println (center-string "[Snoopy Picture]" total-size))
       (println (center-string (str year) total-size))
       (println)
       (print-months (year-vector date) column-count margin))))


user=> (print-cal)
                        [Snoopy Picture]                        
                              1969                              

        January               February               March        
  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
            1  2  3  4                     1                     1
   5  6  7  8  9 10 11   2  3  4  5  6  7  8   2  3  4  5  6  7  8
  12 13 14 15 16 17 18   9 10 11 12 13 14 15   9 10 11 12 13 14 15
  19 20 21 22 23 24 25  16 17 18 19 20 21 22  16 17 18 19 20 21 22
  26 27 28 29 30 31     23 24 25 26 27 28     23 24 25 26 27 28 29
                                              30 31               

         April                  May                   June        
  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
         1  2  3  4  5               1  2  3   1  2  3  4  5  6  7
   6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14
  13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21
  20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28
  27 28 29 30           25 26 27 28 29 30 31  29 30               
                                                                  

          July                 August              September      
  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
         1  2  3  4  5                  1  2      1  2  3  4  5  6
   6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13
  13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20
  20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27
  27 28 29 30 31        24 25 26 27 28 29 30  28 29 30            
                        31                                        

        October               November              December      
  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
            1  2  3  4                     1      1  2  3  4  5  6
   5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13
  12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20
  19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27
  26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31         
                        30                                        

nil

COBOL

the program calls subroutine DATE2DOW to convert any YYYY-MM-DD to Day of Week (1=Sunday). the group names WS-CFGN and WS-CFGW may be moved to WS-CFG to use narrow or wide print line size respectively.

       IDENTIFICATION DIVISION.
       PROGRAM-ID.  CALEND.
       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       DATA DIVISION.
 
       WORKING-STORAGE SECTION.
       01  WS-DAY-NAMES-DEF.
         03 FILLER PIC X(09) VALUE 'SUNDAY   '.
         03 FILLER PIC X(09) VALUE 'MONDAY   '.
         03 FILLER PIC X(09) VALUE 'TUESDAY  '.
         03 FILLER PIC X(09) VALUE 'WEDNESDAY'.
         03 FILLER PIC X(09) VALUE 'THURSDAY '.
         03 FILLER PIC X(09) VALUE 'FRIDAY   '.
         03 FILLER PIC X(09) VALUE 'SATURDAY '.
       01  FILLER REDEFINES WS-DAY-NAMES-DEF.
         03  WS-DAY-NAME       PIC X(09) OCCURS 07 TIMES.
 
       01  WS-MTH-INFO-DEF.
         03 FILLER PIC X(11) VALUE 'JANUARY  31'.
         03 FILLER PIC X(11) VALUE 'FEBRUARY 28'.
         03 FILLER PIC X(11) VALUE 'MARCH    31'.
         03 FILLER PIC X(11) VALUE 'APRIL    30'.
         03 FILLER PIC X(11) VALUE 'MAY      31'.
         03 FILLER PIC X(11) VALUE 'JUNE     30'.
         03 FILLER PIC X(11) VALUE 'JULY     31'.
         03 FILLER PIC X(11) VALUE 'AUGUST   31'.
         03 FILLER PIC X(11) VALUE 'SEPTEMBER30'.
         03 FILLER PIC X(11) VALUE 'OCTOBER  31'.
         03 FILLER PIC X(11) VALUE 'NOVEMBER 30'.
         03 FILLER PIC X(11) VALUE 'DECEMBER 31'.
       01  FILLER REDEFINES WS-MTH-INFO-DEF.
         03  WS-MTH-INFO-TABLE OCCURS 12 TIMES.
           05  WS-MTH-INFO-NAME   PIC X(09).
           05  WS-MTH-INFO-DAYS   PIC 9(02).

       01  WS-MTH-AREA.
         03  WS-MTH-DD         PIC S99.
         03  WS-DAY1           PIC   9.
         03  WS-DAYS           PIC  99.
         03  WS-DD             PIC   9.
         03  WS-WK             PIC   9.
         03  WS-MM             PIC  99.
         03  WS-QQ             PIC  99.
 
         03  WS-MTH-MONTH  OCCURS 12 TIMES.
           05  WS-MTH-WEEK OCCURS 6 TIMES.
             07  WS-DAY-FLD      OCCURS 7 TIMES.
               09  WS-DAY        PIC ZZ.
       01  INPDATE-RECORD.
           05  INPD-YEAR          PIC 9(04).
           05  FILLER             PIC X(01).
           05  INPD-MONTH         PIC 9(02).
           05  FILLER             PIC X(01).
           05  INPD-DAY           PIC 9(02).
       01  WMS-DOW                PIC 9(01).
       01  WS-PRT                 PIC X(132).
       01  WS-COL                 PIC  9(03) VALUE 0.
       01  WS-PP                  PIC  9(03) VALUE 0.
       01  WS-CFGN.
         03  FILLER               PIC  9(03) VALUE  80.
         03  FILLER               PIC  9(02) VALUE  5.
         03  FILLER               PIC  9(01) VALUE  1.
         03  FILLER               PIC  9(02) VALUE  5.
         03  FILLER               PIC  9(01) VALUE  2.
       01  WS-CFGW.
         03  FILLER               PIC  9(03) VALUE 120.
         03  FILLER               PIC  9(02) VALUE 10.
         03  FILLER               PIC  9(01) VALUE  2.
         03  FILLER               PIC  9(02) VALUE 10.
         03  FILLER               PIC  9(01) VALUE  3.
       01  WS-CFG.
         03  WS-LS                PIC  9(03) VALUE 120.
         03  WS-LMAR              PIC  9(02) VALUE 10.
         03  WS-SPBD              PIC  9(01) VALUE  2.
         03  WS-SPBC              PIC  9(02) VALUE 10.
         03  WS-DNMW              PIC  9(01) VALUE  3.
       PROCEDURE DIVISION.
           MOVE '1969-01-01' TO INPDATE-RECORD
           MOVE WS-CFGN   TO WS-CFG
           IF  (FUNCTION MOD ( INPD-YEAR , 400 ) = 0
           OR  (FUNCTION MOD ( INPD-YEAR , 4   ) = 0
               AND
               FUNCTION MOD ( INPD-YEAR , 100 ) NOT = 0))
             MOVE 29         TO WS-MTH-INFO-DAYS (02)
           ELSE
             MOVE 28         TO WS-MTH-INFO-DAYS (02)
           END-IF
 
           PERFORM VARYING WS-MM FROM 1 BY +1
           UNTIL WS-MM > 12
           MOVE WS-MM TO INPD-MONTH
           CALL 'DATE2DOW' USING INPDATE-RECORD, WMS-DOW
           COMPUTE WS-MTH-DD = 1 - WMS-DOW
           COMPUTE WS-DAYS = WS-MTH-INFO-DAYS (INPD-MONTH)
           PERFORM VARYING WS-WK FROM 1 BY +1
           UNTIL WS-WK > 6
             PERFORM VARYING WS-DD FROM 1 BY +1
             UNTIL WS-DD > 7
               COMPUTE WS-MTH-DD = WS-MTH-DD + 1
               IF (WS-MTH-DD < 1)
               OR (WS-MTH-DD > WS-DAYS)
                 MOVE 0         TO WS-DAY (WS-MM, WS-WK, WS-DD)
               ELSE
                 MOVE WS-MTH-DD TO WS-DAY (WS-MM, WS-WK, WS-DD)
               END-IF
             END-PERFORM
           END-PERFORM
           END-PERFORM
 
           COMPUTE WS-MM = 0
           PERFORM VARYING WS-QQ FROM 1 BY +1
           UNTIL WS-QQ > 4
 
             INITIALIZE WS-PRT
             COMPUTE WS-PP = 1
             PERFORM VARYING WS-COL FROM 1 BY +1
             UNTIL WS-COL > 3
             COMPUTE WS-MM = 3 * (WS-QQ - 1) + WS-COL
 
               IF WS-COL = 1
                 COMPUTE WS-PP = WS-PP + WS-LMAR + 2 - WS-DNMW
               ELSE
                 COMPUTE WS-PP = WS-PP + WS-SPBC + 2 - WS-DNMW
               END-IF
               MOVE WS-MTH-INFO-NAME (WS-MM)
                             TO WS-PRT(WS-PP:9)
               COMPUTE WS-PP
               =       WS-PP + ( 2 * 7 + WS-SPBD * 6 + WS-SPBD - 1)
               -       4
               MOVE INPD-YEAR TO WS-PRT (WS-PP:4)
               COMPUTE WS-PP = WS-PP + 4
             END-PERFORM
             DISPLAY WS-PRT (1:WS-LS)
 
             INITIALIZE WS-PRT
             COMPUTE WS-PP = 1
             PERFORM VARYING WS-COL FROM 1 BY +1
             UNTIL WS-COL > 3
             COMPUTE WS-MM = 3 * (WS-QQ - 1) + WS-COL
 
               IF WS-COL = 1
                 COMPUTE WS-PP = WS-PP + WS-LMAR + 2 - WS-DNMW
               ELSE
                 COMPUTE WS-PP = WS-PP + WS-SPBC + 2 - WS-DNMW
               END-IF
               PERFORM VARYING WS-DD FROM 1 BY +1
               UNTIL WS-DD > 7
                 IF WS-DD > 1
                   COMPUTE WS-PP = WS-PP + WS-SPBD + 2 - WS-DNMW
                 END-IF
                 MOVE WS-DAY-NAME (WS-DD) (1:WS-DNMW)
                             TO WS-PRT (WS-PP:WS-DNMW)
                 COMPUTE WS-PP = WS-PP + WS-DNMW
               END-PERFORM
             END-PERFORM
             DISPLAY WS-PRT (1:WS-LS)
 
             PERFORM VARYING WS-WK FROM 1 BY +1
             UNTIL WS-WK > 6
               INITIALIZE WS-PRT
               COMPUTE WS-PP = 1
               PERFORM VARYING WS-COL FROM 1 BY +1
               UNTIL WS-COL > 3
               COMPUTE WS-MM = 3 * (WS-QQ - 1) + WS-COL
 
                 IF WS-COL = 1
                   COMPUTE WS-PP = WS-PP + WS-LMAR
                 ELSE
                   COMPUTE WS-PP = WS-PP + WS-SPBC
                 END-IF
                 PERFORM VARYING WS-DD FROM 1 BY +1
                 UNTIL WS-DD > 7
                   IF WS-DD > 1
                     COMPUTE WS-PP = WS-PP + WS-SPBD
                   END-IF
                   MOVE WS-DAY (WS-MM, WS-WK, WS-DD)
                               TO WS-PRT (WS-PP:2)
                   COMPUTE WS-PP = WS-PP + 2
                 END-PERFORM
               END-PERFORM
               DISPLAY WS-PRT (1:WS-LS)
             END-PERFORM
             DISPLAY ' '
           END-PERFORM
           GOBACK
           .
       END PROGRAM CALEND.
       IDENTIFICATION DIVISION.
       PROGRAM-ID.  DATE2DOW.
       ENVIRONMENT DIVISION.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WMS-WORK-AREA.
         03  WMS-YEAR       PIC 9(04).
         03  WMS-MONTH      PIC 9(02).
         03  WMS-CSYS       PIC 9(01) VALUE 1.
         03  WMS-SUM        pic 9(04).
       LINKAGE SECTION.
       01  INPDATE-RECORD.
           05  INPD-YEAR          PIC 9(04).
           05  FILLER             PIC X(01).
           05  INPD-MONTH         PIC 9(02).
           05  FILLER             PIC X(01).
           05  INPD-DAY           PIC 9(02).
       01  WMS-DOW                PIC 9(01).
       PROCEDURE DIVISION USING INPDATE-RECORD, WMS-DOW.
       1010-CONVERT-DATE-TO-DOW.
           IF INPD-MONTH < 3
               COMPUTE WMS-MONTH = INPD-MONTH + 12
               COMPUTE WMS-YEAR  = INPD-YEAR - 1
           ELSE
               COMPUTE WMS-MONTH = INPD-MONTH
               COMPUTE WMS-YEAR  = INPD-YEAR
           END-IF
           COMPUTE WMS-SUM  = 
                            ( INPD-DAY + 2 * WMS-MONTH + WMS-YEAR
                            + FUNCTION INTEGER (6 * (WMS-MONTH + 1) / 10)
                            + FUNCTION INTEGER ( WMS-YEAR / 4   )
                            - FUNCTION INTEGER ( WMS-YEAR / 100 )
                            + FUNCTION INTEGER ( WMS-YEAR / 400 )
                            + WMS-CSYS )
           COMPUTE WMS-DOW = FUNCTION MOD (WMS-SUM, 7) + 1
           GOBACK
           .
       END PROGRAM DATE2DOW.

Output (based on 80 character wide display)

     JANUARY         1969     FEBRUARY        1969     MARCH           1969
     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA
               1  2  3  4                        1                        1
      5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
     12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
     19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
     26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                       30 31               
                                                                           
     APRIL           1969     MAY             1969     JUNE            1969
     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA
            1  2  3  4  5                  1  2  3      1  2  3  4  5  6  7
      6  7  8  9 10 11 12      4  5  6  7  8  9 10      8  9 10 11 12 13 14
     13 14 15 16 17 18 19     11 12 13 14 15 16 17     15 16 17 18 19 20 21
     20 21 22 23 24 25 26     18 19 20 21 22 23 24     22 23 24 25 26 27 28
     27 28 29 30              25 26 27 28 29 30 31     29 30               
                                                                           
                                                                           
     JULY            1969     AUGUST          1969     SEPTEMBER       1969
     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA
            1  2  3  4  5                     1  2         1  2  3  4  5  6
      6  7  8  9 10 11 12      3  4  5  6  7  8  9      7  8  9 10 11 12 13
     13 14 15 16 17 18 19     10 11 12 13 14 15 16     14 15 16 17 18 19 20
     20 21 22 23 24 25 26     17 18 19 20 21 22 23     21 22 23 24 25 26 27
     27 28 29 30 31           24 25 26 27 28 29 30     28 29 30            
                              31                                           
                                                                           
     OCTOBER         1969     NOVEMBER        1969     DECEMBER        1969
     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA     SU MO TU WE TH FR SA
               1  2  3  4                        1         1  2  3  4  5  6
      5  6  7  8  9 10 11      2  3  4  5  6  7  8      7  8  9 10 11 12 13
     12 13 14 15 16 17 18      9 10 11 12 13 14 15     14 15 16 17 18 19 20
     19 20 21 22 23 24 25     16 17 18 19 20 21 22     21 22 23 24 25 26 27
     26 27 28 29 30 31        23 24 25 26 27 28 29     28 29 30 31         
                              30                                           

Common Lisp

Depends on quicklisp.

(ql:quickload '(date-calc))

(defparameter *day-row* "Su Mo Tu We Th Fr Sa")
(defparameter *calendar-margin* 3)

(defun month-to-word (month)
  "Translate a MONTH from 1 to 12 into its word representation."
  (svref #("January" "February" "March" "April"
           "May" "June" "July" "August"
           "September" "October" "November" "December")
         (1- month)))

(defun month-strings (year month)
  "Collect all of the strings that make up a calendar for a given
MONTH and YEAR."
  `(,(date-calc:center (month-to-word month) (length *day-row*))
     ,*day-row*
     ;; We can assume that a month calendar will always fit into a 7 by 6 block
     ;; of values. This makes it easy to format the resulting strings.
     ,@ (let ((days (make-array (* 7 6) :initial-element nil)))
          (loop :for i :from (date-calc:day-of-week year month 1)
             :for day :from 1 :to (date-calc:days-in-month year month)
             :do (setf (aref days i) day))
          (loop :for i :from 0 :to 5
             :collect
             (format nil "~{~:[  ~;~2,d~]~^ ~}"
                     (loop :for day :across (subseq days (* i 7) (+ 7 (* i 7)))
                        :append (if day (list day day) (list day))))))))

(defun calc-columns (characters margin-size)
  "Calculate the number of columns given the number of CHARACTERS per
column and the MARGIN-SIZE between them."
  (multiple-value-bind (cols excess)
      (truncate characters (+ margin-size (length *day-row*)))
    (incf excess margin-size)
    (if (>= excess (length *day-row*))
        (1+ cols)
        cols)))

(defun take (n list)
  "Take the first N elements of a LIST."
  (loop :repeat n :for x :in list :collect x))

(defun drop (n list)
  "Drop the first N elements of a LIST."
  (cond ((or (<= n 0) (null list)) list)
        (t (drop (1- n) (cdr list)))))

(defun chunks-of (n list)
  "Split the LIST into chunks of size N."
  (assert (> n 0))
  (loop :for x := list :then (drop n x)
     :while x
     :collect (take n x)))

(defun print-calendar (year &key (characters 80) (margin-size 3))
  "Print out the calendar for a given YEAR, optionally specifying
a width limit in CHARACTERS and MARGIN-SIZE between months."
  (assert (>= characters (length *day-row*)))
  (assert (>= margin-size 0))
  (let* ((calendars (loop :for month :from 1 :to 12
                       :collect (month-strings year month)))
         (column-count (calc-columns characters margin-size))
         (total-size (+ (* column-count (length *day-row*))
                        (* (1- column-count) margin-size)))
         (format-string (concatenate 'string
                                     "~{~a~^~" (write-to-string margin-size) ",0@T~}~%")))
    (format t "~a~%~a~%~%"
            (date-calc:center "[Snoopy]" total-size)
            (date-calc:center (write-to-string year) total-size))
    (loop :for row :in (chunks-of column-count calendars)
       :do (apply 'mapcar
                  (lambda (&rest heads)
                    (format t format-string heads))
                  row))))
Output:
CL-USER> (print-calendar 1969)
                             [Snoopy]                             
                               1969                               

      January                February                March        
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
          1  2  3  4                      1                      1
 5  6  7  8  9 10 11    2  3  4  5  6  7  8    2  3  4  5  6  7  8
12 13 14 15 16 17 18    9 10 11 12 13 14 15    9 10 11 12 13 14 15
19 20 21 22 23 24 25   16 17 18 19 20 21 22   16 17 18 19 20 21 22
26 27 28 29 30 31      23 24 25 26 27 28      23 24 25 26 27 28 29
                                              30 31               
       April                   May                    June        
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
       1  2  3  4  5                1  2  3                       
 6  7  8  9 10 11 12    4  5  6  7  8  9 10    1  2  3  4  5  6  7
13 14 15 16 17 18 19   11 12 13 14 15 16 17    8  9 10 11 12 13 14
20 21 22 23 24 25 26   18 19 20 21 22 23 24   15 16 17 18 19 20 21
27 28 29 30            25 26 27 28 29 30 31   22 23 24 25 26 27 28
                                              29 30               
        July                  August               September      
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
       1  2  3  4  5                   1  2       1  2  3  4  5  6
 6  7  8  9 10 11 12    3  4  5  6  7  8  9    7  8  9 10 11 12 13
13 14 15 16 17 18 19   10 11 12 13 14 15 16   14 15 16 17 18 19 20
20 21 22 23 24 25 26   17 18 19 20 21 22 23   21 22 23 24 25 26 27
27 28 29 30 31         24 25 26 27 28 29 30   28 29 30            
                       31                                         
      October                November               December      
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
          1  2  3  4                      1       1  2  3  4  5  6
 5  6  7  8  9 10 11    2  3  4  5  6  7  8    7  8  9 10 11 12 13
12 13 14 15 16 17 18    9 10 11 12 13 14 15   14 15 16 17 18 19 20
19 20 21 22 23 24 25   16 17 18 19 20 21 22   21 22 23 24 25 26 27
26 27 28 29 30 31      23 24 25 26 27 28 29   28 29 30 31         
                       30                                         
NIL

D

import std.stdio, std.datetime, std.string, std.conv;

void printCalendar(in uint year, in uint nCols)
in {
    assert(nCols > 0 && nCols <= 12);
} body {
    immutable rows = 12 / nCols + (12 % nCols != 0);
    auto date = Date(year, 1, 1);
    int offs = date.dayOfWeek;
    const months = "January February March April May June
        July August September October November December".split;

    string[8][12] mons;
    foreach (immutable m; 0 .. 12) {
        mons[m][0] = months[m].center(21);
        mons[m][1] = " Su Mo Tu We Th Fr Sa";
        immutable dim = date.daysInMonth;
        foreach (immutable d; 1 .. 43) {
            immutable day = d > offs && d <= offs + dim;
            immutable str = day ? format(" %2s", d-offs) : "   ";
            mons[m][2 + (d - 1) / 7] ~= str;
        }
        offs = (offs + dim) % 7;
        date.add!"months"(1);
    }

    "[Snoopy Picture]".center(nCols * 24 + 4).writeln;
    writeln(year.text.center(nCols * 24 + 4), "\n");
    foreach (immutable r; 0 .. rows) {
        string[8] s;
        foreach (immutable c; 0 .. nCols) {
            if (r * nCols + c > 11)
                break;
            foreach (immutable i, line; mons[r * nCols + c])
                s[i] ~= format("   %s", line);
        }
        writefln("%-(%s\n%)\n", s);
    }
}

void main() {
    printCalendar(1969, 3);
}
                              [Snoopy Picture]
                                    1969

          January                February                  March
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
              1  2  3  4                       1                       1
     5  6  7  8  9 10 11     2  3  4  5  6  7  8     2  3  4  5  6  7  8
    12 13 14 15 16 17 18     9 10 11 12 13 14 15     9 10 11 12 13 14 15
    19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22
    26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29
                                                    30 31

           April                    May                    June
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
           1  2  3  4  5                 1  2  3     1  2  3  4  5  6  7
     6  7  8  9 10 11 12     4  5  6  7  8  9 10     8  9 10 11 12 13 14
    13 14 15 16 17 18 19    11 12 13 14 15 16 17    15 16 17 18 19 20 21
    20 21 22 23 24 25 26    18 19 20 21 22 23 24    22 23 24 25 26 27 28
    27 28 29 30             25 26 27 28 29 30 31    29 30


           July                   August                 September
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
           1  2  3  4  5                    1  2        1  2  3  4  5  6
     6  7  8  9 10 11 12     3  4  5  6  7  8  9     7  8  9 10 11 12 13
    13 14 15 16 17 18 19    10 11 12 13 14 15 16    14 15 16 17 18 19 20
    20 21 22 23 24 25 26    17 18 19 20 21 22 23    21 22 23 24 25 26 27
    27 28 29 30 31          24 25 26 27 28 29 30    28 29 30
                            31

          October                November                December
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
              1  2  3  4                       1        1  2  3  4  5  6
     5  6  7  8  9 10 11     2  3  4  5  6  7  8     7  8  9 10 11 12 13
    12 13 14 15 16 17 18     9 10 11 12 13 14 15    14 15 16 17 18 19 20
    19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27
    26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31
                            30

Delphi

Translation of: D
program Calendar;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.DateUtils;

function Center(s: string; width: Integer): string;
var
  side: Integer;
begin
  if s.Length >= width then
    exit(s);
  side := (width - s.Length) div 2;
  Result := s + string.Create(' ', side);
  Result := string.Create(' ', width - Result.Length) + Result;
end;

procedure PrintCalendar(year, nCols: word; local: string = 'en-US');
var
  fmt: TFormatSettings;
begin
  if (nCols <= 0) or (nCols > 12) then
    exit;
  fmt := TFormatSettings.Create(local);
  var rows := 12 div nCols + ord(12 mod nCols <> 0);
  var date := EncodeDate(year, 1, 1);
  var offs := DayOfTheWeek(date);

  var months: TArray<string>;
  setlength(months, 12);
  for var i := 1 to 12 do
    months[i - 1] := fmt.LongMonthNames[i];

  var sWeek := '';
  for var i := 1 to 7 do
    sWeek := sWeek + ' ' + copy(fmt.ShortDayNames[i], 1, 2);

  var mons: TArray<TArray<string>>;
  SetLength(mons, 12, 8);
  for var m := 0 to 11 do
  begin
    mons[m, 0] := Center(months[m], 21);
    mons[m, 1] := sWeek;
    var dim := DaysInMonth(date);
    for var d := 1 to 43 do
    begin
      var day := (d > offs) and (d <= offs + dim);
      var str := '   ';
      if day then
        str := format(' %2d', [d - offs]);
      mons[m, 2 + (d - 1) div 7] := mons[m, 2 + (d - 1) div 7] + str;
    end;
    offs := (offs + dim) mod 7;
    date := IncMonth(date, 1);
  end;
  writeln(Center('[Snoopy Picture]', nCols * 24 + 4));
  Writeln(Center(year.ToString, nCols * 24 + 4));
  writeln;

  for var r := 0 to rows - 1 do
  begin
    var s: TArray<string>;
    SetLength(s, 8);
    for var c := 0 to nCols - 1 do
    begin
      if r * nCols + c > 11 then
        Break;
      for var i := 0 to High(mons[r * nCols + c]) do
      begin
        var line := mons[r * nCols + c, i];
        s[i] := s[i] + '   ' + line;
      end;
    end;

    for var ss in s do
    begin
      writeln(ss, ' ');
    end;
    writeln;
  end;

end;

begin
  printCalendar(1969, 4);
  readln;
end.
Output:
                                          [Snoopy Picture]
                                                1969

          January                 February                 March                   April
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
              1  2  3  4                       1                       1           1  2  3  4  5
     5  6  7  8  9 10 11     2  3  4  5  6  7  8     2  3  4  5  6  7  8     6  7  8  9 10 11 12
    12 13 14 15 16 17 18     9 10 11 12 13 14 15     9 10 11 12 13 14 15    13 14 15 16 17 18 19
    19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22    20 21 22 23 24 25 26
    26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29    27 28 29 30
                                                    30 31

            May                     June                    July                   August
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                 1  2  3     1  2  3  4  5  6  7           1  2  3  4  5                    1  2
     4  5  6  7  8  9 10     8  9 10 11 12 13 14     6  7  8  9 10 11 12     3  4  5  6  7  8  9
    11 12 13 14 15 16 17    15 16 17 18 19 20 21    13 14 15 16 17 18 19    10 11 12 13 14 15 16
    18 19 20 21 22 23 24    22 23 24 25 26 27 28    20 21 22 23 24 25 26    17 18 19 20 21 22 23
    25 26 27 28 29 30 31    29 30                   27 28 29 30 31          24 25 26 27 28 29 30
                                                                            31

         September                October                 November                December
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
        1  2  3  4  5  6              1  2  3  4                       1        1  2  3  4  5  6
     7  8  9 10 11 12 13     5  6  7  8  9 10 11     2  3  4  5  6  7  8     7  8  9 10 11 12 13
    14 15 16 17 18 19 20    12 13 14 15 16 17 18     9 10 11 12 13 14 15    14 15 16 17 18 19 20
    21 22 23 24 25 26 27    19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27
    28 29 30                26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31
                                                    30

EasyLang

Translation of: AWK
year = 1969
# year = number substr timestr systime 1 4
#
wkdays$ = "Su Mo Tu We Th Fr Sa"
pagewide = 80
blank$ = ""
month$[] = [ "  January " " February " "   March  " "   April  " "   May    " "  June    " "   July   " "  August  " " September" " October  " " November " " December " ]
days[] = [ 31 28 31 30 31 30 31 31 30 31 30 31 ]
#
func$ center txt$ .
   h$ = substr blank$ 1 ((pagewide - len txt$) / 2)
   return h$ & txt$ & h$
.
func$ makewk fst lst day .
   for i to day - 1
      wstr$ &= "   "
   .
   for i = fst to lst
      i$ = i
      if i <= 9
         i$ = " " & i
      .
      wstr$ &= i$ & " "
   .
   return substr wstr$ & blank$ 1 20
.
proc dow y . ndow leap .
   leap = 0
   if y mod 4 = 0
      leap = 1
   .
   if y mod 100 = 0
      leap = 0
   .
   if y mod 400 = 0
      leap = 1
   .
   ndow = y * 365 + y div 4 - y div 100 + y div 400 + 1
   ndow = (ndow - leap) mod1 7
.
len lin$[] 8
proc prmonth nmonth newdow monsize . .
   lin$[1] &= "     " & month$[nmonth] & "       "
   lin$[2] &= wkdays$ & "  "
   lin$[3] &= makewk 1 (8 - newdow) newdow & "  "
   for i = 4 to 7
      lin$[i] &= makewk (9 + h - newdow) lower monsize (15 + h - newdow) 1 & "  "
      h += 7
   .
   lin$[8] &= makewk (37 - newdow) monsize 1 & "  "
   if len lin$[3] + 22 > pagewide
      for i to 8
         print center lin$[i]
         lin$[i] = ""
      .
   .
.
for i to pagewide
   blank$ &= " "
.
dow year newdow leap
print center "[ picture of Snoopy goes here ]"
print center year
for i = 1 to 12
   monsize = days[i]
   if i = 2 and leap = 1
      monsize = 29
   .
   prmonth i newdow monsize
   newdow = (monsize + newdow) mod1 7
.

F#

let getCalendar year =
    let day_of_week month year =
        let t =  [|0; 3; 2; 5; 0; 3; 5; 1; 4; 6; 2; 4|]
        let y = if month < 3 then year - 1 else year
        let m = month
        let d = 1
        (y + y / 4 - y / 100 + y / 400 + t.[m - 1] + d) % 7
        //0 = Sunday, 1 = Monday, ...

    let last_day_of_month month year =
        match month with
        | 2 -> if (0 = year % 4 && (0 = year % 400 || 0 <> year % 100)) then 29 else 28
        | 4 | 6 | 9 | 11 -> 30
        | _ -> 31

    let get_month_calendar year month =
        let min (x: int, y: int) = if x < y then x else y
        let ld = last_day_of_month month year
        let dw = 7 - (day_of_week month year)
        [|[|1..dw|];
          [|dw + 1..dw + 7|];
          [|dw + 8..dw + 14|]; 
          [|dw + 15..dw + 21|]; 
          [|dw + 22..min(ld, dw + 28)|]; 
          [|min(ld + 1, dw + 29)..ld|]|]
    
    let sb_fold (f:System.Text.StringBuilder -> 'a -> System.Text.StringBuilder) (sb:System.Text.StringBuilder) (xs:'a array)  =
        for x in xs do (f sb  x) |> ignore
        sb

    let sb_append (text:string) (sb:System.Text.StringBuilder) = sb.Append(text)

    let sb_appendln sb = sb |> sb_append "\n" |> ignore

    let sb_fold_in_range a b f sb = [|a..b|] |> sb_fold f sb |> ignore

    let mask_builder mask = Printf.StringFormat<string -> string>(mask)
    let center n (s:string) =
        let l = (n - s.Length) / 2 + s.Length
        let f n s = sprintf (mask_builder ("%" + (n.ToString()) + "s")) s
        (f l s) + (f (n - l) "")
    let left n (s:string)  = sprintf (mask_builder ("%-" + (n.ToString()) + "s")) s
    let right n (s:string) = sprintf (mask_builder ("%" + (n.ToString()) + "s")) s

    let array2string xs = 
        let ys = xs |> Array.map (fun x -> sprintf "%2d " x)
        let sb = ys |> sb_fold (fun sb y -> sb.Append(y)) (new System.Text.StringBuilder())
        sb.ToString()

    let xsss = 
        let m = get_month_calendar year
        [|1..12|] |> Array.map (fun i -> m i)

    let months = [|"January"; "February"; "March"; "April"; "May"; "June"; "July"; "August"; "September"; "October"; "November"; "December"|]

    let sb = new System.Text.StringBuilder()    
    sb |> sb_append "\n" |> sb_append (center 74 (year.ToString())) |> sb_appendln    
    for i in 0..3..9 do
      sb |> sb_appendln
      sb |> sb_fold_in_range i (i + 2) (fun sb i -> sb |> sb_append (center 21 months.[i]) |> sb_append "      ")      
      sb |> sb_appendln
      sb |> sb_fold_in_range i (i + 2) (fun sb i -> sb |> sb_append "Su Mo Tu We Th Fr Sa " |> sb_append "      ")      
      sb |> sb_appendln
      sb |> sb_fold_in_range i (i + 2) (fun sb i -> sb |> sb_append (right 21 (array2string (xsss.[i].[0]))) |> sb_append "      ") 
      sb |> sb_appendln
      for j = 1 to 5 do       
        sb |> sb_fold_in_range i (i + 2) (fun sb i -> sb |> sb_append (left 21 (array2string (xsss.[i].[j]))) |> sb_append "      ")        
        sb |> sb_appendln
    sb.ToString()

let printCalendar year = getCalendar year
Output:
> printCalendar 1969;;
val it : string =
  "
                                   1969

       January                   February                     March
Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
          1  2  3  4                          1                          1
 5  6  7  8  9 10 11        2  3  4  5  6  7  8        2  3  4  5  6  7  8
12 13 14 15 16 17 18        9 10 11 12 13 14 15        9 10 11 12 13 14 15
19 20 21 22 23 24 25       16 17 18 19 20 21 22       16 17 18 19 20 21 22
26 27 28 29 30 31          23 24 25 26 27 28          23 24 25 26 27 28 29
                                                      30 31

        April                       May                       June
Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
       1  2  3  4  5                    1  2  3        1  2  3  4  5  6  7
 6  7  8  9 10 11 12        4  5  6  7  8  9 10        8  9 10 11 12 13 14
13 14 15 16 17 18 19       11 12 13 14 15 16 17       15 16 17 18 19 20 21
20 21 22 23 24 25 26       18 19 20 21 22 23 24       22 23 24 25 26 27 28
27 28 29 30                25 26 27 28 29 30 31       29 30


        July                      August                    September
Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
       1  2  3  4  5                       1  2           1  2  3  4  5  6
 6  7  8  9 10 11 12        3  4  5  6  7  8  9        7  8  9 10 11 12 13
13 14 15 16 17 18 19       10 11 12 13 14 15 16       14 15 16 17 18 19 20
20 21 22 23 24 25 26       17 18 19 20 21 22 23       21 22 23 24 25 26 27
27 28 29 30 31             24 25 26 27 28 29 30       28 29 30
                           31

       October                   November                   December
Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa
          1  2  3  4                          1           1  2  3  4  5  6
 5  6  7  8  9 10 11        2  3  4  5  6  7  8        7  8  9 10 11 12 13
12 13 14 15 16 17 18        9 10 11 12 13 14 15       14 15 16 17 18 19 20
19 20 21 22 23 24 25       16 17 18 19 20 21 22       21 22 23 24 25 26 27
26 27 28 29 30 31          23 24 25 26 27 28 29       28 29 30 31
                           30
"

Factor

USING: arrays calendar.format grouping io.streams.string kernel
math.ranges prettyprint sequences sequences.interleaved ;
IN: rosetta-code.calendar

: calendar ( year -- )
    12 [1,b] [ 2array [ month. ] with-string-writer ] with map
    3 <groups> [ "   " <interleaved> ] map 5 " " <repetition>
    <interleaved> simple-table. ;

: calendar-demo ( -- ) 1969 calendar ;

MAIN: calendar-demo
Output:
January 1969             February 1969            March 1969
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
          1  2  3  4                        1                        1
 5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                  30 31 
                                                   
April 1969               May 1969                 June 1969
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2  3      1  2  3  4  5  6  7
 6  7  8  9 10 11 12      4  5  6  7  8  9 10      8  9 10 11 12 13 14
13 14 15 16 17 18 19     11 12 13 14 15 16 17     15 16 17 18 19 20 21
20 21 22 23 24 25 26     18 19 20 21 22 23 24     22 23 24 25 26 27 28
27 28 29 30              25 26 27 28 29 30 31     29 30 
                                                  
                                                   
July 1969                August 1969              September 1969
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
       1  2  3  4  5                     1  2         1  2  3  4  5  6
 6  7  8  9 10 11 12      3  4  5  6  7  8  9      7  8  9 10 11 12 13
13 14 15 16 17 18 19     10 11 12 13 14 15 16     14 15 16 17 18 19 20
20 21 22 23 24 25 26     17 18 19 20 21 22 23     21 22 23 24 25 26 27
27 28 29 30 31           24 25 26 27 28 29 30     28 29 30 
                         31                       
                                                   
October 1969             November 1969            December 1969
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
          1  2  3  4                        1         1  2  3  4  5  6
 5  6  7  8  9 10 11      2  3  4  5  6  7  8      7  8  9 10 11 12 13
12 13 14 15 16 17 18      9 10 11 12 13 14 15     14 15 16 17 18 19 20
19 20 21 22 23 24 25     16 17 18 19 20 21 22     21 22 23 24 25 26 27
26 27 28 29 30 31        23 24 25 26 27 28 29     28 29 30 31 
                         30                       

Forth

: weekday ( d m y -- u )
   over 3 < if swap 12 + swap 1- then
   dup 4 / over 100 / - over 400 / + + swap 1+ 13 * 5 / + + 2 - 7 mod ;
 
: mdays ( m y -- msize mday )
   over 12 = if 31 1 2swap weekday negate exit then
   2>r 1 2r@ weekday 1 2r> swap 1+ swap weekday over -
   7 + 7 mod 28 + swap negate ;
 
: .week ( msize mday -- msize mday' )
   7 0 do dup 0< if 1+ 3 spaces else
   2dup > if 1+ dup 2 .r space else 3 spaces then then loop ;
 
: .3months ( y m -- )
   3 0 do ." Mo Tu We Th Fr Sa Su   " loop cr
   3 over + swap do i over mdays rot loop drop
   6 0 do 2rot .week 2 spaces 2rot .week 2 spaces 2rot .week cr loop
   2drop 2drop 2drop ;

: cal ( y -- )
   30 spaces ." [Snoopy]" cr
   32 spaces dup . cr
   ."       January                February                March" cr
   dup 1 .3months
   ."        April                   May                    June" cr
   dup 4 .3months
   ."         July                  August                September" cr
   dup 7 .3months
   ."         October               November               December" cr
   10 .3months ;

1969 cal
Output:
                              [Snoopy]
                                1969 
      January                February                March
Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   
       1  2  3  4  5                   1  2                   1  2 
 6  7  8  9 10 11 12    3  4  5  6  7  8  9    3  4  5  6  7  8  9 
13 14 15 16 17 18 19   10 11 12 13 14 15 16   10 11 12 13 14 15 16 
20 21 22 23 24 25 26   17 18 19 20 21 22 23   17 18 19 20 21 22 23 
27 28 29 30 31         24 25 26 27 28         24 25 26 27 28 29 30 
                                              31                   
       April                   May                    June
Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   
    1  2  3  4  5  6             1  2  3  4                      1 
 7  8  9 10 11 12 13    5  6  7  8  9 10 11    2  3  4  5  6  7  8 
14 15 16 17 18 19 20   12 13 14 15 16 17 18    9 10 11 12 13 14 15 
21 22 23 24 25 26 27   19 20 21 22 23 24 25   16 17 18 19 20 21 22 
28 29 30               26 27 28 29 30 31      23 24 25 26 27 28 29 
                                              30
        July                  August                September
Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   
    1  2  3  4  5  6                1  2  3    1  2  3  4  5  6  7 
 7  8  9 10 11 12 13    4  5  6  7  8  9 10    8  9 10 11 12 13 14 
14 15 16 17 18 19 20   11 12 13 14 15 16 17   15 16 17 18 19 20 21 
21 22 23 24 25 26 27   18 19 20 21 22 23 24   22 23 24 25 26 27 28 
28 29 30 31            25 26 27 28 29 30 31   29 30                
                                                                   
        October               November               December
Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   Mo Tu We Th Fr Sa Su   
       1  2  3  4  5                   1  2    1  2  3  4  5  6  7 
 6  7  8  9 10 11 12    3  4  5  6  7  8  9    8  9 10 11 12 13 14 
13 14 15 16 17 18 19   10 11 12 13 14 15 16   15 16 17 18 19 20 21 
20 21 22 23 24 25 26   17 18 19 20 21 22 23   22 23 24 25 26 27 28 
27 28 29 30 31         24 25 26 27 28 29 30   29 30 31

Fortran

Already having a routine to produce a calendar simplified matters. However, it added to each row of days a row of annotations for those days (Xmas, etc. and also +2hh and -2hh for the days with two half-hour changes in length due to daylight saving: NZ also had daylight saving with changes of one half-hour) which meant that the field allowance was always four. With the annotations abandoned, this could be reduced to three, and, the first day column on a line does not need a leading space. Since the method employed variables for the layout, it could easily be twiddled to have three months per line (thus fitting into a line length of 80) or six (using most of a line length of 132) and so it became a matter of pulling together the needed routines from various places.

       MODULE DATEGNASH	!Assorted vexations. Time and calendar games, with local flavourings added.

       TYPE DateBag		!Pack three parts into one.
        INTEGER DAY,MONTH,YEAR	!The usual suspects.
       END TYPE DateBag		!Simple enough.

       CHARACTER*9 MONTHNAME(12),DAYNAME(0:6)	!Re-interpretations.
       PARAMETER (MONTHNAME = (/"January","February","March","April",
     1  "May","June","July","August","September","October","November",
     2  "December"/))
       PARAMETER (DAYNAME = (/"Sunday","Monday","Tuesday","Wednesday",
     1  "Thursday","Friday","Saturday"/))	!Index this array with DayNum mod 7.
       CHARACTER*3 MTHNAME(12)		!The standard abbreviations.
       PARAMETER (MTHNAME = (/"JAN","FEB","MAR","APR","MAY","JUN",
     1  "JUL","AUG","SEP","OCT","NOV","DEC"/))

       INTEGER*4 JDAYSHIFT		!INTEGER*2 just isn't enough.
       PARAMETER (JDAYSHIFT = 2415020)	!Thus shall 31/12/1899 give 0, a Sunday, via DAYNUM.
       CONTAINS
       INTEGER FUNCTION LSTNB(TEXT)  !Sigh. Last Not Blank.
Concocted yet again by R.N.McLean (whom God preserve) December MM.
Code checking reveals that the Compaq compiler generates a copy of the string and then finds the length of that when using the latter-day intrinsic LEN_TRIM. Madness!
Can't   DO WHILE (L.GT.0 .AND. TEXT(L:L).LE.' ')	!Control chars. regarded as spaces.
Curse the morons who think it good that the compiler MIGHT evaluate logical expressions fully.
Crude GO TO rather than a DO-loop, because compilers use a loop counter as well as updating the index variable.
Comparison runs of GNASH showed a saving of ~3% in its mass-data reading through the avoidance of DO in LSTNB alone.
Crappy code for character comparison of varying lengths is avoided by using ICHAR which is for single characters only.
Checking the indexing of CHARACTER variables for bounds evoked astounding stupidities, such as calculating the length of TEXT(L:L) by subtracting L from L!
Comparison runs of GNASH showed a saving of ~25-30% in its mass data scanning for this, involving all its two-dozen or so single-character comparisons, not just in LSTNB.
        CHARACTER*(*),INTENT(IN):: TEXT	!The bumf. If there must be copy-in, at least there need not be copy back.
        INTEGER L		!The length of the bumf.
         L = LEN(TEXT)		!So, what is it?
    1    IF (L.LE.0) GO TO 2	!Are we there yet?
         IF (ICHAR(TEXT(L:L)).GT.ICHAR(" ")) GO TO 2	!Control chars are regarded as spaces also.
         L = L - 1		!Step back one.
         GO TO 1		!And try again.
    2    LSTNB = L		!The last non-blank, possibly zero.
        RETURN			!Unsafe to use LSTNB as a variable.
       END FUNCTION LSTNB	!Compilers can bungle it.
      CHARACTER*2 FUNCTION I2FMT(N)	!These are all the same.
       INTEGER*4 N			!But, the compiler doesn't offer generalisations.
        IF (N.LT.0) THEN	!Negative numbers cop a sign.
          IF (N.LT.-9) THEN	!But there's not much room left.
            I2FMT = "-!"	!So this means 'overflow'.
           ELSE			!Otherwise, room for one negative digit.
            I2FMT = "-"//CHAR(ICHAR("0") - N)	!Thus. Presume adjacent character codes, etc.
          END IF		!So much for negative numbers.
        ELSE IF (N.LT.10) THEN	!Single digit positive?
          I2FMT = " " //CHAR(ICHAR("0") + N)	!Yes. This.
        ELSE IF (N.LT.100) THEN	!Two digit positive?
          I2FMT = CHAR(N/10      + ICHAR("0"))	!Yes.
     1           //CHAR(MOD(N,10) + ICHAR("0")) !These.
        ELSE			!Otherwise,
          I2FMT = "+!" 	!Positive overflow.
        END IF			!So much for that.
      END FUNCTION I2FMT	!No WRITE and FORMAT unlimbering.
      CHARACTER*8 FUNCTION I8FMT(N)	!Oh for proper strings.
       INTEGER*4 N
       CHARACTER*8 HIC
        WRITE (HIC,1) N
    1   FORMAT (I8)
        I8FMT = HIC
      END FUNCTION I8FMT

      SUBROUTINE SAY(OUT,TEXT)	!Gutted version that maintains no file logging output, etc.
       INTEGER OUT
       CHARACTER*(*) TEXT
        WRITE (6,1) TEXT(1:LSTNB(TEXT))
    1   FORMAT (A)
      END SUBROUTINE SAY

       INTEGER*4 FUNCTION DAYNUM(YY,M,D)	!Computes (JDayN - JDayShift), not JDayN.
C   Conversion from a Gregorian calendar date to a Julian day number, JDayN.
C   Valid for any Gregorian calendar date producing a Julian day number
C greater than zero, though remember that the Gregorian calendar
C was not used before y1582m10d15 and often, not after that either.
C thus in England (et al) when Wednesday 2'nd September 1752 (Julian style)
C was followed by Thursday the 14'th, occasioning the Eleven Day riots
C because creditors demanded a full month's payment instead of 19/30'ths.
C   The zero of the Julian day number corresponds to the first of January
C 4713BC on the *Julian* calendar's naming scheme, as extended backwards
C with current usage into epochs when it did not exist: the proleptic Julian calendar.
c This function employs the naming scheme of the *Gregorian* calendar,
c and if extended backwards into epochs when it did not exist (thus the
c proleptic Gregorian calendar) it would compute a zero for y-4713m11d24 *if*
c it is supposed there was a year zero between 1BC and 1AD (as is convenient
c for modern mathematics and astronomers and their simple calculations), *but*
c 1BC immediately preceeds 1AD without any year zero in between (and is a leap year)
c thus the adjustment below so that the date is y-4714m11d24 or 4714BCm11d24,
c not that this name was in use at the time...
c   Although the Julian calendar (introduced by himself in what we would call 45BC,
c which was what the Romans occasionally called 709AUC) was provoked by the
c "years of confusion" resulting from arbitrary application of the rules
c for the existing Roman calendar, other confusions remain unresolved,
c so precise dating remains uncertain despite apparently precise specifications
c (and much later, Dennis the Short chose wrongly for the birth of Christ)
c and the Roman practice of inclusive reckoning meant that every four years
c was interpreted as every third (by our exclusive reckoning) so that the
c leap years were not as we now interpret them. This was resolved by Augustus
c but exactly when (and what date name is assigned) and whose writings used
c which system at the time of writing is a matter of more confusion,
c and this has continued for centuries.
C   Accordingly, although an algorithm may give a regular sequence of date names,
c that does not mean that those date names were used at the time even if the
c calendar existed then, because the interpretation of the algorithm varied.
c This in turn means that a date given as being on the Julian calendar
c prior to about 10AD is not as definite as it may appear and its alignment
c with the astronomical day number is uncertain even though the calculation
c is quite definite.
c
C   Computationally, year 1 is preceded by year 0, in a smooth progression.
C But there was never a year zero despite what astronomers like to say,
C so the formula's year 0 corresponds to 1BC, year -1 to 2BC, and so on back.
C Thus y-4713 in this counting would be 4714BC on the Gregorian calendar,
C were it to have existed then which it didn't.
C   To conform to the civil usage, the incoming YY, presumed a proper BC (negative)
C and AD (positive) year is converted into the computational counting sequence, Y,
C and used in the formula. If a YY = 0 is (improperly) offered, it will manifest
C as 1AD. Thus YY = -4714 will lead to calculations with Y = -4713.
C Thus, 1BC is a leap year on the proleptic Gregorian calendar.
C   For their convenience, astronomers decreed that a day starts at noon, so that
C in Europe, observations through the night all have the same day number.
C The current Western civil calendar however has the day starting just after midnight
C and that day's number lasts until the following midnight.
C
C   There is no constraint on the values of D, which is just added as it stands.
C This means that if D = 0, the daynumber will be that of the last day of the
C previous month. Likewise, M = 0 or M = 13 will wrap around so that Y,M + 1,0
C will give the last day of month M (whatever its length) as one day before
C the first day of the next month.
C
C   Example: Y = 1970, M = 1, D = 1;  JDAYN = 2440588, a Thursday but MOD(2440588,7) = 3.
C   and with the adjustment JDAYSHIFT, DAYNUM = 25568; mod 7 = 4 and DAYNAME(4) = "Thursday".
C   The Julian Day number 2440588.0 is for NOON that Thursday, 2440588.5 is twelve hours later.
C   And Julian Day number 2440587.625 is for three a.m. Thursday.
C
C   DAYNUM and MUNYAD are the infamous routines of H. F. Fliegel and T.C. van Flandern,
C   presented in Communications of the ACM, Vol. 11, No. 10 (October, 1968).
Carefully typed in again by R.N.McLean (whom God preserve) December XXMMIIX.
C   Though I remain puzzled as to why they used I,J,K for Y,M,D,
C given that the variables were named in the INTEGER statement anyway.
        INTEGER*4 JDAYN		!Without rebasing, this won't fit in INTEGER*2.
        INTEGER YY,Y,M,MM,D	!NB! Full year number, so 1970, not 70.
Caution: integer division in Fortran does not produce fractional results.
C The fractional part is discarded so that 4/3 gives 1 and -4/3 gives -1.
C Thus 4/3 might be Trunc(4/3) or 4 div 3 in other languages. Beware of negative numbers!
         Y = YY		!I can fiddle this copy without damaging the original's value.
         IF (Y.LT.1) Y = Y + 1	!Thus YY = -2=2BC, -1=1BC, +1=1AD, ... becomes Y = -1, 0, 1, ...
         MM = (M - 14)/12	!Calculate once. Note that this is integer division, truncating.
         JDAYN = D - 32075	!This is the proper astronomer's Julian Day Number.
     a    + 1461*(Y + 4800  + MM)/4
     b    +  367*(M - 2     - MM*12)/12
     c    -    3*((Y + 4900 + MM)/100)/4
         DAYNUM = JDAYN - JDAYSHIFT	!Thus, *NOT* the actual *Julian* Day Number.
       END FUNCTION DAYNUM		!But one such that Mod(n,7) gives day names.

Could compute the day of the year somewhat as follows...
c  DN:=D + (61*Month + (Month div 8)) div 2 - 30
c        + if Month > 2 then FebLength - 30 else 0;

       TYPE(DATEBAG) FUNCTION MUNYAD(DAYNUM)	!Oh for palindromic programming!
Conversion from a Julian day number to a Gregorian calendar date. See JDAYN/DAYNUM.
        INTEGER*4 DAYNUM,JDAYN	!Without rebasing, this won't fit in INTEGER*2.
        INTEGER Y,M,D,L,N		!Y will be a full year number: 1950 not 50.
         JDAYN = DAYNUM + JDAYSHIFT	!Revert to a proper Julian day number.
         L = JDAYN + 68569	!Further machinations of H. F. Fliegel and T.C. van Flandern.
         N = 4*L/146097
         L = L - (146097*N + 3)/4
         Y = 4000*(L + 1)/1461001
         L = L - 1461*Y/4 + 31
         M = 80*L/2447
         D = L - 2447*M/80
         L = M/11
         M = M + 2 - 12*L
         Y = 100*(N - 49) + Y + L
         IF (Y.LT.1) Y = Y - 1	!The other side of conformity to BC/AD, as in DAYNUM.
         MUNYAD%YEAR  = Y	!Now place for the world to see.
         MUNYAD%MONTH = M
         MUNYAD%DAY   = D
       END FUNCTION MUNYAD	!A year has 365.2421988 days...

       INTEGER FUNCTION PMOD(N,M)	!Remainder, mod M; always positive even if N is negative.
c   For date calculations, the MOD function is expected to yield positive remainders,
c in line with the idea that MOD(a,b) = MOD(a ± b,b) as is involved in shifting the zero
c of the daynumber count by a multiple of seven when considering the day of the week.
c For this reason, the zero day was chosen to be 31/12/1899, a Sunday, so that all
c day numbers would be positive. But, there was generation at Reefton in 1886.
c   For some computers, the positive interpretation is implemented, for others, not.
c In the case MOD(N,M) = N - Truncate(N/M)*M, MOD(-6,7) = -6 even though MOD(1,7) = 1.
        INTEGER N,M			!The numbers. M presumed positive.
         PMOD = MOD(MOD(N,M) + M,M)	!Double do does de deed.
       END FUNCTION PMOD		!Simple enough.

      SUBROUTINE CALENDAR(Y1,Y2,COLUMNS)	!Print a calendar, with holiday annotations.
Careful with the MOD function. MOD(-6,7) may be negative on some systems, positive on others. Thus, PMOD.
       INTEGER Y1,Y2,YEAR		!Ah yes. Year stuff.
       INTEGER M,M1,M2,MONTH		!And within each year are the months.
       INTEGER*4 DN1,DN2,DN,D		!But days are handled via day numbers.
       INTEGER W,G			!Layout: width and gap.
       INTEGER L,LINE			!Vertical layout.
       INTEGER COL,COLUMNS,COLWIDTH	!Horizontal layout.
       INTEGER CODE			!Days are not all alike.
       CHARACTER*200 STRIPE(6),SPECIAL(6),MLINE,DLINE	!Scratchpads.
        IF (Y1.LE.0) CALL SAY(MSG,"Despite the insinuations of "
     1   //"astronomers seduced by the ease of their arithmetic, "
     2   //"there is no year zero. 1AD is preceded by 1BC, "
     3   //"corresponding to year -1, 2BC to year -2, etc.")
        IF (Y1.LT.1582) CALL SAY(MSG,"This Gregorian calendar"
     1   //" scheme did not exist prior to 1582.")
c        COLUMNS = 4	!Number of months across the page.
c        W = 4		!Width of a day's field.
c        G = 3		!Added gap between month columns.
        W = 3		!Abandon the annotation of the day's class, so just a space and two digits.
        G = 1		!Set the gap to one.
        COLWIDTH = 7*W + G	!Seven days to a week, plus a gap.
      Y:DO YEAR = Y1,Y2		!Step through the years.
          CALL SAY(MSG,"")	!Space out between each year's schedule.
          IF (YEAR.EQ.0) THEN	!This year number is improper.
            CALL SAY(MSG,"There is no year zero.")	!Declare correctness.
            CYCLE Y		!Skip this year.
          END IF		!Otherwise, no evasions.
          MLINE = ""		!Prepare a field..
          L = (COLUMNS*COLWIDTH - G - 8)/2	!Find the centre.
          IF (YEAR.GT.0) THEN	!Ordinary Anno Domine years?
            MLINE(L:) = I8FMT(YEAR)	!Yes. Place the year number.
           ELSE			!Otherwise, we're in BC.
            MLINE(L - 1:) = I8FMT(-YEAR)//"BC"	!There is no year zero.
          END IF		!So much for year games.
          CALL SAY(MSG,MLINE)		!Splot the year.
          DO MONTH = 1,12,COLUMNS	!Step through the months of this YEAR.
            M1 = MONTH			!The first of this lot.
            M2 = MIN(12,M1 + COLUMNS - 1)	!The last.
            MLINE = ""			!Scrub the month names.
            DLINE = ""			!Wipe the day names in case COLUMNS does not divide 12.
            STRIPE = ""			!Scrub the day table.
            SPECIAL = ""		!And the associated special day remarks.
c            L0 = W - 1			!Locate the first day number's first column.
            L0 = 1			!Cram: no space in front of the Sunday day-of-the-month.
            DO M = M1,M2		!Work through the months.
              L = (COLWIDTH - G - LSTNB(MONTHNAME(M)))/2 - 1	!Centre the month name.
              MLINE(L0 + L:) = MONTHNAME(M)	!Splot.
              DO D = 0,6		!Prepare this month's day name heading.
                L = L0 + (3 - W) + D*W	!Locate its first column.
                DLINE(L:L + 2) = DAYNAME(D)(1:W - 1)	!Squish.
              END DO		!On to the next day.
              DN1 = DAYNUM(YEAR,M,1)	!Day number of the first day of the month.
              DN2 = DAYNUM(YEAR,M + 1,0)!Thus the last, without annoyance.
              COL = MOD(PMOD(DN1,7) + 7,7)	!What day of the week is the first day?
              LINE = 1			!Whichever it is, it is on the first line.
              D = 1			!Day of the month, not number of the day.
              DO DN = DN1,DN2		!Step through the day numbers of this month.
                L = L0 + COL*W		!Finger the starting column.
                STRIPE(LINE)(L:L + 1) = I2FMT(D)	!Place the two-digit day number.
                D = D + 1		!Advance to the next day of the current month
                COL = COL + 1		!So, one more day along in the week.
                IF (COL.GT.6) THEN	!A fresh week is needed?
                  LINE = LINE + 1	!Yes.
                  COL = 0		!Start the new week.
                END IF		!So much for the end of a week.
              END DO		!On to the next day of this month.
              L0 = L0 + 7*W + G	!Locate the start column of the next month's column.
            END DO	!On to the next month in this layer.
            CALL SAY(MSG,MLINE)	!Name the months.
C            CALL SAY(MSG,"")	!Set off.
            CALL SAY(MSG,DLINE)	!Give the day name headings.
            DO LINE = 1,6	!Now roll the day number table.
              IF (STRIPE(LINE).NE."") THEN	!Perhaps there was no use of the sixth line.
                CALL SAY(MSG,STRIPE(LINE))	!Ah well. Show the day numbers.
              END IF				!So much for that week line.
            END DO			!On to the next week line.
          END DO		!On to the next batch of months of the YEAR.
        END DO Y		!On to the next YEAR.
        CALL SAY(MSG,"")	!Take a breath.
      END SUBROUTINE CALENDAR	!Enough of this.
      END MODULE DATEGNASH	!An ad-hoc assemblage.

      PROGRAM SHOW1968		!Put it to the test.
       USE DATEGNASH
       INTEGER NCOL
        DO NCOL = 1,6
          CALL CALENDAR(1969,1969,NCOL)
        END DO
      END

Selected output, lacking alas the outbursts from Snoopy: three months /line

                               1969
      January              February                March
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
          1  2  3  4                     1                     1
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   2  3  4  5  6  7  8
12 13 14 15 16 17 18   9 10 11 12 13 14 15   9 10 11 12 13 14 15
19 20 21 22 23 24 25  16 17 18 19 20 21 22  16 17 18 19 20 21 22
26 27 28 29 30 31     23 24 25 26 27 28     23 24 25 26 27 28 29
                                            30 31
       April                  May                  June
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5               1  2  3   1  2  3  4  5  6  7
 6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14
13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21
20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28
27 28 29 30           25 26 27 28 29 30 31  29 30
       July                 August               September
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2      1  2  3  4  5  6
 6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13
13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20
20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27
27 28 29 30 31        24 25 26 27 28 29 30  28 29 30
                      31
      October              November              December
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
          1  2  3  4                     1      1  2  3  4  5  6
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13
12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20
19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27
26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31
                      30

And with six...

                                                                1969
      January              February                March                 April                  May                  June
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
          1  2  3  4                     1                     1         1  2  3  4  5               1  2  3   1  2  3  4  5  6  7
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   2  3  4  5  6  7  8   6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14
12 13 14 15 16 17 18   9 10 11 12 13 14 15   9 10 11 12 13 14 15  13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21
19 20 21 22 23 24 25  16 17 18 19 20 21 22  16 17 18 19 20 21 22  20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28
26 27 28 29 30 31     23 24 25 26 27 28     23 24 25 26 27 28 29  27 28 29 30           25 26 27 28 29 30 31  29 30
                                            30 31
       July                 August               September              October              November              December
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2      1  2  3  4  5  6            1  2  3  4                     1      1  2  3  4  5  6
 6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13   5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13
13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20  12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20
20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27  19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27
27 28 29 30 31        24 25 26 27 28 29 30  28 29 30              26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31
                      31                                                                30

FreeBASIC

' version 17-02-2016
' compile with: fbc -s console

' TRUE/FALSE are built-in constants since FreeBASIC 1.04
' For older versions they have to be defined.
#Ifndef TRUE        
    #Define FALSE 0
    #Define TRUE Not FALSE
#EndIf

Function WD(m As Integer, d As Integer, y As Integer) As Integer
    ' Zellerish
    ' 0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday
    ' 4 = Thursday, 5 = Friday, 6 = Saturday

    If m < 3 Then        ' if m = 1 or m = 2 then
        m += 12
        y -= 1
    End If
    Return (y + (y \ 4) - (y \ 100) + (y \ 400) + d + ((153 * m + 8) \ 5)) Mod 7
End Function

Function LEAPYEAR(y As Integer) As Integer
    If (y Mod 4) <> 0 Then Return FALSE
    If (y Mod 100) = 0 AndAlso (y Mod 400) <> 0 Then Return FALSE
    Return TRUE
End Function

' ------=< main >=------

Dim As String wdn = "Mo Tu We Th Fr Sa Su"  ' weekday names
Dim As String mo(1 To 12) => {"January", "February", "March", "April", _
                         "May", "June", "July", "August", "September", _
                                    "October", "November", "December"}
Dim As String tmp1, tmp2, d(1 To 12)

Dim As UInteger ml(1 To 12) => {31,28,31,30,31,30,31,31,30,31,30,31}
Dim As UInteger i, i1, j, k, y = 1969
Dim As UInteger m_row = 6

Do
    While InKey <> "" : Wend  ' clear keyboard buffer
    Print : Print " For wich year do want a calendar"
    Print " Year must be greater then 1752"
    Input " Input year (enter = 1969)";tmp1
    If tmp1 = "" Then Exit Do
    i = Val(tmp1)
    If i < 1752 Then
        Print
        Print " Can only make a calendar for a year after 1752"
        Beep : Sleep 5000, 1 : Print
    Else
        y = i : Exit Do
    End If
Loop

Cls
Do
    While InKey <> "" : Wend  ' clear keyboard buffer
    Print : Print " Make device choice"
    Print " 132 characters Line printer, 6x2 months (Enter or 1)"
    Print " 80x43 display,               3x4 months          (2)"
    Do
        tmp1 = InKey
        If tmp1 = Chr(13) Or tmp1 = "1" Then Exit Do, Do
        If tmp1 = "2" Then
            m_row = 3
            Exit Do, Do
        End If
    Loop Until tmp1 <> ""
    Print : Print " Enter, 1 or 2 only"
    Beep : Sleep 5000, 1 : Print
Loop
Cls

Dim As UInteger char_line = m_row * 22 - 1
If LEAPYEAR(y) = TRUE Then ml(2) = 29

tmp1 = ""
For i = 1 To 31
    tmp1 = tmp1 + Right(("  " + Str(i)), 3)
Next

For i = 1 To 12
    tmp2 = ""
    j = WD(i,1, y)
    If j = 0 Then j = 7
    j = j - 1
    tmp2 = Space(j * 3) + Left(tmp1, ml(i) * 3) + Space(21)
    d(i) = tmp2
Next

Print
tmp1 = Str(y)
Print Space((char_line + (char_line And 1) - Len(tmp1)) \ 2); tmp1
Print

tmp2 = " "    ' make the weekday names line
For i = 1 To m_row
    tmp2 = tmp2 + wdn
    If i < m_row Then tmp2 = tmp2 + "  "
Next

For i = 1 To 12 Step m_row
    tmp1 = ""
    For j = i To i + m_row -2 ' make the month names line
        tmp1 = tmp1 + Left(Space((22 - Len(mo(j))) \ 2) + mo(j) + Space(21), 22)
    Next
    tmp1 = tmp1 + Space((22 - Len(mo(i + m_row -1))) \ 2) + mo(i + m_row -1)
    Print tmp1
    Print tmp2
    For j = 1 To 85 Step 21
        For k = i To i + m_row -2
            Print Mid(d(k), j ,21); " ";
        Next
        Print Mid(d(i + m_row -1), j ,21)
    Next
    Print
Next

' empty keyboard buffer
While InKey <> "" : Wend
'Print : Print "hit any key to end program
Sleep
End
Output:
                                                                1969

       January               February               March                 April                  May                   June
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su
        1  2  3  4  5                  1  2                  1  2      1  2  3  4  5  6            1  2  3  4                     1
  6  7  8  9 10 11 12   3  4  5  6  7  8  9   3  4  5  6  7  8  9   7  8  9 10 11 12 13   5  6  7  8  9 10 11   2  3  4  5  6  7  8
 13 14 15 16 17 18 19  10 11 12 13 14 15 16  10 11 12 13 14 15 16  14 15 16 17 18 19 20  12 13 14 15 16 17 18   9 10 11 12 13 14 15
 20 21 22 23 24 25 26  17 18 19 20 21 22 23  17 18 19 20 21 22 23  21 22 23 24 25 26 27  19 20 21 22 23 24 25  16 17 18 19 20 21 22
 27 28 29 30 31        24 25 26 27 28        24 25 26 27 28 29 30  28 29 30              26 27 28 29 30 31     23 24 25 26 27 28 29

         July                 August              September              October               November              December
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su
     1  2  3  4  5  6               1  2  3   1  2  3  4  5  6  7         1  2  3  4  5                  1  2   1  2  3  4  5  6  7
  7  8  9 10 11 12 13   4  5  6  7  8  9 10   8  9 10 11 12 13 14   6  7  8  9 10 11 12   3  4  5  6  7  8  9   8  9 10 11 12 13 14
 14 15 16 17 18 19 20  11 12 13 14 15 16 17  15 16 17 18 19 20 21  13 14 15 16 17 18 19  10 11 12 13 14 15 16  15 16 17 18 19 20 21
 21 22 23 24 25 26 27  18 19 20 21 22 23 24  22 23 24 25 26 27 28  20 21 22 23 24 25 26  17 18 19 20 21 22 23  22 23 24 25 26 27 28
 28 29 30 31           25 26 27 28 29 30 31  29 30                 27 28 29 30 31        24 25 26 27 28 29 30  29 30 31            

FutureBasic

Legacy version:

window 1, @"Calendar", (0, 0, 520, 520 )

Str255 a

open "UNIX", 1,"cal 1969"
do
  line input #1, a
  print a
until eof(1)
close 1

HandleEvents

Modern version:

include "NSLog.incl"

local fn RunCommand( command as CFStringRef ) as CFStringRef
  TaskRef tsk = fn TaskInit
  TaskSetExecutableURL( tsk, fn URLFileURLWithPath( @"/bin/sh" ) )
  TaskSetArguments( tsk, @[@"-c",command] )
  PipeRef pip = fn PipeInit
  TaskSetStandardOutput( tsk, pip )
  FileHandleRef fh = fn PipeFileHandleForReading( pip )
  fn TaskLaunch( tsk, NULL )
  CFDataRef dta = fn FileHandleReadDataToEndOfFile( fh, NULL )
  CFStringRef outputStr = fn StringWithData( dta, NSUTF8StringEncoding )
end fn = outputStr

NSLog( @"%@", fn RunCommand( @"cal 1969" ) )

HandleEvents

Output of either version:

                             1969

      January               February               March
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
          1  2  3  4                     1                     1
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   2  3  4  5  6  7  8
12 13 14 15 16 17 18   9 10 11 12 13 14 15   9 10 11 12 13 14 15
19 20 21 22 23 24 25  16 17 18 19 20 21 22  16 17 18 19 20 21 22
26 27 28 29 30 31     23 24 25 26 27 28     23 24 25 26 27 28 29
                                            30 31
       April                  May                   June
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5               1  2  3   1  2  3  4  5  6  7
 6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14
13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21
20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28
27 28 29 30           25 26 27 28 29 30 31  29 30
                                            
        July                 August              September
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2      1  2  3  4  5  6
 6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13
13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20
20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27
27 28 29 30 31        24 25 26 27 28 29 30  28 29 30
                      31                    
      October               November              December
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
          1  2  3  4                     1      1  2  3  4  5  6
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13
12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20
19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27
26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31
                      30                                         

Gambas

Public Sub Main()

Shell "cal 1969"

End

Output:

                            1969
      January               February               March          
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  
          1  2  3  4                     1                     1  
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   2  3  4  5  6  7  8  
12 13 14 15 16 17 18   9 10 11 12 13 14 15   9 10 11 12 13 14 15  
19 20 21 22 23 24 25  16 17 18 19 20 21 22  16 17 18 19 20 21 22  
26 27 28 29 30 31     23 24 25 26 27 28     23 24 25 26 27 28 29  
                                            30 31                 

       April                  May                   June          
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  
       1  2  3  4  5               1  2  3   1  2  3  4  5  6  7  
 6  7  8  9 10 11 12   4  5  6  7  8  9 10   8  9 10 11 12 13 14  
13 14 15 16 17 18 19  11 12 13 14 15 16 17  15 16 17 18 19 20 21  
20 21 22 23 24 25 26  18 19 20 21 22 23 24  22 23 24 25 26 27 28  
27 28 29 30           25 26 27 28 29 30 31  29 30                 
                                                                  

        July                 August              September        
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  
       1  2  3  4  5                  1  2      1  2  3  4  5  6  
 6  7  8  9 10 11 12   3  4  5  6  7  8  9   7  8  9 10 11 12 13  
13 14 15 16 17 18 19  10 11 12 13 14 15 16  14 15 16 17 18 19 20  
20 21 22 23 24 25 26  17 18 19 20 21 22 23  21 22 23 24 25 26 27  
27 28 29 30 31        24 25 26 27 28 29 30  28 29 30              
                      31                                          

      October               November              December        
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  
          1  2  3  4                     1      1  2  3  4  5  6  
 5  6  7  8  9 10 11   2  3  4  5  6  7  8   7  8  9 10 11 12 13  
12 13 14 15 16 17 18   9 10 11 12 13 14 15  14 15 16 17 18 19 20  
19 20 21 22 23 24 25  16 17 18 19 20 21 22  21 22 23 24 25 26 27  
26 27 28 29 30 31     23 24 25 26 27 28 29  28 29 30 31           
                      30                             

Go

package main

import (
	"fmt"
	"time"
)

const pageWidth = 80

func main() {
	printCal(1969)
}

func printCal(year int) {
	thisDate := time.Date(year, 1, 1, 1, 1, 1, 1, time.UTC)
	var (
		dayArr                  [12][7][6]int // month, weekday, week
		month, lastMonth        time.Month
		weekInMonth, dayInMonth int
	)
	for thisDate.Year() == year {
		if month = thisDate.Month(); month != lastMonth {
			weekInMonth = 0
			dayInMonth = 1
		}
		weekday := thisDate.Weekday()
		if weekday == 0 && dayInMonth > 1 {
			weekInMonth++
		}
		dayArr[int(month)-1][weekday][weekInMonth] = thisDate.Day()
		lastMonth = month
		dayInMonth++
		thisDate = thisDate.Add(time.Hour * 24)
	}
	centre := fmt.Sprintf("%d", pageWidth/2)
	fmt.Printf("%"+centre+"s\n\n", "[SNOOPY]")
	centre = fmt.Sprintf("%d", pageWidth/2-2)
	fmt.Printf("%"+centre+"d\n\n", year)
	months := [12]string{
		" January ", " February", "  March  ", "  April  ",
		"   May   ", "   June  ", "   July  ", "  August ",
		"September", " October ", " November", " December"}
	days := [7]string{"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
	for qtr := 0; qtr < 4; qtr++ {
		for monthInQtr := 0; monthInQtr < 3; monthInQtr++ { // Month names
			fmt.Printf("      %s           ", months[qtr*3+monthInQtr])
		}
		fmt.Println()
		for monthInQtr := 0; monthInQtr < 3; monthInQtr++ { // Day names
			for day := 0; day < 7; day++ {
				fmt.Printf(" %s", days[day])
			}
			fmt.Printf("     ")
		}
		fmt.Println()
		for weekInMonth = 0; weekInMonth < 6; weekInMonth++ {
			for monthInQtr := 0; monthInQtr < 3; monthInQtr++ {
				for day := 0; day < 7; day++ {
					if dayArr[qtr*3+monthInQtr][day][weekInMonth] == 0 {
						fmt.Printf("   ")
					} else {
						fmt.Printf("%3d", dayArr[qtr*3+monthInQtr][day][weekInMonth])
					}
				}
				fmt.Printf("     ")
			}
			fmt.Println()
		}
		fmt.Println()
	}
}

Output:

                                [SNOOPY]

                                  1969

       January                   February                   March
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
           1  2  3  4                         1                         1
  5  6  7  8  9 10 11       2  3  4  5  6  7  8       2  3  4  5  6  7  8
 12 13 14 15 16 17 18       9 10 11 12 13 14 15       9 10 11 12 13 14 15
 19 20 21 22 23 24 25      16 17 18 19 20 21 22      16 17 18 19 20 21 22
 26 27 28 29 30 31         23 24 25 26 27 28         23 24 25 26 27 28 29
                                                     30 31

        April                      May                       June
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
        1  2  3  4  5                   1  2  3       1  2  3  4  5  6  7
  6  7  8  9 10 11 12       4  5  6  7  8  9 10       8  9 10 11 12 13 14
 13 14 15 16 17 18 19      11 12 13 14 15 16 17      15 16 17 18 19 20 21
 20 21 22 23 24 25 26      18 19 20 21 22 23 24      22 23 24 25 26 27 28
 27 28 29 30               25 26 27 28 29 30 31      29 30


         July                     August                  September
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
        1  2  3  4  5                      1  2          1  2  3  4  5  6
  6  7  8  9 10 11 12       3  4  5  6  7  8  9       7  8  9 10 11 12 13
 13 14 15 16 17 18 19      10 11 12 13 14 15 16      14 15 16 17 18 19 20
 20 21 22 23 24 25 26      17 18 19 20 21 22 23      21 22 23 24 25 26 27
 27 28 29 30 31            24 25 26 27 28 29 30      28 29 30
                           31

       October                   November                  December
 Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa      Su Mo Tu We Th Fr Sa
           1  2  3  4                         1          1  2  3  4  5  6
  5  6  7  8  9 10 11       2  3  4  5  6  7  8       7  8  9 10 11 12 13
 12 13 14 15 16 17 18       9 10 11 12 13 14 15      14 15 16 17 18 19 20
 19 20 21 22 23 24 25      16 17 18 19 20 21 22      21 22 23 24 25 26 27
 26 27 28 29 30 31         23 24 25 26 27 28 29      28 29 30 31
                           30

Haskell

import qualified Data.Text as T
import Data.Time
import Data.Time.Calendar
import Data.Time.Calendar.WeekDate
import Data.List.Split (chunksOf)
import Data.List

data Day = Su | Mo | Tu | We | Th | Fr | Sa
           deriving (Show, Eq, Ord, Enum)

data Month = January | February | March
           | April   | May      | June
           | July    | August   | September
           | October | November | December
             deriving (Show, Eq, Ord, Enum)

monthToInt :: Month -> Int
monthToInt = (+ 1) . fromEnum

dayFromDate :: Integer -> Month -> Int -> Int
dayFromDate year month day = day' `mod` 7
    where (_, _, day') = toWeekDate $ fromGregorian year (monthToInt month) day

nSpaces :: Int -> T.Text
nSpaces n = T.replicate n (T.pack " ")

space :: T.Text
space = nSpaces 1

calMarginWidth = 3

calMargin :: T.Text
calMargin = nSpaces calMarginWidth

calWidth = 20

listMonth :: Integer -> Month -> [T.Text]
listMonth year month = [monthHeader, weekHeader] ++ weeks'
    where
      monthHeader = (T.center calWidth ' ') . T.pack $ show month

      weekHeader = (T.intercalate space) $ map (T.pack . show) [(Su)..]

      monthLength = toInteger $ 
                    gregorianMonthLength year $
                    monthToInt month

      firstDay = dayFromDate year month 1

      days = replicate firstDay (nSpaces 2) ++
             map ((T.justifyRight 2 ' ') . T.pack . show) [1..monthLength]

      weeks = map (T.justifyLeft calWidth ' ') $
              map (T.intercalate space) $
              chunksOf 7 days

      weeks' = weeks ++ replicate (6 - length weeks) (nSpaces calWidth)

listCalendar :: Integer -> Int -> [[[T.Text]]]
listCalendar year calColumns = (chunksOf calColumns) . (map (listMonth year)) $
                               enumFrom January

calColFromCol :: Int -> Int
calColFromCol columns = c + if r >= calWidth then 1 else 0
    where (c, r) = columns `divMod` (calWidth + calMarginWidth)

colFromCalCol :: Int -> Int
colFromCalCol calCol = calCol * calWidth + ((calCol - 1) * calMarginWidth)

center :: Int -> String -> String
center i a = T.unpack . (T.center i ' ') $ T.pack a

printCal :: [[[T.Text]]] -> IO ()
printCal = mapM_ f where
  f c = mapM_ (putStrLn . T.unpack) rows
    where rows = map (T.intercalate calMargin) $ transpose c

printCalendar :: Integer -> Int -> IO ()
printCalendar year columns =
    if columns < 20
    then putStrLn $ "Cannot print less than 20 columns"
    else do
      putStrLn $ center columns' "[Maybe Snoopy]"
      putStrLn $ center columns' $ show year
      putStrLn ""
      printCal $ listCalendar year calcol'
    where
      calcol' = calColFromCol columns

      columns' = colFromCalCol calcol'
*Main> printCalendar 1969 80
                          [Maybe Snoopy]
                               1969

       January               February                 March
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
          1  2  3  4                      1                      1
 5  6  7  8  9 10 11    2  3  4  5  6  7  8    2  3  4  5  6  7  8
12 13 14 15 16 17 18    9 10 11 12 13 14 15    9 10 11 12 13 14 15
19 20 21 22 23 24 25   16 17 18 19 20 21 22   16 17 18 19 20 21 22
26 27 28 29 30 31      23 24 25 26 27 28      23 24 25 26 27 28 29
                                              30 31
        April                   May                   June
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
       1  2  3  4  5                1  2  3    1  2  3  4  5  6  7
 6  7  8  9 10 11 12    4  5  6  7  8  9 10    8  9 10 11 12 13 14
13 14 15 16 17 18 19   11 12 13 14 15 16 17   15 16 17 18 19 20 21
20 21 22 23 24 25 26   18 19 20 21 22 23 24   22 23 24 25 26 27 28
27 28 29 30            25 26 27 28 29 30 31   29 30

        July                  August                September
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
       1  2  3  4  5                   1  2       1  2  3  4  5  6
 6  7  8  9 10 11 12    3  4  5  6  7  8  9    7  8  9 10 11 12 13
13 14 15 16 17 18 19   10 11 12 13 14 15 16   14 15 16 17 18 19 20
20 21 22 23 24 25 26   17 18 19 20 21 22 23   21 22 23 24 25 26 27
27 28 29 30 31         24 25 26 27 28 29 30   28 29 30
                       31
       October               November               December
Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa
          1  2  3  4                      1       1  2  3  4  5  6
 5  6  7  8  9 10 11    2  3  4  5  6  7  8    7  8  9 10 11 12 13
12 13 14 15 16 17 18    9 10 11 12 13 14 15   14 15 16 17 18 19 20
19 20 21 22 23 24 25   16 17 18 19 20 21 22   21 22 23 24 25 26 27
26 27 28 29 30 31      23 24 25 26 27 28 29   28 29 30 31
                       30

Huginn

import DateTime as dt;
import Algorithms as algo;
import Text as text;

class Calendar {
  _monthNames_ = none;
  _dayNames_ = none;
  constructor() {
    t = dt.now();
    _monthNames_ = algo.materialize( algo.map( algo.range( 1, 13 ), @[t]( m ) { dt.format( "%B", t.set_date( 1, m, 1 ) ); } ), tuple );
    _dayNames_ = algo.materialize( algo.map( algo.range( 1, 8 ), @[t]( d ) { dt.format( "%a", t.set_date( 1, 1, d ) )[:2]; } ), tuple );
  }
  print_year( year_, cols_ ) {
    t = dt.now();
    print( "{:^66d}\n".format( year_ ) );
    for ( rm : algo.range( 12 / cols_ ) ) {
      m = rm * cols_;
      print( text.repeat( "{:^22s}", cols_ ).format( _monthNames_[m:m + cols_]... ) + "\n" );
      day = [];
      daysInMonth = [];
      for ( mc : algo.range( cols_ ) ) {
        print( " {} {} {} {} {} {} {} ".format( _dayNames_... ) );
        t.set_date( year_, m + mc + 1, 1 );
        day.push( - t.get_day_of_week() + 1 );
        daysInMonth.push( t.get_days_in_month() );
      }
      print( "\n" );
      haveDay = true;
      while ( haveDay ) {
        haveDay = false;
        for ( mc : algo.range( cols_ ) ) {
          for ( d : algo.range( 7 ) ) {
            if ( ( day[mc] > 0 ) && ( day[mc] <= daysInMonth[mc] ) ) {
              print( " {:2d}".format( day[mc] ) );
              haveDay = true;
            } else {
              print( "   " );
            }
            day[mc] += 1;
          }
          print( " " );
        }
        print( "\n" );
      }
    }
  }
}

main( argv_ ) {
  cal = Calendar();
  cols = size( argv_ ) > 2 ? integer( argv_[2] ) : 3;
  if ( 12 % cols != 0 ) {
    cols = 3;
  }
  cal.print_year(
    size( argv_ ) > 1
      ? integer( argv_[1] )
      : dt.now().get_year(),
    cols
  );
}

Output:

                               1969                               
       January               February               March         
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su 
        1  2  3  4  5                  1  2                  1  2 
  6  7  8  9 10 11 12   3  4  5  6  7  8  9   3  4  5  6  7  8  9 
 13 14 15 16 17 18 19  10 11 12 13 14 15 16  10 11 12 13 14 15 16 
 20 21 22 23 24 25 26  17 18 19 20 21 22 23  17 18 19 20 21 22 23 
 27 28 29 30 31        24 25 26 27 28        24 25 26 27 28 29 30 
                                             31                   
                                                                  
        April                  May                   June         
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su 
     1  2  3  4  5  6            1  2  3  4                     1 
  7  8  9 10 11 12 13   5  6  7  8  9 10 11   2  3  4  5  6  7  8 
 14 15 16 17 18 19 20  12 13 14 15 16 17 18   9 10 11 12 13 14 15 
 21 22 23 24 25 26 27  19 20 21 22 23 24 25  16 17 18 19 20 21 22 
 28 29 30              26 27 28 29 30 31     23 24 25 26 27 28 29 
                                             30                   
                                                                  
         July                 August              September       
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su 
     1  2  3  4  5  6               1  2  3   1  2  3  4  5  6  7 
  7  8  9 10 11 12 13   4  5  6  7  8  9 10   8  9 10 11 12 13 14 
 14 15 16 17 18 19 20  11 12 13 14 15 16 17  15 16 17 18 19 20 21 
 21 22 23 24 25 26 27  18 19 20 21 22 23 24  22 23 24 25 26 27 28 
 28 29 30 31           25 26 27 28 29 30 31  29 30                
                                                                  
       October               November              December       
 Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su  Mo Tu We Th Fr Sa Su 
        1  2  3  4  5                  1  2   1  2  3  4  5  6  7 
  6  7  8  9 10 11 12   3  4  5  6  7  8  9   8  9 10 11 12 13 14 
 13 14 15 16 17 18 19  10 11 12 13 14 15 16  15 16 17 18 19 20 21 
 20 21 22 23 24 25 26  17 18 19 20 21 22 23  22 23 24 25 26 27 28 
 27 28 29 30 31        24 25 26 27 28 29 30  29 30 31

Icon and Unicon

The procedures printCalendar handles formatting of large components and uses co-expressions to keep the formatting of week elements in each column synchronized. The procedure CalendarFormatWeek is a generator that returns heading elements, alignment spacing, and individual days.

procedure main(A)
   printCalendar(\A[1]|1969)	
end

procedure printCalendar(year)          #: Print a 3 column x 80 char calendar
   cols := 3                                        # fixed width 
   mons := []                                       # table of months
   "January February March April May June " || 
   "July August September October November December " ?
          while put(mons, tab(find(" "))) do move(1)    

   write(center("[Snoopy Picture]",cols * 24 + 4))  # mandatory ..
   write(center(year,cols * 24 + 4), "\n")          # ... headers
   
   M := list(cols)                                  # coexpr container               
   every  mon := 0 to 9 by cols do {                # go through months by cols
      writes("    ")
      every i := 1 to cols do {
         writes(center(mons[mon+i],24))             # header months
         M[i] := create CalendarFormatWeek(year,mon + i)  # formatting coexpr 
         }
      write()
      every 1 to 7 do {                             # 1 to max rows                   
         every c := 1 to cols do {                  # for each column
            writes("    ")
            every 1 to 7 do writes(right(@M[c],3))  # each row day element 
            }
         write()
         }
      }
   return
end

link datetime  

procedure CalendarFormatWeek(year,m)                #: Format Week for Calendar
   static D
   initial D := [31,28,31,30,31,30,31,31,30,31,30,31]  

   every suspend "Su"|"Mo"|"Tu"|"We"|"Th"|"Fr"|"Sa"          # header
   every 1 to (d := (julian(m,1,year)+1)%7) do suspend ""    # lead day alignment
   every suspend 1 to D[m] do d +:= 1                        # days
   if m = 2 & IsLeapYear(year) then suspend (d +:= 1, 29)    # LY adjustment
   every d to (6*7) do suspend ""                            # trailer alignment
end

datetime.icn provides julian and IsLeapYear

Output:

                              [Snoopy Picture]
                                    1969

            January                 February                 March
     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
               1  2  3  4                        1                        1
      5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
     12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
     19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
     26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                       30 31
             April                    May                     June
     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
            1  2  3  4  5                  1  2  3      1  2  3  4  5  6  7
      6  7  8  9 10 11 12      4  5  6  7  8  9 10      8  9 10 11 12 13 14
     13 14 15 16 17 18 19     11 12 13 14 15 16 17     15 16 17 18 19 20 21
     20 21 22 23 24 25 26     18 19 20 21 22 23 24     22 23 24 25 26 27 28
     27 28 29 30              25 26 27 28 29 30 31     29 30

              July                   August                September
     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
            1  2  3  4  5                     1  2         1  2  3  4  5  6
      6  7  8  9 10 11 12      3  4  5  6  7  8  9      7  8  9 10 11 12 13
     13 14 15 16 17 18 19     10 11 12 13 14 15 16     14 15 16 17 18 19 20
     20 21 22 23 24 25 26     17 18 19 20 21 22 23     21 22 23 24 25 26 27
     27 28 29 30 31           24 25 26 27 28 29 30     28 29 30
                              31
            October                 November                December
     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
               1  2  3  4                        1         1  2  3  4  5  6
      5  6  7  8  9 10 11      2  3  4  5  6  7  8      7  8  9 10 11 12 13
     12 13 14 15 16 17 18      9 10 11 12 13 14 15     14 15 16 17 18 19 20
     19 20 21 22 23 24 25     16 17 18 19 20 21 22     21 22 23 24 25 26 27
     26 27 28 29 30 31        23 24 25 26 27 28 29     28 29 30 31
                              30

J

Solution:

require 'dates format'               NB. J6.x
require 'dates general/misc/format'  NB. J7.x
calBody=: (1 1 }. _1 _1 }. ":)@(-@(<.@%&22)@[ ]\ calendar@])
calTitle=: (<: - 22&|)@[ center '[Insert Snoopy here]' , '' ,:~ ":@]
formatCalendar=: calTitle , calBody

Example use:

   80 formatCalendar 1969
                      [Insert Snoopy here]                       
                              1969                               
                                                                 
         Jan                  Feb                  Mar         
 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
           1  2  3  4                    1                    1
  5  6  7  8  9 10 11  2  3  4  5  6  7  8  2  3  4  5  6  7  8
 12 13 14 15 16 17 18  9 10 11 12 13 14 15  9 10 11 12 13 14 15
 19 20 21 22 23 24 25 16 17 18 19 20 21 22 16 17 18 19 20 21 22
 26 27 28 29 30 31    23 24 25 26 27 28    23 24 25 26 27 28 29
                                           30 31               
─────────────────────┼─────────────────────┼─────────────────────
         Apr                  May                  Jun         
 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
        1  2  3  4  5              1  2  3  1  2  3  4  5  6  7
  6  7  8  9 10 11 12  4  5  6  7  8  9 10  8  9 10 11 12 13 14
 13 14 15 16 17 18 19 11 12 13 14 15 16 17 15 16 17 18 19 20 21
 20 21 22 23 24 25 26 18 19 20 21 22 23 24 22 23 24 25 26 27 28
 27 28 29 30          25 26 27 28 29 30 31 29 30               
                                                               
─────────────────────┼─────────────────────┼─────────────────────
         Jul                  Aug                  Sep         
 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
        1  2  3  4  5                 1  2     1  2  3  4  5  6
  6  7  8  9 10 11 12  3  4  5  6  7  8  9  7  8  9 10 11 12 13
 13 14 15 16 17 18 19 10 11 12 13 14 15 16 14 15 16 17 18 19 20
 20 21 22 23 24 25 26 17 18 19 20 21 22 23 21 22 23 24 25 26 27
 27 28 29 30 31       24 25 26 27 28 29 30 28 29 30            
                      31                                       
─────────────────────┼─────────────────────┼─────────────────────
         Oct                  Nov                  Dec         
 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
           1  2  3  4                    1     1  2  3  4  5  6
  5  6  7  8  9 10 11  2  3  4  5  6  7  8  7  8  9 10 11 12 13
 12 13 14 15 16 17 18  9 10 11 12 13 14 15 14 15 16 17 18 19 20
 19 20 21 22 23 24 25 16 17 18 19 20 21 22 21 22 23 24 25 26 27
 26 27 28 29 30 31    23 24 25 26 27 28 29 28 29 30 31         
                      30                  

Java

Translation of: D
import java.text.*;
import java.util.*;

public class CalendarTask {

    public static void main(String[] args) {
        printCalendar(1969, 3);
    }

    static void printCalendar(int year, int nCols) {
        if (nCols < 1 || nCols > 12)
            throw new IllegalArgumentException("Illegal column width.");

        Calendar date = new GregorianCalendar(year, 0, 1);

        int nRows = (int) Math.ceil(12.0 / nCols);
        int offs = date.get(Calendar.DAY_OF_WEEK) - 1;
        int w = nCols * 24;

        String[] monthNames = new DateFormatSymbols(Locale.US).getMonths();

        String[][] mons = new String[12][8];
        for (int m = 0; m < 12; m++) {

            String name = monthNames[m];
            int len = 11 + name.length() / 2;
            String format = MessageFormat.format("%{0}s%{1}s", len, 21 - len);

            mons[m][0] = String.format(format, name, "");
            mons[m][1] = " Su Mo Tu We Th Fr Sa";
            int dim = date.getActualMaximum(Calendar.DAY_OF_MONTH);

            for (int d = 1; d < 43; d++) {
                boolean isDay = d > offs && d <= offs + dim;
                String entry = isDay ? String.format(" %2s", d - offs) : "   ";
                if (d % 7 == 1)
                    mons[m][2 + (d - 1) / 7] = entry;
                else
                    mons[m][2 + (d - 1) / 7] += entry;
            }
            offs = (offs + dim) % 7;
            date.add(Calendar.MONTH, 1);
        }

        System.out.printf("%" + (w / 2 + 10) + "s%n", "[Snoopy Picture]");
        System.out.printf("%" + (w / 2 + 4) + "s%n%n", year);

        for (int r = 0; r < nRows; r++) {
            for (int i = 0; i < 8; i++) {
                for (int c = r * nCols; c < (r + 1) * nCols && c < 12; c++)
                    System.out.printf("   %s", mons[c][i]);
                System.out.println();
            }
            System.out.println();
        }
    }
}
Output:
                              [Snoopy Picture]
                                    1969

          January                 February                 March        
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
              1  2  3  4                       1                       1
     5  6  7  8  9 10 11     2  3  4  5  6  7  8     2  3  4  5  6  7  8
    12 13 14 15 16 17 18     9 10 11 12 13 14 15     9 10 11 12 13 14 15
    19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22
    26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29
                                                    30 31               

           April                    May                     June        
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
           1  2  3  4  5                 1  2  3     1  2  3  4  5  6  7
     6  7  8  9 10 11 12     4  5  6  7  8  9 10     8  9 10 11 12 13 14
    13 14 15 16 17 18 19    11 12 13 14 15 16 17    15 16 17 18 19 20 21
    20 21 22 23 24 25 26    18 19 20 21 22 23 24    22 23 24 25 26 27 28
    27 28 29 30             25 26 27 28 29 30 31    29 30               
                                                                        

            July                   August                September      
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
           1  2  3  4  5                    1  2        1  2  3  4  5  6
     6  7  8  9 10 11 12     3  4  5  6  7  8  9     7  8  9 10 11 12 13
    13 14 15 16 17 18 19    10 11 12 13 14 15 16    14 15 16 17 18 19 20
    20 21 22 23 24 25 26    17 18 19 20 21 22 23    21 22 23 24 25 26 27
    27 28 29 30 31          24 25 26 27 28 29 30    28 29 30            
                            31                                          

          October                 November                December      
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
              1  2  3  4                       1        1  2  3  4  5  6
     5  6  7  8  9 10 11     2  3  4  5  6  7  8     7  8  9 10 11 12 13
    12 13 14 15 16 17 18     9 10 11 12 13 14 15    14 15 16 17 18 19 20
    19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27
    26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31         
                            30                                          

JavaScript

/**
 * 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);
Output:

                                  🐶                                  
                             --- 1969 ---                             
                                                                      
      January                  February                  March        
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
          1  2  3  4                        1                        1
 5  6  7  8  9 10 11      2  3  4  5  6  7  8      2  3  4  5  6  7  8
12 13 14 15 16 17 18      9 10 11 12 13 14 15      9 10 11 12 13 14 15
19 20 21 22 23 24 25     16 17 18 19 20 21 22     16 17 18 19 20 21 22
26 27 28 29 30 31        23 24 25 26 27 28        23 24 25 26 27 28 29
                                                  30 31               
                                                                      
       April                     May                      June        
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
       1  2  3  4  5                  1  2  3      1  2  3  4  5  6  7
 6  7  8  9 10 11 12      4  5  6  7  8  9 10      8  9 10 11 12 13 14
13 14 15 16 17 18 19     11 12 13 14 15 16 17     15 16 17 18 19 20 21
20 21 22 23 24 25 26     18 19 20 21 22 23 24     22 23 24 25 26 27 28
27 28 29 30              25 26 27 28 29 30 31     29 30               
                                                                      
                                                                      
        July                    August                 September      
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
       1  2  3  4  5                     1  2         1  2  3  4  5  6
 6  7  8  9 10 11 12      3  4  5  6  7  8  9      7  8  9 10 11 12 13
13 14 15 16 17 18 19     10 11 12 13 14 15 16     14 15 16 17 18 19 20
20 21 22 23 24 25 26     17 18 19 20 21 22 23     21 22 23 24 25 26 27
27 28 29 30 31           24 25 26 27 28 29 30     28 29 30            
                         31                                           
                                                                      
      October                  November                 December      
Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa     Su Mo Tu We Th Fr Sa
          1  2  3  4                        1         1  2  3  4  5  6
 5  6  7  8  9 10 11      2  3  4  5  6  7  8      7  8  9 10 11 12 13
12 13 14 15 16 17 18      9 10 11 12 13 14 15     14 15 16 17 18 19 20
19 20 21 22 23 24 25     16 17 18 19 20 21 22     21 22 23 24 25 26 27
26 27 28 29 30 31        23 24 25 26 27 28 29     28 29 30 31         
                         30                       

jq

Adapted from Wren

Works with: jq

Also works with gojq, the Go implementation of jq

Also works with fq, a Go implementation of a large subset of jq

Utility Functions

def nwise($n):
  def n: if length <= $n then . else .[0:$n] , (.[$n:] | n) end;
  n;

def lpad($len): tostring | ($len - length) as $l | (" " * $l)[:$l] + .;

def center(width):
  tostring
  | length as $l
  | ((width - $l)/2 | floor) as $k
  | (" " * $k) + . + (" " * (width - ($k+$l)));

Calendrical Functions

def weekdaynames: ["Su", "Mo","Tu", "We", "Th", "Fr", "Sa"];

# q.v. weekday
# Output the integer index of weekdaynames, i.e. Sunday is 0
def dayofweek($year; $month; $day):
  "\($year)-\($month)-\($day)" | strptime("%Y-%m-%d") | .[-2];

def isLeapYear(y):
   y%4 == 0 and ((y%100 != 0) or (y%400 == 0));

# January is 1
def monthLength($y; $m):
  def __diy: [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
  def __diy2: [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];

  if isLeapYear($y)
  then __diy2[$m] - __diy2[$m-1]
  else __diy[$m] - __diy[$m-1]
  end;

def calendar(year):
  def snoopy: "🐶";
  def months: [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December" ];
  (weekdaynames | join(" ")) as $days
  # $chunk is a list of three months in group number $c
  | def printRow($chunk; $c):
    # The line of month names:
    (" " + ($chunk | map(center(20)) | join("     "))),
    # The line of weekday names
    (" " + ([range(0;3)| $days] | join("     "))),
    # The body of the calendar
    (   [ dayofweek(year; $c*3 + 1; 1),
          dayofweek(year; $c*3 + 2; 1),
          dayofweek(year; $c*3 + 3; 1)] as $first
      | [ monthLength(year; $c*3 + 1),
          monthLength(year; $c*3 + 2),
          monthLength(year; $c*3 + 3) ] as $mlen
      # Print up to 6 lines
      | range(0;6) as $i
      | reduce range(0;3) as $j ("";
            (1 + (7 * $i) - $first[$j]) as $start
            | (reduce range($start; $start+7) as $k (.;
                   if ($k >= 1 and $k <= $mlen[$j]) 
                   then . + ($k|lpad(3))
                   else . + "   "
                   end )  +  "    " ) )
        ),
	"";

    (snoopy, "--- \(year) ---" | center(72)),
    ( [months|nwise(3)] as $chunks
      | range(0;3) | printRow( $chunks[.]; .) );

calendar(1969)
Output:

As for Wren.

Julia

using Dates
 
const pagesizes = Dict( "lpr" => [132, 66], "tn3270" => [80, 43])
 
pagefit(prn) = haskey(pagesizes, prn) ?
    [div(pagesizes[prn][1], 22), div(pagesizes[prn][2], 12)] : [1, 1]
pagecols(prn) = haskey(pagesizes, prn) ? pagesizes[prn][1] : 20
 
function centerobject(x, cols)
    content = string(x)
    rpad(lpad(content, div(cols + length(content), 2)), cols)
end
 
function ljustlines(x, cols)
    arr = Vector{String}()
    for s in split(x, "\n")
        push!(arr, rpad(s, cols)[1:cols])
    end
    join(arr, "\n")
end
 
function formatmonth(yr, mo)
    dt = Date("$yr-$mo-01")
    dayofweekfirst = dayofweek(dt)
    numweeklines = 1
    str = centerobject(monthname(dt), 20) * "\nMo Tu We Th Fr Sa Su\n"
    str *= " " ^ (3 * (dayofweekfirst - 1)) * lpad(string(1), 2)
    for i = 2:daysinmonth(dt)
        if (i + dayofweekfirst + 5) % 7 == 0
            str *= "\n" * lpad(i, 2)
            numweeklines += 1
        else
            str *= lpad(string(i), 3)
        end
    end
    str *= numweeklines < 6 ? "\n\n\n" : "\n\n"
    ljustlines(str, 20)
end
 
function formatyear(displayyear, printertype)
    calmonths = [formatmonth(displayyear, mo) for mo in 1:12]
    columns = pagecols(printertype)
    monthsperline = pagefit(printertype)[1]
    joinspaces = max( (monthsperline > 1) ? 
        div(columns - monthsperline * 20, monthsperline - 1) : 1, 1)
    str = "\n" * centerobject(displayyear, columns) * "\n"
    monthcal = [split(formatmonth(displayyear, i), "\n") for i in 1:12]
    for i in 1:monthsperline:length(calmonths) - 1
        for j in 1:length(monthcal[1])
            monthlines = map(x->monthcal[x][j], i:i + monthsperline - 1)
            str *= rpad(join(monthlines, " " ^ joinspaces), columns) * "\n"
        end
        str *= "\n"
    end
    str
end
 
function lineprintcalendar(years)
    for year in years, printer in keys(pagesizes)
        println(formatyear(year, printer))
    end
end
 
lineprintcalendar(1969)

Kotlin

Translation of: D
import java.io.PrintStream
import java.text.DateFormatSymbols
import java.text.MessageFormat
import java.util.Calendar
import java.util.GregorianCalendar
import java.util.Locale

internal fun PrintStream.printCalendar(year: Int, nCols: Byte, locale: Locale?) {
    if (nCols < 1 || nCols > 12)
        throw IllegalArgumentException("Illegal column width.")
    val w = nCols * 24
    val nRows = Math.ceil(12.0 / nCols).toInt()

    val date = GregorianCalendar(year, 0, 1)
    var offs = date.get(Calendar.DAY_OF_WEEK) - 1

    val days = DateFormatSymbols(locale).shortWeekdays.slice(1..7).map { it.slice(0..1) }.joinToString(" ", " ")
    val mons = Array(12) { Array(8) { "" } }
    DateFormatSymbols(locale).months.slice(0..11).forEachIndexed { m, name ->
        val len = 11 + name.length / 2
        val format = MessageFormat.format("%{0}s%{1}s", len, 21 - len)
        mons[m][0] = String.format(format, name, "")
        mons[m][1] = days
        val dim = date.getActualMaximum(Calendar.DAY_OF_MONTH)
        for (d in 1..42) {
            val isDay = d > offs && d <= offs + dim
            val entry = if (isDay) String.format(" %2s", d - offs) else "   "
            if (d % 7 == 1)
                mons[m][2 + (d - 1) / 7] = entry
            else
                mons[m][2 + (d - 1) / 7] += entry
        }
        offs = (offs + dim) % 7
        date.add(Calendar.MONTH, 1)
    }

    printf("%" + (w / 2 + 10) + "s%n", "[Snoopy Picture]")
    printf("%" + (w / 2 + 4) + "s%n%n", year)

    for (r in 0 until nRows) {
        for (i in 0..7) {
            var c = r * nCols
            while (c < (r + 1) * nCols && c < 12) {
                printf("   %s", mons[c][i])
                c++
            }
            println()
        }
        println()
    }
}

fun main(args: Array<String>) {
    System.out.printCalendar(1969, 3, Locale.US)
}
Output:

See D output.

Liberty BASIC

rem Adapted from LB examples included with software
[start]
    prompt "Enter year(yyyy)?";year
    if year<1900 then notice "1900 or later":goto [start]
    ax=1:gx=8:ay=3:gy=10
    locate 52,1:print year
    for mr = 0 to 3
        for mc = 0 to 2
            mt=mt+1
            aDate$ = str$(mt)+"/01/"+str$(year)
            px = ax+mc*gx
            py = ay+mr*gy
            gosub [printout]
        next mc
    next mr
    gosub [snoopy]
end

[printout]
    locate 4*px-3+int((30-len(monthname$(aDate$)))/2),py
    print monthname$(aDate$)
    FirstDay=date$(word$(aDate$,1,"/")+"/1/"+word$(aDate$,3,"/"))
    LastDay$=date$(date$(word$(date$(FirstDay+32),1,"/")+"/1/"+word$(date$(FirstDay+32),3,"/"))-1)
    dow=val(word$("3 4 5 x 6 7 x 1 2",int((FirstDay/7-int(FirstDay/7))*10)+1))
    locate px*4-3, py+1
    print " Su Mo  Tu  We  Th  Fr  Sa"
    for i=1 to val(mid$(LastDay$,4,2))
        y=int((i+dow-2)/7)
        x=px+(i+dow-2)-y*7
        x=4*x
        locate x-4,py+y+2
        print using("###",i)
    next i
return

[snoopy]
    locate ax, ay+4*gy
    print space$(4*gx);"           ,-~~-.___."
    print space$(4*gx);"          / ()=(()   \"
    print space$(4*gx);"         (   (        0"
    print space$(4*gx);"          \._\, ,----'"
    print space$(4*gx);"     ##XXXxxxxxxx"
    print space$(4*gx);"            /  ---'~;"
    print space$(4*gx);"           /    /~|-"
    print space$(4*gx);"    _____=(   ~~  |______ "
    print space$(4*gx);"   /_____________________\ "
    print space$(4*gx);"  /_______________________\"
    print space$(4*gx);" /_________________________\"
    print space$(4*gx);"/___________________________\"
    print space$(4*gx);"   |____________________|"
    print space$(4*gx);"   |____________________|"
    print space$(4*gx);"   |____________________|"
    print space$(4*gx);"   |                    |"
return

function monthname$(aDate$)
    month=val(aDate$)
    monthname$=word$("January February March April May June July August September October November December",month)
end function
Output:
                                                   1969

           January                         February                         March
 Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa
             1   2   3   4                               1                               1
  5  6   7   8   9  10  11       2   3   4   5   6   7   8       2   3   4   5   6   7   8
 12 13  14  15  16  17  18       9  10  11  12  13  14  15       9  10  11  12  13  14  15
 19 20  21  22  23  24  25      16  17  18  19  20  21  22      16  17  18  19  20  21  22
 26 27  28  29  30  31          23  24  25  26  27  28          23  24  25  26  27  28  29
                                                                30  31


            April                            May                             June
 Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa
         1   2   3   4   5                       1   2   3       1   2   3   4   5   6   7
  6  7   8   9  10  11  12       4   5   6   7   8   9  10       8   9  10  11  12  13  14
 13 14  15  16  17  18  19      11  12  13  14  15  16  17      15  16  17  18  19  20  21
 20 21  22  23  24  25  26      18  19  20  21  22  23  24      22  23  24  25  26  27  28
 27 28  29  30                  25  26  27  28  29  30  31      29  30



             July                           August                        September
 Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa
         1   2   3   4   5                           1   2           1   2   3   4   5   6
  6  7   8   9  10  11  12       3   4   5   6   7   8   9       7   8   9  10  11  12  13
 13 14  15  16  17  18  19      10  11  12  13  14  15  16      14  15  16  17  18  19  20
 20 21  22  23  24  25  26      17  18  19  20  21  22  23      21  22  23  24  25  26  27
 27 28  29  30  31              24  25  26  27  28  29  30      28  29  30
                                31


           October                         November                        December
 Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa       Su Mo  Tu  We  Th  Fr  Sa
             1   2   3   4                               1           1   2   3   4   5   6
  5  6   7   8   9  10  11       2   3   4   5   6   7   8       7   8   9  10  11  12  13
 12 13  14  15  16  17  18       9  10  11  12  13  14  15      14  15  16  17  18  19  20
 19 20  21  22  23  24  25      16  17  18  19  20  21  22      21  22  23  24  25  26  27
 26 27  28  29  30  31          23  24  25  26  27  28  29      28  29  30  31
                                30


                                           ,-~~-.___.
                                          / ()=(()   \
                                         (   (        0
                                          \._\, ,----'
                                     ##XXXxxxxxxx
                                            /  ---'~;
                                           /    /~|-
                                    _____=(   ~~  |______
                                   /_____________________\
                                  /_______________________\
                                 /_________________________\
                                /___________________________\
                                   |____________________|
                                   |____________________|
                                   |____________________|
                                   |                    |

Lingo

----------------------------------------
-- @desc      Class "Calendar"
-- @file      parent script "Calendar"
----------------------------------------
property _months
property _weekdayStr
property _refDateObj
property _year
property _calStr

on new (me)
  me._months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
  me._weekdayStr = "Mo Tu We Th Fr Sa Su"
  me._refDateObj = date(1905,1,2)
  return me
end

on make (me, year)
  me._year = year
  me._calStr = ""
  -- prefill cal string with spaces
  emptyLine = bytearray(68,32).readRawString(68)&RETURN
  repeat with i = 1 to 38
    put emptyLine after _calStr
  end repeat
  me._write (string(year), 32, 1)
  repeat with i = 1 to 12
    me._writeMonth(i)
  end repeat
  return me._calStr
end

on _writeMonth (me, monthNum)
  xOffset = (monthNum-1) mod 3 * 24
  yOffset = (monthNum-1)/3 * 9 + 2
  pre = (20 - me._months[monthNum].length)/2
  me._write(me._months[monthNum], 1+xOffset+pre, 1+yOffset)
  me._write(me._weekdayStr, 1+xOffset, 2+yOffset)
  y = 3
  x = ((date(me._year, monthNum, 1) - me._refDateObj) mod 7)+1
  repeat with i = 1 to 31
    if date(me._year, monthNum, i).month<>monthNum then exit repeat
    dayStr = string(i)
    if i<10 then put " " before dayStr
    me._write(dayStr, x*3-2+xOffset, y+yOffset)
    if x=7 then
      y = y+1
      x = 1
    else
      x = x+1
    end if
  end repeat
end

on _write (me, str, x, y)
  put str into char x to x+str.length-1 of line y of _calStr
end

Usage:

calObj = script("Calendar").new()
calStr = calObj.make(1969)
put calStr
Output:
                               1969                                
                                                                    
      January                 February                 March     
Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
       1  2  3  4  5                    1  2                    1  2
 6  7  8  9 10 11 12     3  4  5  6  7  8  9     3  4  5  6  7  8  9
13 14 15 16 17 18 19    10 11 12 13 14 15 16    10 11 12 13 14 15 16
20 21 22 23 24 25 26    17 18 19 20 21 22 23    17 18 19 20 21 22 23
27 28 29 30 31          24 25 26 27 28          24 25 26 27 28 29 30
                                                31                 
                                                                    
       April                    May                     June     
Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6              1  2  3  4                       1
 7  8  9 10 11 12 13     5  6  7  8  9 10 11     2  3  4  5  6  7  8
14 15 16 17 18 19 20    12 13 14 15 16 17 18     9 10 11 12 13 14 15
21 22 23 24 25 26 27    19 20 21 22 23 24 25    16 17 18 19 20 21 22
28 29 30                26 27 28 29 30 31       23 24 25 26 27 28 29
                                                30                 
                                                                    
        July                   August                September   
Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
    1  2  3  4  5  6                 1  2  3     1  2  3  4  5  6  7
 7  8  9 10 11 12 13     4  5  6  7  8  9 10     8  9 10 11 12 13 14
14 15 16 17 18 19 20    11 12 13 14 15 16 17    15 16 17 18 19 20 21
21 22 23 24 25 26 27    18 19 20 21 22 23 24    22 23 24 25 26 27 28
28 29 30 31             25 26 27 28 29 30 31    29 30  
                                                                    
                                                                    
      October                 November                December   
Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su    Mo Tu We Th Fr Sa Su
       1  2  3  4  5                    1  2     1  2  3  4  5  6  7
 6  7  8  9 10 11 12     3  4  5  6  7  8  9     8  9 10 11 12 13 14
13 14 15 16 17 18 19    10 11 12 13 14 15 16    15 16 17 18 19 20 21
20 21 22 23 24 25 26    17 18 19 20 21 22 23    22 23 24 25 26 27 28
27 28 29 30 31          24 25 26 27 28 29 30    29 30 31

Lua

function print_cal(year)
  local months={"JANUARY","FEBRUARY","MARCH","APRIL","MAY","JUNE",
                "JULY","AUGUST","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"}
  local daysTitle="MO TU WE TH FR SA SU"
  local daysPerMonth={31,28,31,30,31,30,31,31,30,31,30,31}
  local startday=((year-1)*365+math.floor((year-1)/4)-math.floor((year-1)/100)+math.floor((year-1)/400))%7
  if year%4==0 and year%100~=0 or year%400==0 then
    daysPerMonth[2]=29
  end
  local sep=5
  local monthwidth=daysTitle:len()
  local calwidth=3*monthwidth+2*sep

  function center(str, width)
    local fill1=math.floor((width-str:len())/2)
    local fill2=width-str:len()-fill1
    return string.rep(" ",fill1)..str..string.rep(" ",fill2)
  end

  function makeMonth(name, skip,days)
    local cal={
      center(name,monthwidth),
      daysTitle
    }
    local curday=1-skip
    while #cal<9 do
      line={}
      for i=1,7 do
        if curday<1 or curday>days then
          line[i]="  "
        else
          line[i]=string.format("%2d",curday)
        end
        curday=curday+1
      end
      cal[#cal+1]=table.concat(line," ")
    end
    return cal
  end

  local calendar={}
  for i,month in ipairs(months) do
    local dpm=daysPerMonth[i]
    calendar[i]=makeMonth(month, startday, dpm)
    startday=(startday+dpm)%7
  end


  print(center("[SNOOPY]",calwidth),"\n")
  print(center("--- "..year.." ---",calwidth),"\n")

  for q=0,3 do
    for l=1,9 do
      line={}
      for m=1,3 do
        line[m]=calendar[q*3+m][l]
      end
      print(table.concat(line,string.rep(" ",sep)))
    end
  end
end

print_cal(1969)
Output:
                               [SNOOPY]                               

                             --- 1969 ---                             

      JANUARY                  FEBRUARY                  MARCH        
MO TU WE TH FR SA SU     MO TU WE TH FR SA SU     MO TU WE TH FR SA SU
       1  2  3  4  5                     1  2                     1  2
 6  7  8  9 10 11 12      3  4  5  6  7  8  9      3  4  5  6  7  8  9
13 14 15 16 17 18 19     10 11 12 13 14 15 16     10 11 12 13 14 15 16
20 21 22 23 24 25 26     17 18 19 20 21 22 23     17 18 19 20 21 22 23
27 28 29 30 31           24 25 26 27 28           24 25 26 27 28 29 30
                                                  31                  
                                                                      
       APRIL                     MAY                      JUNE        
MO TU WE TH FR SA SU     MO TU WE TH FR SA SU     MO TU WE TH FR SA SU
    1  2  3  4  5  6               1  2  3  4                        1
 7  8  9 10 11 12 13      5  6  7  8  9 10 11      2  3  4  5  6  7  8
14 15 16 17 18 19 20     12 13 14 15 16 17 18      9 10 11 12 13 14 15
21 22 23 24 25 26 27     19 20 21 22 23 24 25     16 17 18 19 20 21 22
28 29 30                 26 27 28 29 30 31        23 24 25 26 27 28 29
                                                  30                  
                                                                      
        JULY                    AUGUST                 SEPTEMBER      
MO TU WE TH FR SA SU     MO TU WE TH FR SA SU     MO TU WE TH FR SA SU
    1  2  3  4  5  6                  1  2  3      1  2  3  4  5  6  7
 7  8  9 10 11 12 13      4  5  6  7  8  9 10      8  9 10 11 12 13 14
14 15 16 17 18 19 20     11 12 13 14 15 16 17     15 16 17 18 19 20 21
21 22 23 24 25 26 27     18 19 20 21 22 23 24     22 23 24 25 26 27 28
28 29 30 31              25 26 27 28 29 30 31     29 30               
                                                                      
                                                                      
      OCTOBER                  NOVEMBER                 DECEMBER      
MO TU WE TH FR SA SU     MO TU WE TH FR SA SU     MO TU WE TH FR SA SU
       1  2  3  4  5                     1  2      1  2  3  4  5  6  7
 6  7  8  9 10 11 12      3  4  5  6  7  8  9      8  9 10 11 12 13 14
13 14 15 16 17 18 19     10 11 12 13 14 15 16     15 16 17 18 19 20 21
20 21 22 23 24 25 26     17 18 19 20 21 22 23     22 23 24 25 26 27 28
27 28 29 30 31           24 25 26 27 28 29 30     29 30 31            

M2000 Interpreter

Set console to 80 character by 43 lines. Produce calendar line by line, three columns of months.

To simulate printing in paper we print at the bottom line, so every new line scroll up the console screen.

Module Calendar get the Year and the Language Id (1033 for English, 1032 for Greek and any other which support the Window OS).

Module Calendar (Year, LocaleId) {
      Function GetMax(Year, Month) {
            a=date(str$(Year)+"-"+str$(Month)+"-1")
            max=32
            do {
                  max--
                  m=val(str$(cdate(a,0,0,max), "m"))  
            } until m=Month
             =max+1    
      }
      Function SkipMo(Year, Month) {
            a=date(str$(Year)+"-"+str$(Month)+"-1")
            =(val(str$(a, "w"))-8) mod 7 +7
      }
      Function Title$(a$) {
            =Ucase$(left$(a$,1))+Lcase$(Mid$(a$, 2))
      }
      locale LocaleId
      Cursor 0,Height-1  ' last line, so each new line scroll all lines up
      Print Over $(2), Year
      Print
      For j=0 to 3 {
            Print
            For i=1 to 3 {
                  Month=i+j*3      
                  Print  Part @((i-1)*29-1), $(2,22), Title$(Ucase$(locale$(55+Month)))
            }
            Print
            Dim Skip(1 to 3), Count(1 to 3), D(1 to 3)=1
            For i=1 to 3 {
                  Month=i+j*3
                  if i>1 Then Print String$(" ",8);
                  For k=42 to 48 :Print Title$(Ucase$(Left$(locale$(k),2)));" ";:Next k 
                  Skip(i)=SkipMo(Year, Month)
                  Count(i)=GetMax(Year, Month)
            }
            Print
            For i=1 to 3 {
                      if i>1 Then Print String$(" ",8);
                      For k=1 to 7 {
                        skip(i)--
                        if  skip(i)>0 Then Print "   "; :continue
                        Count(i)--
                        Print format$("{0::-2} ", d(i));
                        d(i)++
                  }
            }
            Print
            Print @(0)
            For m=1 to 5 {
                  For i=1 to 3 {
                        if i>1 Then Print String$(" ",8);
                        For k=1 to 7 {
                              Count(i)--
                              if Count(i)<0 Then Print "   "; : Continue
                              Print format$("{0::-2} ", d(i));
                              d(i)++
                        }
                  }
            Print 
            }
      }     
}
Form 80,43
Calendar 1969, 1033 ' English
k=Key$ ' wait key
Calendar 2018, 1032  ' Greek

Mathematica /Wolfram Language

Overview

This task seems to conflate 3 different programming challenges

1. Transforming a nested, hierarchical structure of data into linear presentation structure (a stream of printable characters)

2. Acquiring/generating the calendar data that will be presented (specifically, support for arbitrary years and for non-Gregorian schemes)

3. Constraining the presentation according to some configuration information (e.g. terminal width and ASCII character codes).

Additionally, there seems to be some implicit desire for presentation decoration options (e.g. grid dividers, spacing between elements, addition of a banner) My strategy is to separate these various domains into separate families of functions that can be composed to solve the problem of actually generating a specific calendar.

1. Grid structure functions

The following functions were motivated by the recognition that a calendar is really a set of nested grid structures. The overall calendar is just a few elements arranged in a column, e.g. banner, year/title, and a grid of months. The grid of months is a rectangular arrangement of individual months that are all styled similarly but vary a bit in the data they contain. Each month is a grid of 3 elements in a column: the month name as title, a header row of the names of the weekdays, and a grid of integers representing the specific days/dates. Each day is a cell that could be considered an atomic element or could be considered a 1x1 matrix. The latter allows for cells to have all of the styling options of the higher level grid elements.

At each level, a grid is "regular" in the sense that its elements are constrained to columns and rows with nothing "spilling out" of those columns/rows. By nesting the grids in various ways, you can achieve "non-regular" patterns.

Mathematica has many wrappers for handling the presentation of such structured and nested data, including Grid, TextGrid, Row, Column, and Multicolumn. However, these are just wrappers that a Mathematica Notebook knows how to present in the UI. You could apply Normal or ToString to these wrappers to get a true textual representation, but the results are not easily predictable and are extremely sensitive to the options you have included in the wrappers. Since we are going to want fine level control over spacing, alignment, borders, etc, using these wrappers would be, unfortunately, not very helpful for providing a plain, textual output.

Therefore, I've chosen to implement "raw" textual grids from scratch with a function called DataGrid. There are two versions of DataGrid. The first version allows you to specify exactly the height and width of each row and column in terms of number of string characters. The second version allows you to specify just the "macro" grid dimensions, and the actual size of each cell will be determined by calculating the width among cells in the same column and max height among cells in the same row. With those max values, it calls the first GridData function. The cell dimensions do not account for spacing and borders, which are added after the cell is scaled to the correct character dimensions. A variety of decoration options can also be supplied. The effect of these will be demonstrated in the examples below. Each version acts on a list of cells that are already rectangular arrays of data (though their dimensions obviously don't already need to be identical--that's part of the job of DataGrid, and it's why DataGrid can be composed/nested). Also, the list of cells is "flat", and its length does not need to conform to an exact rectangle (again, this is a job of DataGrid).

DataGrid[
  rowHeights:{__Integer},
  colWidths:{__Integer},
  spacings:{_Integer,_Integer},
  borderWidths:{{_Integer,_Integer},{_Integer,_Integer}},
  options_Association,
  data:{__List?MatrixQ}]:=
  With[
    (*Need to make sure we have sensible defaults for the decoration options.*)
    {alignment=Lookup[options,"alignment",{0,0}],
     background=Lookup[options,"background"," "],
     dividers=Lookup[options,"dividers",{" "," "," "}],
     border=Lookup[options,"border"," "],
     dims={Length[rowHeights],Length[colWidths]}},
    (*Pad the data so that it will fit into the specified rectangle (list of lists).*)
    With[{augmentedData=PadRight[data,Times@@dims,{{{background}}}]},
      (*Create a matrix of dimensions based on desired rectangle. Once we have a matrix of cells we can "thread" these two matrices and use that data to coerce each cell into its final dimensions.*)
      With[{cellDims=ArrayReshape[Outer[List,rowHeights,colWidths],{Times@@dims,2}]},
        (*MatrixAlign, defined below, rescales and aligns each cell's data.*)
        With[{undecoratedGrid=Partition[MapThread[MatrixAlign[alignment,#1,background][#2]&, {cellDims,augmentedData}],dims[[2]]]},
          (*Add the spacing to each row.*)
          With[{dividedRows=MapThread[Transpose[Riffle[#2,{ConstantArray[dividers[[2]],{#1,spacings[[2]]}]},{2,-2,2}]]&, {rowHeights,undecoratedGrid}]},
            (*Add the spacing between rows.*)
            With[{dividedColumn=Riffle[dividedRows,{Transpose[Riffle[ConstantArray[dividers[[1]],{spacings[[1]],#}]&/@colWidths,{ConstantArray[dividers[[3]],spacings]},{2,-2,2}]]},{2,-2,2}]},
              (*Assemble all cell rows into actual character rows. We now have one large matrix.*)
              With[{dividedGrid=Catenate[Map[Flatten,dividedColumn,{2}]]},
                (*Add borders.*)
                ArrayPad[dividedGrid,borderWidths,border]]]]]]]];

DataGrid[dims:{_Integer,_Integer},spacings_,borderWidths_,options_,data:{__List?MatrixQ}]:=
(*Calculate the max height for each row and max width for each column, and then just call the previous DataGrid function above.*)
  With[
    {rowHeights=Flatten@BlockMap[Max[Part[#,All,All,1]]&,ArrayReshape[Dimensions/@data,Append[dims,2],1],{1,dims[[2]]}],
     colWidths=Flatten@BlockMap[Max[Part[#,All,All,2]]&,ArrayReshape[Dimensions/@data,Append[dims,2],1],{dims[[1]],1}]},
    DataGrid[rowHeights,colWidths,spacings,borderWidths,options,data]];

(*This could probably be simplified, but I like having all of the aligment options explicit and separate for testability.*)
MatrixAlign[{-1,-1},dims_,pad_]:=PadRight[#,dims,pad]&;
MatrixAlign[{-1,0},dims_,pad_]:=PadRight[CenterArray[#,{Dimensions[#][[1]],dims[[2]]},pad],dims,pad]&;
MatrixAlign[{-1,1},dims_,pad_]:=PadRight[PadLeft[#,{Dimensions[#][[1]],dims[[2]]},pad],dims,pad]&;
MatrixAlign[{0,-1},dims_,pad_]:=CenterArray[PadRight[#,{Dimensions[#][[1]],dims[[2]]},pad],dims,pad]&;
MatrixAlign[{0,0},dims_,pad_]:=CenterArray[#,dims,pad]&;
MatrixAlign[{0,1},dims_,pad_]:=CenterArray[PadLeft[#,{Dimensions[#][[1]],dims[[2]]},pad],dims,pad]&;
MatrixAlign[{1,-1},dims_,pad_]:=PadLeft[PadRight[#,{Dimensions[#][[1]],dims[[2]]},pad],dims,pad]&;
MatrixAlign[{1,0},dims_,pad_]:=PadLeft[CenterArray[#,{Dimensions[#][[1]],dims[[2]]},pad],dims,pad]&;
MatrixAlign[{1,1},dims_,pad_]:=PadLeft[#,dims,pad]&;

(*While the grid functions make no assumptions about the format of the data, we will be using them with string/character data, and we will eventually want to output a calendar as a single large string. AsString gives us a standard method for transforming a matrix of characters into a string with rows delimited by newlines.*)
AsString[matrix_List?MatrixQ]:=StringRiffle[matrix,"\n",""];

To illustrate how DataGrid works, I'll create some sample data-grids and assemble them together into a larger data-grid, and then I'll use DataGrid itself to present all of these results. Notice the options for alignment, borders, spacings, etc.

DataA={{"A"}};
GridA = DataGrid[{2},{5},{0,0},{{1,1},{1,1}},<|"border"->"*","alignment"->{1,-1}|>,{DataA}];
DataB = {{"B"},{"B"}};
GridB = 
  DataGrid[{4,3},{4,8},{2,3},{{0,0},{0,0}},<|"background"->".","alignment"->{0,1},"dividers"->{"-","|","+"}|>,{DataB,DataB,DataB}];
GridC = 
  DataGrid[{2,3},{2,3},{{0,1},{2,3}},<|"border"->"@","alignment"->{0,0},"dividers"->{"/","/","/"}|>,{GridA,GridB,GridA,GridB,GridA}];

DataGrid[{2,3},{2,12},{{0,0},{0,0}},<||>,{{{"A"}},{{"B"}},{{"C"}},GridA,GridB,GridC}]//AsString
Output:
   A                      B                                          C                        
                                                                                              
                                                                                              
                                              @@               ///....|||........///       @@@
                                              @@               ///...B|||.......B///       @@@
                                              @@    *******    ///...B|||.......B///*******@@@
                                              @@    *     *    ///....|||........///*     *@@@
                                              @@    *A    *    ///----+++--------///*A    *@@@
                                              @@    *******    ///----+++--------///*******@@@
                   ....|||........            @@               ///...B|||........///       @@@
                   ...B|||.......B            @@               ///...B|||........///       @@@
*******            ...B|||.......B            @@               ///....|||........///       @@@
*     *            ....|||........            @@///////////////////////////////////////////@@@
*A    *            ----+++--------            @@///////////////////////////////////////////@@@
*******            ----+++--------            @@....|||........///               ///       @@@
                   ...B|||........            @@...B|||.......B///               ///       @@@
                   ...B|||........            @@...B|||.......B///    *******    ///       @@@
                   ....|||........            @@....|||........///    *     *    ///       @@@
                                              @@----+++--------///    *A    *    ///       @@@
                                              @@----+++--------///    *******    ///       @@@
                                              @@...B|||........///               ///       @@@
                                              @@...B|||........///               ///       @@@
                                              @@....|||........///               ///       @@@
                                              @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

HeaderGrid is a helper that defers the "gridding" of the individual header cells and data cells to functions that get passed in. There are some assumptions about presentation that feed into this design, but if your requirements differ, it's fairly easy to follow this same basic pattern of accepting functions that do the lower level work--you would just need to organize that work differently.

HeadedGrid[
  finalSpacings:{_Integer,_Integer},
  finalBorderWidths:{{_Integer,_Integer},{_Integer,_Integer}},
  finalOptions_Association,
  headerCellGridder_Function,
  rawHeaders:{__String},
  dataCellGridder_Function,
  rawData:{__String}]:=
  With[
    {finalDims={Ceiling[(Length@rawData+Length@rawHeaders)/Length@rawHeaders],Length@rawHeaders},
     headerCells=headerCellGridder/@List/@List/@Characters[rawHeaders],
     dataCells=dataCellGridder/@List/@List/@Characters[rawData]},
    DataGrid[finalDims,finalSpacings,finalBorderWidths,finalOptions,Join[headerCells,dataCells]]];

(*An example with a few decorations:*)
HeadedGrid[
  {1,1},
  {{1,1},{1,1}},
  <|"dividers"->{"-","|","+"},"border"->"*"|>,
  DataGrid[{3},{6},{0,0},{{0,0},{0,0}},<|"background"->"-"|>,#]&,
  {"Su","Mo","Tu","We","Th","Fr","Sa"},
  DataGrid[{3},{4},{0,0},{{0,0},{0,0}},<|"alignment"->{-1,1}|>,#]&,
  ToString/@Range[31]]//AsString
Output:
**************************************************
*------|------|------|------|------|------|------*
*--Su--|--Mo--|--Tu--|--We--|--Th--|--Fr--|--Sa--*
*------|------|------|------|------|------|------*
*------+------+------+------+------+------+------*
*    1 |    2 |    3 |    4 |    5 |    6 |    7 *
*      |      |      |      |      |      |      *
*      |      |      |      |      |      |      *
*------+------+------+------+------+------+------*
*    8 |    9 |   10 |   11 |   12 |   13 |   14 *
*      |      |      |      |      |      |      *
*      |      |      |      |      |      |      *
*------+------+------+------+------+------+------*
*   15 |   16 |   17 |   18 |   19 |   20 |   21 *
*      |      |      |      |      |      |      *
*      |      |      |      |      |      |      *
*------+------+------+------+------+------+------*
*   22 |   23 |   24 |   25 |   26 |   27 |   28 *
*      |      |      |      |      |      |      *
*      |      |      |      |      |      |      *
*------+------+------+------+------+------+------*
*   29 |   30 |   31 |      |      |      |      *
*      |      |      |      |      |      |      *
*      |      |      |      |      |      |      *
**************************************************

MonthGrid will use HeadedGrid for the main part of the month, and it will take another gridding function to handle the title (month name). I could have simplified the method signature by having the caller pass in a HeadedGrid function directly, but I liked exposing more control with this function. MonthGrid offers the convenience of specifying a leading offset for the month (month and week boundaries don't typically align, so this allows for starting on the correct day of the week without requiring the caller to munge the data).

MonthGrid[
  finalSpacings:{_Integer,_Integer},
  finalBorderWidths:{{_Integer,_Integer},{_Integer,_Integer}},
  finalOptions_Association,
  titleGridder_Function,
  monthName_String,
  monthSpacings:{_Integer,_Integer},
  monthBorderWidths:{{_Integer,_Integer},{_Integer,_Integer}},
  monthOptions_Association,
  headerCellGridder_Function,
  weekdayNames:{__String},
  dayCellGridder_Function,
  dayOffset_Integer,
  days:{__String}]:=
  DataGrid[{2,1},finalSpacings,finalBorderWidths,finalOptions,
    {titleGridder[List/@List/@Characters[monthName]],
    HeadedGrid[monthSpacings,monthBorderWidths,monthOptions,headerCellGridder,weekdayNames,dayCellGridder,ArrayPad[days,{dayOffset,0},""]]}];

(*A compact example:*)
MonthGrid[{0, 0}, {{0, 0}, {0, 0}}, <||>,
  DataGrid[{1, Length@#}, {0, 1}, {{1, 1}, {0, 0}}, <||>,ToUpperCase@#] &,
  "September", {0, 1}, {{0, 0}, {0, 0}}, <||>,
  DataGrid[{1}, {2}, {0, 0}, {{0, 0}, {0, 0}}, <||>, #] &,
  {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"},
  DataGrid[{1}, {2}, {0, 0}, {{0, 0}, {0, 0}}, <|"alignment" -> {-1, 1}|>, #] &,
  2, ToString /@ Range[31]]//AsString
Output:
                    
 S E P T E M B E R  
                    
Su Mo Tu We Th Fr Sa
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31      

At this point we have everything we need to generate a calendar. Since ASCII art is just a matrix of characters, we could trivially handle the display of the Snoopy banner. To make things more interesting, let's instead just display a magnified version of the calendar year as a banner.

FractalChars takes a string and rasterizes each character and then replaces each black pixel with the original character. The output is a matrix of characters suitable as a datum in the argument to DataGrid.

FractalChars[rasterSize_,font_,char_String/;1==StringLength[char]]:=
  ReplaceAll[ImageData[ImageCrop[Binarize[Rasterize[Style[char,FontFamily->font],RasterSize->rasterSize]]]],{1->" ",0->char}];
FractalChars[rasterSize_,font_,word_String]:=FractalChars[rasterSize,font,#]&/@Characters[word];

(*And here's the convenience function that ultimately calls DataGrid.*)
BannerGrid[finalSpacings_,finalBorderWidths_,finalOptions_,charGridder_Function,rasterSize_,text_String]:=
With[
  {charData=FractalChars[rasterSize,"Courier",text]},
  DataGrid[{1,StringLength@text},finalSpacings,finalBorderWidths,finalOptions,charGridder/@List/@charData]];

(*An example banner.*)
BannerGrid[{0,5},{{1,1},{0,0}},<|"border"->"X"|>,DataGrid[{1,1},{0,0},{{1,1},{0,0}},<||>,#]&,20,"1969"]//AsString
Output:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                                                                  
     11              9999                 666666         9999     
 1111111           99999999             66666666       99999999   
11111111          9999999999           66666666       9999999999  
11111111          9999   9999         6666            9999   9999 
     111         9999     999        6666            9999     999 
     111         999       999       666             999       999
     111         999       999      6666             999       999
     111         999       999      666              999       999
     111         999      9999      666 66666        999      9999
     111         9999     9999     66666666666       9999     9999
     111          9999   99999     666666666666       9999   99999
     111           99999999999     66666    6666       99999999999
     111            9999999999     6666      666        9999999999
     111              99   999     6666      666          99   999
     111                  9999     6666      666              9999
     111                  999      6666      666              999 
     111                 9999       666      666             9999 
     111                9999        6666    6666            9999  
 11111111111          99999          6666  6666           99999   
111111111111      99999999           6666666666       99999999    
111111111111     99999999              6666666       99999999     
                   99                     6            99         
                                                                  
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

2. Calendar data functions

Fortunately, Mathematica has many powerful calendar-related functions. Unfortunately, the semantics of calendars is sufficiently complicated that there is no practical way to create just a few standard data generators that will cover all of the implied cases. For example, the seemingly simple concept of "first day of the year" is ambiguous. The Jewish year uses one start day for religious holidays and another day for civil/secular purposes. The task definition referenced the switch-over from the Julian calendar to the Gregorian calendar, but this did not happen simultaneously around the globe, and many regions adopted the Gregorian calendar surprisingly recently. Assigning a number to a year is not standardized across calendars (e.g. consider the Chinese and Jewish calendars). The Cotsworth Calendar (effectively the International Fixed Calendar) has days that aren't assigned to any month or week--they're just dangling special days. So, we can provide some simple, general helper functions, but we assume that the calendar maker will be responsible for dealing with special cases.

(*Mathematica makes it easy to get month names and day names for several standard calendars.*)
MonthNames[]:=MonthNames["Gregorian"];
MonthNames[calType_]:=Lookup[CalendarData[calType,"PropertyAssociation"],"MonthNames"];

WeekdayNames[]:=WeekdayNames["Gregorian"];
WeekdayNames[calType_]:=Lookup[CalendarData[calType,"PropertyAssociation"],"DayNames"];

(*Since month boundaries don't align with week boundaries on most calendars, we need to pad month data with empty cells. I was tempted to create a function that would generate offsets for a given year on a given calendar, but even that required too many decisions on the part of the calendar maker to be feasible. So, I removed all of the calendar semantics. If you provide the fixed small group length (week length), the initial offset, and the list of the large group lengths (month lengths), this function will give you the offsets you need for each large group (month).*)
Offsets[groupLength_,firstOffset_,lengths_List]:=FoldPairList[{#1,Mod[#1+#2,groupLength]}&,firstOffset,lengths];

For the year 1969 on the Gregorian calendar, Mathematica can give us all of the data we need.

Data1969=GroupBy[Most[DateRange[DateObject[{1969,1,1}],DateObject[{1+1969,1,1}]]],DateValue[#,"MonthName"]&];
InitialOffset1969=QuantityMagnitude[DateDifference[PreviousDate[DateObject[{1969,1,1}],Sunday],DateObject[{1969,1,1}]]];
MonthLengths1969=Length/@Values[Data1969];
Offsets1969=Offsets[7,InitialOffset1969,MonthLengths1969];
MonthNames1969=Keys@Data1969;

YearBanner1969=BannerGrid[{0,0},{{1,1},{0,0}},<|"border"->"X"|>,DataGrid[{1,1},{1,1},{{1,1},{5,5}},<||>,#]&,13,"1969"];

MonthGrids1969=
  With[
    {monthNameGridder=DataGrid[{1,Length@#},{0,1},{{1,1},{0,0}},<||>,ToUpperCase@#]&,
     dayNameGridder=DataGrid[{1},{2},{0,0},{{0,0},{0,0}},<|"alignment"->{0,-1}|>,#]&,
     dayGridder=DataGrid[{1},{2},{0,0},{{0,0},{0,0}},<|"alignment"->{-1,1}|>,#]&},
    With[
      {monthGridder=
         MonthGrid[{0,0},{{0,0},{0,0}},<||>,monthNameGridder,#1,
         {0,1},{{0,0},{0,0}},<||>,dayNameGridder,WeekdayNames[],
         dayGridder,#2,#3]&},
      MapThread[monthGridder,{MonthNames1969,Offsets1969,Map[ToString,Range/@MonthLengths1969,{2}]}]]];

MonthsGrid1969=DataGrid[{3,4},{1,4},{{0,0},{0,0}},<||>,MonthGrids1969];

DataGrid[{2,1},{2,0},{{0,0},{0,0}},<||>,{YearBanner1969,MonthsGrid1969}]//AsString
Output:
        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX         
                                                                                            
                1                 99                    66              99                  
             11111              999999               66666            999999                
             11111             999  999             666              999  999               
                11             99    99            666               99    99               
                11             99    99            66                99    99               
                11             99    999          66                 99    999              
                11             999   999          6666666            999   999              
                11              99999999          66666666            99999999              
                11               999999           666   66             999999               
                11                   99           66    666                99               
                11                   99           66    666                99               
                11                  99             66   66                99                
             1111111              999              6666666              999                 
             11111111          99999                66666            99999                  
                                                                                            
        XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX         
                                                                                            
                                                                                            
                                                                                            
   J A N U A R Y          F E B R U A R Y            M A R C H               A P R I L      
                                                                                            
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
          1  2  3  4                       1                       1           1  2  3  4  5
 5  6  7  8  9 10 11     2  3  4  5  6  7  8     2  3  4  5  6  7  8     6  7  8  9 10 11 12
12 13 14 15 16 17 18     9 10 11 12 13 14 15     9 10 11 12 13 14 15    13 14 15 16 17 18 19
19 20 21 22 23 24 25    16 17 18 19 20 21 22    16 17 18 19 20 21 22    20 21 22 23 24 25 26
26 27 28 29 30 31       23 24 25 26 27 28       23 24 25 26 27 28 29    27 28 29 30         
                                                30 31                                       
                                                                                            
                                                                                            
       M A Y                  J U N E                 J U L Y               A U G U S T     
                                                                                            
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
             1  2  3     1  2  3  4  5  6  7           1  2  3  4  5                    1  2
 4  5  6  7  8  9 10     8  9 10 11 12 13 14     6  7  8  9 10 11 12     3  4  5  6  7  8  9
11 12 13 14 15 16 17    15 16 17 18 19 20 21    13 14 15 16 17 18 19    10 11 12 13 14 15 16
18 19 20 21 22 23 24    22 23 24 25 26 27 28    20 21 22 23 24 25 26    17 18 19 20 21 22 23
25 26 27 28 29 30 31    29 30                   27 28 29 30 31          24 25 26 27 28 29 30
                                                                        31                  
                                                                                            
                                                                                            
 S E P T E M B E R         O C T O B E R          N O V E M B E R         D E C E M B E R   
                                                                                            
Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6              1  2  3  4                       1        1  2  3  4  5  6
 7  8  9 10 11 12 13     5  6  7  8  9 10 11     2  3  4  5  6  7  8     7  8  9 10 11 12 13
14 15 16 17 18 19 20    12 13 14 15 16 17 18     9 10 11 12 13 14 15    14 15 16 17 18 19 20
21 22 23 24 25 26 27    19 20 21 22 23 24 25    16 17 18 19 20 21 22    21 22 23 24 25 26 27
28 29 30                26 27 28 29 30 31       23 24 25 26 27 28 29    28 29 30 31         
                                                30                                          

It's a bit more involved to create the data for a calendar that displays the switch from Julian to Gregorian. A few major European Catholic countries made the switch in 1582, so I'll generate data for that scenario. The switch happened in October, with Thursday Oct 4 being followed by Friday Oct 15. Mathematica provides tools to both fetch the calendar data and then fiddle with the structures so that I can highlight the relevant month and dates.

Dates1582France=
  Join[
    DateRange[DateObject[{1582,1,1},CalendarType->"Julian"],DateObject[{1582,10,4},CalendarType->"Julian"],CalendarType->"Julian"],
    DateRange[DateObject[{1582,10,15}],DateObject[{1582,12,31}]]];
Data1582France=GroupBy[Dates1582France,DateValue[#,"MonthName"]&];
InitialOffset1582France=
  QuantityMagnitude[DateDifference[
    PreviousDate[CalendarConvert[DateObject[{1582,1,1},CalendarType->"Julian"],"Gregorian"],Sunday],
    CalendarConvert[DateObject[{1582,1,1},CalendarType->"Julian"],"Gregorian"]]];
MonthLengths1582France=Length/@Values[Data1582France];
DatesByMonth1582France=Map[DateString[#,"DayShort"]&,Values[Data1582France],{2}];
Offsets1582France=Offsets[7,InitialOffset1582France,MonthLengths1582France];
MonthNames1582France=Keys@Data1582France;

With[
  {yearBanner=BannerGrid[{0,0},{{1,1},{0,0}},<|"border"->"X"|>,DataGrid[{1,1},{1,1},{{1,1},{5,5}},<||>,#]&,13,"1582"],
   monthNameGridder=DataGrid[{1,Length@#},{0,1},{{1,1},{0,0}},<||>,ToUpperCase@#]&,
   dayNameGridder=DataGrid[{1},{2},{0,0},{{0,0},{0,0}},<|"alignment"->{0,-1}|>,#]&,
   dayGridder=DataGrid[{1},{2},{0,0},{{0,0},{0,0}},<|"alignment"->{-1,1}|>,#]&},
  With[
    {monthGridder=MonthGrid[{0,0},{{0,0},{0,0}},<||>,monthNameGridder,#1,{0,1},{{0,0},{0,0}},<||>,dayNameGridder,WeekdayNames[],dayGridder,#2,#3]&},
    With[
      {monthCells=MapThread[monthGridder,{MonthNames1582France,Offsets1582France,Map[ToString,Range/@MonthLengths1582France,{2}]}],
       octoberCells=DataGrid[{1},{2},{0,0},Switch[#,"4"|"15",{{1,1},{1,1}},_,{{0,0},{0,0}}],<|"alignment"->{-1,1},"border"->"|","background"->Switch[#,"4"|"15","|",_," "]|>,{{Characters[#]}}]&/@ArrayPad[DatesByMonth1582France[[10]],{Offsets1582France[[10]],0},""]},
      With[
        {octoberGrid=
           DataGrid[{2,1},{0,0},{{1,1},{1,1}},<|"border"->"*"|>,
             {monthNameGridder[{{Characters[MonthNames1582France[[10]]]}}],
              DataGrid[{1+Ceiling[Length@octoberCells/7],7},{0,1},{{0,0},{0,0}},<|"alignment"->{0,0}|>,octoberCells]}]},
        DataGrid[{2,1},{2,0},{{0,0},{0,0}},<||>,{yearBanner,DataGrid[{3,4},{2,4},{{0,0},{0,0}},<|"alignment"->{-1,0}|>,ReplacePart[monthCells,10->octoberGrid]]}]]]]]//AsString
Output:
            XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX            
                                                                                                  
                    1              55555555              888                22                    
                 11111             5555555             888888             222222                  
                 11111             55                 888  888           222  222                 
                    11             55                 88    888          22    22                 
                    11             555555             88    888                22                 
                    11             55555555           888   88                 22                 
                    11              5   555            8888888                222                 
                    11                   555           888888                222                  
                    11                    55          8888 888              222                   
                    11                   555          88    888            222                    
                    11                   55           88    888           222                     
                    11             55555555           888   888          222    2                 
                 1111111           5555555             8888888           22222222                 
                 11111111                              888888            22222222                 
                                                                                                  
            XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX            
                                                                                                  
                                                                                                  
                                                                                                  
   J A N U A R Y             F E B R U A R Y               M A R C H               A P R I L      
                                                                                                  
Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6                    1  2  3                    1  2  3     1  2  3  4  5  6  7
 7  8  9 10 11 12 13        4  5  6  7  8  9 10        4  5  6  7  8  9 10     8  9 10 11 12 13 14
14 15 16 17 18 19 20       11 12 13 14 15 16 17       11 12 13 14 15 16 17    15 16 17 18 19 20 21
21 22 23 24 25 26 27       18 19 20 21 22 23 24       18 19 20 21 22 23 24    22 23 24 25 26 27 28
28 29 30 31                25 26 27 28                25 26 27 28 29 30 31    29 30               
                                                                                                  
                                                                                                  
                                                                                                  
       M A Y                     J U N E                    J U L Y               A U G U S T     
                                                                                                  
Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa       Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
       1  2  3  4  5                       1  2        1  2  3  4  5  6  7              1  2  3  4
 6  7  8  9 10 11 12        3  4  5  6  7  8  9        8  9 10 11 12 13 14     5  6  7  8  9 10 11
13 14 15 16 17 18 19       10 11 12 13 14 15 16       15 16 17 18 19 20 21    12 13 14 15 16 17 18
20 21 22 23 24 25 26       17 18 19 20 21 22 23       22 23 24 25 26 27 28    19 20 21 22 23 24 25
27 28 29 30 31             24 25 26 27 28 29 30       29 30 31                26 27 28 29 30 31   
                                                                                                  
                                                                                                  
                        **************************                                                
 S E P T E M B E R      *                        *      N O V E M B E R         D E C E M B E R   
                        *        OCTOBER         *                                                
Su Mo Tu We Th Fr Sa    *                        *    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
                   1    *            |||| ||||   *        1  2  3  4  5  6              1  2  3  4
 2  3  4  5  6  7  8    *    1  2  3 ||4| |15| 16*     7  8  9 10 11 12 13     5  6  7  8  9 10 11
 9 10 11 12 13 14 15    *            |||| ||||   *    14 15 16 17 18 19 20    12 13 14 15 16 17 18
16 17 18 19 20 21 22    *17 18 19 20  21   22  23*    21 22 23 24 25 26 27    19 20 21 22 23 24 25
23 24 25 26 27 28 29    *24 25 26 27  28   29  30*    28 29 30                26 27 28 29 30 31   
30                      *31                      *                                                
                        *                        *                                                
                        **************************                                                

3. Output configuration

The only remaining task is to automate the fitting of calendar data to a specified width. We could approach this in the most general way, which would allow us to fit an arbitrary data set into any pre-specified rectangle. This approach would need to actually iterate over the possible dimensions for the large scale grid, creating the grid each time and computing its dimensions. It would stop when it found dimensions that fit (for some definition of "fit"). Alternatively, if we pick a specific set of formatting options such that each month has a standard size, it's a simple matter of arithmetic to determine a grid that fits. I'm going to implement something closer to the more general approach, but only concerned with widths, and only doing the grid generation in an abstract way.

WidestFitDimensions will output data about the best fit it found, including the dimensions (which could be used in DataGrid) and the width of the best fit grid. There is no guarantee that a strict fit can be found, so it also provides the actual width of the best fit, and the user can determine what to do with the results. One could create another helper gridding function that uses WidestFitDimensions to automatically generate the grid, but I've already demonstrated such helper functions, so I'll just focus on computing the grid dimensions here.

WidestFitDimensions[
  targetWidth_Integer,
  columnSpacings_Integer,
  leftRightBorderWidths:{_Integer,_Integer},
  data:{__List?MatrixQ}]:=
  With[
    {widths=Last/@Dimensions/@data,
     fullWidthOfRow=Total[ArrayPad[Riffle[#,columnSpacings],leftRightBorderWidths,1]]&},
    With[
      {fullWidthOfGrid=Max[fullWidthOfRow/@#]&},
      With[
        {isTooLarge=(targetWidth<fullWidthOfGrid[#])&},
        With[
          {bestFitGrid=
             NestWhile[Partition[widths,-1+Last@Dimensions@#,-1+Last@Dimensions@#,{1,1},0]&,
               {widths},isTooLarge[#]&,1,-1+Length@widths]},
          <|"dimensions"->Dimensions@bestFitGrid,"width"->fullWidthOfGrid@bestFitGrid|>]]]];

Checking best fit for our 1969 calendar for a few widths.

(*Choose a best fit for our 1969 calendar data on 80 character wide display.*)
WidestFitDimensions[80,4,{0,0},MonthGrids1969]
Output:
<|dimensions->{4,3},width->68|>
(*Choose a best fit for our 1969 calendar data on 132 character wide display.*)
WidestFitDimensions[132,4,{0,0},MonthGrids1969]
Output:
<|dimensions->{3,5},width->116|>
(*Can we fit into a 20-character wide display?*)
WidestFitDimensions[20,4,{0,0},MonthGrids1969]
Output:
<|dimensions->{12,1},width->20|>

Nim

Translation of: D
import times
import strformat

proc printCalendar(year, nCols: int) =
  var rows = 12 div nCols
  var date = initDateTime(1, mJan, year, 0, 0, 0, utc())
  if rows mod nCols != 0:
    inc rows
  var offs = getDayOfWeek(date.monthday, date.month, date.year).int
  var mons: array[12, array[8, string]]
  for m in 0..11:
    mons[m][0] = &"{$date.month:^21}"
    mons[m][1] = " Su Mo Tu We Th Fr Sa"
    var dim = getDaysInMonth(date.month, date.year)
    for d in 1..42:
      var day = d > offs and d <= offs + dim
      var str = if day: &" {d-offs:2}" else: "   "
      mons[m][2 + (d - 1) div 7] &= str
    offs = (offs + dim) mod 7
    date = date + months(1)
  var snoopyString, yearString: string
  formatValue(snoopyString, "[Snoopy Picture]", "^" & $(nCols * 24 + 4))
  formatValue(yearString, $year, "^" & $(nCols * 24 + 4))
  echo snoopyString, "\n" , yearString, "\n"
  for r in 0..<rows:
    var s: array[8, string]
    for c in 0..<nCols:
      if r * nCols + c > 11:
        break
      for i, line in mons[r * nCols + c]:
        s[i] &= &"   {line}"
    for line in s:
      if line == "":
        break
      echo line
    echo ""

printCalendar(1969, 3)
Output:
                              [Snoopy Picture]                              
                                    1969                                    

          January                February                  March        
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
           1  2  3  4  5                    1  2                    1  2
     6  7  8  9 10 11 12     3  4  5  6  7  8  9     3  4  5  6  7  8  9
    13 14 15 16 17 18 19    10 11 12 13 14 15 16    10 11 12 13 14 15 16
    20 21 22 23 24 25 26    17 18 19 20 21 22 23    17 18 19 20 21 22 23
    27 28 29 30 31          24 25 26 27 28          24 25 26 27 28 29 30
                                                    31                  

           April                    May                    June         
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
        1  2  3  4  5  6              1  2  3  4                       1
     7  8  9 10 11 12 13     5  6  7  8  9 10 11     2  3  4  5  6  7  8
    14 15 16 17 18 19 20    12 13 14 15 16 17 18     9 10 11 12 13 14 15
    21 22 23 24 25 26 27    19 20 21 22 23 24 25    16 17 18 19 20 21 22
    28 29 30                26 27 28 29 30 31       23 24 25 26 27 28 29
                                                    30                  

           July                   August                 September      
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
        1  2  3  4  5  6                 1  2  3     1  2  3  4  5  6  7
     7  8  9 10 11 12 13     4  5  6  7  8  9 10     8  9 10 11 12 13 14
    14 15 16 17 18 19 20    11 12 13 14 15 16 17    15 16 17 18 19 20 21
    21 22 23 24 25 26 27    18 19 20 21 22 23 24    22 23 24 25 26 27 28
    28 29 30 31             25 26 27 28 29 30 31    29 30               
                                                                        

          October                November                December       
    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa    Su Mo Tu We Th Fr Sa
           1  2  3  4  5                    1  2     1  2  3  4  5  6  7
     6  7  8  9 10 11 12     3  4  5  6  7  8  9     8  9 10 11 12 13 14
    13 14 15 16 17 18 19    10 11 12 13 14 15 16    15 16 17 18 19 20 21
    20 21 22 23 24 25 26    17 18 19 20 21 22 23    22 23 24 25 26 27 28
    27 28 29 30 31          24 25 26 27 28 29 30    29 30 31            
                                                                        


OCaml

#load "unix.cma"

let lang = "en"  (* language: English *)

let usage () =
  Printf.printf "Usage:\n%s\n" Sys.argv.(0)

let month_pattern =
  [
    [  0;  4;  8 ];
    [  1;  5;  9 ];
    [  2;  6; 10 ];
    [  3;  7; 11 ];

    (*
    [  0;  1;  2;  3;  4;  5 ];
    [  6;  7;  8;  9; 10; 11 ];

    [  0;  1;  2;  3 ];
    [  4;  5;  6;  7 ];
    [  8;  9; 10; 11 ];
    *)
  ]

let month_langs = [
  "en", [|
    "January"; "February"; "March"; "April";
    "May"; "June"; "July"; "August"; "September";
    "October"; "November"; "December";
  |];
  "fr", [|
    "janvier"; "février"; "mars"; "avril"; "mai";
    "juin"; "juillet"; "août"; "septembre";
    "octobre"; "novembre"; "décembre";
  |];
]

let days_lang = [
  "en", [| "Monday"; "Tuesday"; "Wednesday";
    "Thursday"; "Friday"; "Saturday"; "Sunday" |];
  "fr", [| "lundi"; "mardi"; "mercredi";
    "jeudi"; "vendredi"; "samedi"; "dimanche" |];
]

let titles_lang = [
  "en", "( Snoopy's best pic )";
  "fr", "( Le meilleur profil de Snoopy )";
]

let days = List.assoc lang days_lang
let month = List.assoc lang month_langs
let title = List.assoc lang titles_lang

let monday_first = 6, [| 0; 1; 2; 3; 4; 5; 6 |]
let sunday_first = 0, [| 6; 0; 1; 2; 3; 4; 5 |]

let off, days_order = sunday_first
let off, days_order = monday_first


let shorten n s =
  let len = String.length s in
  if n >= len then s else
    let n = if s.[n-1] = '\xC3' then n+1 else n in
    if n >= len then s else
      (String.sub s 0 n)


let pad size c s =
  let len = String.length s in
  let n1 = (size - len) / 2 in
  let n2 = size - len - n1 in
  String.make n1 c ^ s ^
  String.make n2 c


let days = Array.map (shorten 2) days


let indices ofs =
  (ofs / 7, ofs mod 7)


let t_same t1 t2 =
  ( t1.Unix.tm_year = t2.Unix.tm_year &&
    t1.Unix.tm_mon  = t2.Unix.tm_mon &&
    t1.Unix.tm_mday = t2.Unix.tm_mday )


let current_year () =
  let t = Unix.localtime (Unix.time <