Physicist, Emacser, Digitales Spielkind

Managing calendar events in Emacs
Published on Sep 17, 2021.

Whether at home or at work, much of my usual work flow revolves around my Emacs setup (and many, many open browser tabs). Until recently, I also switched occasionally to Thunderbird for my emails – but now, I mostly use mu4e to interact even with mail within Emacs. Some rough edges in the configuration aside, I am really loving the tighter integration with my org-mode notes and my familiar key chords.

However, I have been missing the other functionality that I used Thunderbird for, namely accessing CalDAV calendars and a CardDAV address book. So why not integrate those in Emacs as well? Today, I will start with listing, editing and creating calendar events from within Emacs!

Where to start

Whenever one has a problem to solve, someone else has probably already undertaken at least the first steps. Which is nice, as it turns out that getting calendar event handling right is not so easy.

There is org-caldav which seems to fit the bill perfectly: it offers direct CalDAV sync for org-mode. As the synchronization is handled within org-caldav itself, it needs to deal with conflict handling, deletions and storage of sync information internally. I was worried that any misconfiguration or user mistake could lead to loss of data (and missed meetings) and instead opted to set up a tool chain composed of specialized tools for each step.

Building on nifty tools the Unix way

The first building block is vdirsyncer, a command-line tool capable of synchronizing calendars and address books between a variety of servers and the local file system. It stores calendars locally in directories where each event is recorded in a single .ics file. These are human-readable plain text files containing all fields and their data. Interaction with the remote server only happens when triggered by running vdirsyncer. This makes it easy to track changes or make backups and safe to experiment with.

To interact with the local calendar and parse the data therein, another tool comes in handy: khal. This Python-based command allows to list, edit and create events either directly from the command-line or via an interactive text-based interface. khal is somewhat limited in what it can handle (in terms of supported fields in calendar events, for example), but for my purposes of managing one shared and one personal calendar it was ideal.

Adjusting the default upcoming event listing of khal slightly, one can quite easily get something that already resembles the org-mode syntax:

khal list --format "* {title} \n <{start-date} {start-time}-{end-time}> \n {location} \n {description}" --day-format "" today 10d

The output above shows the events in the coming 10 days from now. This gives us something to work with and means we don’t have to touch the .ics files directly.

Last but not least, org-mode comes with export functions to ics with many configurable options: org-icalendar-export-to-ics. This makes it possible to create new events from org-mode, export to ics and import to the local calendar store using khal import.

So I started playing with elisp to tie these components together and out came my very first Emacs package: khalel!


khalel provides a relatively thin wrapper around the above mentioned tools allowing to list, edit and create calendar events from within Emacs: Once installed, you can import upcoming events through M-x khalel-import-upcoming-events into an org-mode file, edit individual events with M-x khalel-edit-calendar-event or create new ones by starting org-capture (C-c c or M-x org-capture) and pressing e (default key) for a new calendar event. To synchronize with remote calendars simply call M-x khalel-run-vdirsyncer.

Along the way I discovered how easy org-mode makes it to wrap elisp code: If you visit the org file with the imported events, you will notice links below each event. Using these you can directly edit the corresponding calendar entry with a single click or trigger a synchronization and import. Sweet! :)

This is how the resulting org-mode agenda and the calendar file with the imported events looks like: 210917_khalel_screenshot.png

Now I have have all my events at hand and where they belong: in the org agenda :)

If you want to give the (rather young) code a shot, you can find it here: https://gitlab.com/hperrey/khalel. The code has also been submitted to melpa and awaits a review (which I am quite excited about!).

In the khalel repository you will also find more details on the configuration of vdirsyncer, khal and khalel as well as on the inherent limitations of this approach. If you do try this out, please share your experience with me! :)

Tags: emacs, pim, programming, lisp