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
!