I assume that you are talking about the property, containing last modification time of the entry, but not its subheadings.
The way to approach it may be computing and saving the hash of each entry text before, say, file save and updating the modification time of the property if the hash changes:
(defun yant/getentryhash ()
"Get the hash sum of the text in current entry, except :HASH: and :MODIFIED: property texts."
(save-excursion
(let* ((beg (progn (org-back-to-heading) (point)))
(end (progn
(forward-char)
(if (not (re-search-forward "^\*+ " (point-max) t))
(point-max)
(match-beginning 0))))
(full-str (buffer-substring beg end))
(str-nohash (if (string-match "^ *:HASH:.+\n" full-str)
(replace-match "" nil nil full-str)
full-str))
(str-nohash-nomod (if (string-match "^ *:MODIFIED:.+\n" str-nohash)
(replace-match "" nil nil str-nohash)
str-nohash))
(str-nohash-nomod-nopropbeg (if (string-match "^ *:PROPERTIES:\n" str-nohash-nomod)
(replace-match "" nil nil str-nohash-nomod)
str-nohash-nomod))
(str-nohash-nomod-nopropbeg-end (if (string-match "^ *:END:\n" str-nohash-nomod-nopropbeg)
(replace-match "" nil nil str-nohash-nomod-nopropbeg)
str-nohash-nomod-nopropbeg)))
(sxhash str-nohash-nomod-nopropbeg-end))))
(defun yant/update-modification-time ()
"Set the :MODIFIED: property of the current entry to NOW and update :HASH: property."
(org-set-property "HASH" (format "%s" (yant/getentryhash)))
(org-set-property "MODIFIED" (format-time-string "%Y-%m-%d %H:%M")))
(defun yant/skip-nonmodified ()
"Skip org entries, which were not modified according to the :HASH: property"
(let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
(if (string= (org-entry-get (point) "HASH" nil) (format "%s" (yant/getentryhash)))
next-headline
nil)))
(add-hook 'before-save-hook (lambda ()
(when (eq major-mode 'org-mode)
(org-map-entries #'yant/update-modification-time nil 'file #'yant/skip-nonmodified))))
EDIT:
Addressing the following issues:
Non-existent agenda file /path/to/test.org. [R]emove from list or [A]bort?
message appears in the buffer, which is not saved to a file.
The hash calculation is affected by inserting the hash info after the first hash data insertion.
Solutions:
The issue is due to the implementation of org-map-entries
, which calls org-check-agenda-file
causing the message (see org-mode maillist). I do not know a good solution for this, but one can consider advising org-check-agenda-file
just for the time when the hook above is beeing executed like the following:
(define-advice org-check-agenda-file (:around (OLDFUN file) ignore-non-existant-files)
"Suppress \"Non existent agenda file...\" query. Just continue."
(if (file-exists-p file)
(funcall OLDFUN file)
(progn
(org-remove-file file)
(throw 'nextfile t))))
(advice-remove 'org-check-agenda-file #'org-check-agenda-file@ignore-non-existant-files)
(add-hook 'before-save-hook
(lambda ()
(when (eq major-mode 'org-mode)
(advice-add 'org-check-agenda-file :around #'org-check-agenda-file@ignore-non-existant-files)
(org-map-entries #'yant/update-modification-time nil 'file #'yant/skip-nonmodified)
(advice-remove 'org-check-agenda-file #'org-check-agenda-file@ignore-non-existant-files))))
I have changed the code in the answer to ignore :PROPERTIES:
and :END:
statements. Adding of these keywords were causing the hash change in the entry, which did not have any property drawers at the beginning.
EDIT2:
If one would like to ignore the text properties during hash calculation, buffer-substring
in yant/getentryhash
should be replaced by buffer-substring-no-properties
.
# Time-stamp:
in your header and enablingtime-stamp-active
. If this isn't what you're looking for, perhaps clarify your question.z
) or change status, the time is captured: orgmode.org/manual/…