Org-mode iCalendar

I do all my calendar organization from Org-mode but I want to be able to check my calendar on the go. Compatibility means using iCalendar as my phone and my work computer can both access it as a read only file. The setup I have works for me, but it might need tweaking to work for others.

(defun env-icalendar-create-combined-file (icalendar-file)
  "Wrapper around `(org-calendar-combine-agenda-files)'.
Writes file to ICALENDAR-FILE, and changes a bunch of defaults
to be appropriate for this particular use."
  (let ((org-icalendar-combined-agenda-file icalendar-file)
        (org-icalendar-alarm-time 15)
        (org-export-use-babel nil)
        (org-icalendar-include-sexps t)
        (org-icalendar-exclude-tags '("noexport"))
        (icalendar-export-sexp-enumeration-days 365)
        (org-icalendar-use-deadline '(event-if-todo-not-done todo-due))
        (org-icalendar-deadline-summary-prefix "Due: ")
        (org-icalendar-use-scheduled nil)
        (org-icalendar-combined-description "Calendar")
        (org-icalendar-combined-name "Calendar"))
    (org-icalendar-combine-agenda-files)))

Once the combined iCalendar file has been created, I upload it via WebDAV because that's the only way I can connect to my host. I experimented with using rclone to transfer files to my webdav server but since I can use the built-in curl to do this just as well, that's what I do. One fewer thing to install.

I want to store all the secure information in an authinfo file so that I can use Emacs' default features to access them securely. The URLs are stored here so that I can use publicly-accessible-but-hard-to-guess structures just like how Outlook does. The actual URL for the calendar is a random string that's not in an obvious location (I hope).

(defun env-upload-icalendar (local remote password)
  "Upload LOCAL file to REMOTE WebDAV server using password PASSWORD.
Relies on having curl available to Emacs."
  (if (executable-find "curl")
      (start-process
       "Upload calendar to webdav"
       "*dav*"
       "curl" "--user"  password "--anyauth" "-T"
       (expand-file-name local) remote)
    (error "Curl isn't available")))

Finally, I have a

(defun env-icalendar-create-combined-file-and-upload ()
  "Glue-function to pull the parts of the iCalendar process together.
This probably should be done in a nicer fashion, maybe with a bunch of
variables or something, but I can't be bothered."
  (message "Creating and uploading calendar...")
  (require 'icalendar)
  (require 'ox-icalendar)
  (let ((local  (make-temp-file "calendar" nil ".ical"))
        (remote (plist-get (car (auth-source-search
                                 :host "diary"))
                           :upload))
        (password (auth-source-pick-first-password :host "diary")))
    (env-icalendar-create-combined-file local)
    (env-upload-icalendar local remote password)
    (delete-file local)
    (message "Creating and uploading calendar... done.")))
# Example authinfo file
machine diary url http://www.example.com/calendar.ics upload http://webdav.example.com/calendar.ics
machine webdav password VerySecurePassword123 username Name

This file isn't fast to generate, but that's fine. I run this once per day usually in the morning while I'm having coffee and after I've gone through my tasks and schedule for the day. I don't use this exactly, but it's close enough.

* TODO Daily review
SCHEDULED: <1999-03-19 Fri .+1d>
- [X] Review upcoming schedule
- [ ] [[elisp:(env-icalendar-create-combined-file-and-upload)][Create calendar from Org-mode]]

Then there's the last few tweaks. Outlook fusses about everything, and providing time zone information in the calendar is one way to make it stop. I really don't like that I have location information littered all over my config that I will miss the next time I move.

(setopt org-icalendar-timezone "America/Edmonton")
(setopt org-icalendar-date-time-format ";TZID=%Z:%Y%m%dT%H%M%S")

Lastly I'm going to try to clean up the bodies of the iCalendar entries. By default, the time stamps appear in them. This is unneeded and also looks ugly. So they go!

(defun env-filter-timestamp-icalendar (timestamp backend _info)
  "Remove TIMESTAMP from description in icalendar BACKEND."
  (when
      (org-export-derived-backend-p backend 'icalendar)
    (setq timestamp "")))
(with-eval-after-load 'ox
  (add-to-list 'org-export-filter-timestamp-functions
               #'env-filter-timestamp-icalendar))