> > +     (let* ((headline (org-element-lineage
> > +                          (org-element-at-point)
> > +                          (list 'headline 'inlinetasks) t))
>
> *"inlinetask", not "inlinetasks"
>
> I guess we need a test here as well.
>

Fixed. Not sure what is going on here but `org-auto-repeat-maybe' does not
work properly when tested with inlinetasks. What I see is the todo being
toggled and the timestamp with the repeater being ignored. That is what
happened when I switched back to main and tried to
test `org-auto-repeat-maybe'. This does not happen outside of tests, pre or
post my refactor. It's of course possible that there is some issue on my
end. But this problem seems to predate my refactor.

>
> Also,
> * TODO test
> <2012-03-29 Thu +2h>
>
> no longer throws a user-error.

This test still passes on my end. I also added two more test cases for
timestamps with hour repeaters and no start times. I even confirmed
manually that a user-error is thrown for those types of timestamps. When
are you seeing this?

Le mar. 7 juil. 2026 à 13:14, Ihor Radchenko <[email protected]> a écrit :

> Earl Chase <[email protected]> writes:
>
> > +     (let* ((headline (org-element-lineage
> > +                          (org-element-at-point)
> > +                          (list 'headline 'inlinetasks) t))
>
> *"inlinetask", not "inlinetasks"
>
> I guess we need a test here as well.
>
>
> Also,
> * TODO test
> <2012-03-29 Thu +2h>
>
> no longer throws a user-error.
>
> --
> Ihor Radchenko // yantar92,
> Org mode maintainer,
> 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>
>
From a86e02bb9b00080d53c1d1af1b1bdcf43fd96790 Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Wed, 1 Jul 2026 17:44:58 -0500
Subject: [PATCH] lisp/org.el: Transition `org-auto-repeat-maybe' to
 org-element API

* lisp/org.el (org-auto-repeat-maybe): Use org-element
API to get repeater data for timestamps.
* testing/lisp/test-org.el (test-org/auto-repeat-maybe):
New tests for `org-auto-repeat-maybe'.
---
 lisp/org.el              | 150 ++++++-----
 testing/lisp/test-org.el | 542 ++++++++++++++++++++++-----------------
 2 files changed, 400 insertions(+), 292 deletions(-)

diff --git a/lisp/org.el b/lisp/org.el
index 294a5eb52..0723756bb 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -10481,7 +10481,6 @@ This function is run automatically after each state change to a DONE state."
 	 (aa (assoc org-last-state org-todo-kwd-alist))
 	 (interpret (nth 1 aa))
 	 (head (nth 2 aa))
-	 (whata '(("h" . hour) ("d" . day) ("m" . month) ("y" . year)))
 	 (msg "Entry repeats: ")
 	 (org-log-done nil)
 	 (org-todo-log-states nil)
@@ -10525,75 +10524,110 @@ This function is run automatically after each state change to a DONE state."
       ;; a SCHEDULED timestamp without one is removed, as they are no
       ;; longer relevant.
       (save-excursion
-	(let ((scheduled (org-entry-get (point) "SCHEDULED")))
-	  (when (and scheduled (not (string-match-p org-repeat-re scheduled)))
+	(let* ((headline (org-element-lineage
+                          (org-element-at-point)
+                          (list 'headline 'inlinetask) t))
+               (scheduled (org-element-property
+                           :scheduled
+                           headline))
+               (repeater-unit (org-element-property
+                               :repeater-unit
+                               scheduled))
+               (repeater-value (org-element-property
+                                :repeater-value
+                                scheduled))
+               (repeater-type (org-element-property
+                               :repeater-type
+                               scheduled))
+               (has-valid-repeater (and repeater-unit
+                                        repeater-value
+                                        repeater-type)))
+	  (when (and scheduled (not has-valid-repeater))
 	    (org-remove-timestamp-with-keyword org-scheduled-string 'planning))))
       ;; Update every timestamp with a repeater in the entry.
       (let ((planning-re (regexp-opt
 			  (list org-scheduled-string org-deadline-string))))
 	(while (re-search-forward org-repeat-re end t)
-	  (let* ((ts (match-string 0))
+	  (let* ((timestamp (save-match-data
+                              (save-excursion
+                                (goto-char (match-beginning 0))
+                                (org-element-timestamp-parser))))
+                 (repeater-type (org-element-property
+                                 :repeater-type
+                                 timestamp))
+                 (repeater-unit (org-element-property
+                                 :repeater-unit
+                                 timestamp))
+                 (repeater-value (org-element-property
+                                  :repeater-value
+                                  timestamp))
+                 (has-start-time (and (org-element-property
+                                       :hour-start
+                                       timestamp)
+                                      (org-element-property
+                                       :minute-start
+                                       timestamp)))
+                 (time (org-timestamp-to-time timestamp))
+                 (time-in-seconds (float-time time))
 		 (type (if (not (org-at-planning-p)) "Plain:"
 			 (save-excursion
 			   (re-search-backward
 			    planning-re (line-beginning-position) t)
 			   (match-string 0)))))
 	    (when (and (org-at-timestamp-p 'agenda)
-		       (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)" ts))
-	      (let ((n (string-to-number (match-string 2 ts)))
-		    (what (match-string 3 ts)))
-		(when (equal what "w") (setq n (* n 7) what "d"))
-		(when (and (equal what "h")
-			   (not (string-match-p "[0-9]\\{1,2\\}:[0-9]\\{2\\}"
-						ts)))
-		  (user-error
-		   "Cannot repeat in %d hour(s) because no hour has been set"
-		   n))
-		;; Preparation, see if we need to modify the start
-		;; date for the change.
-		(when (match-end 1)
-		  (let ((time (save-match-data (org-time-string-to-time ts)))
-			(repeater-type (match-string 1 ts)))
-		    (cond
-		     ((equal "." repeater-type)
-		      ;; Shift starting date to today, or now if
-		      ;; repeater is by hours.
-		      (if (equal what "h")
-			  (org-timestamp-change
-			   (floor (- (org-timestamp-to-now ts t)) 60) 'minute)
-			(org-timestamp-change
-			 (- (org-today) (time-to-days time)) 'day)))
-		     ((equal "+" repeater-type)
-		      (let ((nshiftmax 10)
-			    (nshift 0))
-			(while (or (= nshift 0)
-				   (if (equal what "h")
-				       (not (time-less-p nil time))
-				     (>= (org-today)
-					 (time-to-days time))))
-			  (when (= nshiftmax (cl-incf nshift))
-			    (or (y-or-n-p
-				 (format "%d repeater intervals were not \
+		       repeater-unit
+                       repeater-value)
+	      (when (equal repeater-unit `week)
+                (setq repeater-value (* repeater-value 7)
+                      repeater-unit 'day))
+              (print (list has-start-time (org-current-line-string)))
+	      (when (and (equal repeater-unit `hour)
+			 (not has-start-time))
+		(user-error
+		 "Cannot repeat in %d hour(s) because no hour has been set"
+		 repeater-value))
+	      ;; Preparation, see if we need to modify the start
+	      ;; date for the change.
+	      (cond
+	       ((equal `restart repeater-type)
+		;; Shift starting date to today, or now if
+		;; repeater is by hours.
+		(if (equal repeater-unit `hour)
+		    (org-timestamp-change
+		     (floor (- (- time-in-seconds (float-time))) 60) 'minute)
+		  (org-timestamp-change
+		   (- (org-today) (time-to-days time)) 'day)))
+	       ((equal `catch-up repeater-type)
+		(let ((nshiftmax 10)
+		      (nshift 0))
+		  (while (or (= nshift 0)
+			     (if (equal repeater-unit `hour)
+				 (not (time-less-p nil time))
+			       (>= (org-today)
+				   (time-to-days time))))
+		    (when (= nshiftmax (cl-incf nshift))
+		      (or (y-or-n-p
+			   (format "%d repeater intervals were not \
 enough to shift date past today.  Continue? "
-					 nshift))
-				(user-error "Abort")))
-			  (org-timestamp-change n (cdr (assoc what whata)))
-			  (org-in-regexp org-ts-regexp3)
-			  (setq ts (match-string 1))
-			  (setq time
-				(save-match-data
-				  (org-time-string-to-time ts)))))
-		      (org-timestamp-change (- n) (cdr (assoc what whata)))
-		      ;; Rematch, so that we have everything in place
-		      ;; for the real shift.
-		      (org-in-regexp org-ts-regexp3)
-		      (setq ts (match-string 1))
-		      (string-match "\\([.+]\\)?\\(\\+[0-9]+\\)\\([hdwmy]\\)"
-				    ts)))))
-		(save-excursion
-		  (org-timestamp-change n (cdr (assoc what whata)) nil t))
-		(setq msg
-		      (concat msg type " " org-last-changed-timestamp " ")))))))
+				   nshift))
+			  (user-error "Abort")))
+		    (org-timestamp-change repeater-value repeater-unit)
+		    (org-in-regexp org-ts-regexp3)
+		    (setq timestamp (save-match-data
+                                      (save-excursion
+                                        (goto-char (match-beginning 0))
+                                        (org-element-timestamp-parser))))
+		    (setq time
+			  (save-match-data
+			    (org-timestamp-to-time timestamp)))))
+		(org-timestamp-change (- repeater-value) repeater-unit)
+		;; Rematch, so that we have everything in place
+		;; for the real shift.
+		(org-in-regexp org-ts-regexp3)))
+	      (save-excursion
+		(org-timestamp-change repeater-value repeater-unit nil t))
+	      (setq msg
+		    (concat msg type " " org-last-changed-timestamp " "))))))
       (save-excursion
         (run-hooks 'org-todo-repeat-hook))
       (setq org-log-post-message msg)
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 9e9be9ebc..ddcdcdf8f 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -8906,267 +8906,341 @@ should not be touched."))
 
 (ert-deftest test-org/auto-repeat-maybe ()
   "Test `org-auto-repeat-maybe' specifications."
-  ;; Do not auto repeat when there is no valid time stamp with
-  ;; a repeater in the entry.
-  (should-not
-   (string-prefix-p
-    "* TODO H"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  (should-not
-   (string-prefix-p
-    "* TODO H"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text "* TODO H\n# <2012-03-29 Thu>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; When switching to DONE state, switch back to first TODO keyword
-  ;; in sequence, or the same keyword if they have different types.
-  (should
-   (string-prefix-p
-    "* TODO H"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  (should
-   (string-prefix-p
-    "* KWD1 H"
-    (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE"))))
-      (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  (should
-   (string-prefix-p
-    "* KWD2 H"
-    (let ((org-todo-keywords '((type "KWD1" "KWD2" "DONE"))))
-      (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; If there was no TODO keyword in the first place, do not insert
-  ;; any either.
-  (should
-   (string-prefix-p
-    "* H"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text "* H\n<2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; Revert to REPEAT_TO_STATE, if set.
-  (should
-   (string-prefix-p
-    "* KWD2 H"
-    (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE"))))
-      (org-test-with-temp-text
-	  "* KWD2 H
+  (cl-macrolet ((test-auto-repeat-maybe (buffer-text test-time expected)
+                  `(let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+                     (org-test-with-temp-text ,buffer-text
+                       (org-test-at-time ,test-time
+                         (org-todo "DONE")
+                         (should (string-match-p
+                                  (regexp-quote ,expected)
+                                  (buffer-string))))))))
+    ;; Do not auto repeat when there is no valid time stamp with
+    ;; a repeater in the entry.
+    (should-not
+     (string-prefix-p
+      "* TODO H"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    (should-not
+     (string-prefix-p
+      "* TODO H"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text "* TODO H\n# <2012-03-29 Thu>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    ;; When switching to DONE state, switch back to first TODO keyword
+    ;; in sequence, or the same keyword if they have different types.
+    (should
+     (string-prefix-p
+      "* TODO H"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    (should
+     (string-prefix-p
+      "* KWD1 H"
+      (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE"))))
+        (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    (should
+     (string-prefix-p
+      "* KWD2 H"
+      (let ((org-todo-keywords '((type "KWD1" "KWD2" "DONE"))))
+        (org-test-with-temp-text "* KWD2 H\n<2012-03-29 Thu +2y>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    ;; If there was no TODO keyword in the first place, do not insert
+    ;; any either.
+    (should
+     (string-prefix-p
+      "* H"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text "* H\n<2012-03-29 Thu +2y>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    ;; Revert to REPEAT_TO_STATE, if set.
+    (should
+     (string-prefix-p
+      "* KWD2 H"
+      (let ((org-todo-keywords '((sequence "KWD1" "KWD2" "DONE"))))
+        (org-test-with-temp-text
+	    "* KWD2 H
 :PROPERTIES:
 :REPEAT_TO_STATE: KWD2
 :END:
 <2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; When switching to DONE state, update base date.  If there are
-  ;; multiple repeated time stamps, update them all.
-  (should
-   (string-match-p
-    "<2014-03-29 .* \\+2y>"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  (should
-   (string-match-p
-    "<2015-03-04 .* \\+1y>"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text
-	  "* TODO H\n<2012-03-29 Thu. +2y>\n<2014-03-04 Tue +1y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; Throw an error if repeater unit is the hour and no time is
-  ;; provided in the timestamp.
-  (should-error
-   (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-     (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2h>"
-       (org-todo "DONE")
-       (buffer-string))))
-  ;; Handle every repeater type using hours step.
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 02:00 +8h>")
-    (org-test-without-dow
-     (org-test-at-time "<2014-03-04 02:35>"
-      (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 +8h>"
-	(org-todo "DONE")
-	(buffer-string))))))
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 10:00 ++8h>")
-    (org-test-without-dow
-     (org-test-at-time "<2014-03-04 02:35>"
-      (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>"
-	(org-todo "DONE")
-	(buffer-string))))))
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 10:35 .+8h>")
-    (org-test-without-dow
-     (org-test-at-time "<2014-03-04 02:35>"
-      (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>"
-	(org-todo "DONE")
-	(buffer-string))))))
-  ;; Handle `org-extend-today-until'.
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 ++1d>")
-    (let ((org-extend-today-until 4))
-      (org-test-without-dow
-       (org-test-at-time "<2014-03-04 02:35>"
-        (org-test-with-temp-text "* TODO H\n<2014-03-03 ++1d>"
 	  (org-todo "DONE")
-	  (buffer-string)))))))
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-06 17:00 ++1d>")
-    (let ((org-extend-today-until 4))
-      (org-test-without-dow
-       (org-test-at-time "<2014-03-05 18:00>"
-        (org-test-with-temp-text "* TODO H\n<2014-03-04 17:00 ++1d>"
+	  (buffer-string)))))
+    ;; When switching to DONE state, update base date.  If there are
+    ;; multiple repeated time stamps, update them all.
+    (should
+     (string-match-p
+      "<2014-03-29 .* \\+2y>"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2y>"
 	  (org-todo "DONE")
-	  (buffer-string)))))))
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 10:00 ++8h>")
-    (let ((org-extend-today-until 4))
+	  (buffer-string)))))
+    ;; week tests
+    (test-auto-repeat-maybe
+     "* TODO Not Done Yet\n<2026-02-01 Sun 23:29 +2w>"
+     "2026-02-04 Mon 08:00"
+     "<2026-02-15 Sun 23:29 +2w>")
+    (test-auto-repeat-maybe
+     "* TODO Pending <2026-03-16 Mon .+3w>"
+     "2026-03-16 Mon 13:11"
+     "<2026-04-06 Mon .+3w>")
+    (test-auto-repeat-maybe
+     "* TODO Not Done Yet\nSCHEDULED: <2026-02-18 Wed 09:30 ++1w>"
+     "2026-02-19 Thu 09:30"
+     "<2026-02-25 Wed 09:30 ++1w")
+    ;; month tests
+    (test-auto-repeat-maybe
+     "* TODO Not Done Yet\n<2026-02-11 Wed 11:00 +2m>"
+     "<2026-02-12 Thu 11:18>"
+     "<2026-04-11 Sat 11:00 +2m>")
+    (test-auto-repeat-maybe
+     "* TODO Pending <2026-06-20 Sat .+3m>"
+     "<2026-06-20 Sat 02:12>"
+     "<2026-09-20 Sun .+3m>")
+    (test-auto-repeat-maybe
+     "* TODO Not Done Yet\nSCHEDULED: <2026-05-09 Wed 05:30 ++1m>"
+     "<2026-05-09 Sat 06:10>"
+     "<2026-06-09 Tue 05:30 ++1m")
+    (should
+     (string-match-p
+      "<2015-03-04 .* \\+1y>"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text
+	    "* TODO H\n<2012-03-29 Thu. +2y>\n<2014-03-04 Tue +1y>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    ;; Throw an error if repeater unit is the hour and no time is
+    ;; provided in the timestamp.
+    (should-error
+     (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+       (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu +2h>"
+         (org-todo "DONE")
+         (buffer-string))))
+    (should-error
+     (org-test-with-temp-text "* TODO H\n<<2026-06-22 Mon .+6h>"
+       (org-todo "DONE")
+       (buffer-string)))
+    (should-error
+     (org-test-with-temp-text "* TODO H\n<2020-02-02 Sun ++3h>"
+       (org-todo "DONE")
+       (buffer-string)))
+    ;; Handle every repeater type using hours step.
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 02:00 +8h>")
       (org-test-without-dow
        (org-test-at-time "<2014-03-04 02:35>"
-        (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>"
-	  (org-todo "DONE")
-	  (buffer-string)))))))
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 18:00 .+1d>")
-    (let ((org-extend-today-until 4))
+         (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 +8h>"
+	   (org-todo "DONE")
+	   (buffer-string))))))
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 10:00 ++8h>")
       (org-test-without-dow
        (org-test-at-time "<2014-03-04 02:35>"
-        (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+1d>"
-	  (org-todo "DONE")
-	  (buffer-string)))))))
-  (should
-   (string-match-p
-    (regexp-quote "<2014-03-04 10:35 .+8h>")
-    (let ((org-extend-today-until 4))
+         (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>"
+	   (org-todo "DONE")
+	   (buffer-string))))))
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 10:35 .+8h>")
       (org-test-without-dow
        (org-test-at-time "<2014-03-04 02:35>"
-        (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>"
+         (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>"
+	   (org-todo "DONE")
+	   (buffer-string))))))
+    ;; Handle `org-extend-today-until'.
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 ++1d>")
+      (let ((org-extend-today-until 4))
+        (org-test-without-dow
+         (org-test-at-time "<2014-03-04 02:35>"
+           (org-test-with-temp-text "* TODO H\n<2014-03-03 ++1d>"
+	     (org-todo "DONE")
+	     (buffer-string)))))))
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-06 17:00 ++1d>")
+      (let ((org-extend-today-until 4))
+        (org-test-without-dow
+         (org-test-at-time "<2014-03-05 18:00>"
+           (org-test-with-temp-text "* TODO H\n<2014-03-04 17:00 ++1d>"
+	     (org-todo "DONE")
+	     (buffer-string)))))))
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 10:00 ++8h>")
+      (let ((org-extend-today-until 4))
+        (org-test-without-dow
+         (org-test-at-time "<2014-03-04 02:35>"
+           (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 ++8h>"
+	     (org-todo "DONE")
+	     (buffer-string)))))))
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 18:00 .+1d>")
+      (let ((org-extend-today-until 4))
+        (org-test-without-dow
+         (org-test-at-time "<2014-03-04 02:35>"
+           (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+1d>"
+	     (org-todo "DONE")
+	     (buffer-string)))))))
+    (should
+     (string-match-p
+      (regexp-quote "<2014-03-04 10:35 .+8h>")
+      (let ((org-extend-today-until 4))
+        (org-test-without-dow
+         (org-test-at-time "<2014-03-04 02:35>"
+           (org-test-with-temp-text "* TODO H\n<2014-03-03 18:00 .+8h>"
+	     (org-todo "DONE")
+	     (buffer-string)))))))
+    ;; week tests
+    (let ((org-extend-today-until 4))
+      (test-auto-repeat-maybe
+       "* TODO Not Done Yet\n<2026-02-01 Sun 04:15 +2w>"
+       "2026-02-04 Mon 04:30"
+       "<2026-02-15 Sun 04:15 +2w>"))
+    (let ((org-extend-today-until 4))
+      (test-auto-repeat-maybe
+       "* TODO Pending <2026-03-16 Mon 03:45 .+3w>"
+       "2026-03-16 Mon 03:45"
+       "<2026-04-05 Sun 03:45 .+3w>"))
+    (let ((org-extend-today-until 4))
+      (test-auto-repeat-maybe
+       "* TODO Not Done Yet\nSCHEDULED: <2026-02-18 Wed 02:00 ++1w>"
+       "2026-02-19 Thu 02:00"
+       "<2026-02-25 Wed 02:00 ++1w"))
+    ;; month tests
+    (let ((org-extend-today-until 3))
+      (test-auto-repeat-maybe
+       "* TODO Not Done Yet\n<2026-02-11 Wed 02:30 +2m>"
+       "<2026-02-12 Wed 3:18>"
+       "<2026-04-11 Sat 02:30 +2m>"))
+    (let ((org-extend-today-until 3))
+      (test-auto-repeat-maybe
+       "* TODO Pending <2026-06-20 Sat 01:30 .+3m>"
+       "<2026-06-20 Sat 04:12>"
+       "<2026-09-20 Sun 01:30 .+3m>"))
+    (let ((org-extend-today-until 3))
+      (test-auto-repeat-maybe
+       "* TODO Not Done Yet\nSCHEDULED: <2026-05-09 Wed 01:30 ++1m>"
+       "<2026-05-09 Sat 05:00>"
+       "<2026-06-09 Tue 01:30 ++1m"))
+    ;; Do not repeat inactive time stamps with a repeater.
+    (should-not
+     (string-match-p
+      "\\[2014-03-29 .* \\+2y\\]"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text
+	    "* TODO H\n[2012-03-29 Thu. +2y]"
 	  (org-todo "DONE")
-	  (buffer-string)))))))
-  ;; Do not repeat inactive time stamps with a repeater.
-  (should-not
-   (string-match-p
-    "\\[2014-03-29 .* \\+2y\\]"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text
-	  "* TODO H\n[2012-03-29 Thu. +2y]"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; Do not repeat commented time stamps.
-  (should-not
-   (string-prefix-p
-    "<2015-03-04 .* \\+1y>"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text
-	  "* TODO H\n<2012-03-29 Thu +2y>\n# <2014-03-04 Tue +1y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  (should-not
-   (string-prefix-p
-    "<2015-03-04 .* \\+1y>"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text
-	  "* TODO H
+	  (buffer-string)))))
+    ;; Do not repeat commented time stamps.
+    (should-not
+     (string-prefix-p
+      "<2015-03-04 .* \\+1y>"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text
+	    "* TODO H\n<2012-03-29 Thu +2y>\n# <2014-03-04 Tue +1y>"
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    (should-not
+     (string-prefix-p
+      "<2015-03-04 .* \\+1y>"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text
+	    "* TODO H
 <2012-03-29 Thu. +2y>
 #+BEGIN_EXAMPLE
 <2014-03-04 Tue +1y>
 #+END_EXAMPLE"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; When `org-log-repeat' is non-nil or there is a CLOCK in the
-  ;; entry, record time of last repeat.
-  (should-not
-   (string-match-p
-    ":LAST_REPEAT:"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE")))
-	  (org-log-repeat nil))
-      (cl-letf (((symbol-function 'org-add-log-setup)
-		 (lambda (&rest _args) nil)))
-	(org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>"
 	  (org-todo "DONE")
-	  (buffer-string))))))
-  (should
-   (string-match-p
-    ":LAST_REPEAT:"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE")))
-	  (org-log-repeat t))
-      (cl-letf (((symbol-function 'org-add-log-setup)
-		 (lambda (&rest _args) nil)))
-	(org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>"
-	  (org-todo "DONE")
-	  (buffer-string))))))
-  (should
-   (string-match-p
-    ":LAST_REPEAT:"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (cl-letf (((symbol-function 'org-add-log-setup)
-		 (lambda (&rest _args) nil)))
-	(org-test-with-temp-text
-	    "* TODO H\n<2012-03-29 Thu +2y>\nCLOCK: [2012-03-29 Thu 16:40]"
+	  (buffer-string)))))
+    ;; When `org-log-repeat' is non-nil or there is a CLOCK in the
+    ;; entry, record time of last repeat.
+    (should-not
+     (string-match-p
+      ":LAST_REPEAT:"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE")))
+	    (org-log-repeat nil))
+        (cl-letf (((symbol-function 'org-add-log-setup)
+		   (lambda (&rest _args) nil)))
+	  (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>"
+	    (org-todo "DONE")
+	    (buffer-string))))))
+    (should
+     (string-match-p
+      ":LAST_REPEAT:"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE")))
+	    (org-log-repeat t))
+        (cl-letf (((symbol-function 'org-add-log-setup)
+		   (lambda (&rest _args) nil)))
+	  (org-test-with-temp-text "* TODO H\n<2012-03-29 Thu. +2y>"
+	    (org-todo "DONE")
+	    (buffer-string))))))
+    (should
+     (string-match-p
+      ":LAST_REPEAT:"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (cl-letf (((symbol-function 'org-add-log-setup)
+		   (lambda (&rest _args) nil)))
+	  (org-test-with-temp-text
+	      "* TODO H\n<2012-03-29 Thu +2y>\nCLOCK: [2012-03-29 Thu 16:40]"
+	    (org-todo "DONE")
+	    (buffer-string))))))
+    ;; When a SCHEDULED entry has no repeater, remove it upon repeating
+    ;; the entry as it is no longer relevant.
+    (should-not
+     (string-match-p
+      "^SCHEDULED:"
+      (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text
+	    "* TODO H\nSCHEDULED: <2014-03-04 Tue>\n<2012-03-29 Thu +2y>"
 	  (org-todo "DONE")
-	  (buffer-string))))))
-  ;; When a SCHEDULED entry has no repeater, remove it upon repeating
-  ;; the entry as it is no longer relevant.
-  (should-not
-   (string-match-p
-    "^SCHEDULED:"
-    (let ((org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text
-	  "* TODO H\nSCHEDULED: <2014-03-04 Tue>\n<2012-03-29 Thu +2y>"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; Properly advance repeater even when a clock entry is specified
-  ;; and `org-log-repeat' is nil.
-  (should
-   (string-match-p
-    "SCHEDULED: <2014-03-29"
-    (let ((org-log-repeat nil)
-	  (org-todo-keywords '((sequence "TODO" "DONE"))))
-      (org-test-with-temp-text
-	  "* TODO H
+	  (buffer-string)))))
+    ;; Properly advance repeater even when a clock entry is specified
+    ;; and `org-log-repeat' is nil.
+    (should
+     (string-match-p
+      "SCHEDULED: <2014-03-29"
+      (let ((org-log-repeat nil)
+	    (org-todo-keywords '((sequence "TODO" "DONE"))))
+        (org-test-with-temp-text
+	    "* TODO H
 SCHEDULED: <2012-03-29 Thu +2y>
 CLOCK: [2012-03-29 Thu 10:00]--[2012-03-29 Thu 16:40] =>  6:40"
-	(org-todo "DONE")
-	(buffer-string)))))
-  ;; Make sure that logbook state change record does not get
-  ;; duplicated when `org-log-repeat' `org-log-done' are non-nil.
-  (should
-   (string-match-p
-    (rx "* TODO Read book
+	  (org-todo "DONE")
+	  (buffer-string)))))
+    ;; Make sure that logbook state change record does not get
+    ;; duplicated when `org-log-repeat' `org-log-done' are non-nil.
+    (should
+     (string-match-p
+      (rx "* TODO Read book
 SCHEDULED: <2021-06-16 " (1+ (not space)) " +1d>
 :PROPERTIES:
 :LAST_REPEAT:" (1+ nonl) "
 :END:
 - State \"DONE\"       from \"TODO\"" (1+ nonl) buffer-end)
-    (let ((org-log-repeat 'time)
-	  (org-todo-keywords '((sequence "TODO" "|" "DONE(d!)")))
-          (org-log-into-drawer nil))
-      (org-test-with-temp-text
-          "* TODO Read book
+      (let ((org-log-repeat 'time)
+	    (org-todo-keywords '((sequence "TODO" "|" "DONE(d!)")))
+            (org-log-into-drawer nil))
+        (org-test-with-temp-text
+            "* TODO Read book
 SCHEDULED: <2021-06-15 Tue +1d>"
-        (org-todo "DONE")
-        (when (memq 'org-add-log-note (default-value 'post-command-hook))
-          (org-add-log-note))
-        (buffer-string))))))
+          (org-todo "DONE")
+          (when (memq 'org-add-log-note (default-value 'post-command-hook))
+            (org-add-log-note))
+          (buffer-string)))))))
 
 (ert-deftest test-org/org-log-done ()
   "Test `org-log-done' specifications.
-- 
2.54.0

Reply via email to