10

I have so many tasks I do far away from my computer with emacs. Now, when I change state of a TODO, it just logs the current time into the drawer. But the difference with actual time of the event may be many hours, if not days.

Is there a way to make it prompt for a timestamp I'd like it to log?

koddo
  • 233
  • 1
  • 7

4 Answers4

7

I've wanted the exact same "I did this yesterday" behavior for a while and never got around to trying to implement it. But now if I can get points for it ....

This behavior seems to be hard-coded into org-todo. The line in org.el that sets the CLOSED timestamp is (org-add-planning-info 'closed (org-current-effective-time)) and the LOGBOOK notes are added by org-add-log-setup, which in turn calls org-effective-current-time. org-effective-current-time does what it sounds like and returns the effective time.

The obvious solution is to temporarily change org-effective-current-time to something that prompts for a date. But then we get prompted for the date multiple times with every call, which is annoying. I don't know a good way to avoid it, but you can just save off the user inputed value and keep that around until the end of the function.

This code seems to work and only prompts once when a state change would be logged.

(defun org-todo-with-date (&optional arg)
  (interactive "P")
  (cl-letf* ((org-read-date-prefer-future nil)
             (my-current-time (org-read-date t t nil "when:" nil nil nil))
            ((symbol-function #'org-current-effective-time)
             #'(lambda () my-current-time)))
    (org-todo arg)
    )) 
erikstokes
  • 12,686
  • 2
  • 34
  • 56
  • Great, thanks. I refactored your code a bit. Please feel free to paste this to your answer: https://gist.github.com/koddo/aa6d497e0d1df4023915 – koddo Feb 21 '15 at 15:43
  • 1
    Seems that the next occurrence of a repeated task (e.g. `.+7d`) wouldn't be scheduled correctly with this function (it is scheduled as if the task were finished today). – xji Apr 19 '19 at 22:51
  • @xji I've been annoyed by that behavior too, but don't have a solution. These kinds of hacks are all pretty fragile. – erikstokes Apr 20 '19 at 16:10
  • @erikstokes Yeah now I'm thinking of giving up org-mode as the tool to manage recurring tasks/habits. Some mobile apps do it better. I'll try to just use it to produce a list of one-time tasks with priority attached, review them every day and put timestamps on them to schedule my daily agenda. – xji Apr 22 '19 at 09:30
3

You can layer more hacks on top the first answer to address the problem with scheduling repeaters not updating correctly as so. Note that if LAST_REPEAT is set, it will be set to the actual date, not the chosen one. I wish Org would add this as a first-class feature; the actual date leaks into org-todo in many places; I'm sure this answer still misses some:

    (defun org-todo-with-date (&optional arg)
      (interactive "P")
      (cl-letf* ((org-read-date-prefer-future nil)
                 (my-current-time (org-read-date t t nil "when:" nil nil nil))
                 ((symbol-function #'current-time)
                  #'(lambda () my-current-time))
                 ((symbol-function #'org-today)
                  #'(lambda () (time-to-days my-current-time)))
                 ((symbol-function #'org-current-effective-time)
                  #'(lambda () my-current-time)))
        (org-todo arg)))
Drew
  • 75,699
  • 9
  • 109
  • 225
Alex
  • 31
  • 1
3

The following code works with org 20210308 and it handles CLOSED, LOGBOOK, SCHEDULED/DEADLINE as well LAST_REPEAT correctly for me.

  (defun org-todo-with-date (&optional arg)
    (interactive "P")
    (cl-letf* ((org-read-date-prefer-future nil)
               (my-current-time (org-read-date t t nil "when:" nil nil nil))
               ((symbol-function 'current-time)
                #'(lambda () my-current-time))
               ((symbol-function 'org-today)
                #'(lambda () (time-to-days my-current-time)))
               ((symbol-function 'org-current-effective-time)
                #'(lambda () my-current-time))

               (super-org-entry-put (symbol-function 'org-entry-put))
               ((symbol-function 'org-entry-put)
                #'(lambda (pom property value)
                    (print property)
                    (if (equal property "LAST_REPEAT")
                        (let ((my-value (format-time-string (org-time-stamp-format t t) my-current-time)))
                          (funcall super-org-entry-put pom property my-value))
                      (funcall super-org-entry-put pom property value)
                      ))))
      (if (eq major-mode 'org-agenda-mode) (org-agenda-todo arg) (org-todo arg))))

This an alternative to the answer of Nosferatu, which is in turn based on the answers by erikstokes and Alex). It redefines format-time-string only when setting LAST_REPEAT and thereby ensures that SCHEDULED/DEADLINE are not effected by the redefinition. Moreover, this variant calls org-agenda-todo instead of org-todo in agenda mode.

2

And here is another layer to fix LAST_REPEAT case:

(defun org-todo-with-date (&optional arg)
  (interactive "P")
  (cl-letf* ((org-read-date-prefer-future nil)
             (my-current-time (org-read-date t t nil "when:" nil nil nil))
             ((symbol-function 'current-time)
              #'(lambda () my-current-time))
             ((symbol-function 'org-today)
              #'(lambda () (time-to-days my-current-time)))
             ((symbol-function 'org-current-effective-time)
              #'(lambda () my-current-time))
             (super (symbol-function 'format-time-string))
             ((symbol-function 'format-time-string)
              #'(lambda (fmt &optional time time-zone)
                  (funcall super fmt my-current-time time-zone))))
    (org-todo arg)))

hopefully format-time-string is only called from org-auto-repeat-maybe in org-todo context and only for LAST_REPEAT insertion.

Anyway you can always copy the org-todo defun to your emacs.el init file and modify it there instead of cherry-pickifying functions with cl-letf (althought it's kind of cool to do it)

Stefan
  • 26,154
  • 3
  • 46
  • 84
Nosferatu
  • 21
  • 1
  • Unfortunately, `format-time-string` is both called from other functions here and within `org-auto-repeat-maybe` it is used for more than the `LAST_REPEAT` insertion. See my answer below. – real-or-random Mar 09 '21 at 12:57