> >> > +     (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.
>
> Could you elaborate what exactly does not work?
> Repeaters do work for inlinetasks on my side.
>

Same for me. I am saying that I can't test them. I attached a patch to
this email that contains two simple ert tests for repeaters. These
tests were not written on the branch that I created for this commit.
One is for inlinetasks and the other uses the same repeaters with
regular headings. For the inlinetasks, the TODO state changes and the
repeaters are ignored.

> >> 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?
>
> Well. The org-todo itself does not throw an error, because the error
> from org-auto-repeat-maybe is suppressed. But it does show up in the
> messages on main. After your patch, it does not.
>

I am confused here. When I run my patch, I still see: "user-error:
Cannot repeat in 2 hour(s) because no hour has been set" in Messages
when I use org-todo on a heading with a repeater like <2012-03-29 Thu
+2h>. I just did a git pull --rebase on the branch that I created for
this patch. Can you try the below version of my patch and see if the
error still shows up.
Le sam. 11 juil. 2026 à 05:03, 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.
> >
> > 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.
>
> Could you elaborate what exactly does not work?
> Repeaters do work for inlinetasks on my side.
>
> >> 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?
>
> Well. The org-todo itself does not throw an error, because the error
> from org-auto-repeat-maybe is suppressed. But it does show up in the
> messages on main. After your patch, it does not.
>
>
> --
> 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 9e7d29bbab80703a3d36b1f675423ee4a1dba2dd Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Sun, 12 Jul 2026 13:06:28 -0400
Subject: [PATCH] Failing inlinetask tests

---
 testing/lisp/test-org.el | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 9e9be9ebc..0a3eea78d 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -9168,6 +9168,29 @@ SCHEDULED: <2021-06-15 Tue +1d>"
           (org-add-log-note))
         (buffer-string))))))
 
+(ert-deftest test-org/auto-repeat-maybe/inlinetask ()
+  "These fail."
+  (org-test-with-temp-text "*************** TODO inline <2012-03-29 Thu +2y>"
+    (org-todo "DONE")
+    (should (string-match-p (regexp-quote "[2026-07-12 Sun") (buffer-string))))
+  
+  (org-test-with-temp-text "*************** TODO inline
+SCHEDULED: <2012-03-29 Thu +7d>"
+    (org-todo "DONE")
+    (should (string-match-p (regexp-quote "<2012-04-05 Thu +7d>") (buffer-string)))))
+
+(ert-deftest test-org/auto-repeat-maybe/not-inlinetask ()
+  "These don't fail."
+  (org-test-with-temp-text "* TODO inline <2012-03-29 Thu +2y>"
+    (org-todo "DONE")
+    (should (string-match-p (regexp-quote "[2026-07-12 Sun") (buffer-string))))
+  
+  (org-test-with-temp-text "* TODO inline
+SCHEDULED: <2012-03-29 Thu +7d>"
+    (org-todo "DONE")
+    (should (string-match-p (regexp-quote "<2012-04-05 Thu +7d>") (buffer-string)))))
+
+
 (ert-deftest test-org/org-log-done ()
   "Test `org-log-done' specifications.
 Behavior can be modified by setting `org-log-done', by keywords in
-- 
2.54.0

From afaca0b3fd40f9a7f04025b05c6289851db529b5 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