I use Emacs for practically everything at my day job so it’s no surprise that I would get around to incorporating my work calendar into org-mode. With just a little fiddling I was able to get it to work. I’ve written a lot of tools in Emacs for work; some of them turn out to be not very useful, but integrating my work calendar has been a net positive to my workflow.
This post will outline the process I went through to get this working, along with some Emacs tools I use with my calendar.
Getting the Google calendar’s secret address in iCal format
The first thing I needed to do was to get the iCal address which I could access from a script on my machine. To get this, go to your calendar settings, find the calendar you want to expose in the “Settings for my calendars” section. You can then find the secret address for this calendar.
Script to download calendar and convert it to org
The program I use to convert the ics calendar to org is ical2orgpy. With this installed, you can create a script as follows:
#!/bin/bash
set -e
date >> /Users/me/log.txt
rm -f -- /Users/me/basic.ics
wget '<secret ics url>' --output-document=/Users/me/basic.ics
ical2orgpy /Users/me/basic.ics /Users/me/my-org-files/calendar.org
sed -i '' 's/\(<[^>]*\)>--<[^>]* \([^>]*\)>/\1-\2' /Users/me/my-org-files/calendar.org
The sed line at the end is to change strings of <2006-11-01 Wed 19:00>--<2006-11-01 Wed 20:00>
to <2006-11-01 Wed 19:00-20:00>
. This is important so duplicate items show up correctly when using org-timeblock (discussed later).
Crontab
You could easily create an idle timer function to run the above script (run-with-idle-timer). This may be the easiest way actually, but if you want, on MacOS you can create a new crontab entry to have this script run periodically. Just run crontab -e
and add a line like follows:
*/5 * * * * cd ~/ && /Users/me/bin/calgen-script.sh > ~/errorlog.out
If you go this route you may get errors with the path not being able to resolve binaries like ical2orgpy
so you may need to change the script to have their full path.
Org-agenda integration
With the “calendar.org” file added to your list of org-agenda-files
you should now see all of your meetings in your agenda.
org-timeblock
The package org-timeblock works very well for visualizing your daily schedule. Just install the package and run M-x org-timeblock and you should see a nice visualization of your daily schedule. I definitely recommend looking into this package.
Instant join zoom meeting
So many times I’ll find myself running late to a meeting, only to find that Google has logged me off and I have to go through the whole 2FA authentication process again. Having the calendar data in an org-mode file, I thought I could speed up this process by creating a command which will automatically open the zoom link of the current meeting. I can definitively say that this extension has paid back the initial investment of writing it.
The following is the code I came up with:
(require 'org)
(defconst zoom-calendar-file "/my/path/to/calendar.org")
(defconst zoom-domain-url "https://MY_ZOOM_DOMAIN_HERE.zoom.us")
(defun zoom--time-overlap (time1 time2)
"Return t if TIME1 and TIME2 are close to each other."
(let* ((y1 (decoded-time-year time1))
(y2 (decoded-time-year time2))
(m1 (decoded-time-month time1))
(m2 (decoded-time-month time2))
(d1 (decoded-time-day time1))
(d2 (decoded-time-day time2))
(h1 (decoded-time-hour time1))
(h2 (decoded-time-hour time2))
(min1 (decoded-time-minute time1))
(min2 (decoded-time-minute time2)))
(and (= y1 y2)
(= m1 m2)
(= d1 d2)
(< (- (+ (* 60 h2) min2)
(+ (+ (* 60 h1) min1)))
30)
(> (- (+ (* 60 h2) min2)
(+ (+ (* 60 h1) min1)))
-30)
(- (+ (* 60 h2) min2)
(+ (* 60 h1) min1)))))
(defun zoom--current-meetings ()
(let ((now (decode-time))
(today-string (format-time-string "%Y-%m-%d")))
(with-temp-buffer
(insert-file-contents zoom-calendar-file)
(org-mode)
(goto-char (point-min))
(seq-filter #'identity
(org-map-entries
(lambda ()
(save-restriction
(org-narrow-to-subtree)
(goto-char (point-min))
(when (search-forward-regexp "<\\(.*\\)-[0-9][0-9]:[0-9][0-9]>" nil t)
(let* ((date (org-parse-time-string (match-string 1)))
(heading (nth 4 (org-heading-components)))
(mins-before (zoom--time-overlap now date))
(zoom-link (save-excursion (goto-char (point-min))
(search-forward zoom-domain-url nil t)
(thing-at-point 'url))))
(and mins-before
(cons mins-before
(propertize heading 'zoom-link zoom-link))))))))))))
(defun zoom-browse-join-link ()
(interactive)
(let* ((meeting-heading (cdar (seq-sort-by #'car #'>
(seq-filter
(lambda (elt)
(< (car elt) 15)) ;; Don't pick a meeting more than 15 min in future.
(zoom--current-meetings)))))
(zoom-url (get-text-property 0 'zoom-link meeting-heading)))
(browse-url zoom-url)))
I then bound C-c z to zoom-browse-join-link
. With this, joining a
zoom call is effortless.
Meeting reminder, a failed experiment
Another situation I found myself in that I thought I would try to fix with some elisp was missing meetings while working on code. The cost of missing a meeting was high enough that investing in any solution to reduce the chance of it happening seemed worthwhile.
I had some code to find the next upcoming meeting and add an alert to the mode line, as well as sending an alert via ntfy.sh.
My solution wasn’t that useful so I stopped using it. I’m sure there’s got to be an org-mode package out there that does this same thing so maybe when I come across it, I can look into this problem again.
My calendar setup is crashing my Emacs???
After upgrading to the latest Emacs version (on 31.0.50), I noticed a concerning problem. Every now and then, my editor would freeze up, leading me to have to force quit Emacs and restart. This was a very frustrating problem. Luckily most of the time Emacs gives us the tools to resolve such bugs and problems ourselves so I went to see if I could fix it.
Unfortunately I don’t have any cool debugging story about some low-level internals. I still don’t even know what caused this. All I knew was that every time this would happen, I would see the message text reading like “Reverting buffer `calendar.org’”.
Ok, so something about autorevert mode not liking this calendar.org file I’m generating… I killed the calendar.org buffer and the problem would go away. I would open the agenda and lo and behold the calendar.org buffer would open again… eventually leading my Emacs to crash.
So not wanting to give up my calendar integration, I added the following code to my init.el:
(defvar kill-calendar-buffer
(run-with-idle-timer
5
t
(lambda ()
(let ((backup-inhibited t))
(when-let* ((cal-buf (get-buffer "calendar.org")))
(kill-buffer cal-buf))))))
The problem has never come back since.