From 8348f5b8c56087f0fb8cdd775a816f63cb57f38f Mon Sep 17 00:00:00 2001 From: Jack Kamm Date: Sun, 26 Mar 2023 10:37:47 -0700 Subject: [PATCH 2/2] ox-icalendar: Support repeating timestamps in TODOs * lisp/ox-icalendar.el (org-icalendar--rrule): New helper function to generate RRULE. (org-icalendar--vevent): Use `org-icalendar--rrule' instead of generating the RRULE directly. (org-icalendar--vtodo): Generate RRULE for repeating scheduled and deadline timestamps. --- etc/ORG-NEWS | 13 ++++++++++++ lisp/ox-icalendar.el | 50 +++++++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index fb4f82b29..3919b240e 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -159,6 +159,19 @@ Running shell blocks with the ~:session~ header freezes Emacs until execution completes. The new ~:async~ header allows users to continue editing with Emacs while a ~:session~ block executes. +*** Add support for repeating tasks in iCalendar export + +Repeating Scheduled and Deadline timestamps in TODOs are now exported +as recurring tasks in iCalendar export. + +Note that in Org-mode, the repeaters for the Scheduled and Deadline +timestamps can be different; whereas in iCalendar, the recurrence rule +applies to both the scheduled start time and the deadline due date. + +In case the timestamp repeaters contradict, the correct export +behavior is not well-defined. Currently, Org-mode will issue a +warning and skip the repeaters in this case. + ** Miscellaneous *** Remove undocumented ~:target~ header parameter in ~ob-clojure~ diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 63aefcc84..179795ac9 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -726,6 +726,13 @@ (defun org-icalendar-entry (entry contents info) ;; Don't forget components from inner entries. contents)))) +(defun org-icalendar--rrule (unit value) + (format "RRULE:FREQ=%s;INTERVAL=%d\n" + (cl-case unit + (hour "HOURLY") (day "DAILY") (week "WEEKLY") + (month "MONTHLY") (year "YEARLY")) + value)) + (defun org-icalendar--vevent (entry timestamp uid summary location description categories timezone class) "Create a VEVENT component. @@ -752,12 +759,9 @@ (\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others (org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n" (org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n" ;; RRULE. - (when (org-element-property :repeater-type timestamp) - (format "RRULE:FREQ=%s;INTERVAL=%d\n" - (cl-case (org-element-property :repeater-unit timestamp) - (hour "HOURLY") (day "DAILY") (week "WEEKLY") - (month "MONTHLY") (year "YEARLY")) - (org-element-property :repeater-value timestamp))) + (org-icalendar--rrule + (org-element-property :repeater-unit timestamp) + (org-element-property :repeater-value timestamp)) "SUMMARY:" summary "\n" (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) (and (org-string-nw-p class) (format "CLASS:%s\n" class)) @@ -792,7 +796,9 @@ (defun org-icalendar--vtodo :hour-start (nth 2 now) :day-start (nth 3 now) :month-start (nth 4 now) - :year-start (nth 5 now)))))))) + :year-start (nth 5 now))))))) + (due (and (memq 'todo-due org-icalendar-use-deadline) + (org-element-property :deadline entry)))) (org-icalendar-fold-string (concat "BEGIN:VTODO\n" "UID:TODO-" uid "\n" @@ -801,11 +807,31 @@ (defun org-icalendar--vtodo (concat (org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n")) - (and (memq 'todo-due org-icalendar-use-deadline) - (org-element-property :deadline entry) - (concat (org-icalendar-convert-timestamp - (org-element-property :deadline entry) "DUE" nil timezone) - "\n")) + (when due + (concat (org-icalendar-convert-timestamp + due "DUE" nil timezone) + "\n")) + ;; RRULE + (let ((start-repeater-unit (org-element-property + :repeater-unit start)) + (start-repeater-value (org-element-property + :repeater-value start)) + (due-repeater-unit (org-element-property + :repeater-unit due)) + (due-repeater-value (org-element-property + :repeater-value due))) + (when (or start-repeater-value due-repeater-value) + (if (and start due + (not (and (eql start-repeater-unit + due-repeater-unit) + (eql start-repeater-value + due-repeater-value)))) + (progn (warn "Scheduled and Deadline repeaters are not equal. Skipping repeater export.") + nil) + (org-icalendar--rrule (or start-repeater-unit + due-repeater-unit) + (or start-repeater-value + due-repeater-value))))) "SUMMARY:" summary "\n" (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) (and (org-string-nw-p class) (format "CLASS:%s\n" class)) -- 2.39.2