Memento, homo, quia pulvis es, et in pulverem reverteris.

After some months of trying out a prototype approach and tweaking it, I believe to have landed on a viable system to manage my appointments seamlessly from within Emacs, on my Android phone and any other calendar application.

I use a calendar self-hosted on a Nextcloud instance, accessible through the CalDAV protocol - the Distributed Authoring and Versioning protocol for calendar data that came out of Apple in the early 2000s (interesting details on that in Dusseault, Lisa & Whitehead, J.. (2005). Open Calendar Sharing and Scheduling with CalDAV. Internet Computing, IEEE. 9. 81 - 89. 10.1109/MIC.2005.43. )

The calendar data from the server is synchronised with my phone using DAVx5 where I currently use the Fossify Calendar to display and make or alter appointments, and on my laptop using org-caldav in GNU Emacs, writing to a Org mode calendar file. I occasionally also use the web-based view from the Nextcloud web client from within a browser.

Graphically, this results in a a system as shown hereunder:

caldav-diagram.png

The Org file accessed through Emacs is set up using a date tree as the outline mode of the file. This creates headings by year and by month that can be folded and unfolded using standard functionality from Org mode. That offers a unique way to access appointments - alternatives to have headlines by week exist as well, but I don't have enough appointments to warrant that level of granularity in the outline of the file.

* 2025
** 2025-01 January
*** New Year lunch
,:PROPERTIES:
,:location: Our place
,:END:
Lunch with family to celebrate the new year.
<2025-01-01 Wed 11:30>--<2025-01-01 Wed 15:00>
** 2025-02 February
*** New Year's resolutions progress review
<2025-02-01 Sat>--<2025-02-02 Sun>

Alternatively, I can view my appointments on a traditional calendar layout using the Emacs calendar framework or calfw.

2025 / April
[ < ] [ > ] [Today] [Day] [Week] [Two Weeks] [Month]

          Sunday                      Monday                      Tuesday                    Wednesday                   Thursday                     Friday                     Saturday          
 30                        
                           
                           
 31                        
                           
                           
 1  April Fools' Day       
                           
                           
 2                         
                           
                           
 3                         
                           
                           
 4                         
                           
                           
 5                         
                           
                           
 6                         
                           
                           
 7                         
                           
                           
 8                         
                           
                           
 9                         
                           
                           
 10                        
                           
                           
 11                        
                           
                           
 12                        
                           
                           
 13  Passover              
                           
                           
 14                        
                           
                           
 15                        
                           
                           
 16                        
                           
                           
 17                        
                           
                           
 18  Good Friday           
                           
                           
 19                        
                           
                           
 20  Easter Sunday         
                           
                           
 21  First Day of Ridvan   
                           
                           
 22                        
                           
                           
 23                        
                           
                           
 24                        
                           
                           
 25                        
                           
                           
 26                        
                           
                           
 27                        
                           
                           
 28                        
                           
                           
 29  Ninth Day of Ridvan   
                           
                           
 30                        
                           
                           
 1                         
                           
                           
 2  Twelfth Day of Ridvan  
                           
                           
 3                         
                           
                           

I also set up a new capture template, allowing the quick creation of an appointment using C-c c followed by a.

(org-capture-templates
 '(("a" "Appointment" entry
    (file+olp+datetree "~/Nextcloud/notes/calendar-nextcloud.org")
    "* %?\n :PROPERTIES:\n :location: %^{Location}\n :END:\n%(fv/org-capture-appointment-timestam)\n\n"
    :jump-to-captured t
    :empty-lines 1
    :tree-type month
    :time-prompt t)))

(defun fv/org-capture-appointment-timestamp (&optional duration)
  "Get an Org timestamp for an appointment.
Prompt for a start time, calculate the end time by adding DURATION (default 30
minutes), and return a formatted Org timestamp with start and end times."
  (let* ((duration (or duration 30))
         (start-time (org-read-date t t nil "From:"))
         (end-time (time-add start-time (seconds-to-time (* duration 60)))))
    (concat (format-time-string (org-time-stamp-format t) start-time)
            "--"
            (format-time-string (org-time-stamp-format t) end-time))))

One gripe at the moment: the capture template needs a custom function to properly capture a time range, even though the standard Org timestamp can be formatted in a time range easily using syntax like 2pm+1h to create a 1h block starting at 14:00. When using this within a capture template using the shorthand %^T which prompts for a date, the timestamp is unfortunately simplified using some logic in the template engine at the moment.

Full setup can be found in Dotty, my dot files repository.