Emacs and Caldav

Menu

This post summarizes my attempts to manage my CalDAV based calendars in Emacs.

Managing your Calendar/Diary in Emacs

Emacs has three main packages for managing a diary:

  • Diary Mode ships with Emacs and allows one to manage an agenda stored as a plain text file on the computer. It has many interesting features and I used it in the past, but it lacks any of the sharing and syncing functions we are used to.
  • OrgMode ships with M-x org-agenda, a command that collects time-stamped information from your Org mode files and present them in an agenda view. By default Org mode ships with function to sync files among different devices, but it does not provide any support for external protocols, such as CalDAV (extensions exist, though).
  • Calfw is a calendar viewing framework with extensions to sync with CalDAV-based calendars.

Starting Point and Goals

Starting point: you are managing your appointments with Thunderbird Lightning or another CalDAV capable app and store them on various cloud services, such as Google Calendar, NextCloud, and iCloud.

Primary goal: you want to use Emacs to view your CalDAV calendars. (Spoiler: this is easy!)

Secondary goal: you want to be able to modify your events directly from Emacs and sync them on the server (Spoiler: more difficult).

Viewing your CalDAV Calendars in Emacs

Solution 1. Calfw

Calfw is a framework which presents a calendar in a way similar any modern calendaring application. There are of course some limitations in the experience and interaction, mainly related to the Calendar views being rendered in pure text.

The package comes with extensions meant to integrate with different back-ends, including iCal. This makes it possible to download a copy of your CalDAV calendars locally and then display it using Emacs.

To use it, install the required packages:

M-x package-install calfw 
M-x package-install calfw-ical

and then add to your .emacs, replacing the URL with the URL of your iCal calendar; see What is the iCal/CalDav URL of my calendar? for determining the URL of your iCal calendar:

(require 'calfw)
(require 'calfw-ical)
(cfw:open-ical-calendar "http://SERVER/.../basic.ics")

You can now display your calendar with:

M-x cfw:open-calendar-buffer

The Calfw repository is very well documented and provides detailed instructions on how to configure the package to display different calendars.

In synthesis, it is a very nice solution, but it does not work for me, since I prefer to use Org Mode and Diary to display my agenda in Emacs. Another limitation is that the week and month view might be cumbersome to use, if your calendars have many events, since there is only so much you can do with a textual table. In any other respect, it is impressive.

Solution 2. Org CalDAV

org-caldav provides two-way synchronization with CalDAV servers. It downloads calendars as Org Mode documents, creating one header per event.

The package uses an ID property to maintain the correspondence between the events in the Org Mode files and those in the CalDAV events. This allows to synchronize events between Emacs and a CalDAV server.

For each CalDAV calendar, org-caldav allows specify two Org Mode files: one for receiving the events downloaded from the server and the other for uploading events to the server or keeping the events your write directly in Org Mode. This is a kind of a safety net, so that org-caldav does not mess up with the Org Mode files you do not want to be touched by the package.

The package is available on Melpa:

M-x package-install org-caldav

Note: The installation on my machine reported an error, but the package gets installed. I am running Emacs 25.3.1 on a Arch Linux box.

To use the package, you need to specify what files get synced. This is accomplished by setting the org-caldav-calendars variable.

Quoting from the documentation, you might add something like the following to your .emacs, which tells org-caldav to store events from work@whatever to ~/org/from_work.org and push events in ~/org/work.org to the server:

(setq org-caldav-calendars
      '((:calendar-id "work@whatever" :files ("~/org/work.org")
         :inbox "~/org/from_work.org")
         ...
       )
)

You start the synchronization with:

M-x org-caldav-sync

Notice that synchronization is bi-directional and changes made locally in all files, including those used to receive events, are pushed to the server.

Notice that only basic information is synced (title, description, and date-time); other information is simply discarded (e.g., no location, no reminders).

To add an event, simply add a new time-stamped entry to your org file and sync again. For instance:

* Do something 
  <2015-06-28 Sun 18:00-20:00>

After a successful sync, org-caldav will set the ID of the event you just uploaded:

* Do something 
  :PROPERTIES:
  :ID: 905F350D-F437-4AEC-B73C-52A2B72ECBCD
  :END:
  <2015-06-29 Mon 19:00-19:25>

Note that deletions are also synced: if you delete files or events, these changes will be uploaded next time you sync. More important: if the events you delete contain invitations, emails will be sent to the attendees, even for events in the past. Be careful about this point if you want to this package a try (I did it and ended up spamming some of my collaborators, with cancellations of old events.)

One variable worth mentioning is:

org-caldav-delete-calendar-entries

that allows you to control whether events on the server are really deleted.

If your sync state somehow gets broken, you can make a clean slate by doing:

C-u M-x org-caldav-delete-everything

The package is very well documented and you can consult it for the finer grained details.

As a final remark I need to mention that in an old configuration of mine, the package did not work as-is, because of a problem in the signature of an org-mode function. To make it work I had to change the function org-caldav-generate-ics. The problem was due to a call to org-icalendar--combine-files. The fix is relatively simple. Look for the following piece of code (in org-caldav-sync):

;; New exporter (Org 8) Signature changed in version 8.2.8 (if (version<
;; org-version "8.2.8") (apply 'org-icalendar--combine-files nil orgfiles)
(apply 'org-icalendar--combine-files orgfiles)

and replace it with:

(apply 'org-icalendar--combine-files nil orgfiles)

See Adapt to signature change of org-icalendar–combine-files for some notes on the matter.

Solution 3. iCal to Diary

Another solution worth mentioning is using an Emacs Lisp function to import your CalDAV files to Emacs diary files.

These files can be viewed using the diary package or with the agenda provided by org mode.

Add the following code to your .emacs file:

(setq diary-location "a directory where you store your diary files")

; calendars you want to download
; each item links to a remote iCal calendar
(setq calendars
      '(("calendar1" . "http://.../home.ics")
        ("calendar2" . "http://.../work.ics")
        ...
        ))

(defun getcal (url file)
  "Download ics file and add it to file"
  (let ((tmpfile (url-file-local-copy url)))
    (icalendar-import-file tmpfile file)
    (kill-buffer (car (last (split-string tmpfile "/"))))))

(defun getcals ()
  "Load a set of ICS calendars into Emacs diary files"
  (interactive)
  (mapcar #'(lambda (x)
              (let ((file (concat diary-location (car x)))
                    (url (cdr x)))
                (message (concat "Loading " url " into " file))
                (find-file file)
                ;; (flush-lines "^[& ]") ;; if you import ical as non marking
                (erase-buffer) ;; to avoid duplicating events
                (getcal url file)
                ))
          calendars))

When you invoke the function with M-x getcals it will start downloading the iCal files specified in the variable calendars and generate one diary file per calendar. Notice that the diary files must exist in the directory specified by diary-location before calling the function.

The function erases the files and retrieves all the events every time. It is an effective strategy, since iCals don’t grow much (unless you attach documents to events). As a consequence of the previous point, each iCal must be connected to a different diary file.

To view the files in the diary, your diary file should include all files you download:

#include "calendar1"
#include "calendar2"
...

To view the Emacs diary file in your Org Mode agenda, add their paths to the org-agenda-files.