New year's todos: Making a PDF calendar with cairo

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:

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:

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!).

Code on this site is licensed under a 2 clause BSD license, everything else unless noted otherwise is licensed under a CreativeCommonsAttribution-ShareAlike3.0UnportedLicense