2

I'm a heavy Emacs Org-mode user and VERY dependent on the date functionality. I frequently enter events, receipts, etc. using capture templates after they've already occurred. My current solution is to prompt for the relevant dates or change them manually, but this is time consuming and error prone. Emacs documentation and Googling failed me.

I'd like to be able to hit a few keys (or execute a function), change and freeze the date-time at (for example) noon yesterday, [2016-01-11 12:00], perform some Org-mode functions (state changes, capture templates, logging time, etc.) which all operate as indented with the new "frozen date-time", then hit a few keys (or execute a function) to return the clock to normal behavior.

Thanks!

1 Answer 1

6

Sure such evil stuff is possible with elisp. But be warned. I am not aware of all the dark consequences that this evil piece of black art has.

When you interactively run freeze-time you can input the date string at which you want to freeze the time. From then on time is frozen until you run release-time which undoes this science fiction scenario.

Do not expect any version control system or any file system function which trusts in the modification time of files to work reliable after such a hack!

Note, that I have running something similar in my org-system. But, I use it only locally within an advice of `org-time-stamp-inactive'.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The real evil-mode:
(require 'org)
(defun freeze-time (time)
  "Freeze `current-time' at the given TIME"
  (interactive (list (org-read-date nil nil nil "Input freeze time:")))
  (eval (macroexpand `(defadvice current-time (around freeze activate)
            (setq ad-return-value ',(append (org-read-date nil t time) '(0 0)))))))

(defun release-time ()
  "Release the time frozen by `freeze-time'."
  (interactive)
  (ad-remove-advice 'current-time 'around 'freeze)
  (ad-update 'current-time))

EDIT: Thanks Phils for pointing out the need to activate the advice after its removal.


There follows a more subtle method that only advices a set of org functions that use current-time. The advantage is that it does not potentially blow up emacs. The disadvantage is that some maintenance of org-commands-with-current-time may be required. It may be that some functions are not covered in this list and that this list must be adapted to the further development of org-mode.

(defvar org-commands-with-current-time '(org-current-time org-read-date-analyze org-today org-current-effective-time org-todo-yesterday org-read-date-analyze org-time-stamp-to-now org-small-year-to-year org-closest-date org-goto-calendar org-get-cursor-date org-time-string-to-absolute org-time-stamp-inactive org-time-stamp org-sort-entries)
  "Functions to be adviced by `org-ad-freeze-time'.")

(defvar org-frozen-time nil
  "Frozen time for org-commands. It should have the same format as the return value of `current-time'.
Time flows normal if set to `nil'.")

(defun org-freeze-time (time)
  "Freeze `current-time' at the given TIME in the org-functions from `org-commands-with-current-time'"
  (interactive (list (org-read-date nil nil nil "Input freeze time:")))
  (setq org-frozen-time (append (org-read-date nil t time) '(0 0))))

(defun org-release-time ()
  "Release the time frozen by `org-freeze-time'."
  (interactive)
  (setq org-frozen-time nil))

(defmacro org-ad-freeze-time (fun)
  "Advice FUN to use `org-frozen-time'."
  (let ((old-def (make-symbol "old-def")))
    `(defadvice ,fun (around freeze-time activate)
       (let ((,old-def (symbol-function 'current-time)))
     (unwind-protect
         (progn
           (when org-frozen-time
         (fset 'current-time (lambda () org-frozen-time)))
           ad-do-it)
       (fset 'current-time ,old-def))))))

(dolist (fun org-commands-with-current-time)
  (eval (macroexpand `(org-ad-freeze-time ,fun))))

And here's what the end should look like if you want to use nadvice instead:

(defun org-ad-frozen-time () org-frozen-time)
(defun org-ad-with-frozen-time (&rest args)
  (unwind-protect
      (progn
        (when org-frozen-time
          (advice-add 'current-time :override #'org-ad-frozen-time))
        (apply args))
    (advice-remove 'current-time #'org-ad-frozen-time)))
(dolist (fun org-commands-with-current-time)
  (advice-add fun :around #'org-ad-with-frozen-time))

Look ma! No eval, no macroexpand, no defmacro!

7
  • 1
    You will need to call (ad-activate 'current-time) after ad-remove-advice to make that take effect.
    – phils
    Commented Jan 11, 2016 at 23:09
  • @Tobias, this is the exact sorcery that I was looking for--this black magic is going to save me eons of time! Please add (ad-active 'current-time) to the end of the release-time function and I'll accept your answer. @phils, great catch. I was having trouble getting the time to return. Is there a clever way we could limit this to JUST org buffers?
    – sk8ingdom
    Commented Jan 11, 2016 at 23:37
  • @phils Thanks for pointing that out. From my point of perspective this is a documentation bug of emacs. The need for activation after removal is not mentioned in the doc string of ad-remove-advice and also not in the info manual. Do you agree?
    – Tobias
    Commented Jan 12, 2016 at 4:13
  • Tobais: Well the documentation for advice.el has been reduced (as part of pushing the nadvice library as a replacement) to advice.el's own Commentary, and that does point this out. If the info manual nodes are (or have since been) restored and do not mention this, I would tend to concur.
    – phils
    Commented Jan 12, 2016 at 4:32
  • @phils Okay phils, you are right. I am a bit of an old-timer and still using emacs 24.3.1 That is the version in Ubuntu 14.04 LTS. Everything is new in emacs 25. So there is no need to make any fuss about that. Sorry about the noise.
    – Tobias
    Commented Jan 12, 2016 at 4:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.