## Cal

### January 1, 2010

The Standard Prelude provides functions `julian`, `gregorian` and `today` to handle the date arithmetic, so this is primarily an exercise in formatting. A month is 8 rows high and 22 columns wide (including empty columns at left and right to possibly hold an angle bracket for the current date); a three-month calendar is 66 columns wide and a twelve-month calendar is 35 rows long:

```     January 2010
Su Mo Tu We Th Fr Sa
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```

Many of the functions need to know the current year, month and day. We store them in global variables `year`, `month` and `day`, and set them once in the call to `cal`:

```(define year #f) (define month #f) (define day #f)```

```(define (today-set!)   (let ((t (today)))     (let-values (((y m d) (gregorian t)))       (set! year y)       (set! month m)       (set! day d))))```

We begin at the end with the function that parses the arguments and prints the desired calendar:

```(define (cal . args)   (today-set!)   (cond ((null? args) (display-month year month))         ((and (number? (car args)) (= (car args) -3))           (display-three             (if (= month 1)                 (make-month (- year 1) 12)                 (make-month year (- month 1)))             (make-month year month)             (if (= month 12)                 (make-month (+ year 1) 1)                 (make-month year (+ month 1)))))         ((= (length args) 1) (display-year (car args)))         (else (display-month (cadr args) (car args)))))```

There are three functions that print one month, three months, and twelve months. We’ll be representing a month as a string with 22 × 8 = 176 characters (there are twenty-two print columns and eight rows in a month) and slicing it as needed for display. Here is the function to print a single month:

```(define (display-month y m)   (let ((month-string (make-month y m)))     (do ((i 0 (+ i 1))) ((= i 8))       (display (substring month-string (* i 22) (* (+ i 1) 22)))       (newline))))```

The function to print three months side-by-side is similar:

```(define (display-three m1 m2 m3)   (do ((i 0 (+ i 1))) ((= i 8))     (display (substring m1 (* i 22) (* (+ i 1) 22)))     (display (substring m2 (* i 22) (* (+ i 1) 22)))     (display (substring m3 (* i 22) (* (+ i 1) 22)))     (newline)))```

To print a year, we call the function to print three months side-by-side four times:

```(define (display-year y)   (display-three (make-month y 1) (make-month y 2) (make-month y 3))   (newline)   (display-three (make-month y 4) (make-month y 5) (make-month y 6))   (newline)   (display-three (make-month y 7) (make-month y 8) (make-month y 9))   (newline)   (display-three (make-month y 10) (make-month y 11) (make-month y 12)))```

All that’s left is the function that forms a month-string for a given month. It’s a complicated function, dealing with both the logical calendar and the physical representation on the printed page.

On the logical side, variable n is the number of days in the month, variable b is the number of “extra” days on the calendar before the start of the month, and variable e is the number of extra days on the calendar after the end of the month. N is calculated by subtracting the julian number at the beginning of the current month from the julian number at the beginning of the succeeding month. B is calculated from the day of the week by taking the julian number at the beginning of the current month modulo seven. E is calculated from the relationship b + n + e = 42, which is 6 rows times 7 days per week.

On the physical side, variable r is the number of the current row (the month and year name are on row 0, the day names are on row 1, and the six rows of the calendar are rows 2 through 7), variable c is the number of the “cell” across the calendar from 0 through 6, and variable s is the offset into the string of the left-most digit position of a particular calendar cell. S is calculated as r × 22 + c × 3 + 1.

The logical and physical sides of the calendar are related by two equations that calculate r and c given b and i, where i indexes through the days of the month from 1 to n. R is given by (b + i – 1) / 7 + 2, where the division is integer division, and c is given by (b + i – 1) modulo 7. As a practical matter, we don’t really need e, and it is easier to calculate s directly from b and i than to make the intermediate calculations of r and c. All of these formulas can be derived with a little bit of effort from the basic structure of a calendar month.

`Make-month` takes a month and year and returns a month-string suitable for printing by the three display functions. It’s not hard, as long as you keep the equations described above firmly in mind:

```(define (make-month y m)   (define (s b i)     (+ (* (+ (quotient (+ b i -1) 7) 2) 22)        (* (modulo (+ b i -1) 7) 3) 1))   (define (r-just n)     (if (< n 10) (string-append " " (number->string n)) (number->string n)))   (let* ((str (make-string 176 #\space))          (j (julian y m 1))          (n (- (julian (if (= m 12) (+ y 1) y) (if (= m 12) 1 (+ m 1)) 1) j))          (b (modulo (+ j 1) 7)))     (string-put! str 0       (center 22 (string-append (month-name m) " " (number->string y))))     (string-put! str 22 " Su Mo Tu We Th Fr Sa ")     (do ((i 1 (+ i 1))) ((> i n))       (string-put! str (s b i) (r-just i))       (when (and (= y year) (= m month) (= i day))         (string-set! str (- (s b i) 1) #\<)         (string-set! str (+ (s b i) 2) #\>)))     str))```

We need a few helper functions. `String-put!` is like `string-set!`, but takes a string instead of a single character. `Month-name` returns a spelled-out month name given a month number. `Center` centers a substring in a larger string.

```(define (string-put! str k substr)   (do ((i 0 (+ i 1)) (k k (+ k 1)))       ((= i (string-length substr)) str)     (string-set! str k (string-ref substr i))))```

```(define (month-name m)   (vector-ref (vector "January" "February" "March" "April" "May" "June"     "July" "August" "September" "October" "November" "December") (- m 1)))```

```(define (center wid str)   (let* ((len (string-length str))          (lpad (quotient (- wid len) 2))          (rpad (- wid len lpad)))     (if (< wid len)         (substr str 0 wid)         (string-append           (make-string lpad #\space) str (make-string rpad #\space)))))```

And that’s it. Here is the calendar for 2010:

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

April 2010             May 2010             June 2010
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         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 2010            August 2010          September 2010
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
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 2010         November 2010         December 2010
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
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                                                               ```

You can run the program at http://programmingpraxis.codepad.org/5T41NUut.

Pages: 1 2

### 5 Responses to “Cal”

1. […] today’s Programming Praxis exercise we have to implement the Unix utility cal, which prints calendars. […]

2. Remco Niemeijer said

```import Data.List
import Data.List.Split
import qualified Data.Text as T
import Data.Time
import System.Environment
import System.Locale

days :: Integer -> Int -> [Day]
days y m = map (fromGregorian y m) [1..gregorianMonthLength y m]

fmt :: FormatTime t => String -> t -> String
fmt = formatTime defaultTimeLocale

monthCal :: Integer -> Int -> String
monthCal y m = unlines \$ (T.unpack . (T.center 20 ' ') . T.pack .
fmt "%B %Y" \$ fromGregorian y m 1) : "Su Mo Tu We Th Fr Sa" :
(map unwords . take 6 . chunk 7 \$
replicate (read . fmt "%w" . head \$ days y m) "  " ++
map (fmt "%e") (days y m) ++ repeat "  ")

showCal :: [String] -> IO ()
showCal = putStrLn . unlines . map (unlines . map
(intercalate "  ") . transpose . map lines) . chunk 3

surround :: UTCTime -> [String]
surround d = map ((\(y, m, _) -> monthCal y m) . toGregorian .

main :: IO ()
main = do args <- getArgs
now  <- getCurrentTime
let (curYear, curMonth) = read \$ fmt "(%Y,%m)" now
case args of
["-3"] -> showCal \$ surround now
[y]    -> showCal \$ map (monthCal \$ read y) [1..12]
[]     -> showCal [monthCal curYear curMonth]
_      -> error "Invlaid parameters"
```
3. johnwcowan said

There’s a bug here: compare the output of your cal with the standard one (either BSD or GNU) on September 1752 or any earlier date.

“Give us back our eleven days!”

C++ style

#include
#include
#include
using namespace std;

const int MONTHS_IN_YEAR = 12;
const int DAYS_IN_WEEK = 7;
const int MAX_DAYS_IN_MONTH = 31;
const int MIN_DAYS_IN_MONTH = 28;
const int LEAP_YEAR_FACTOR = 4;
const int DAY_WIDTH = 3;

const int JAN = 1;
const int FEB = 2;
const int MAR = 3;
const int APR = 4;
const int MAY = 5;
const int JUN = 6;
const int JUL = 7;
const int AUG = 8;
const int SEP = 9;
const int OCT = 10;
const int NOV = 11;
const int DEC = 12;

string MonthString(int month);
void PrintMonthBody(int year, int month, int& day);
void PrintMonthDates(int& day, int lastday);
void PrintSelectedMonths(int year, int start_month,
int day, int total_months);

int main()
{
int month, totalMonths, startDay, year;

cout <> year;

while (year != 0)
{
cout <> month;
cout <> totalMonths;
cout << "What day of the week does the starting month fall on?\n";
cout <> startDay;
PrintSelectedMonths(year, month, startDay, totalMonths);
cout <> year;
}
return 0;
}

//———————————————————————–
// This function uses the integer month to print out that months name.
// It also prints out the one letter abbreviations for Sunday
// through Saturday and the underscores under them.
// Parameters: (in)
//———————————————————————–
{
cout << "\n" << " " << MonthString(month) << '\n';
cout << " S M T W T F S\n";
cout < 0)
{
if (day == DAYS_IN_WEEK)
daySpaces = 0;
else
{
cout << " ";
daySpaces–;
}
}
while (daysInMonth <= lastday)
{
if (day % DAYS_IN_WEEK == 0 && daysInMonth != 1)
{
cout << "\n";
day = 0;
}

cout << setw(DAY_WIDTH) << daysInMonth;
daysInMonth++;
day++;
}
cout << endl;
return;
}

//———————————————————————–
// This function prints out the calendar for the given number
// of months (total_months), the given year, using the
// start_month and starting day (day). The function repeatedly
// Parameters: (in, in, in, in)
//———————————————————————–
void PrintSelectedMonths(int year, int start_month,
int day, int total_months)
{
int monthCount = 0;
cout << "\n\n" << " " << year << "\n" ;
while(monthCount < total_months)
{
PrintMonthBody(year, start_month, day);
if (start_month % 12 == 0 && total_months != 12)
{
year++;
cout << "\n\n\n" << " " << year << "\n" ;
monthCount = 1;
start_month = 0;
}
monthCount++;
start_month++;
}
return;
}

5. Mike said
```import calendar
import datetime as dt
import itertools as it
import re

calendar.setfirstweekday(calendar.SUNDAY)

def onemonth( month=None, year=None ):
today = dt.date.today()
month = month or today.month
year = year or today.year

mstring = re.sub( r"\n", r" \n ",  calendar.month( year, month ) )

if year == today.year and month == today.month:
mstring = re.sub( r" (%2s) "%today.day, r"[\1]", mstring )

return mstring.splitlines()

def threemonth( month=None, year=None ):
today = dt.date.today()
month = month or today.month
year = year or today.year

m1 = onemonth( month-1, year ) if month > 1 else onemonth( 12, year-1 )
m2 = onemonth( month,   year )
m3 = onemonth( month+1, year ) if month < 12 else onemonth( 1, year+1 )

fmt = "{0:22}{1:22}{2:22}".format
return it.starmap( fmt, it.izip_longest( m1, m2, m3, fillvalue='' ) )

def twelvemonths(year=None):
return it.chain( *( threemonth( m, year ) for m in (2, 5, 8, 11) ) )

def cal( *args ):
if len( args ) == 1:
year = int( args )
if year == -3:
rows = threemonth()
else:
rows = twelvemonths( year )
else:
rows = onemonth( *map( int, args ) )

return '\n'.join( rows )

if __name__ == '__main__':
from sys import argv
print cal( *argv[1:] )

```
{"ft_lang_label":"__label__en","ft_lang_prob":0.63079786,"math_prob":0.974782,"size":10254,"snap":"2022-27-2022-33","text_gpt3_token_len":3389,"char_repetition_ratio":0.14058536,"word_repetition_ratio":0.18531129,"special_character_ratio":0.40910864,"punctuation_ratio":0.080756806,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9766268,"pos_list":[0],"im_url_duplicate_count":[null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2022-07-03T05:08:31Z\",\"WARC-Record-ID\":\"<urn:uuid:c7c2756f-de74-4d15-b2fa-c407d673415b>\",\"Content-Length\":\"127581\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:78ff3add-4750-4992-9c35-9bb5f636ae8f>\",\"WARC-Concurrent-To\":\"<urn:uuid:3da04fc7-d050-4d4e-9e3c-817311049c40>\",\"WARC-IP-Address\":\"192.0.78.24\",\"WARC-Target-URI\":\"https://programmingpraxis.com/2010/01/01/cal-2/2/\",\"WARC-Payload-Digest\":\"sha1:XWE5T4OYWU5FA326EKTH6HIF35R6HQX6\",\"WARC-Block-Digest\":\"sha1:SW553ETCJ6WTDJR3KNZJZH4VD47XLN4Y\",\"WARC-Identified-Payload-Type\":\"application/xhtml+xml\",\"warc_filename\":\"/cc_download/warc_2022/CC-MAIN-2022-27/CC-MAIN-2022-27_segments_1656104215790.65_warc_CC-MAIN-20220703043548-20220703073548-00667.warc.gz\"}"}