Emacs and Caldav
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
.
What is the iCal/CalDav URL of my calendar?
If you do not know what is the iCal or CalDAV URL of your calendar, you can read the post I wrote on the matter: Determining the URL of CalDAV calendars.