emacs-elpa-diffs
[Top][All Lists]
Advanced

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

[elpa] externals/org 294a4d2fe2: ox-icalendar: Add support for unschedul


From: ELPA Syncer
Subject: [elpa] externals/org 294a4d2fe2: ox-icalendar: Add support for unscheduled and repeating TODOs
Date: Sun, 18 Jun 2023 09:59:20 -0400 (EDT)

branch: externals/org
commit 294a4d2fe21ffcdc1acbbafed1bfc69a1ece7d13
Author: Jack Kamm <jackkamm@gmail.com>
Commit: Jack Kamm <jackkamm@gmail.com>

    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--repeater-type): Helper function to get the repeater
    type, and display warning if not supported.
    (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.
    (test-ox-icalendar/warn-unsupported-repeater): Unit test to warn for
    unsupported repeater types.
    * 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.
    * doc/org-manual.org (iCalendar Export): Add link to new variable
    `org-icalendar-todo-unscheduled-start'.
---
 doc/org-manual.org                |   6 +-
 etc/ORG-NEWS                      |  64 ++++++++++++++
 lisp/org-lint.el                  |  34 +++++++
 lisp/ox-icalendar.el              | 182 ++++++++++++++++++++++++++++++++------
 testing/lisp/test-org-lint.el     |   7 ++
 testing/lisp/test-ox-icalendar.el |  88 ++++++++++++++++++
 6 files changed, 353 insertions(+), 28 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index 4b00d1e6fd..4feb15e71f 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -16071,14 +16071,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/etc/ORG-NEWS b/etc/ORG-NEWS
index 7e70150640..a24caddfed 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 418ed8c6f5..8d9047ba97 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,
@@ -905,6 +906,34 @@ Use \"export %s\" instead"
                    "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)
@@ -1516,6 +1545,11 @@ AST is the buffer parse tree."
   #'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 163b3b9833..55ada8e60b 100644
--- a/lisp/ox-icalendar.el
+++ b/lisp/ox-icalendar.el
@@ -168,8 +168,9 @@ This is a list with possibly several symbols in it.  Valid 
symbols are:
 
 `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
@@ -231,6 +232,38 @@ t                    include tasks that are not in DONE 
state.
          (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 +764,13 @@ inlinetask within the section."
        ;; 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 +796,11 @@ Return VEVENT component as a string."
            (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))
@@ -772,6 +811,23 @@ Return VEVENT component as a string."
            (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.
@@ -784,27 +840,101 @@ task.  CATEGORIES defines the categories the task 
belongs to.
 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)))
+         (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)
+                          (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
+              (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
+              (org-display-warning "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 6ee1b1fab8..f61b8647ce 100644
--- a/testing/lisp/test-org-lint.el
+++ b/testing/lisp/test-org-lint.el
@@ -406,6 +406,13 @@ This is not a node property
    (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 bfc756d513..e631b21193 100644
--- a/testing/lisp/test-ox-icalendar.el
+++ b/testing/lisp/test-ox-icalendar.el
@@ -40,5 +40,93 @@
           (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)))))
+
+(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



reply via email to

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