emacs-orgmode
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC] ox-icalendar: Unscheduled tasks & repeating tasks


From: Jack Kamm
Subject: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks
Date: Sun, 26 Mar 2023 11:56:31 -0700

Hello,

The attached 2 patches add support for exporting unscheduled tasks and
repeating tasks to iCalendar, respectively.

For patch 1 (unscheduled tasks):

Currently, ox-icalendar does not allow creating an iCalendar task
without a scheduled start date. If an Org TODO is missing a SCHEDULED
timestamp, then ox-icalendar sets today as the scheduled start date for
the exported task.

Patch 1 changes this by adding a new customization
org-icalendar-todo-force-scheduling. When non-nil, the start date is set
to today (same as the current behavior). When nil, unscheduled Org TODOs
are instead exported without a start date.

I also propose the default value to be nil. Note, this is
backwards-incompatible with the previous behavior!

But I think it should be the default anyways, because IMO it is the more
correct and useful behavior. An iCalendar VTODO without a DTSTART
property is valid, and has the same meaning as an Org TODO without a
SCHEDULED timestamp. Also, all the iCalendar programs I have tried
support unscheduled tasks, including Thunderbird, Evolution, Nextcloud,
and Tasks.org.

For patch 2 (repeating timestamps):

I add recurrence rule (RRULE) export for repeating SCHEDULED and
DEADLINE timestamps in TODOs, similar to how repeating non-TODO events
are currently handled.

The main complication here is that iCalendar's RRULE applies to both
DTSTART and DUE properties; by contrast, Org's SCHEDULED and DEADLINE
timestamps may have different repeaters. I am not sure the best way to
handle the case where SCHEDULED and DEADLINE have different repeaters,
so in that case I issue a warning and skip the repeater.

>From 1bd268ab260d5077d7456c0d64fea36128772f86 Mon Sep 17 00:00:00 2001
From: Jack Kamm <jackkamm@gmail.com>
Date: Sun, 26 Mar 2023 07:43:53 -0700
Subject: [PATCH 1/2] ox-icalendar: Allow exporting unscheduled VTODOs

* lisp/ox-icalendar.el (org-icalendar-todo-force-scheduling): New
option to revert to previous export behavior of unscheduled TODOs.
(org-icalendar--vtodo): Don't force unscheduled TODOs to have a
scheduled start time of today, unless
`org-icalendar-todo-force-scheduling' is set.
---
 etc/ORG-NEWS         | 15 +++++++++++++++
 lisp/ox-icalendar.el | 32 +++++++++++++++++++++-----------
 2 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index ac233a986..fb4f82b29 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -23,6 +23,15 @@ If you still want to use python-mode with ob-python, you 
might
 consider 
[[https://gitlab.com/jackkamm/ob-python-mode-mode][ob-python-mode-mode]], where 
the code to support python-mode
 has been ported to.
 
+*** Icalendar export of TODOs no longer forces a start time
+
+For TODOs without a scheduled start time, ox-icalendar no longer
+forces them to have a scheduled start time of today when exporting.
+This makes it possible to create icalendar TODOs without a start time.
+
+To revert to the old behavior, set the new custom option
+~org-icalendar-todo-force-scheduling~ to non-nil.
+
 ** New and changed options
 *** New ~org-cite-natbib-export-bibliography~ option defining fallback 
bibliography style
 
@@ -111,6 +120,12 @@ backend used for evaluation of ClojureScript.
 official [[https://clojure.org/guides/deps_and_cli][Clojure CLI tools]].
 The command can be customized with ~ob-clojure-cli-command~.
 
+*** New ~org-icalendar-todo-force-scheduling~ option for old ox-icalendar TODO 
scheduling behavior
+
+Set ~org-icalendar-todo-force-scheduling~ to non-nil to revert to the
+old ox-icalendar TODO export behavior, that forced all exported TODOs
+to have a scheduled start time.
+
 ** New features
 *** Add support for ~logind~ idle time in ~org-user-idle-seconds~
 
diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el
index 81a77a770..63aefcc84 100644
--- a/lisp/ox-icalendar.el
+++ b/lisp/ox-icalendar.el
@@ -231,6 +231,12 @@ (defcustom org-icalendar-include-todo nil
          (repeat :tag "Specific TODO keywords"
                  (string :tag "Keyword"))))
 
+(defcustom org-icalendar-todo-force-scheduling nil
+  "Non-nil means unscheduled tasks are exported as scheduled.
+The current date is used as the scheduled time for such tasks."
+  :group 'org-export-icalendar
+  :type 'boolean)
+
 (defcustom org-icalendar-include-bbdb-anniversaries nil
   "Non-nil means a combined iCalendar file should include anniversaries.
 The anniversaries are defined in the BBDB database."
@@ -776,21 +782,25 @@ (defun org-icalendar--vtodo
 Return VTODO component as a string."
   (let ((start (or (and (memq 'todo-start org-icalendar-use-scheduled)
                        (org-element-property :scheduled entry))
-                  ;; If we can't use a scheduled time for some
-                  ;; reason, start task now.
-                  (let ((now (decode-time)))
-                    (list 'timestamp
-                          (list :type 'active
-                                :minute-start (nth 1 now)
-                                :hour-start (nth 2 now)
-                                :day-start (nth 3 now)
-                                :month-start (nth 4 now)
-                                :year-start (nth 5 now)))))))
+                   (when org-icalendar-todo-force-scheduling
+                    ;; If we can't use a scheduled time for some
+                    ;; reason, start task now.
+                     (let ((now (decode-time)))
+                      (list 'timestamp
+                            (list :type 'active
+                                  :minute-start (nth 1 now)
+                                  :hour-start (nth 2 now)
+                                  :day-start (nth 3 now)
+                                  :month-start (nth 4 now)
+                                  :year-start (nth 5 now))))))))
     (org-icalendar-fold-string
      (concat "BEGIN:VTODO\n"
             "UID:TODO-" uid "\n"
             (org-icalendar-dtstamp) "\n"
-            (org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n"
+             (when start
+               (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
-- 
2.39.2

>From 8348f5b8c56087f0fb8cdd775a816f63cb57f38f Mon Sep 17 00:00:00 2001
From: Jack Kamm <jackkamm@gmail.com>
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


reply via email to

[Prev in Thread] Current Thread [Next in Thread]