* [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
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 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 ` (2 more replies) 0 siblings, 3 replies; 21+ messages in thread From: Ihor Radchenko @ 2023-03-27 11:59 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > 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. I agree that omitting DTSTART will make more sense. > 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. In the case of different repeaters, we can use RDATE (https://icalendar.org/iCalendar-RFC-5545/3-8-5-2-recurrence-date-times.html) and generate occurrences manually sufficiently far into future. ("how far" should be a defcustom). However, different repeaters for deadline and schedule are most likely a mistake - we can report it via org-lint and in ox-icalendar, as warning. Another scenario we may need to consider is when schedule has a repeater while deadline does not, and vice versa. The former scenario is probably valid - a VTODO with limited number of occurrences. The latter is likely a mistake we should raise warning about. It is also not clear how to represent moving event deadline in iCalendar. See more inline comments below. > +(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) Please add :package-version and possibly :safe keywords. We may also refer to `org-icalendar-include-todo' in the docstring. > - (org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n" > + (when start > + (concat (org-icalendar-convert-timestamp > + start "DTSTART" nil timezone) > + "\n")) Side note: here, and in other places, we use "\n" as end of line. Yet, for example https://icalendar.org/iCalendar-RFC-5545/3-8-2-4-date-time-start.html prescribes CRLF (\r\n). Also, see https://orgmode.org/list/87ilgljv6i.fsf@localhost If you are familiar with iCalendar spec, may you look through the ox-icalendar code and check other places where we do not conform to the newline spec? Ideally, we want a set of private functions ensuring proper prescribed format for all the used iCalendar syntax entries. Otherwise, we will keep forgetting about these subtleties. > +(defun org-icalendar--rrule (unit value) > + (format "RRULE:FREQ=%s;INTERVAL=%d\n" \r\n -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-27 11:59 ` Ihor Radchenko @ 2023-03-31 5:55 ` Jack Kamm 2023-03-31 13:07 ` Ihor Radchenko 2023-04-14 16:57 ` Jack Kamm 2023-06-11 15:35 ` [PATCH] " Jack Kamm 2 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-03-31 5:55 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail [-- Attachment #1: Type: text/plain, Size: 1348 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > Side note: here, and in other places, we use "\n" as end of line. Yet, > for example > https://icalendar.org/iCalendar-RFC-5545/3-8-2-4-date-time-start.html > prescribes CRLF (\r\n). Also, see > https://orgmode.org/list/87ilgljv6i.fsf@localhost > If you are familiar with iCalendar spec, may you look through the > ox-icalendar code and check other places where we do not conform to the > newline spec? org-icalendar--vtodo is wrapped in org-icalendar-fold-string, so this "\n" gets converted to CRLF later on. However you are right that other parts of the iCalendar export have inconsistent line endings. Currently, VEVENT and VTODO components have the correct CRLF endings, but the other parts of the VCALENDAR do not (such as the preamble). I like your suggestion in the above thread to just wrap the whole export in `org-icalendar-fold-string'. Though I think it's slightly nicer to do it in `org-icalendar--vcalendar' instead of `org-icalendar-template'. So, I've attached a standalone patch to do this. It also fixes an issue with `org-icalendar-fold-string' where the last newline was missing "\r", and adds a unit test. Note that fixing the line endings causes a surprising compatibility issue with org-caldav. I fixed this problem on the org-caldav side, and made a note in ORG-NEWS. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-ox-icalendar-Use-consistent-CRLF-line-endings.patch --] [-- Type: text/x-patch, Size: 12143 bytes --] From 712a4ef09b63b2f6bdec2a3967712be912dce0d2 Mon Sep 17 00:00:00 2001 From: Jack Kamm <jackkamm@gmail.com> Date: Thu, 30 Mar 2023 22:19:09 -0700 Subject: [PATCH] ox-icalendar: Use consistent CRLF line endings Fixes issue where the ox-icalendar export uses an inconsistent mix of dos and unix style line endings. * lisp/ox-icalendar.el (org-icalendar-fold-string): Don't use "\r" during the string construction, instead replace "\n" with "\r\n" after string has been created. This fixes an issue where the final "\n" added by `org-element-normalize-string' was missing "\r". (org-icalendar--vevent): Remove call to `org-icalendar-fold-string'. (org-icalendar--vtodo): Remove call to `org-icalendar-fold-string'. (org-icalendar--vcalendar): Wrap in `org-icalendar-fold-string'. * testing/lisp/test-ox-icalendar.el: New file for unit tests of ox-icalendar. Add an initial test for CRLF line endings. See also: https://list.orgmode.org/87o7oetneo.fsf@localhost/T/#m3e3eb80f9fc51ba75854b33ebfe9ecdefa2ded24 https://list.orgmode.org/orgmode/87ilgljv6i.fsf@localhost/ --- etc/ORG-NEWS | 12 +++ lisp/ox-icalendar.el | 159 +++++++++++++++--------------- testing/lisp/test-ox-icalendar.el | 46 +++++++++ 3 files changed, 138 insertions(+), 79 deletions(-) create mode 100644 testing/lisp/test-ox-icalendar.el diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index ac233a986..9f7d01707 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -23,6 +23,18 @@ 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. +*** =ox-icalendar.el= line ending fix may affect downstream packages + +iCalendar export now uses dos-style CRLF ("\r\n") line endings +throughout, as required by the iCalendar specification (RFC 5545). +Previously, the export used an inconsistent mix of dos and unix line +endings. + +This might cause errors in external packages that parse output from +ox-icalendar. In particular, older versions of org-caldav may +encounter issues, and users are advised to update to the most recent +version of org-caldav. See [[https://github.com/dengste/org-caldav/commit/618bf4cdc9be140ca1993901d017b7f18297f1b8][this org-caldav commit]] for more information. + ** New and changed options *** New ~org-cite-natbib-export-bibliography~ option defining fallback bibliography style diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 81a77a770..06e90d032 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -526,25 +526,27 @@ (defun org-icalendar-cleanup-string (s) (defun org-icalendar-fold-string (s) "Fold string S according to RFC 5545." - (org-element-normalize-string - (mapconcat - (lambda (line) - ;; Limit each line to a maximum of 75 characters. If it is - ;; longer, fold it by using "\r\n " as a continuation marker. - (let ((len (length line))) - (if (<= len 75) line - (let ((folded-line (substring line 0 75)) - (chunk-start 75) - chunk-end) - ;; Since continuation marker takes up one character on the - ;; line, real contents must be split at 74 chars. - (while (< (setq chunk-end (+ chunk-start 74)) len) - (setq folded-line - (concat folded-line "\r\n " - (substring line chunk-start chunk-end)) - chunk-start chunk-end)) - (concat folded-line "\r\n " (substring line chunk-start)))))) - (org-split-string s "\n") "\r\n"))) + (replace-regexp-in-string + "\n" "\r\n" + (org-element-normalize-string + (mapconcat + (lambda (line) + ;; Limit each line to a maximum of 75 characters. If it is + ;; longer, fold it by using "\r\n " as a continuation marker. + (let ((len (length line))) + (if (<= len 75) line + (let ((folded-line (substring line 0 75)) + (chunk-start 75) + chunk-end) + ;; Since continuation marker takes up one character on the + ;; line, real contents must be split at 74 chars. + (while (< (setq chunk-end (+ chunk-start 74)) len) + (setq folded-line + (concat folded-line "\n " + (substring line chunk-start chunk-end)) + chunk-start chunk-end)) + (concat folded-line "\n " (substring line chunk-start)))))) + (org-split-string s "\n") "\n")))) \f @@ -736,31 +738,30 @@ (\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others should be treated as \"PRIVATE\" if they are unknown to the iCalendar server. Return VEVENT component as a string." - (org-icalendar-fold-string - (if (eq (org-element-property :type timestamp) 'diary) - (org-icalendar-transcode-diary-sexp - (org-element-property :raw-value timestamp) uid summary) - (concat "BEGIN:VEVENT\n" - (org-icalendar-dtstamp) "\n" - "UID:" uid "\n" - (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))) - "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)) - (and (org-string-nw-p description) - (format "DESCRIPTION:%s\n" description)) - "CATEGORIES:" categories "\n" - ;; VALARM. - (org-icalendar--valarm entry timestamp summary) - "END:VEVENT")))) + (if (eq (org-element-property :type timestamp) 'diary) + (org-icalendar-transcode-diary-sexp + (org-element-property :raw-value timestamp) uid summary) + (concat "BEGIN:VEVENT\n" + (org-icalendar-dtstamp) "\n" + "UID:" uid "\n" + (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))) + "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)) + (and (org-string-nw-p description) + (format "DESCRIPTION:%s\n" description)) + "CATEGORIES:" categories "\n" + ;; VALARM. + (org-icalendar--valarm entry timestamp summary) + "END:VEVENT"))) (defun org-icalendar--vtodo (entry uid summary location description categories timezone class) @@ -786,34 +787,33 @@ (defun org-icalendar--vtodo :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" - (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")) - "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)) - (and (org-string-nw-p description) - (format "DESCRIPTION:%s\n" description)) - "CATEGORIES:" categories "\n" - "SEQUENCE:1\n" - (format "PRIORITY:%d\n" - (let ((pri (or (org-element-property :priority entry) - org-priority-default))) - (floor (- 9 (* 8. (/ (float (- org-priority-lowest pri)) - (- org-priority-lowest - org-priority-highest))))))) - (format "STATUS:%s\n" - (if (eq (org-element-property :todo-type entry) 'todo) - "NEEDS-ACTION" - "COMPLETED")) - "END:VTODO")))) + (concat "BEGIN:VTODO\n" + "UID:TODO-" uid "\n" + (org-icalendar-dtstamp) "\n" + (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")) + "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)) + (and (org-string-nw-p description) + (format "DESCRIPTION:%s\n" description)) + "CATEGORIES:" categories "\n" + "SEQUENCE:1\n" + (format "PRIORITY:%d\n" + (let ((pri (or (org-element-property :priority entry) + org-priority-default))) + (floor (- 9 (* 8. (/ (float (- org-priority-lowest pri)) + (- org-priority-lowest + org-priority-highest))))))) + (format "STATUS:%s\n" + (if (eq (org-element-property :todo-type entry) 'todo) + "NEEDS-ACTION" + "COMPLETED")) + "END:VTODO"))) (defun org-icalendar--valarm (entry timestamp summary) "Create a VALARM component. @@ -879,19 +879,20 @@ (defun org-icalendar--vcalendar (name owner tz description contents) NAME, OWNER, TZ, DESCRIPTION and CONTENTS are all strings giving, respectively, the name of the calendar, its owner, the timezone used, a short description and the other components included." - (concat (format "BEGIN:VCALENDAR + (org-icalendar-fold-string + (concat (format "BEGIN:VCALENDAR VERSION:2.0 X-WR-CALNAME:%s PRODID:-//%s//Emacs with Org mode//EN X-WR-TIMEZONE:%s X-WR-CALDESC:%s CALSCALE:GREGORIAN\n" - (org-icalendar-cleanup-string name) - (org-icalendar-cleanup-string owner) - (org-icalendar-cleanup-string tz) - (org-icalendar-cleanup-string description)) - contents - "END:VCALENDAR\n")) + (org-icalendar-cleanup-string name) + (org-icalendar-cleanup-string owner) + (org-icalendar-cleanup-string tz) + (org-icalendar-cleanup-string description)) + contents + "END:VCALENDAR\n"))) \f diff --git a/testing/lisp/test-ox-icalendar.el b/testing/lisp/test-ox-icalendar.el new file mode 100644 index 000000000..539d2a0e0 --- /dev/null +++ b/testing/lisp/test-ox-icalendar.el @@ -0,0 +1,46 @@ +;;; test-ox-icalendar.el --- tests for ox-icalendar.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2023 Jack Kamm + +;; Author: Jack Kamm <jackkamm@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests checking validity of Org iCalendar export output. + +;;; Code: + +(require 'ox-icalendar) + +(ert-deftest test-ox-icalendar/crfl-endings () + "Test every line of iCalendar export has CRFL ending." + (should + (seq-every-p + (lambda (x) (equal (substring x -1) "\r")) + (org-split-string + (org-test-with-temp-text + "* Test event +:PROPERTIES: +:ID: b17d8f92-1beb-442e-be4d-d2060fa3c7ff +:END: +<2023-03-30 Thu>" + (with-current-buffer + (org-export-to-buffer 'icalendar "*Test iCalendar Export*") + (buffer-string))) + "\n")))) + +(provide 'test-ox-icalendar) +;;; test-ox-icalendar.el ends here -- 2.39.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-31 5:55 ` Jack Kamm @ 2023-03-31 13:07 ` Ihor Radchenko 2023-03-31 15:50 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-03-31 13:07 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > However you are right that other parts of the iCalendar export have > inconsistent line endings. Currently, VEVENT and VTODO components have > the correct CRLF endings, but the other parts of the VCALENDAR do not > (such as the preamble). > > I like your suggestion in the above thread to just wrap the whole > export in `org-icalendar-fold-string'. Though I think it's slightly > nicer to do it in `org-icalendar--vcalendar' instead of > `org-icalendar-template'. > > So, I've attached a standalone patch to do this. It also fixes an issue > with `org-icalendar-fold-string' where the last newline was missing > "\r", and adds a unit test. Thanks! Note that I did not implement my suggestion because I am concerned if putting CRLF is safe as every single line ending. I am looking at https://icalendar.org/iCalendar-RFC-5545/3-6-2-to-do-component.html, and I note that only BEGIN:VTODO and END:VTODO lines must actually have CRLF. For example, https://icalendar.org/iCalendar-RFC-5545/3-3-11-text.html has no mentions of CRLF, but does talk about escaping staff. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-31 13:07 ` Ihor Radchenko @ 2023-03-31 15:50 ` Jack Kamm 2023-03-31 17:51 ` Ihor Radchenko 0 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-03-31 15:50 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail Ihor Radchenko <yantar92@posteo.net> writes: > Thanks! > Note that I did not implement my suggestion because I am concerned if > putting CRLF is safe as every single line ending. > > I am looking at > https://icalendar.org/iCalendar-RFC-5545/3-6-2-to-do-component.html, and > I note that only BEGIN:VTODO and END:VTODO lines must actually have > CRLF. For example, > https://icalendar.org/iCalendar-RFC-5545/3-3-11-text.html has no > mentions of CRLF, but does talk about escaping staff. My reading of [1] is that all lines must end with CRLF: > The iCalendar object is organized into individual lines of text, > called content lines. Content lines are delimited by a line break, > which is a CRLF sequence And in particular, for the component properties after BEGIN:VTODO, [1] gives the general notation as: > contentline = name *(";" param ) ":" value CRLF For example, the DTSTART notation [2] is: > dtstart = "DTSTART" dtstparam ":" dtstval CRLF And the same is true for all the other properties. [1] https://icalendar.org/iCalendar-RFC-5545/3-1-content-lines.html [2] https://icalendar.org/iCalendar-RFC-5545/3-8-2-4-date-time-start.html ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-31 15:50 ` Jack Kamm @ 2023-03-31 17:51 ` Ihor Radchenko 2023-03-31 22:20 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-03-31 17:51 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: >> I am looking at >> https://icalendar.org/iCalendar-RFC-5545/3-6-2-to-do-component.html, and >> I note that only BEGIN:VTODO and END:VTODO lines must actually have >> CRLF. For example, >> https://icalendar.org/iCalendar-RFC-5545/3-3-11-text.html has no >> mentions of CRLF, but does talk about escaping staff. > > My reading of [1] is that all lines must end with CRLF: Good to hear that we do not need to worry about the need to mix CRLF and \n. I now only have one minor concern about `org-icalendar-fold-string' when the original buffer contains DOS line endings. May they mess things up producing \r\r\n? If not, feel free to install the patch. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-31 17:51 ` Ihor Radchenko @ 2023-03-31 22:20 ` Jack Kamm 2023-04-01 8:30 ` Ihor Radchenko 0 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-03-31 22:20 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail Ihor Radchenko <yantar92@posteo.net> writes: > I now only have one minor concern about `org-icalendar-fold-string' when > the original buffer contains DOS line endings. May they mess things up > producing \r\r\n? There are 2 issues here: what does `org-icalendar-fold-string' do when string already contains \r, and what does `org-export-to-file' do when `org-export-coding-system' or `buffer-file-coding-system' is dos-like. In both cases, the patch doesn't change the existing behavior -- which is to produce \r\r\n. For issue 1, what `org-icalendar-fold-string' does when string already contains \r\n, you can see that it produces \r\r\n as follows: emacs -Q -l ox-icalendar M-: (org-icalendar-fold-string (org-icalendar-fold-string "Line1\nLine2")) This is why the patch removes the calls to `org-icalendar-fold-string' in `org-icalendar--vevent' and `org-icalendar--vtodo' -- otherwise we would add \r multiple times to the same string. To change this behavior of `org-icalendar-fold-string', we could modify the patch to do: (defun org-icalendar-fold-string (s) "Fold string S according to RFC 5545." (replace-regexp-in-string - "\n" "\r\n" + "\r*\n" "\r\n" which would strip out any extra \r at end of line. Another alternative would be to use "\r?\n" instead of "\r*\n". For the second issue -- when `org-export-coding-system' is dos (or similar), the file created by `org-export-to-file' will contain \r\r\n. This was already the pre-existing behavior, but note the patch does cause a minor change here: before the patch just the main body will have \r\r\n, but after the patch, the preamble will also have it. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-31 22:20 ` Jack Kamm @ 2023-04-01 8:30 ` Ihor Radchenko 2023-04-02 0:47 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-04-01 8:30 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > For issue 1, what `org-icalendar-fold-string' does when string already > contains \r\n, you can see that it produces \r\r\n as follows: > > emacs -Q -l ox-icalendar > M-: > (org-icalendar-fold-string (org-icalendar-fold-string "Line1\nLine2")) > > This is why the patch removes the calls to `org-icalendar-fold-string' > in `org-icalendar--vevent' and `org-icalendar--vtodo' -- otherwise we > would add \r multiple times to the same string. > > To change this behavior of `org-icalendar-fold-string', we could > modify the patch to do: > > (defun org-icalendar-fold-string (s) > "Fold string S according to RFC 5545." > (replace-regexp-in-string > - "\n" "\r\n" > + "\r*\n" "\r\n" > > which would strip out any extra \r at end of line. Another alternative > would be to use "\r?\n" instead of "\r*\n". "\r*\n" looks safer. > For the second issue -- when `org-export-coding-system' is dos (or > similar), the file created by `org-export-to-file' will contain > \r\r\n. This was already the pre-existing behavior, but note the patch > does cause a minor change here: before the patch just the main body > will have \r\r\n, but after the patch, the preamble will also have it. I see. Looking at https://icalendar.org/iCalendar-RFC-5545/3-1-4-character-set.html: There is not a property parameter to declare the charset used in a property value. The default charset for an iCalendar stream is UTF-8 as defined in [RFC3629]. So, we should probably override `org-export-coding-system', even when it is set. iCalendar demands UTF8 anyway. We likely want (according to 34.10.1 Basic Concepts of Coding Systems): The coding system ‘utf-8-emacs’ specifies that the data is represented in the internal Emacs encoding (*note Text Representations::). This is like ‘raw-text’ in that no code conversion happens, but different in that the result is multibyte data. The name ‘emacs-internal’ is an alias for ‘utf-8-emacs-unix’ (so it forces no conversion of end-of-line, unlike ‘utf-8-emacs’, which can decode all 3 kinds of end-of-line conventions). -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-01 8:30 ` Ihor Radchenko @ 2023-04-02 0:47 ` Jack Kamm 2023-04-02 8:48 ` Ihor Radchenko 0 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-04-02 0:47 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail [-- Attachment #1: Type: text/plain, Size: 706 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > So, we should probably override `org-export-coding-system', even when it > is set. iCalendar demands UTF8 anyway. Also, ox-icalendar already sets ":ascii-charset utf-8" in the ext-plist during export. > We likely want (according to 34.10.1 Basic Concepts of Coding Systems): I attach a new patch, which takes the approach of converting to utf-8-dos in `org-icalendar-after-save-hook', instead of converting newlines in `org-icalendar-fold-string'. I think this way is simpler, and should be more robust across locales. Note, this means the string returned by `org-export-as' won't contain CRLF. Instead, the newlines are converted during post-process. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-ox-icalendar-Use-consistent-CRLF-line-endings.patch --] [-- Type: text/x-patch, Size: 5093 bytes --] From 04761429f82bfd2aee63f4978afec3449abaa37d Mon Sep 17 00:00:00 2001 From: Jack Kamm <jackkamm@gmail.com> Date: Sat, 1 Apr 2023 16:53:35 -0700 Subject: [PATCH] ox-icalendar: Use consistent CRLF line endings Fixes issue where the ox-icalendar export uses an inconsistent mix of dos and unix style line endings. * lisp/ox-icalendar.el (org-icalendar-fold-string): No longer converts to CRLF, instead delegating that to `org-icalendar--convert-eol'. (org-icalendar--convert-eol): New function to convert EOL to CRLF. It runs early in `org-icalendar-after-save-hook'. * testing/lisp/test-ox-icalendar.el: New file for unit tests of ox-icalendar. Add an initial test for CRLF line endings. See also: https://list.orgmode.org/87o7oetneo.fsf@localhost/T/#m3e3eb80f9fc51ba75854b33ebfe9ecdefa2ded24 https://list.orgmode.org/orgmode/87ilgljv6i.fsf@localhost/ --- etc/ORG-NEWS | 12 +++++++++ lisp/ox-icalendar.el | 14 +++++++--- testing/lisp/test-ox-icalendar.el | 44 +++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 testing/lisp/test-ox-icalendar.el diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index ac233a986..9f7d01707 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -23,6 +23,18 @@ 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. +*** =ox-icalendar.el= line ending fix may affect downstream packages + +iCalendar export now uses dos-style CRLF ("\r\n") line endings +throughout, as required by the iCalendar specification (RFC 5545). +Previously, the export used an inconsistent mix of dos and unix line +endings. + +This might cause errors in external packages that parse output from +ox-icalendar. In particular, older versions of org-caldav may +encounter issues, and users are advised to update to the most recent +version of org-caldav. See [[https://github.com/dengste/org-caldav/commit/618bf4cdc9be140ca1993901d017b7f18297f1b8][this org-caldav commit]] for more information. + ** New and changed options *** New ~org-cite-natbib-export-bibliography~ option defining fallback bibliography style diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 81a77a770..7f675b5d0 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -540,12 +540,20 @@ (defun org-icalendar-fold-string (s) ;; line, real contents must be split at 74 chars. (while (< (setq chunk-end (+ chunk-start 74)) len) (setq folded-line - (concat folded-line "\r\n " + (concat folded-line "\n " (substring line chunk-start chunk-end)) chunk-start chunk-end)) - (concat folded-line "\r\n " (substring line chunk-start)))))) - (org-split-string s "\n") "\r\n"))) + (concat folded-line "\n " (substring line chunk-start)))))) + (org-split-string s "\n") "\n"))) +(defun org-icalendar--convert-eol (f) + "Convert line endings to CRLF as per RFC 5545." + (with-temp-buffer + (insert-file-contents f) + (let ((coding-system-for-write 'utf-8-dos)) + (write-region nil nil f)))) + +(add-hook 'org-icalendar-after-save-hook #'org-icalendar--convert-eol -90) \f ;;; Filters diff --git a/testing/lisp/test-ox-icalendar.el b/testing/lisp/test-ox-icalendar.el new file mode 100644 index 000000000..bfc756d51 --- /dev/null +++ b/testing/lisp/test-ox-icalendar.el @@ -0,0 +1,44 @@ +;;; test-ox-icalendar.el --- tests for ox-icalendar.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2023 Jack Kamm + +;; Author: Jack Kamm <jackkamm@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests checking validity of Org iCalendar export output. + +;;; Code: + +(require 'ox-icalendar) + +(ert-deftest test-ox-icalendar/crlf-endings () + "Test every line of iCalendar export has CRLF ending." + (let ((tmp-ics (org-test-with-temp-text-in-file + "* Test event +:PROPERTIES: +:ID: b17d8f92-1beb-442e-be4d-d2060fa3c7ff +:END: +<2023-03-30 Thu>" + (expand-file-name (org-icalendar-export-to-ics))))) + (unwind-protect + (with-temp-buffer + (insert-file-contents tmp-ics) + (should (eql 1 (coding-system-eol-type last-coding-system-used)))) + (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) + +(provide 'test-ox-icalendar) +;;; test-ox-icalendar.el ends here -- 2.39.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-02 0:47 ` Jack Kamm @ 2023-04-02 8:48 ` Ihor Radchenko 2023-04-02 15:34 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-04-02 8:48 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: >> We likely want (according to 34.10.1 Basic Concepts of Coding Systems): > > I attach a new patch, which takes the approach of converting to > utf-8-dos in `org-icalendar-after-save-hook', instead of converting > newlines in `org-icalendar-fold-string'. > > I think this way is simpler, and should be more robust across locales. > > Note, this means the string returned by `org-export-as' won't contain > CRLF. Instead, the newlines are converted during post-process. Looks reasonable, but I have one comment on the code. > +(add-hook 'org-icalendar-after-save-hook #'org-icalendar--convert-eol -90) We should not use user-defined hooks for things that must be executed. Imagine that a user customizes the hook after loading ox-icalendar and removes the call to `org-icalendar--convert-eol'? Instead, we should better explicitly call the necessary functions. I was told this by Emacs devs. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-02 8:48 ` Ihor Radchenko @ 2023-04-02 15:34 ` Jack Kamm 2023-04-02 16:32 ` Ihor Radchenko 0 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-04-02 15:34 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail [-- Attachment #1: Type: text/plain, Size: 737 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > Looks reasonable, but I have one comment on the code. > We should not use user-defined hooks for things that must be executed. > Instead, we should better explicitly call the necessary functions. Thanks, I updated the patch to explicitly call the function. Also, I tweaked the coding-system-for-write to be a bit safer, in case of edge cases where utf-8 doesn't work -- I think RFC 5545 just says it's the default charset. Attached is the (I think) final version of the patch. I'll install it soon, unless I hear otherwise. PS I haven't forgotten your feedback on the original VTODO-related patches (thanks for that review). I'll work on that next, but it might take me a bit longer. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-ox-icalendar-Use-consistent-CRLF-line-endings.patch --] [-- Type: text/x-patch, Size: 6850 bytes --] From aa59625cd08dcee767f42ad8d45d8902aa8d38bd Mon Sep 17 00:00:00 2001 From: Jack Kamm <jackkamm@gmail.com> Date: Sat, 1 Apr 2023 16:53:35 -0700 Subject: [PATCH] ox-icalendar: Use consistent CRLF line endings Fixes issue where the ox-icalendar export uses an inconsistent mix of dos and unix style line endings. * lisp/ox-icalendar.el (org-icalendar-fold-string): No longer converts to CRLF, instead delegating that to `org-icalendar--post-process-file'. (org-icalendar--post-process-file): New function to handle exported file post-processing. Converts EOL to CRLF, and then runs `org-icalendar-after-save-hook'. (org-icalendar-export-to-ics, org-icalendar-export-current-agenda, org-icalendar--combine-files): Call `org-icalendar--post-process-file' instead of running `org-icalendar-after-save-hook' directly. * testing/lisp/test-ox-icalendar.el: New file for unit tests of ox-icalendar. Add an initial test for CRLF line endings. See also: https://list.orgmode.org/87o7oetneo.fsf@localhost/T/#m3e3eb80f9fc51ba75854b33ebfe9ecdefa2ded24 https://list.orgmode.org/orgmode/87ilgljv6i.fsf@localhost/ --- etc/ORG-NEWS | 12 +++++++++ lisp/ox-icalendar.el | 27 ++++++++++++------- testing/lisp/test-ox-icalendar.el | 44 +++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 testing/lisp/test-ox-icalendar.el diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index ac233a986..9f7d01707 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -23,6 +23,18 @@ 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. +*** =ox-icalendar.el= line ending fix may affect downstream packages + +iCalendar export now uses dos-style CRLF ("\r\n") line endings +throughout, as required by the iCalendar specification (RFC 5545). +Previously, the export used an inconsistent mix of dos and unix line +endings. + +This might cause errors in external packages that parse output from +ox-icalendar. In particular, older versions of org-caldav may +encounter issues, and users are advised to update to the most recent +version of org-caldav. See [[https://github.com/dengste/org-caldav/commit/618bf4cdc9be140ca1993901d017b7f18297f1b8][this org-caldav commit]] for more information. + ** New and changed options *** New ~org-cite-natbib-export-bibliography~ option defining fallback bibliography style diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 81a77a770..ccc237721 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -540,12 +540,23 @@ (defun org-icalendar-fold-string (s) ;; line, real contents must be split at 74 chars. (while (< (setq chunk-end (+ chunk-start 74)) len) (setq folded-line - (concat folded-line "\r\n " + (concat folded-line "\n " (substring line chunk-start chunk-end)) chunk-start chunk-end)) - (concat folded-line "\r\n " (substring line chunk-start)))))) - (org-split-string s "\n") "\r\n"))) - + (concat folded-line "\n " (substring line chunk-start)))))) + (org-split-string s "\n") "\n"))) + +(defun org-icalendar--post-process-file (file) + "Post-process the exported iCalendar FILE. +Converts line endings to dos-style CRLF as per RFC 5545, then +runs `org-icalendar-after-save-hook'." + (with-temp-buffer + (insert-file-contents file) + (let ((coding-system-for-write (coding-system-change-eol-conversion + last-coding-system-used 'dos))) + (write-region nil nil file))) + (run-hook-with-args 'org-icalendar-after-save-hook file) + nil) \f ;;; Filters @@ -932,8 +943,7 @@ (defun org-icalendar-export-to-ics (org-export-to-file 'icalendar outfile async subtreep visible-only body-only '(:ascii-charset utf-8 :ascii-links-to-notes nil) - '(lambda (file) - (run-hook-with-args 'org-icalendar-after-save-hook file) nil)))) + #'org-icalendar--post-process-file))) ;;;###autoload (defun org-icalendar-export-agenda-files (&optional async) @@ -1019,7 +1029,7 @@ (defun org-icalendar-export-current-agenda (file) (or (org-string-nw-p org-icalendar-timezone) (format-time-string "%Z")) org-icalendar-combined-description contents))) - (run-hook-with-args 'org-icalendar-after-save-hook file))) + (org-icalendar--post-process-file file))) (defun org-icalendar--combine-files (&rest files) "Combine entries from multiple files into an iCalendar file. @@ -1061,8 +1071,7 @@ (defun org-icalendar--combine-files (&rest files) (when (and org-icalendar-include-bbdb-anniversaries (require 'ol-bbdb nil t)) (with-output-to-string (org-bbdb-anniv-export-ical))))))) - (run-hook-with-args 'org-icalendar-after-save-hook - org-icalendar-combined-agenda-file)) + (org-icalendar--post-process-file org-icalendar-combined-agenda-file)) (org-release-buffers org-agenda-new-buffers)))) diff --git a/testing/lisp/test-ox-icalendar.el b/testing/lisp/test-ox-icalendar.el new file mode 100644 index 000000000..bfc756d51 --- /dev/null +++ b/testing/lisp/test-ox-icalendar.el @@ -0,0 +1,44 @@ +;;; test-ox-icalendar.el --- tests for ox-icalendar.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2023 Jack Kamm + +;; Author: Jack Kamm <jackkamm@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Tests checking validity of Org iCalendar export output. + +;;; Code: + +(require 'ox-icalendar) + +(ert-deftest test-ox-icalendar/crlf-endings () + "Test every line of iCalendar export has CRLF ending." + (let ((tmp-ics (org-test-with-temp-text-in-file + "* Test event +:PROPERTIES: +:ID: b17d8f92-1beb-442e-be4d-d2060fa3c7ff +:END: +<2023-03-30 Thu>" + (expand-file-name (org-icalendar-export-to-ics))))) + (unwind-protect + (with-temp-buffer + (insert-file-contents tmp-ics) + (should (eql 1 (coding-system-eol-type last-coding-system-used)))) + (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) + +(provide 'test-ox-icalendar) +;;; test-ox-icalendar.el ends here -- 2.39.2 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-02 15:34 ` Jack Kamm @ 2023-04-02 16:32 ` Ihor Radchenko 0 siblings, 0 replies; 21+ messages in thread From: Ihor Radchenko @ 2023-04-02 16:32 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > Attached is the (I think) final version of the patch. I'll install it > soon, unless I hear otherwise. I have no further comments. Thanks! -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-27 11:59 ` Ihor Radchenko 2023-03-31 5:55 ` Jack Kamm @ 2023-04-14 16:57 ` Jack Kamm 2023-04-14 18:46 ` Ihor Radchenko 2023-06-11 15:35 ` [PATCH] " Jack Kamm 2 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-04-14 16:57 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail Ihor Radchenko <yantar92@posteo.net> writes: > Another scenario we may need to consider is when schedule has a repeater > while deadline does not, and vice versa. The former scenario is probably > valid - a VTODO with limited number of occurrences. That is an interesting idea; and we can use the UNTIL or COUNT keywords in RRULE to implement it. However, it doesn't seem completely faithful to the way the TODO ends up in the Org Agenda (or does Org have some option to use DEADLINE to bound a repeating SCHEDULED in this way?) I think the most faithful way to represent different SCHEDULED and DEADLINE repeaters is to export 2 separate VTODOs, each with different RRULE. Then the exported iCalendar will look just like the Org Agenda. It is also in line with how ox-icalendar exports multiple timestamps to separate VEVENTs. That said, I am not really happy with this solution either. The fact that ox-icalendar can create multiple VEVENT per entry already creates headaches for any setup doing bidirectional sync between Org and iCalendar, such as with org-caldav, ical2org.awk, or ical2orgpy. And I am hesitant to make this problem worse, by making it happen for VTODO as well. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-14 16:57 ` Jack Kamm @ 2023-04-14 18:46 ` Ihor Radchenko 2023-04-15 3:13 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-04-14 18:46 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > Ihor Radchenko <yantar92@posteo.net> writes: > >> Another scenario we may need to consider is when schedule has a repeater >> while deadline does not, and vice versa. The former scenario is probably >> valid - a VTODO with limited number of occurrences. > > That is an interesting idea; and we can use the UNTIL or COUNT keywords > in RRULE to implement it. > > However, it doesn't seem completely faithful to the way the TODO ends up > in the Org Agenda (or does Org have some option to use DEADLINE to bound > a repeating SCHEDULED in this way?) DUE in iCalendar and DEADLINE in Org are not exactly the same. So, of course, there is a room for ambiguity. The question is: does iCalendar allow something like DTSTART;TZID=America/New_York:19970105T083000 RRULE:FREQ=YEARLY DUE;TZID=America/New_York:20070105T083000 and repeats past DUE? If not, we have to choose when exporting from Org source - either to keep DUE or not. > I think the most faithful way to represent different SCHEDULED and > DEADLINE repeaters is to export 2 separate VTODOs, each with different > RRULE. Then the exported iCalendar will look just like the Org > Agenda. It is also in line with how ox-icalendar exports multiple > timestamps to separate VEVENTs. If we want to leave as many options as possible to the users, we can (1) Implement ICALENAR_DUE property that will set DUE explicitly on export; (2) ICALENDAR_DUE may allow special values that will indicate how to treat Org DEADLINEs - make them into DUE, use Org DEADLINE as a bound for SCHEDULED repeater, or ignore DEADLINE completely. > That said, I am not really happy with this solution either. The fact > that ox-icalendar can create multiple VEVENT per entry already creates > headaches for any setup doing bidirectional sync between Org and > iCalendar, such as with org-caldav, ical2org.awk, or ical2orgpy. And I > am hesitant to make this problem worse, by making it happen for VTODO as > well. Is there any reason for this? May we instead export to a single VEVENT with appropriate RDATE list? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-14 18:46 ` Ihor Radchenko @ 2023-04-15 3:13 ` Jack Kamm 2023-04-15 9:56 ` Ihor Radchenko 0 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-04-15 3:13 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail Ihor Radchenko <yantar92@posteo.net> writes: > The question is: does iCalendar allow something like > > DTSTART;TZID=America/New_York:19970105T083000 > RRULE:FREQ=YEARLY > DUE;TZID=America/New_York:20070105T083000 > > and repeats past DUE? > > If not, we have to choose when exporting from Org source - either to > keep DUE or not. From the defintion of RRULE [1]: If the duration of the recurring component is specified with the "DTEND" or "DUE" property, then the same exact duration will apply to all the members of the generated recurrence set. So the RRULE applies to both DTSTART and DUE, and the repeats continue past DUE. But, another thing to note from the definition of DTSTART [2]: This property [DTSTART] is REQUIRED in all types of recurring calendar components that specify the "RRULE" property. So technically, a standalone DEADLINE + repeater isn't allowed -- a repeating task must always have a start date. But my impression is that not all iCalendar programs respect this. In particular, Tasks.org app with Nextcloud server seemed to allow a standalone repeating deadline. But I will check this more carefully, and also in a couple more programs (radicale, Evolution). But still, maybe we should stick to the requirement, and only export repeater on SCHEDULED. That would simplify the implementation. The downside is that repeating deadlines won't show up in iCalendar, which seems undesirable. [1] https://www.rfc-editor.org/rfc/rfc5545#section-3.8.5.3: [2] https://www.rfc-editor.org/rfc/rfc5545#section-3.8.2.4 > If we want to leave as many options as possible to the users, we can (1) > Implement ICALENAR_DUE property that will set DUE explicitly on export; > (2) ICALENDAR_DUE may allow special values that will indicate how to > treat Org DEADLINEs - make them into DUE, use Org DEADLINE as a > bound for SCHEDULED repeater, or ignore DEADLINE completely. A couple of these behaviors can already be achieved by customizing `org-icalendar-use-deadline' (making DUE or ignoring). For using DEADLINE as a bound, we could potentially add another option for that. > Is there any reason for this? May we instead export to a single VEVENT > with appropriate RDATE list? I guess if there are multiple timestamps with repeaters, it's easier to export these as separate VEVENT, because it's not possible to have multiple RRULE in one VEVENT. But, your suggestion earlier in thread could also solve this: in case of different repeaters, we can use RDATE to generate occurrences manually sufficiently far into future (with defcustom for "how far"). ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-15 3:13 ` Jack Kamm @ 2023-04-15 9:56 ` Ihor Radchenko 2023-04-16 17:19 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-04-15 9:56 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > So the RRULE applies to both DTSTART and DUE, and the repeats continue > past DUE. > > But, another thing to note from the definition of DTSTART [2]: > > This property [DTSTART] is REQUIRED in all types of recurring > calendar components that specify the "RRULE" property. > > So technically, a standalone DEADLINE + repeater isn't allowed -- a > repeating task must always have a start date. May we then use org-deadline-warning-days/timestamp warntime spec as DTSTART? VALARM component is not fitting for warning days anyway. > But still, maybe we should stick to the requirement, and only export > repeater on SCHEDULED. That would simplify the implementation. The > downside is that repeating deadlines won't show up in iCalendar, which > seems undesirable. Agree. We should better stick to the spec. >> If we want to leave as many options as possible to the users, we can (1) >> Implement ICALENAR_DUE property that will set DUE explicitly on export; >> (2) ICALENDAR_DUE may allow special values that will indicate how to >> treat Org DEADLINEs - make them into DUE, use Org DEADLINE as a >> bound for SCHEDULED repeater, or ignore DEADLINE completely. > > A couple of these behaviors can already be achieved by customizing > `org-icalendar-use-deadline' (making DUE or ignoring). For using > DEADLINE as a bound, we could potentially add another option for that. Yup. That's what I meant. >> Is there any reason for this? May we instead export to a single VEVENT >> with appropriate RDATE list? > > I guess if there are multiple timestamps with repeaters, it's easier to > export these as separate VEVENT, because it's not possible to have > multiple RRULE in one VEVENT. > > But, your suggestion earlier in thread could also solve this: in case of > different repeaters, we can use RDATE to generate occurrences manually > sufficiently far into future (with defcustom for "how far"). RDATE is exactly what I had in mind here. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks 2023-04-15 9:56 ` Ihor Radchenko @ 2023-04-16 17:19 ` Jack Kamm 0 siblings, 0 replies; 21+ messages in thread From: Jack Kamm @ 2023-04-16 17:19 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail Ihor Radchenko <yantar92@posteo.net> writes: >> So technically, a standalone DEADLINE + repeater isn't allowed -- a >> repeating task must always have a start date. > > May we then use org-deadline-warning-days/timestamp warntime spec as DTSTART? > VALARM component is not fitting for warning days anyway. > >> But still, maybe we should stick to the requirement, and only export >> repeater on SCHEDULED. That would simplify the implementation. The >> downside is that repeating deadlines won't show up in iCalendar, which >> seems undesirable. > > Agree. We should better stick to the spec. I took a closer look into how other programs handle RRULE, DTSTART, DUE. I tried the following CalDav servers: Nextcloud, radicale And the following clients: Tasks.org, Thunderbird, Evolution. (I did not use Nextcloud client because it doesn't support repeating tasks, even though the Nextcloud server does). Thunderbird and Evolution clients do not allow creating repeating tasks without start date -- if you try to do so, they will force you to specify one. Tasks.org client does allow repeating tasks with only a deadline (no start date). Nextcloud and radicale servers happily accept the repeating deadline from Tasks.org without start date. When I download the ICS file from the server, the VTODO contains RRULE and DUE, but not DTSTART. When I validate the ICS file with icalendar.org [1], it accepts the ICS as valid, even though it seemingly violates the spec by missing DTSTART. So, it seems there is some inconsistency about this in the iCalendar ecosystem. I have not yet reached a firm conclusion on the best solution, but am leaning towards your suggestion to use org-deadline-warning-days for DTSTART in this case. I'll try to have a more concrete, updated patch on this ready in a couple weeks or so. [1] https://icalendar.org/validator.html ^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH] ox-icalendar: Unscheduled tasks & repeating tasks 2023-03-27 11:59 ` Ihor Radchenko 2023-03-31 5:55 ` Jack Kamm 2023-04-14 16:57 ` Jack Kamm @ 2023-06-11 15:35 ` Jack Kamm 2023-06-12 10:36 ` Ihor Radchenko 2 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-06-11 15:35 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail [-- Attachment #1: Type: text/plain, Size: 1770 bytes --] Hello, I am attaching an updated patch for ox-icalendar unscheduled and repeating TODOs, incorporating some of Ihor's feedback to my RFC some months ago. Compared to my original RFC, here are the main changes: - For unscheduled TODOs with repeating deadline, the deadline warning days is used as the start time by default, in order to comply with the iCalendar spec which demands a start time in this case. - Previously I had separate patches for unscheduled and repeating TODOs, but now I combine them into a single patch because of the way repeats and start times are intertwined for repeating deadlines. - New customization `org-icalendar-todo-unscheduled-start' controls the exported start time for unscheduled TODOs. It replaces `org-icalendar-todo-force-scheduling' from my previous version of the patch. - In case of a SCHEDULED repeater, and a DEADLINE with no repeater, the task repeats until the deadline, using the RRULE UNTIL keyword. - Added linting for the case where SCHEDULED and DEADLINE have mismatching repeaters. - Added several tests for ox-icalendar, and a test for the new lint as well. There are still a few cases that are not yet handled, but they are less common and will take some more work to implement, so I would prefer to leave them to future patches: - Case where SCHEDULED and DEADLINE have mismatched repeaters. We can use RDATE with differing DURATION for this. - Case where DEADLINE has repeater but SCHEDULED does not. We can use RDATE for the first instance, and RRULE for the subsequent repeats. - Case of catch-up "++" repeaters. We can use EXDATE to exclude repeats before today. - Case of restart ".+" repeaters. I don't think iCalendar can handle this case, and we should ignore it. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-ox-icalendar-Add-support-for-unscheduled-and-repeati.patch --] [-- Type: text/x-patch, Size: 22707 bytes --] From 1135e3e7cb08353892c439b085d3bf0bf1072ecb Mon Sep 17 00:00:00 2001 From: Jack Kamm <jackkamm@gmail.com> Date: Sun, 11 Jun 2023 07:50:20 -0700 Subject: [PATCH] ox-icalendar: Add support for unscheduled and repeating TODOs * lisp/ox-icalendar.el (org-icalendar-todo-unscheduled-start): New customization to control the exported start time of unscheduled tasks. (org-icalendar--rrule): Helper function for RRULE export. (org-icalendar--vevent): Use the new helper function for RRULE. (org-icalendar--vtodo): Change how unscheduled TODOs are handled using the new customization option. Export SCHEDULED and DEADLINE repeaters. In case of SCHEDULED repeater and a DEADLINE without repeater, treat DEADLINE as RRULE UNTIL. Emit a warning for tricky edge cases that are not yet implemented. * testing/lisp/test-ox-icalendar.el (test-ox-icalendar/todo-repeater-shared): Test for exporting shared SCHEDULED/DEADLINE repeater. (test-ox-icalendar/todo-repeating-deadline-warndays): Test using warning days as DTSTART of repeating deadline. (test-ox-icalendar/todo-repeater-until): Test using DEADLINE as RRULE UNTIL. (test-ox-icalendar/todo-repeater-until-utc): Test RRULE UNTIL is in UTC format when DTSTART is not in local time format. * lisp/org-lint.el (org-lint-mismatched-planning-repeaters): Add lint for mismatched SCHEDULED and DEADLINE repeaters. * testing/lisp/test-org-lint.el (test-org-lint/mismatched-planning-repeaters): Add test for linting of mismatched SCHEDULED and DEADLINE repeaters. --- etc/ORG-NEWS | 64 ++++++++++++ lisp/org-lint.el | 34 ++++++ lisp/ox-icalendar.el | 165 +++++++++++++++++++++++++----- testing/lisp/test-org-lint.el | 7 ++ testing/lisp/test-ox-icalendar.el | 74 ++++++++++++++ 5 files changed, 320 insertions(+), 24 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 7e7015064..a24caddfe 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -50,6 +50,21 @@ ox-icalendar. In particular, older versions of org-caldav may encounter issues, and users are advised to update to the most recent version of org-caldav. See [[https://github.com/dengste/org-caldav/commit/618bf4cdc9be140ca1993901d017b7f18297f1b8][this org-caldav commit]] for more information. +*** Icalendar export of unscheduled TODOs no longer have start time of today + +For TODOs without a scheduled start time, ox-icalendar no longer +forces them to have a scheduled start time of today when exporting. + +Instead, the new customization ~org-icalendar-todo-unscheduled-start~ +controls the exported start date for unscheduled tasks. Its default +is ~recurring-deadline-warning~ which will export unscheduled tasks +with no start date, unless it has a recurring deadline (in which case +the iCalendar spec demands a start date, and +~org-deadline-warning-days~ is used for that). + +To revert to the old behavior, set +~org-icalendar-todo-unscheduled-start~ to ~current-datetime~. + ** New and changed options *** Commands affected by ~org-fold-catch-invisible-edits~ can now be customized @@ -188,6 +203,28 @@ default settings of "Body only", "Visible only", and "Force publishing" in the ~org-export-dispatch~ UI to be customized, respectively. +*** New option ~org-icalendar-todo-unscheduled-start~ to control unscheduled TODOs in ox-icalendar + +~org-icalendar-todo-unscheduled-start~ controls how ox-icalendar +exports the starting datetime for unscheduled TODOs. Note this option +only has an effect when ~org-icalendar-include-todo~ is non-nil. + +By default, ox-icalendar will not export a start datetime for +unscheduled TODOs, except in cases where the iCalendar spec demands a +start (specifically, for recurring deadlines, in which case +~org-deadline-warning-days~ is used). + +Currently implemented options are: + +- ~recurring-deadline-warning~: The default as described above. +- ~deadline-warning~: Use ~org-deadline-warning-days~ to set the start + time if the unscheduled task has a deadline (recurring or not). +- ~current-datetime~: Revert to old behavior, using the current + datetime as the start of unscheduled tasks. +- ~nil~: Never add a start time for unscheduled tasks. For repeating + tasks this technically violates the iCalendar spec, but some + iCalendar programs support this usage. + ** New features *** ~org-insert-todo-heading-respect-content~ now accepts prefix arguments @@ -230,6 +267,33 @@ editing with Emacs while a ~:session~ block executes. When ~org-return-follows-link~ is non-nil and cursor is over an org-cite citation, ~org-return~ will call ~org-open-at-point~. +*** Add support for repeating tasks in iCalendar export + +Repeating Scheduled and Deadline timestamps in TODOs are now exported +as recurring tasks in iCalendar export. + +In case the TODO has just a single planning timestamp (Scheduled or +Deadline, but not both), its repeater is used as the iCalendar +recurrence rule (RRULE). + +If the TODO has both Scheduled and Deadline planning timestamps, then +the following cases are implemented: + +- If both have the same repeater, then it is used as the RRULE. +- Scheduled has repeater but Deadline does not: the Scheduled repeater + is used as RRULE, and Deadline is used as UNTIL (the end date for + the repeater). This is similar to ~repeated-after-deadline~ in + ~org-agenda-skip-scheduled-if-deadline-is-shown~. + +The following 2 cases are not yet implemented, and the repeater is +skipped (with a warning) if the ox-icalendar export encounters them: + +- Deadline has a repeater but Scheduled does not. +- Scheduled and Deadline have different repeaters. + +Also note that only vanilla repeaters are currently exported; the +special repeaters ~++~ and ~.+~ are skipped. + ** Miscellaneous *** =org-crypt.el= now applies initial visibility settings to decrypted entries diff --git a/lisp/org-lint.el b/lisp/org-lint.el index c2ed007ab..bec1340c5 100644 --- a/lisp/org-lint.el +++ b/lisp/org-lint.el @@ -70,6 +70,7 @@ ;; - non-footnote definitions in footnote section, ;; - probable invalid keywords, ;; - invalid blocks, +;; - mismatched repeaters in planning info line, ;; - misplaced planning info line, ;; - probable incomplete drawers, ;; - probable indented diary-sexps, @@ -882,6 +883,34 @@ (defun org-lint-colon-in-name (ast) "Name \"%s\" contains a colon; Babel cannot use it as input" name))))))) +(defun org-lint-mismatched-planning-repeaters (ast) + (org-element-map ast 'planning + (lambda (e) + (let* ((scheduled (org-element-property :scheduled e)) + (deadline (org-element-property :deadline e)) + (scheduled-repeater-type (org-element-property + :repeater-type scheduled)) + (deadline-repeater-type (org-element-property + :repeater-type deadline)) + (scheduled-repeater-value (org-element-property + :repeater-value scheduled)) + (deadline-repeater-value (org-element-property + :repeater-value deadline))) + (when (and scheduled deadline + (memq scheduled-repeater-type '(cumulate catch-up)) + (memq deadline-repeater-type '(cumulate catch-up)) + (> scheduled-repeater-value 0) + (> deadline-repeater-value 0) + (not + (and + (eq scheduled-repeater-type deadline-repeater-type) + (eq (org-element-property :repeater-unit scheduled) + (org-element-property :repeater-unit deadline)) + (eql scheduled-repeater-value deadline-repeater-value)))) + (list + (org-element-property :begin e) + "Different repeaters in SCHEDULED and DEADLINE timestamps.")))))) + (defun org-lint-misplaced-planning-info (_) (let ((case-fold-search t) reports) @@ -1488,6 +1517,11 @@ (org-lint-add-checker 'invalid-block #'org-lint-invalid-block :trust 'low) +(org-lint-add-checker 'mismatched-planning-repeaters + "Report mismatched repeaters in planning info line" + #'org-lint-mismatched-planning-repeaters + :trust 'low) + (org-lint-add-checker 'misplaced-planning-info "Report misplaced planning info line" #'org-lint-misplaced-planning-info diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 163b3b983..8c569752b 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -231,6 +231,38 @@ (defcustom org-icalendar-include-todo nil (repeat :tag "Specific TODO keywords" (string :tag "Keyword")))) +(defcustom org-icalendar-todo-unscheduled-start 'recurring-deadline-warning + "Exported start date of unscheduled TODOs. + +If `org-icalendar-use-scheduled' contains `todo-start' and a task +has a \"SCHEDULED\" timestamp, that is always used as the start +date. Otherwise, this variable controls whether a start date is +exported and what its value is. + +Note that the iCalendar spec RFC 5545 does not generally require +tasks to have a start date, except for repeating tasks which do +require a start date. However some iCalendar programs ignore the +requirement for repeating tasks, and allow repeating deadlines +without a matching start date. + +This variable has no effect when `org-icalendar-include-todo' is nil. + +Valid values are: +`recurring-deadline-warning' If deadline repeater present, + use `org-deadline-warning-days' as start. +`deadline-warning' If deadline present, + use `org-deadline-warning-days' as start. +`current-datetime' Use the current date-time as start. +nil Never add a start time for unscheduled tasks." + :group 'org-export-icalendar + :type '(choice + (const :tag "Warning days if deadline recurring" recurring-deadline-warning) + (const :tag "Warning days if deadline present" deadline-warning) + (const :tag "Now" current-datetime) + (const :tag "No start date" nil)) + :package-version '(Org . "9.7") + :safe #'symbolp) + (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." @@ -731,6 +763,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" + (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. @@ -756,12 +795,11 @@ (\"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))) + (when (org-element-property :repeater-type timestamp) + (concat (org-icalendar--rrule + (org-element-property :repeater-unit timestamp) + (org-element-property :repeater-value timestamp)) + "\n")) "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)) @@ -784,27 +822,106 @@ (defun org-icalendar--vtodo TIMEZONE specifies a time zone for this TODO only. 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))))))) + (let* ((sc (and (memq 'todo-start org-icalendar-use-scheduled) + (org-element-property :scheduled entry))) + (dl (and (memq 'todo-due org-icalendar-use-deadline) + (org-element-property :deadline entry))) + ;; TODO Implement catch-up repeaters using EXDATE + (sc-repeat-p (and (eq (org-element-property :repeater-type sc) + 'cumulate) + (> (org-element-property :repeater-value sc) 0))) + (dl-repeat-p (and (eq (org-element-property :repeater-type dl) + 'cumulate) + (> (org-element-property :repeater-value dl) 0))) + (repeat-value (or (org-element-property :repeater-value sc) + (org-element-property :repeater-value dl))) + (repeat-unit (or (org-element-property :repeater-unit sc) + (org-element-property :repeater-unit dl))) + (repeat-until (and sc-repeat-p (not dl-repeat-p) dl)) + (start + (cond + (sc) + ((eq org-icalendar-todo-unscheduled-start 'current-datetime) + (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))))) + ((or (and (eq org-icalendar-todo-unscheduled-start + 'deadline-warning) + dl) + (and (eq org-icalendar-todo-unscheduled-start + 'recurring-deadline-warning) + dl-repeat-p)) + (let ((dl-raw (org-element-property :raw-value dl))) + (with-temp-buffer + (insert dl-raw) + (goto-char (point-min)) + (org-timestamp-down-day (org-get-wdays dl-raw)) + (org-element-timestamp-parser))))))) (concat "BEGIN:VTODO\n" "UID:TODO-" uid "\n" (org-icalendar-dtstamp) "\n" - (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 start (concat (org-icalendar-convert-timestamp + start "DTSTART" nil timezone) + "\n")) + (when (and dl (not repeat-until)) + (concat (org-icalendar-convert-timestamp + dl "DUE" nil timezone) + "\n")) + ;; RRULE + (cond + ;; SCHEDULED, DEADLINE have different repeaters + ((and dl-repeat-p + (not (and (eq repeat-value (org-element-property + :repeater-value dl)) + (eq repeat-unit (org-element-property + :repeater-unit dl))))) + ;; TODO Implement via RDATE with changing DURATION + (warn "Not yet implemented: \ +different repeaters on SCHEDULED and DEADLINE. Skipping.") + nil) + ;; DEADLINE has repeater but SCHEDULED doesn't + ((and dl-repeat-p (and sc (not sc-repeat-p))) + ;; TODO SCHEDULED should only apply to first instance; + ;; use RDATE with custom DURATION to implement that + (warn "Not yet implemented: \ +repeater on DEADLINE but not SCHEDULED. Skipping.") + nil) + ((or sc-repeat-p dl-repeat-p) + (concat + (org-icalendar--rrule repeat-unit repeat-value) + ;; add UNTIL part to RRULE + (when repeat-until + (let* ((start-time + (org-element-property :minute-start start)) + ;; RFC5545 requires UTC iff DTSTART is not local time + (local-time-p + (and (not timezone) + (equal org-icalendar-date-time-format + ":%Y%m%dT%H%M%S"))) + (encoded + (org-encode-time + 0 + (or (org-element-property :minute-start repeat-until) + 0) + (or (org-element-property :hour-start repeat-until) + 0) + (org-element-property :day-start repeat-until) + (org-element-property :month-start repeat-until) + (org-element-property :year-start repeat-until)))) + (concat ";UNTIL=" + (cond + ((not start-time) + (format-time-string "%Y%m%d" encoded)) + (local-time-p + (format-time-string "%Y%m%dT%H%M%S" encoded)) + ((format-time-string "%Y%m%dT%H%M%SZ" + encoded t)))))) + "\n"))) "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)) diff --git a/testing/lisp/test-org-lint.el b/testing/lisp/test-org-lint.el index 6ee1b1fab..f61b8647c 100644 --- a/testing/lisp/test-org-lint.el +++ b/testing/lisp/test-org-lint.el @@ -406,6 +406,13 @@ (ert-deftest test-org-lint/colon-in-name () (org-test-with-temp-text "#+name: name\n| a |" (org-lint '(colon-in-name))))) +(ert-deftest test-org-lint/mismatched-planning-repeaters () + "Test `org-lint-mismatched-planning-repeaters' checker." + (should + (org-test-with-temp-text "* H +DEADLINE: <2023-03-26 Sun +2w> SCHEDULED: <2023-03-26 Sun +1w>" + (org-lint '(mismatched-planning-repeaters))))) + (ert-deftest test-org-lint/misplaced-planning-info () "Test `org-lint-misplaced-planning-info' checker." (should diff --git a/testing/lisp/test-ox-icalendar.el b/testing/lisp/test-ox-icalendar.el index bfc756d51..6a0c961d7 100644 --- a/testing/lisp/test-ox-icalendar.el +++ b/testing/lisp/test-ox-icalendar.el @@ -40,5 +40,79 @@ (ert-deftest test-ox-icalendar/crlf-endings () (should (eql 1 (coding-system-eol-type last-coding-system-used)))) (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) +(ert-deftest test-ox-icalendar/todo-repeater-shared () + "Test shared repeater on todo scheduled and deadline." + (let* ((org-icalendar-include-todo 'all) + (tmp-ics (org-test-with-temp-text-in-file + "* TODO Both repeating +DEADLINE: <2023-04-02 Sun +1m> SCHEDULED: <2023-03-26 Sun +1m>" + (expand-file-name (org-icalendar-export-to-ics))))) + (unwind-protect + (with-temp-buffer + (insert-file-contents tmp-ics) + (save-excursion + (should (search-forward "DTSTART;VALUE=DATE:20230326"))) + (save-excursion + (should (search-forward "DUE;VALUE=DATE:20230402"))) + (save-excursion + (should (search-forward "RRULE:FREQ=MONTHLY;INTERVAL=1")))) + (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) + +(ert-deftest test-ox-icalendar/todo-repeating-deadline-warndays () + "Test repeating deadline with DTSTART as warning days." + (let* ((org-icalendar-include-todo 'all) + (org-icalendar-todo-unscheduled-start 'recurring-deadline-warning) + (tmp-ics (org-test-with-temp-text-in-file + "* TODO Repeating deadline +DEADLINE: <2023-04-02 Sun +2w -3d>" + (expand-file-name (org-icalendar-export-to-ics))))) + (unwind-protect + (with-temp-buffer + (insert-file-contents tmp-ics) + (save-excursion + (should (search-forward "DTSTART;VALUE=DATE:20230330"))) + (save-excursion + (should (search-forward "DUE;VALUE=DATE:20230402"))) + (save-excursion + (should (search-forward "RRULE:FREQ=WEEKLY;INTERVAL=2")))) + (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) + +(ert-deftest test-ox-icalendar/todo-repeater-until () + "Test repeater on todo scheduled until deadline." + (let* ((org-icalendar-include-todo 'all) + (tmp-ics (org-test-with-temp-text-in-file + "* TODO Repeating scheduled with nonrepeating deadline +DEADLINE: <2023-05-01 Mon> SCHEDULED: <2023-03-26 Sun +3d>" + (expand-file-name (org-icalendar-export-to-ics))))) + (unwind-protect + (with-temp-buffer + (insert-file-contents tmp-ics) + (save-excursion + (should (search-forward "DTSTART;VALUE=DATE:20230326"))) + (save-excursion + (should (not (re-search-forward "^DUE" nil t)))) + (save-excursion + (should (search-forward "RRULE:FREQ=DAILY;INTERVAL=3;UNTIL=20230501")))) + (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) + +(ert-deftest test-ox-icalendar/todo-repeater-until-utc () + "Test that UNTIL is in UTC when DTSTART is not in local time format." + (let* ((org-icalendar-include-todo 'all) + (org-icalendar-date-time-format ":%Y%m%dT%H%M%SZ") + (tmp-ics (org-test-with-temp-text-in-file + "* TODO Repeating scheduled with nonrepeating deadline +DEADLINE: <2023-05-02 Tue> SCHEDULED: <2023-03-26 Sun 15:00 +3d>" + (expand-file-name (org-icalendar-export-to-ics))))) + (unwind-protect + (with-temp-buffer + (insert-file-contents tmp-ics) + (save-excursion + (should (re-search-forward "DTSTART:2023032.T..0000"))) + (save-excursion + (should (not (re-search-forward "^DUE" nil t)))) + (save-excursion + (should (re-search-forward "RRULE:FREQ=DAILY;INTERVAL=3;UNTIL=2023050.T..0000Z")))) + (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) + (provide 'test-ox-icalendar) ;;; test-ox-icalendar.el ends here -- 2.40.1 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] ox-icalendar: Unscheduled tasks & repeating tasks 2023-06-11 15:35 ` [PATCH] " Jack Kamm @ 2023-06-12 10:36 ` Ihor Radchenko 2023-06-17 17:32 ` Jack Kamm 0 siblings, 1 reply; 21+ messages in thread From: Ihor Radchenko @ 2023-06-12 10:36 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > I am attaching an updated patch for ox-icalendar unscheduled and > repeating TODOs, incorporating some of Ihor's feedback to my RFC some > months ago. Thanks! See some comments below. > +*** New option ~org-icalendar-todo-unscheduled-start~ to control unscheduled TODOs in ox-icalendar > +*** Add support for repeating tasks in iCalendar export > ... > +Also note that only vanilla repeaters are currently exported; the > +special repeaters ~++~ and ~.+~ are skipped. Would it make sense to throw a warning instead of silently skipping ~++~ and ~.+~ repeaters? > +(defcustom org-icalendar-todo-unscheduled-start 'recurring-deadline-warning > + "Exported start date of unscheduled TODOs. > + > +If `org-icalendar-use-scheduled' contains `todo-start' and a task > +has a \"SCHEDULED\" timestamp, that is always used as the start > +date. Otherwise, this variable controls whether a start date is > +exported and what its value is. I think it would make sense to link to this variable in the `org-icalendar-use-scheduled' docstring and possibly in the manual. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH] ox-icalendar: Unscheduled tasks & repeating tasks 2023-06-12 10:36 ` Ihor Radchenko @ 2023-06-17 17:32 ` Jack Kamm 2023-06-18 11:28 ` Ihor Radchenko 0 siblings, 1 reply; 21+ messages in thread From: Jack Kamm @ 2023-06-17 17:32 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode, mail [-- Attachment #1: Type: text/plain, Size: 643 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > Would it make sense to throw a warning instead of silently skipping ~++~ > and ~.+~ repeaters? > > I think it would make sense to link to this variable in the > `org-icalendar-use-scheduled' docstring and possibly in the manual. Thanks, I agree. I've updated the patch to add a warning for the nonstandard repeaters (plus a unit test), and also added links to `org-icalendar-todo-unscheduled-start' in the manual and `org-icalendar-use-scheduled' docstring. For convenience I attach these as separate patches here. If it looks OK I will squash with the prior patch before applying to main. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0002-ox-icalendar-Display-warning-for-unsupported-repeate.patch --] [-- Type: text/x-patch, Size: 4906 bytes --] From 80c05e00335062cc96bdcd85ec507066af4a1d3b Mon Sep 17 00:00:00 2001 From: Jack Kamm <jackkamm@gmail.com> Date: Sat, 17 Jun 2023 07:55:17 -0700 Subject: [PATCH 2/3] ox-icalendar: Display warning for unsupported repeaters This commit to be squashed with the previous one * lisp/ox-icalendar.el (org-icalendar--repeater-type): Helper function to get the repeater type, and display warning if not supported. * testing/lisp/test-ox-icalendar.el (test-ox-icalendar/warn-unsupported-repeater): Unit test to warn for unsupported repeater types. --- lisp/ox-icalendar.el | 30 +++++++++++++++++++++--------- testing/lisp/test-ox-icalendar.el | 14 ++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 8c569752b..0dbc623b4 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -810,6 +810,23 @@ (\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others (org-icalendar--valarm entry timestamp summary) "END:VEVENT"))) +(defun org-icalendar--repeater-type (elem) + "Return ELEM's repeater-type if supported, else warn and return nil." + (let ((repeater-value (org-element-property :repeater-value elem)) + (repeater-type (org-element-property :repeater-type elem))) + (cond + ((not (and repeater-type + repeater-value + (> repeater-value 0))) + nil) + ;; TODO Add catch-up to supported repeaters (use EXDATE to implement) + ((not (memq repeater-type '(cumulate))) + (org-display-warning + (format "Repeater-type %s not currently supported by iCalendar export" + (symbol-name repeater-type))) + nil) + (repeater-type)))) + (defun org-icalendar--vtodo (entry uid summary location description categories timezone class) "Create a VTODO component. @@ -826,13 +843,8 @@ (defun org-icalendar--vtodo (org-element-property :scheduled entry))) (dl (and (memq 'todo-due org-icalendar-use-deadline) (org-element-property :deadline entry))) - ;; TODO Implement catch-up repeaters using EXDATE - (sc-repeat-p (and (eq (org-element-property :repeater-type sc) - 'cumulate) - (> (org-element-property :repeater-value sc) 0))) - (dl-repeat-p (and (eq (org-element-property :repeater-type dl) - 'cumulate) - (> (org-element-property :repeater-value dl) 0))) + (sc-repeat-p (org-icalendar--repeater-type sc)) + (dl-repeat-p (org-icalendar--repeater-type dl)) (repeat-value (or (org-element-property :repeater-value sc) (org-element-property :repeater-value dl))) (repeat-unit (or (org-element-property :repeater-unit sc) @@ -881,14 +893,14 @@ (defun org-icalendar--vtodo (eq repeat-unit (org-element-property :repeater-unit dl))))) ;; TODO Implement via RDATE with changing DURATION - (warn "Not yet implemented: \ + (org-display-warning "Not yet implemented: \ different repeaters on SCHEDULED and DEADLINE. Skipping.") nil) ;; DEADLINE has repeater but SCHEDULED doesn't ((and dl-repeat-p (and sc (not sc-repeat-p))) ;; TODO SCHEDULED should only apply to first instance; ;; use RDATE with custom DURATION to implement that - (warn "Not yet implemented: \ + (org-display-warning "Not yet implemented: \ repeater on DEADLINE but not SCHEDULED. Skipping.") nil) ((or sc-repeat-p dl-repeat-p) diff --git a/testing/lisp/test-ox-icalendar.el b/testing/lisp/test-ox-icalendar.el index 6a0c961d7..e631b2119 100644 --- a/testing/lisp/test-ox-icalendar.el +++ b/testing/lisp/test-ox-icalendar.el @@ -114,5 +114,19 @@ (ert-deftest test-ox-icalendar/todo-repeater-until-utc () (should (re-search-forward "RRULE:FREQ=DAILY;INTERVAL=3;UNTIL=2023050.T..0000Z")))) (when (file-exists-p tmp-ics) (delete-file tmp-ics))))) +(ert-deftest test-ox-icalendar/warn-unsupported-repeater () + "Test warning is emitted for unsupported repeater type." + (let ((org-icalendar-include-todo 'all)) + (should + (member + "Repeater-type restart not currently supported by iCalendar export" + (org-test-capture-warnings + (let ((tmp-ics (org-test-with-temp-text-in-file + "* TODO Unsupported restart repeater +SCHEDULED: <2023-03-26 Sun .+1m>" + (expand-file-name (org-icalendar-export-to-ics))))) + (when (file-exists-p tmp-ics) + (delete-file tmp-ics)))))))) + (provide 'test-ox-icalendar) ;;; test-ox-icalendar.el ends here -- 2.40.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0003-ox-icalendar-Links-in-docs-for-org-icalendar-todo-un.patch --] [-- Type: text/x-patch, Size: 2104 bytes --] From 94f1c01273878e2a7403c1d47ebabe40595de23d Mon Sep 17 00:00:00 2001 From: Jack Kamm <jackkamm@gmail.com> Date: Sat, 17 Jun 2023 09:59:18 -0700 Subject: [PATCH 3/3] ox-icalendar: Links in docs for org-icalendar-todo-unscheduled-start --- doc/org-manual.org | 6 ++++-- lisp/ox-icalendar.el | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/org-manual.org b/doc/org-manual.org index c11694849..89589e32a 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -16054,14 +16054,16 @@ standard iCalendar format. #+vindex: org-icalendar-include-todo #+vindex: org-icalendar-use-deadline #+vindex: org-icalendar-use-scheduled +#+vindex: org-icalendar-todo-unscheduled-start The iCalendar export backend can also incorporate TODO entries based on the configuration of the ~org-icalendar-include-todo~ variable. The backend exports plain timestamps as =VEVENT=, TODO items as =VTODO=, and also create events from deadlines that are in non-TODO items. The backend uses the deadlines and scheduling dates in Org TODO items for setting the start and due dates for the iCalendar TODO -entry. Consult the ~org-icalendar-use-deadline~ and -~org-icalendar-use-scheduled~ variables for more details. +entry. Consult the ~org-icalendar-use-deadline~, +~org-icalendar-use-scheduled~, and +~org-icalendar-todo-unscheduled-start~ variables for more details. #+vindex: org-icalendar-categories #+vindex: org-icalendar-alarm-time diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 0dbc623b4..55ada8e60 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -168,8 +168,9 @@ (defcustom org-icalendar-use-scheduled '(todo-start) `todo-start' - Scheduling time stamps in TODO entries become start date. Some - calendar applications show TODO entries only after that date." + Scheduling time stamps in TODO entries become start date. (See + also `org-icalendar-todo-unscheduled-start', which controls the + start date for TODO entries without a scheduling time stamp)" :group 'org-export-icalendar :type '(set :greedy t -- 2.40.1 ^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH] ox-icalendar: Unscheduled tasks & repeating tasks 2023-06-17 17:32 ` Jack Kamm @ 2023-06-18 11:28 ` Ihor Radchenko 0 siblings, 0 replies; 21+ messages in thread From: Ihor Radchenko @ 2023-06-18 11:28 UTC (permalink / raw) To: Jack Kamm; +Cc: emacs-orgmode, mail Jack Kamm <jackkamm@gmail.com> writes: > For convenience I attach these as separate patches here. If it looks > OK I will squash with the prior patch before applying to main. Approved. ^ permalink raw reply [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).