In this piece I will tell you about my latest hackish script: Creating a PDF calendar with CHICKEN Scheme's cairo binding. This includes a nice detour about date arithmentic as well as unreadable code for the actual calendar generation.
Still interested? Good! Read on, my dear.
In the past the wife has made a family calendar for our wall in A3 format. This has been done manually (in Adobe Illustrator IIRC) and involved a lot of tedious repetitive entries of all holidays and anniversaries.
The argument has been that computer generated calendars look ugly. A quick research showed that, there are hardly any free software programs out there that would come close to the original.
So I started out to write up my own. What would I need for a calendar? Let‘s count:
- A date representation, that allows one to query the weekday of a given date, and getting to know the date for the easter sunday.
- A graphic backend that can render PDFs so I can go to a copy shop to print the A3 pages.
Not too much! From a previous (unfinished) project I had some bits and pieces for representing dates in a '(year month day) list structure, even possibly representing „partial“ dates as I called them. With partial dates one would leave off the right tail(s) for uncertain dates, i.e. when you want all entries from a given month you‘d use '(year month) and so on. But I digress…
I also implemented the weekday query already (since in said project I have implemented a date-picker widget) using Gauss‘ method. Searching for a method to calculate the date for easter sunday as a lot of german holidays depend on this, I stumbled again over Mr. Gauss for having found a method to properly calculate this date. I am using the enhanced method as told by wikipedia (german only, sorry).
In scheme that beast looks like this (almost verbatim taken from wikipedia):
(define (easter-sunday date)
(let* ((x (first date))
(k (quotient x 100))
(m (- (+ 15 (quotient (+ (* 3 k) 3) 4))
(quotient (+ (* 8 k) 13) 25)))
(s (- 2 (quotient (+ (* 3 k) 3) 4) ))
(a (modulo x 19))
(d (modulo (+ (* 19 a) m) 30))
(r (quotient
(quotient (+ d a) 11)
29))
(og (- (+ 21 d) r))
(sz (- 7 (modulo (+ x (quotient x 4) s) 7)))
(oe (- 7 (modulo (- og sz) 7)))
(os (+ og oe)))
(if (< os 32)
(list x 3 os)
(list x 4 (modulo os 31)))))
The only adjustment I have made is that I return a proper date and not March 32nd for April 1st.
So the essential holidays can be created like this:
;; the name is a bit misleading, it should be called 'days depending ;; on the easter date' but that's also a dumb name (define (roman-catholic-holidays date) (let* ((y (first date)) (es (easter-sunday date)) (christmas-day (day-of-week `(,y 12 24))) (4th-advent-sunday (if (= christmas-day 6) `(,y 12 24) (days+ `(,y 12 24) (- (add1 christmas-day)))))) `(((,y 1 6) . "Heilig. Drei Könige") (,(days+ es -52) . "Weiberfastnacht") (,(days+ es -48) . "Rosenmontag") (,(days+ es -47) . "Faschingsdienstag") (,(days+ es -46) . "Aschermittwoch") (,(days+ es -7) . "Palmsonntag") (,(days+ es -3) . "Gründonnerstag") (,(days+ es -2) . "Karfreitag") (,es . "Ostersonntag") (,(days+ es 1) . "Ostermontag") (,(days+ es 39) . "Christi Himmelfahrt") (,(days+ es 49) . "Pfingstsonntag") (,(days+ es 50) . "Pfingstmontag") (,(days+ es 60) . "Fronleichnam") (,(days+ 4th-advent-sunday -21) . "1. Advent") (,(days+ 4th-advent-sunday -14) . "2. Advent") (,(days+ 4th-advent-sunday -7) . "3. Advent") (,4th-advent-sunday . "4. Advent") ((,y 12 24) . "Heiligabend") ((,y 12 25) . "1. Weihnachtsfeiertag") ((,y 12 26) . "2. Weihnachtsfeiertag"))))
(Not all of these are bank holidays, but I have included them for good measure, after I got through all this trouble…)
You may note that this may contain more than one entry for a given date. The code recognises this by „querying“ with filter-map instead of alist-ref and concatenating results in a proper way.
So with all this we can think about drawing stuff. In CHICKEN Scheme I have a nice binding to the cairo library available, that does support PDF surfaces, so let‘s use this.
As for drawing primitives I need a rectangle and some nice TTF font rendering, both easily available.
As I had a nice layout to copy from, writing the resulting calendar has been a breeze.
The result looks like this:
See the full image for further details.
The font used in this example is the nice EB Garamond by Georg Duffner.
The code includes a second mechanism for anniversaries/birthdays which have been omitted in this screenshot.
Things you will have to change to make it work for yourself:
- Localisation of weekday names and months
- Check the holidays for your location, even for Germany these differ wildly from state to state
- Adjust the layout to your needs
- Another layout for different paper sizes
- Adjust the global year variable
- A currently broken idea is the display of moon phases for fun. Calculating moon phases is another problem altogether I have learned…
If you are willing to do all this, then download cal.tgz (142KB) and run it with „csi -s cal.scm“ to get a „calendar-2017.pdf“.
If you have cool ideas, I am looking forward to reading about them (or patches!).