> >> I suggest building upon existing patch that uses global variable
> >> instead. See https://list.orgmode.org/orgmode/[email protected]/
> >
> > I reverted my changes and followed your advice. Unfortunately, I was not
> > able to create ert-tests for this approach. But it does work when I test it
> > manually.
>
> Indeed. We need some Elisp API to provide date to org-todo-at.
> org-read-date should only be used interactively.
>
> > +- {{{kbd(C-c M-t)}}} (~org-todo-at~) ::
> > +
> > + #+kindex: C-c M-t
> > + #+cindex: cycling, of TODO states
> > + Rotate the TODO state of the current item,
> > + just like ~org-todo~. Will prompt you for
> > + a time for each state change.
>
> We also need other similar commands. For example, org-agenda-todo-at.
>
I added `org-agenda-todo-at' as a separate patch. Both
`org-agenda-todo-at' and `org-todo-at' only use the interactive
version of `org-read-date' when they are called interactively. But
the specific problem that I was referring to is more complicated than
that. In addition to the `org-todo-at' patch and the
`org-agenda-todo-at', I created another patch with a simple test that
shows why this approach makes it impossible to test `org-todo-at' with
repeaters, for the moment at least. You will see that when you call
`org-todo-at', LAST_REPEAT gets set to the correct value but the
repeater itself is not set to the right value. I still haven't found
the root cause of this issue. But I do know that LAST_REPEAT is always
set to the correct time because `org-auto-repeat-maybe' directly uses
`org-current-effective-time' to create the new value of LAST_REPEAT.
On the other hand, `org-today' and `org-timestamp-to-now' are the
functions that `org-auto-repeat-maybe' uses to actually update a
repeater. Those functions of course can be overridden. But I think a
better idea would be to create org versions of `time-to-days',
`time-since' and `float-time', as those are the functions that
`org-timestamp-to-now' and `org-today' uses internally. I still don't
understand why none of that is needed for manual tests.
> > +(defvar org-overriding-effective-time nil
> > + "Place for `org-todo-at' to store user-entered time.
> > +Used to override the value return by `org-current-effective-time'.")
>
> This affects more than just org-todo-at, but everything using
> org-current-effective-time. The docstring should reflect that.
>
Done.
> > +(defun org-todo-at (&optional arg)
> > + "Like `org-todo', but you will be prompted for a time.
> > +The value you provided will be used as the time value of any
> > +state change that is triggered by `org-todo'.
> > +
> > +With `\\[universal-argument]' prefix ARG, force logging the state change \
> > +and take a
> > +logging note.
> > +With a `\\[universal-argument] \\[universal-argument]' prefix, switch to
> > the \
> > +next set of TODO \
> > +keywords (nextset).
> > +Another way to achieve this is `S-C-<right>'.
> > +With a `\\[universal-argument] \\[universal-argument]
> > \\[universal-argument]' \
> > +prefix, circumvent any state blocking.
> > +With numeric prefix arg, switch to the Nth state.
> > +
> > +With a numeric prefix arg of 0, inhibit note taking for the change.
> > +With a numeric prefix arg of -1, cancel repeater to allow marking as DONE.
>
> Copy-pasting docstring from org-todo is not useful. We should better
> just put a link to org-todo docstring.
>
Done.
> > + (interactive)
>
> This will make org-todo-at ignore all the prefix arguments.
>
Fixed.
Le sam. 11 juil. 2026 à 05:42, Ihor Radchenko <[email protected]> a écrit :
>
> Earl Chase <[email protected]> writes:
>
> >> I suggest building upon existing patch that uses global variable
> >> instead. See https://list.orgmode.org/orgmode/[email protected]/
> >
> > I reverted my changes and followed your advice. Unfortunately, I was not
> > able to create ert-tests for this approach. But it does work when I test it
> > manually.
>
> Indeed. We need some Elisp API to provide date to org-todo-at.
> org-read-date should only be used interactively.
>
> > +- {{{kbd(C-c M-t)}}} (~org-todo-at~) ::
> > +
> > + #+kindex: C-c M-t
> > + #+cindex: cycling, of TODO states
> > + Rotate the TODO state of the current item,
> > + just like ~org-todo~. Will prompt you for
> > + a time for each state change.
>
> We also need other similar commands. For example, org-agenda-todo-at.
>
> > +(defvar org-overriding-effective-time nil
> > + "Place for `org-todo-at' to store user-entered time.
> > +Used to override the value return by `org-current-effective-time'.")
>
> This affects more than just org-todo-at, but everything using
> org-current-effective-time. The docstring should reflect that.
>
> > +(defun org-todo-at (&optional arg)
> > + "Like `org-todo', but you will be prompted for a time.
> > +The value you provided will be used as the time value of any
> > +state change that is triggered by `org-todo'.
> > +
> > +With `\\[universal-argument]' prefix ARG, force logging the state change \
> > +and take a
> > +logging note.
> > +With a `\\[universal-argument] \\[universal-argument]' prefix, switch to
> > the \
> > +next set of TODO \
> > +keywords (nextset).
> > +Another way to achieve this is `S-C-<right>'.
> > +With a `\\[universal-argument] \\[universal-argument]
> > \\[universal-argument]' \
> > +prefix, circumvent any state blocking.
> > +With numeric prefix arg, switch to the Nth state.
> > +
> > +With a numeric prefix arg of 0, inhibit note taking for the change.
> > +With a numeric prefix arg of -1, cancel repeater to allow marking as DONE.
>
> Copy-pasting docstring from org-todo is not useful. We should better
> just put a link to org-todo docstring.
>
> > + (interactive)
>
> This will make org-todo-at ignore all the prefix arguments.
>
> --
> 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 b4fa737c94da980b65cd72a668d8e2ce20ae7aff Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Mon, 13 Jul 2026 08:41:34 -0400
Subject: [PATCH 2/3] lisp/org-agenda.el: Call `org-agenda-todo' at a given
time.
* lisp/org-agenda.el (org-agenda-todo-at): New function for
calling `org-agenda-todo' at a given time.
(org-agenda-mode-map): New keybinding for `org-agenda-todo-at'.
---
doc/org-manual.org | 10 ++++++++++
etc/ORG-NEWS | 8 ++++----
lisp/org-agenda.el | 12 ++++++++++++
3 files changed, 26 insertions(+), 4 deletions(-)
diff --git a/doc/org-manual.org b/doc/org-manual.org
index 70e274611..a5367ce00 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -10443,6 +10443,16 @@ the other commands, point needs to be in the desired line.
~org-todo~ command, so for example a {{{kbd(C-u)}}} prefix are will
trigger taking a note to document the state change.
+- {{{kbd(C-c M-t)}}} (~org-agenda-todo-at~) ::
+
+ #+kindex: C-c M-t
+ #+findex: org-agenda-todo-at
+ Change the TODO state of the item, both in the agenda and in the
+ original Org file, just like ~org-agenda-todo~. You will first
+ be prompted for a time. This time will be used as the time of
+ the state change of your TODO. Prefix arguments will be passed
+ through to the ~org-todo~ command.
+
- {{{kbd(C-S-RIGHT)}}} (~org-agenda-todo-nextset~) ::
#+kindex: C-S-RIGHT
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index edc336988..504fbeb77 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -227,11 +227,11 @@ needs. The recommended alternative value is ~itemize~.
Given the completed and total number of tasks, format the percent
cookie =[N%]=.
-*** New function ~org-todo-at~
+*** New functions ~org-todo-at~ and ~org-agenda-todo-at~
-Like `org-todo', but you will be first be prompted for a time.
-This time will be used as the time of the TODO state change of
-the item that you are editing.
+Like `org-todo', but you will be first be prompted for a time
+by both of these functions. This time will be used as the
+time of the TODO state change of the item that you are editing.
** Removed or renamed functions and variables
diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index 707444b8a..12c912784 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -97,6 +97,7 @@
(defvar org-habit-show-habits-only-for-today)
(defvar org-habit-show-all-today)
(defvar org-habit-scheduled-past-days)
+(defvar org-overriding-effective-time)
;; Defined somewhere in this file, but used before definition.
(defvar org-agenda-buffer-name "*Org Agenda*")
@@ -2449,6 +2450,7 @@ The following commands are available:
(org-defkey org-agenda-mode-map "L" #'org-agenda-recenter)
(org-defkey org-agenda-mode-map "\C-c\C-t" #'org-agenda-todo)
(org-defkey org-agenda-mode-map "t" #'org-agenda-todo)
+(org-defkey org-agenda-mode-map "\C-c\M-t" #'org-agenda-todo-at)
(org-defkey org-agenda-mode-map "a" #'org-agenda-archive-default-with-confirmation)
(org-defkey org-agenda-mode-map ":" #'org-agenda-set-tags)
(org-defkey org-agenda-mode-map "\C-c\C-q" #'org-agenda-set-tags)
@@ -11265,6 +11267,16 @@ when defining today."
(org-extend-today-until (1+ hour)))
(org-agenda-todo arg)))
+(defun org-agenda-todo-at (time &optional arg)
+ "Like `org-agenda-todo', but you will be prompted for a TIME.
+The value you provided will be used as the time value of any
+state change that is triggered by `org-todo'. The value of
+prefix ARG will be given to `org-todo'. See the docstring
+of `org-todo' for more information."
+ (interactive (list (org-read-date t) current-prefix-arg) org-mode)
+ (let ((org-overriding-effective-time (org-read-date nil t time)))
+ (org-agenda-todo arg)))
+
(defun org-agenda-ctrl-c-ctrl-c ()
"Set tags in agenda buffer."
(interactive nil org-agenda-mode)
--
2.54.0
From d81837a83d5d3298b7cc2ba9865a736f13cd2ab9 Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Mon, 13 Jul 2026 11:24:59 -0400
Subject: [PATCH 3/3] lisp/test-org.el: Example failing tests
---
testing/lisp/test-org.el | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el
index 9e9be9ebc..dec6381ef 100644
--- a/testing/lisp/test-org.el
+++ b/testing/lisp/test-org.el
@@ -9168,6 +9168,19 @@ SCHEDULED: <2021-06-15 Tue +1d>"
(org-add-log-note))
(buffer-string))))))
+(ert-deftest test-org/auto-repeat-maybe/org-todo-at ()
+ "Test `org-auto-repeat-maybe' with `org-todo-at'."
+ (org-test-with-temp-text "* TODO H\n<2014-03-03 Mon 18:00 .+8h>"
+ (org-todo-at "<2014-03-04 Tue 02:35>" "DONE")
+ (should (string-match-p
+ (regexp-quote "<2014-03-04 10:35 .+8h>")
+ (buffer-string))))
+ (org-test-with-temp-text "* TODO H\nSCHEDULED: <2020-09-15 Tue 18:00 .+2d>"
+ (org-todo-at "<2020-09-17 Thu 10:00 .+2d>" "DONE")
+ (should (string-match-p
+ (regexp-quote "<2020-09-19 10:00 .+2d>")
+ (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 06f9ae2e37d20916079c2032b06d9419e1c04711 Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Thu, 2 Jul 2026 16:43:08 -0500
Subject: [PATCH 1/3] lisp/org.el: New function for calling `org-todo' at a
given time.
* lisp/org.el (org-overriding-effective-time): Place for
`org-todo-at' to store user-entered time.
(org-current-effective-time): Return the value of
`org-overriding-effective-time' when it is non-nil.
(org-todo-at): Call `org-todo' at a given
time.
* lisp/org-keys.el (org-mode-map): New keybinding
for `org-todo-at'.
---
doc/org-manual.org | 22 ++++++++++++++++++++++
etc/ORG-NEWS | 5 +++++
lisp/org-keys.el | 2 ++
lisp/org.el | 21 ++++++++++++++++++++-
4 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/doc/org-manual.org b/doc/org-manual.org
index 9201a341a..70e274611 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -3977,6 +3977,14 @@ The most important commands to work with TODO entries are:
buffer with the {{{kbd(t)}}} command key (see [[*Commands in the
Agenda Buffer]]).
+- {{{kbd(C-c M-t)}}} (~org-todo-at~) ::
+
+ #+kindex: C-c M-t
+ #+cindex: cycling, of TODO states
+ Rotate the TODO state of the current item,
+ just like ~org-todo~. Will prompt you for
+ a time for each state change.
+
- {{{kbd(S-RIGHT)}}} {{{kbd(S-LEFT)}}} ::
#+kindex: S-RIGHT
@@ -4369,6 +4377,13 @@ command ~org-todo~ with a prefix argument.
The note is inserted as a list item below the headline, but can also
be placed into a drawer, see [[*Tracking TODO state changes]].
+- {{{kbd(C-u C-c M-t)}}} (~org-todo-at~) ::
+
+ #+kindex: C-u C-M C-t
+ Prompt for a time for the TODO state change and a note.
+ The note is inserted as a list item below the headline,
+ but can also be placed into a drawer, just like ~org-todo~.
+
If you want to be more systematic, Org mode can automatically record a
timestamp and optionally a note when you mark a TODO item as DONE, or
even each time you change the state of a TODO item. This system is
@@ -7002,6 +7017,13 @@ about what to do with it.
Changing the TODO state of an item to DONE automatically stops the
clock if it is running in this same item.
+- {{{kbd(C-c M-t)}}} (~org-todo-at~) ::
+
+ #+kindex: C-c M-t
+ #+findex: org-todo-at
+ Changing the TODO state of an item to DONE automatically stops the
+ clock at the time that you provide if it is running in this same item.
+
- {{{kbd(C-c C-x C-q)}}} (~org-clock-cancel~) ::
#+kindex: C-c C-x C-q
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 261d5d3be..edc336988 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -227,6 +227,11 @@ needs. The recommended alternative value is ~itemize~.
Given the completed and total number of tasks, format the percent
cookie =[N%]=.
+*** New function ~org-todo-at~
+
+Like `org-todo', but you will be first be prompted for a time.
+This time will be used as the time of the TODO state change of
+the item that you are editing.
** Removed or renamed functions and variables
diff --git a/lisp/org-keys.el b/lisp/org-keys.el
index 84eb3bf13..6853e9f3d 100644
--- a/lisp/org-keys.el
+++ b/lisp/org-keys.el
@@ -215,6 +215,7 @@
(declare-function org-timer-start "org-timer" (&optional offset))
(declare-function org-timer-stop "org-timer" ())
(declare-function org-todo "org" (&optional arg1))
+(declare-function org-todo-at "org" (time &optional arg))
(declare-function org-toggle-archive-tag "org-archive" (&optional find-done))
(declare-function org-toggle-checkbox "org-list" (&optional toggle-presence))
(declare-function org-toggle-radio-button "org-list" (&optional arg))
@@ -576,6 +577,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names."
(org-defkey org-mode-map (kbd "C-c C-x q") #'org-toggle-tags-groups)
(org-defkey org-mode-map (kbd "C-c C-j") #'org-goto)
(org-defkey org-mode-map (kbd "C-c C-t") #'org-todo)
+(org-defkey org-mode-map (kbd "C-c M-t") #'org-todo-at)
(org-defkey org-mode-map (kbd "C-c C-q") #'org-set-tags-command)
(org-defkey org-mode-map (kbd "C-c C-s") #'org-schedule)
(org-defkey org-mode-map (kbd "C-c C-d") #'org-deadline)
diff --git a/lisp/org.el b/lisp/org.el
index 294a5eb52..b9342a527 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -9611,12 +9611,21 @@ nil or a string to be used for the todo mark." )
(defvar org-agenda-headline-snapshot-before-repeat)
+(defvar org-overriding-effective-time nil
+ "Place to store user-entered time for `org-current-effective-time'.
+As long as this variable is set, `org-current-effective-time' will
+only return this value. Used by `org-todo-at' to set the time of
+TODO state changes.")
+
(defun org-current-effective-time ()
- "Return current time adjusted for `org-extend-today-until' variable."
+ "Return current time adjusted for `org-extend-today-until' variable.
+If `org-overriding-effective-time' is non-nil, then its value will
+be returned instead."
(let* ((ct (org-current-time))
(dct (decode-time ct))
(ct1
(cond
+ (org-overriding-effective-time org-overriding-effective-time)
(org-use-last-clock-out-time-as-effective-time
(or (org-clock-get-last-clock-out-time) ct))
((and org-use-effective-time (< (decoded-time-hour dct) org-extend-today-until))
@@ -9918,6 +9927,16 @@ When called through Elisp, arg is also interpreted in the following way:
(run-hook-with-args 'org-trigger-hook change-plist)))
(when commentp (org-toggle-comment))))))))
+(defun org-todo-at (time &optional arg)
+ "Like `org-todo', but you will be prompted for a TIME.
+The value you provided will be used as the time value of any
+state change that is triggered by `org-todo'. The value of
+prefix ARG will be given to `org-todo'. See the docstring
+of `org-todo' for more information."
+ (interactive (list (org-read-date t) current-prefix-arg) org-mode)
+ (let ((org-overriding-effective-time (org-read-date nil t time)))
+ (org-todo arg)))
+
(defun org-block-todo-from-children-or-siblings-or-parent (change-plist)
"Block turning an entry into a TODO, using the hierarchy.
This checks whether the current task should be blocked from state
--
2.54.0