Hi Org Mode community,

I am writing to share a solution to a common workflow issue I
encounter and to propose making this a built-in feature.

*The Problem:*

When using Org Mode, I often face this scenario: I completed a
recurring TODO task (e.g., with a `+1w` repeater) a few days ago, but
I forgot to mark it as DONE at that time.

If I mark it as DONE today using `C-c C-t`, Org Mode naturally uses
the **current time** for the completion timestamp in the
Logbook. Consequently, the next occurrence’s DEADLINE/SCHEDULED date
is calculated based on today, pushing the schedule forward
incorrectly, rather than based on the actual completion date.

Previously, I had to manually edit the timestamp in the Logbook and
calculate the next deadline by hand, which is quite tedious.

*My Current Solution:*

I wrote a custom function that uses `advice-add` around
`org-todo`. The core idea is to use `cl-letf` to temporarily shadow
internal time-fetching functions (like `org-current-effective-time`,
`org-today`, etc.) so that Org believes “now” is actually the time I
specify.

The workflow is:
1. Run `M-x my/set-next-org-todo-time`.
2. Select the actual completion time using `org-read-date` (e.g., typing `-3` 
for three days ago).
3. Immediately press `C-c C-t` to switch the TODO state.

The Logbook entry and the repeater calculation are then correctly
based on the retroactive date. The advice is designed to be one-off
and removes itself after execution.

Here is the proof-of-concept code:

┌────
│ (defun my/set-next-org-todo-time (time-string)
│   "Set the timestamp for the next `org-todo' invocation.
│ Normally, `org-todo' uses the current time. This function uses an
│ advice to temporarily shadow time-related functions like
│ `org-current-effective-time' so that the next `org-todo' call
│ operates relative to the provided timestamp."
│   (interactive
│    (list (org-read-date nil nil nil "Time for next org-todo: ")))
│   (let* ((time (cond
│                 ((stringp time-string)
│                  (org-time-string-to-time time-string))
│                 ((and (consp time-string)
│                       (numberp (car time-string)))
│                  time-string)
│                 (t
│                  (current-time))))
│          (advice
│           (lambda (orig-fn &rest args)
│             (cl-letf (((symbol-function 'org-current-effective-time)
│                        (lambda (&optional _ignored) time))
│                       ((symbol-function 'org-today)
│                        (lambda ()
│                          (time-to-days time)))
│                       ((symbol-function 'org-timestamp-to-now)
│                        (lambda (timestamp-string &optional seconds)
│                          (let ((fdiff (if seconds #'float-time 
#'time-to-days)))
│                            (- (funcall fdiff (org-time-string-to-time 
timestamp-string))
│                               (funcall fdiff time))))))
│               (unwind-protect
│                   (apply orig-fn args)
│                 (advice-remove 'org-todo 'override-todo-timestamp-once))))))
│     (advice-add 'org-todo :around advice
│                 '((name . override-todo-timestamp-once)))))
└────

*Discussion:*

I believe this “retroactive completion” is a fairly common use
case. While my advice-based approach works, it feels like a hack.

I am interested to know:
1. Do others find this feature useful?
2. Could we consider adding an official mechanism to the core
   `org-todo` logic to support this? Perhaps a prefix argument or a
   configuration option that prompts for a time before marking a task
   as done?

I would welcome any feedback or suggestions on how to implement this
more cleanly within Org’s codebase.

Best regards,

Milan Glacier

Reply via email to