emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [RFC] ox-icalendar: Unscheduled tasks & repeating tasks
@ 2023-03-26 18:56 Jack Kamm
  2023-03-27 11:59 ` Ihor Radchenko
  0 siblings, 1 reply; 21+ messages in thread
From: Jack Kamm @ 2023-03-26 18:56 UTC (permalink / raw)
  To: emacs-orgmode; +Cc: mail

[-- Attachment #1: Type: text/plain, Size: 1636 bytes --]

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.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-ox-icalendar-Allow-exporting-unscheduled-VTODOs.patch --]
[-- Type: text/x-patch, Size: 4258 bytes --]

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


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-ox-icalendar-Support-repeating-timestamps-in-TODOs.patch --]
[-- Type: text/x-patch, Size: 5652 bytes --]

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


^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2023-06-18 11:24 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-26 18:56 [RFC] ox-icalendar: Unscheduled tasks & repeating tasks Jack Kamm
2023-03-27 11:59 ` Ihor Radchenko
2023-03-31  5:55   ` Jack Kamm
2023-03-31 13:07     ` Ihor Radchenko
2023-03-31 15:50       ` Jack Kamm
2023-03-31 17:51         ` Ihor Radchenko
2023-03-31 22:20           ` Jack Kamm
2023-04-01  8:30             ` Ihor Radchenko
2023-04-02  0:47               ` Jack Kamm
2023-04-02  8:48                 ` Ihor Radchenko
2023-04-02 15:34                   ` Jack Kamm
2023-04-02 16:32                     ` Ihor Radchenko
2023-04-14 16:57   ` Jack Kamm
2023-04-14 18:46     ` Ihor Radchenko
2023-04-15  3:13       ` Jack Kamm
2023-04-15  9:56         ` Ihor Radchenko
2023-04-16 17:19           ` Jack Kamm
2023-06-11 15:35   ` [PATCH] " Jack Kamm
2023-06-12 10:36     ` Ihor Radchenko
2023-06-17 17:32       ` Jack Kamm
2023-06-18 11:28         ` Ihor Radchenko

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).