Evolution calendars in Org Mode's agenda

Posted on Jan 25, 2024

On a Linux GNOME desktop it’s convenient to use Evolution as a calendar sync tool (for Fastmail in my case). Once a configuration has been created, you can stash away the <mumble>.source files, drop them in place on another machine and (subject to re-entering your password), everything just works.

Such calendars are also available through GNOME Calendar, which is a little easier on the eye for casual glances.

Spending a lot of time in emacs, and enjoying org-mode more all of the time, I’d really like all of my calendar events shown in the org agenda. Achieving that consists of two parts:

  1. getting the events out of the Evolution calendar into a more pliable format,
  2. adding the events to an org file so that they are shown in the agenda.

To simplify the implementation, I use khal as a parser for vCalendar files. It’s definitely the case that you could use vdirsyncer and avoid Evolution, but given that the GUI is sometimes convenient, this way requires configuring only a single sync tool.

Getting events out of Evolution is achieved by grovelling in the sqlite database that Evolution uses as a cache:

#!/usr/bin/env zsh

set -e

calendir=${HOME}/v/calendar
dblist=(
    9ccf564d3aac1c2a02a29be8d44720ec7ea0d5a3
    62870b2ad9e6a1e04b800fea354ae44e6b20f754
    ca1c1bad361ca6e59b57d86f9e06c3fd5ffd4ccc
)

convert_one_calendar ()
{
    # Evolution sqlite db:
    local db=$1
    # Khal directory to populate:
    local dst=$2

    [[ -d ${dst} ]] || mkdir -p ${dst}

    cd ${dst}

    sqlite3 ${db} "select ECacheOBJ from ECacheObjects" |\
	csplit \
	    --quiet \
	    --elide-empty-files \
	    --suffix-format "%03d.ics" \
	    - \
	    '/BEGIN:VEVENT/' '{*}'
}

# Convert each of the Evolution cache databases to a Khal compatible
# directory of .ics files:
#
for db in ${dblist}; do
    rm -r ${calendir}/${db}

    convert_one_calendar \
	${HOME}/.cache/evolution/calendar/${db}/cache.db \
	${calendir}/${db}
done

# Write a khal config to match:

config_dir=${HOME}/.config/khal
config_file=${config_dir}/config

[[ -d ${config_dir} ]] || mkdir ${config_dir}

cat > ${config_file} <<EOF
[calendars]

EOF

for db in ${dblist}; do
    cat >> ${config_file} <<EOF
[[${db}]]
path = ${calendir}/${db}
type = calendar

EOF
done

cat >> ${config_file} <<EOF
[locale]
timeformat = %H:%M
dateformat = %Y-%m-%d
longdateformat = %Y-%m-%d
datetimeformat = %Y-%m-%d %H:%M
longdatetimeformat = %Y-%m-%d %H:%M
EOF

In the script, update the dblist variable to include the list of Evolution calendars that you want to convert - you can see their names in ~/.cache/evolution/calendar. The outputs of the script end up in ~/v/calendar, or update the calendir variable to suit your preferences.

Note that the script will overwrite your current khal configuration file.

Now we have a pile of .ics files and a khal configuration configured to parse them. Using that in the org agenda requires another conversion:

#!/usr/bin/env zsh

diary=${HOME}/.emacs.d/diary.org

start=$(date --date='-30days' '+%Y-%m-%d')
end=$(date --date='+30days' '+%Y-%m-%d')

khal list \
     --day-format "" \
     --format "* {title}{repeat-symbol}{alarm-symbol}
<{start-date} {start-end-time-style}>
{description}" \
     ${start} ${end} \
     > ${diary} \
     2> /dev/null

This one is less thorough - it only includes events for the previous 30 and next 30 days. You could tweak this, of course, but I find it sufficient for awareness in the org agenda.

The script generates ~/.emacs.d/diary.org, which you should ensure to include in org-agenda-files so that the events contained therein are displayed.

That’s it! I run the pair of scripts in sequence every hour, but you can decide what to do about that.

Originally I was generating an emacs diary file, and that works out okay. It means that org-timeblock is unaware of the entries, though, which can sometimes be an inconvenience.