Recreational APL Making a Calendar
This article describes one way of making a calendar for any year in the Gregorian system, which began in 1582. When it was instituted, papal decree required that Thursday, October 4th, be followed by Friday, October 15th. Many people complained about the loss of the ten days from the calendar, and with good reason. Every debtor had the period to to next payment on the debt shortened by that amount. There were probably few creditors among those complaining about the loss of the ten days. The method for making a calendar described in this article is due to Howard Smith, of IBM. It contains elegant uses of the rotate and alternating sum functions. The first step is to create a matrix of twelve rows and forty-two columns, and containing the numbers one through forty-two in each row. The reason for the twelve rows should be obvious. The forty-two columns are needed because at a later point a reshaping of each row into a six by seven matrix will be required, since a month can span as many as six weeks. The columns are intended to correspond to the days of the week, beginning with Sunday, and continuing cyclically through the days of the week. 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 111111111111111111111111111111111111111111 The figure above shows schematically what we are starting with, where a 1 represents any non-zero number. Our next step will be to replace the unneeded digits at the end of each row by zeroes which can later be replaced with blanks. The zeroes will also serve a useful purpose when we rotate the rows to align the day numbers with the proper day of the week. The months have varying lengths, in a somewhat irregular pattern. The number of days in each month of a Gregorian year can’t be determined until it is known whether the year is a leap year or not. The leap year function ly is easily defined using an elegant inner product: ly:0≠.=4 100 400|⍵ where the parity of the number of Gregorian corrections serves to give the result 0 for common years and 1 for leap years. For example, ly 1980 1 Given ly , the function md which gives the number of days in each month is straightforward, exploiting as much of pattern as there is in the sequence: md:28+3,(ly⍵),10⍴5⍴3 2 md 1980 31 29 31 30 31 30 31 31 30 31 30 31 This can be used to create a mask for multiplying our original matrix by the expression: (md⍵)∘.≥⍳42 and when we perform the multiplication the original matrix pattern now shows zeroes at the end of each row as follows: 111111111111111111111111111111100000000000 111111111111111111111111111110000000000000 111111111111111111111111111111100000000000 111111111111111111111111111111000000000000 111111111111111111111111111111100000000000 111111111111111111111111111111000000000000 111111111111111111111111111111100000000000 111111111111111111111111111111100000000000 111111111111111111111111111111000000000000 111111111111111111111111111111100000000000 111111111111111111111111111111000000000000 111111111111111111111111111111100000000000 The next step is to rotate each row so that the day numbers are aligned with the columns representing the days of the week in the proper way. In order to be able to do this, we need to know with what day of the week each month begins. To know this, it suffices to know on what day of the week the year began. This is perhaps the most difficult part of the whole problem. If this is known, the rest, while not simple, is relatively straightforward. Imagine that there was a year 1 in the Gregorian calendar, and that it began on day 1. We don’t know what day of the week to associate with day 1 yet, but it will turn out that it is Monday, quit fortuitously. At any rate, if year 1 began on day 1, then since there were 365 days in year 1, that is, 52 weeks and a day, it follows that year 2 will begin on the next day following, that is, on day 2. This advancing of the day on which the year begins will continue evenly until year 5, and then, because of the Gregorian 4-year correction, year 4 will have had 366 days in it, or 52 weeks and 2 days, and thus year 5 will begin on day 6 rather than day 5. Since year 5 had 365 days, year 6 will begin on the next day cyclically following, or on day 0. For the years 1 through 100, the formula for the day of the week on which the year begins is the seven-residue of the year number added to the integer part of one less than the year number, divided by four. f:7|⍵+⌊(⍵-1)÷4 f⍳21 1 2 3 4 6 0 1 2 4 5 6 0 2 3 4 5 0 1 2 3 5 This combination of intercalation every fourth year and cycling of day numbers from 0 to 6, and then to 0 again, continues through year 100. The the second Gregorian correction, which says that every 100 years the 4-year correction doesn’t take place, comes into effect. Because of this, we find that year 101 begins on the day given by the seven-residue of the sum of 101, the year number, plus the number of intercalated four-year days, less one (for the hundred-year correction). The seven residue of 101+25-1 is 6 , and so we know that year 101 begins on the sixth day. This sequence continues until the year 401, adding 1 to the year number for each previous four years, but subtracting 1 for each previous hundred years. The last Gregorian correction now takes effect, which says that every 400th year shall be a leap year, and we thus have to compensate again. By the year 401 we have made 100 4-year corrections, four 100-year correction, and one 400-year correction. Thus in 400 years there are (365×400)+100-4-1 , or 146097 days. Since this number is divisible by 7, we are guaranteed that any Gregorian calendar dates exactly 400 years apart will fall on the same day of the week. The values 100, 4, and 1 in the above expression
can be computed from the
expression yb:7|⍵+-/⌊(⍵-1)÷4 100 400 The form of the function can be maintained
even if, as has been suggested by some calendar designers,
further corrections are added to the Gregorian scheme,
such as the 3200- and 86400-year corrections
described in my article in a recent issue of this magazine
[1].
We would simply extend the vector Since we have yb 1980 2 we know that the year 1980 begins on day 2, or Tuesday. We could adjust the first row of our matrix so that the number 1 is in the third column, which is the column of the matrix corresponding to the first Tuesday, by rotating the first row to the right by two positions, and thus aligning properly all the day numbers for January. We should like, however, to have all of the months properly aligned, so now let us pause a moment to see whether it’s not possible to adjust all of the rows at once. The amount by which each row is to be right-rotated is between 0 and 6, depending on which day of the week the first day of the month falls, with 0 for Sunday, 1 for Monday, , and 6 for Saturday. On performing the right rotation we see the utility of having zeroes at the right end of the matrix, since it is these that fill in the positions at the left end of the matrix. A sum scan of the number of days in each month will tell us how many days have elapsed from the beginning of the year to the beginning of year month. By adding the number of the day on which the year begins to each of these (except the last), and taking the 7-residue, we will get the number of the day of the week on which the first day of each month occurs. We can then use the negatives of these numbers to rotate all of the rows of our matrix at once. First we produce the rotation vector: -7|(yb 1980)+0,+\¯1↓md 1980 ¯2 ¯5 ¯6 ¯2 ¯4 0 ¯2 ¯5 ¯1 ¯3 ¯6 ¯1 and then use this to rotate the rows of our matrix, giving (schematically still): 001111111111111111111111111111111000000000 000001111111111111111111111111111100000000 000000111111111111111111111111111111100000 001111111111111111111111111111110000000000 000011111111111111111111111111111110000000 111111111111111111111111111111000000000000 001111111111111111111111111111111000000000 000001111111111111111111111111111111000000 011111111111111111111111111111100000000000 000111111111111111111111111111111100000000 000000111111111111111111111111111111000000 011111111111111111111111111111110000000000 This begins to look pretty good, and there shouldn’t
be many difficult problems ahead of us, but there will
be some interesting techniques used that will repay study.
There may be a spot of trouble for those readers who
don’t have access to a formatting primitive
that allows you to suppress zeroes.
I know of two ways to accomplish this.
One is with the extension to the ⍕
primitive discussed by Adin Falkoff in his paper
at the APL73 conference in Copenhagen
[2].
If this extended function were available to you,
you would merely have to write ' 55'⍕m
to format the matrix we now have
into a character matrix where each column of the argument
goes into three columns of the result,
and where zero entries in the argument are replaced
by blanks in the result.
The other way of achieving zero-suppression
is with the ⎕fmt function which is available
on some APL systems such as those offered by
I.P. Sharp Associates and Scientific Time Sharing Corporation.
There are undoubtedly others that I am not familiar with
(I’d like to hear about them).
With the ⎕fmt function we could
write After formatting the matrix (and suppressing zeroes by the way) we have a character matrix with 12 rows and 21 columns. Although I have seen calendars displayed in this form, it is much more usually the case that a calendar shows the individual months as 6-row, 7-column matrices. In terms of our character matrix, this means that we could achieve the proper form now by reshaping into a three-dimensional array having 12 planes, 6 rows, and 21 columns. To conserve space, I’ll exhibit here just the first three planes: 3 6 21⍴' 55'⍕m 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 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 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 To add captions for the days of the week, the matrix 12 21⍴' ',7 2⍴'SuMoTuWeThFrSa' can be catenated along the second dimension of the character matrix, at the top. Then to provide captions for the months, the matrix 12 21↑12 ¯12↑12 3⍴'JanFebMarAprMayJunJulAugSepOctNovDec' can be catenated, also along the second dimension, at the top. When this has been done, the first three planes of the array look like this: Jan 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 Feb 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 Mar 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 We now have to decide how we want the months of the calendar displayed. There are a number of conventional choices, which I shall schematically describe by various character matrices consisting of the letters 'JFMAMJJASOND' . J JF JFM JFMA JFMAMJ JFMAMJJASOND F MA AMJ MJJA JASOND M MJ JAS SOND A JA OND M SO J ND J A S O N D The number of rows in these choices are each of the divisors of twelve, namely 12, 6, 4, 3, 2, and 1. If our previous result is h , we can obtain the result of our choice by using the appropriate divisor of twelve in the following expression, in place of ⍺ : 2 1 3⍉(8,⍺,264÷⍺)⍴8 12 22↑2 1 3⍉h . To end this article, I’ll show the ‘4-row’ result for 1980: 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 5 1 2 1 6 7 8 9 10 11 12 3 4 5 6 7 8 9 2 3 4 5 6 7 8 13 14 15 16 17 18 19 10 11 12 13 14 15 16 9 10 11 12 13 14 15 20 21 22 23 24 25 26 17 18 19 20 21 22 23 16 17 18 19 20 21 22 27 28 29 30 31 24 25 26 27 28 29 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 The functions and expressions discussed in the article are gathered here for your convenience. ca:2 1 3⍉(8,⍺,264÷⍺)⍴8 12 22↑2 1 3⍉ (12 21↑12 ¯12↑12 3⍴'JanFebMarAprMayJunJulAugSepOctNovDec'),[2] (12 21⍴' ',7 2⍴'SuMoTuWeThFrSa'),[2]12 6 21⍴ ' 55'⍕(-7|(yb⍵)+0,+\¯1↓md⍵)⌽((md⍵)∘.≥⍳42)×12 42⍴⍳42 ⍝ calendar for Gregorian year ⍵, arranged so that months ⍝ are displayed ⍺ rows deep by 12÷⍺ wide. ⍝ ' 55'⍕ ←→ 'bi3'⎕fmt. yb:7|⍵+-/⌊(⍵-1)÷4 100 400 ⍝ day (0=Su,...,6=Sa) on which Gregorian year ⍵ begins md:28+3,(ly⍵),10⍴5⍴3 2 ⍝ number of days in the 12 months of Gregorian year ⍵ ly:0≠.=4 100 400|⍵ ⍝ result is 1 (0) if Gregorian year ⍵ is (is not) a leap year Howard Smith’s program is actually more general than the version I have described here, in the respect that it gives a valid calendar for any year of the Christian era, using the Julian calendar until October 4, 1582, and the Gregorian calendar thereafter. References
First appeared in APL Quote-Quad, Volume 10, Number 1, 1979-09. The text requires the APL385 Unicode font, which can be downloaded from http://www.vector.org.uk/resource/apl385.ttf . To resolve (or at least explain) problems with displaying APL characters see http://www.vector.org.uk/archive/display.htm .
|