Yup, sorry for the wait. Here is a patch to make the change via text properties.
Charles On Sun, Aug 10, 2025 at 4:16 AM Ihor Radchenko <yanta...@posteo.net> wrote: > Ihor Radchenko <yanta...@posteo.net> writes: > > > Charles T <m...@charlest.net> writes: > > > >> Yes; sorry for the lack of response, I did get started on this for a > bit, > >> and will pick it back up. > > > > Thanks for the update! > > Feel free to ask questions if you encounter any difficulties. > > It has been 2 months since the last update in this thread. > May I know if there is any update? > > -- > Ihor Radchenko // yantar92, > Org mode maintainer, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> >
From 274658b92a323904223774c522772de13586db2c Mon Sep 17 00:00:00 2001 From: Charles Tam <m...@charlest.net> Date: Sun, 10 Aug 2025 19:16:09 -0400 Subject: [PATCH] lisp/org-agenda.el: Account for timestamps in agenda sort methods * org-agenda.el (4 functions): Account for full datetime when comparing timestamps in todo-list. (org-agenda-entry-get-agenda-timestamp): Retrieve a timestamp instead of a datestamp. The original functionality has been renamed to org-agenda-entry-get-agenda-datestamp, with all calls to the original redirected there. (org-agenda-get-todos): Add a 'ts-datetime text property assignment. (org-cmp-ts): Consult the new 'ts-datetime text property during comparison. If the new property is not found, fall back to the pre-existing 'ts-date, but multiply by 24*60*60 for compatibility. (org-agenda-sorting-strategy): Update docstring to reflect new behavior. * org.el (org-scan-tags): Add datetime text property. * testing/lisp/test-org-agenda.el (todo-list-timestamp-sort): New unit test covering this behavior. (test-org-agenda/todo-list-timestamp-sort): With some added test data, checks that scheduled-up respects timestamps by interweaving the scheduled headlines from two agenda files, one of which is in unsorted order. * testing/examples/agenda-file3.org: New unit test data. Headline sortation in agenda views was originally done by generating the agenda text, then inspecting text properties like 'ts-date and 'time-of-day therein. The agenda text generation fully populates these values in agenda view, but not in todo-list view. As a result, todo-list only partially respects `org-agenda-sorting-strategy'; in particular, when considering things like SCHEDULED timestamps for the scheduled-up/down strategies, it discards time-of-day information and instead ends up using other parameters like filename or order of appearance in file. See the linked messages below for simple examples. This change uses the full timestamp, not just the date. Reported-by: Kuba Orlik <kont...@kuba-orlik.name> Link: https://orgmode.org/list/16ea261a-b94c-43b2-91e0-a1ff01d0b...@kuba-orlik.name Reported-by: Charles Tam <m...@charlest.net> Link: https://orgmode.org/list/caku+9yxmuxgzg_lxosbgudkb6rtsenmtnctpnxw5z8kbbwf...@mail.gmail.com/ --- lisp/org-agenda.el | 74 ++++++++++++++++++++++++------- lisp/org.el | 11 +++-- testing/examples/agenda-file3.org | 11 +++++ testing/lisp/test-org-agenda.el | 34 +++++++++++++- 4 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 testing/examples/agenda-file3.org diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index a10ae1888..983957590 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -1637,16 +1637,16 @@ symbols are recognized: time-up Put entries with time-of-day indications first, early first. time-down Put entries with time-of-day indications first, late first. -timestamp-up Sort by any timestamp date, early first. -timestamp-down Sort by any timestamp date, late first. -scheduled-up Sort by scheduled timestamp date, early first. -scheduled-down Sort by scheduled timestamp date, late first. -deadline-up Sort by deadline timestamp date, early first. -deadline-down Sort by deadline timestamp date, late first. -ts-up Sort by active timestamp date, early first. -ts-down Sort by active timestamp date, late first. -tsia-up Sort by inactive timestamp date, early first. -tsia-down Sort by inactive timestamp date, late first. +timestamp-up Sort by any timestamp datetime, early first. +timestamp-down Sort by any timestamp datetime, late first. +scheduled-up Sort by scheduled timestamp datetime, early first. +scheduled-down Sort by scheduled timestamp datetime, late first. +deadline-up Sort by deadline timestamp datetime, early first. +deadline-down Sort by deadline timestamp datetime, late first. +ts-up Sort by active timestamp datetime, early first. +ts-down Sort by active timestamp datetime, late first. +tsia-up Sort by inactive timestamp datetime, early first. +tsia-down Sort by inactive timestamp datetime, late first. category-keep Keep the default order of categories, corresponding to the sequence in `org-agenda-files'. category-up Sort alphabetically by category, A-Z. @@ -5586,6 +5586,40 @@ the documentation of `org-diary'." (defvar org-heading-keyword-regexp-format) ; defined in org.el (defvar org-agenda-sorting-strategy-selected nil) +(defun org-agenda-entry-get-agenda-datestamp (epom) + "Retrieve date information for sorting agenda views. +Given an element, point, or marker EPOM, returns a cons cell of the +date and the timestamp type relevant for the sorting strategy in +`org-agenda-sorting-strategy-selected'." + (let (ts ts-date-type) + (save-match-data + (cond ((org-em 'scheduled-up 'scheduled-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get epom "SCHEDULED") + ts-date-type " scheduled")) + ((org-em 'deadline-up 'deadline-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get epom "DEADLINE") + ts-date-type " deadline")) + ((org-em 'ts-up 'ts-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get epom "TIMESTAMP") + ts-date-type " timestamp")) + ((org-em 'tsia-up 'tsia-down + org-agenda-sorting-strategy-selected) + (setq ts (org-entry-get epom "TIMESTAMP_IA") + ts-date-type " timestamp_ia")) + ((org-em 'timestamp-up 'timestamp-down + org-agenda-sorting-strategy-selected) + (setq ts (or (org-entry-get epom "SCHEDULED") + (org-entry-get epom "DEADLINE") + (org-entry-get epom "TIMESTAMP") + (org-entry-get epom "TIMESTAMP_IA")) + ts-date-type "")) + (t (setq ts-date-type ""))) + (cons (when ts (ignore-errors (org-time-string-to-absolute ts))) + ts-date-type)))) + (defun org-agenda-entry-get-agenda-timestamp (epom) "Retrieve timestamp information for sorting agenda views. Given an element, point, or marker EPOM, returns a cons cell of the @@ -5617,7 +5651,7 @@ timestamp and the timestamp type relevant for the sorting strategy in (org-entry-get epom "TIMESTAMP_IA")) ts-date-type "")) (t (setq ts-date-type ""))) - (cons (when ts (ignore-errors (org-time-string-to-absolute ts))) + (cons (when ts (ignore-errors (org-time-string-to-seconds ts))) ts-date-type)))) (defun org-agenda-get-todos () @@ -5647,7 +5681,7 @@ timestamp and the timestamp type relevant for the sorting strategy in "\\)")) (t org-not-done-regexp)))) marker priority urgency category level tags todo-state - ts-date ts-date-type ts-date-pair + ts-date ts-date-type ts-date-pair ts-datetime ee txt beg end inherited-tags todo-state-end-pos effort effort-minutes) (goto-char (point-min)) @@ -5671,9 +5705,10 @@ timestamp and the timestamp type relevant for the sorting strategy in effort (save-match-data (or (get-text-property (point) 'effort) (org-entry-get (point) org-effort-property))) effort-minutes (when effort (save-match-data (org-duration-to-minutes effort))) - ts-date-pair (org-agenda-entry-get-agenda-timestamp (point)) + ts-date-pair (org-agenda-entry-get-agenda-datestamp (point)) ts-date (car ts-date-pair) ts-date-type (cdr ts-date-pair) + ts-datetime (car (org-agenda-entry-get-agenda-timestamp (point))) txt (org-trim (buffer-substring (match-beginning 2) (match-end 0))) inherited-tags (or (eq org-agenda-show-inherited-tags 'always) @@ -5699,6 +5734,7 @@ timestamp and the timestamp type relevant for the sorting strategy in 'effort effort 'effort-minutes effort-minutes 'level level 'ts-date ts-date + 'ts-datetime ts-datetime 'type (concat "todo" ts-date-type) 'todo-state todo-state) (push txt ee) (if org-agenda-todo-list-sublevels @@ -7587,12 +7623,16 @@ When TYPE is \"scheduled\", \"deadline\", \"timestamp\" or \"timestamp_ia\", compare within each of these type. When TYPE is the empty string, compare all timestamps without respect of their type." - (let* ((def (if org-agenda-sort-notime-is-late 99999999 -1)) - (ta (or (and (string-match type (or (get-text-property 1 'type a) "")) - (get-text-property 1 'ts-date a)) + (let* ((def (if org-agenda-sort-notime-is-late 99999999999 -1)) + (ta (or (and (string-match type (or (get-text-property 1 'type a) "")) + (or (get-text-property 1 'ts-datetime a) + (let (datestamp (get-text-property 1 'ts-date a)) + (and datestamp (* 24 60 60 datestamp))))) def)) (tb (or (and (string-match type (or (get-text-property 1 'type b) "")) - (get-text-property 1 'ts-date b)) + (or (get-text-property 1 'ts-datetime b) + (let (datestamp (get-text-property 1 'ts-date b)) + (and datestamp (* 24 60 60 datestamp))))) def))) (cond ((if ta (and tb (< ta tb)) tb) -1) ((if tb (and ta (< tb ta)) ta) +1)))) diff --git a/lisp/org.el b/lisp/org.el index 65abfbe1a..34a9205b7 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -128,6 +128,7 @@ Stars are put in group 1 and the trimmed body in group 2.") (declare-function Info-goto-node "info" (nodename &optional fork strict-case)) (declare-function isearch-no-upper-case-p "isearch" (string regexp-flag)) (declare-function org-add-archive-files "org-archive" (files)) +(declare-function org-agenda-entry-get-agenda-datestamp "org-agenda" (pom)) (declare-function org-agenda-entry-get-agenda-timestamp "org-agenda" (pom)) (declare-function org-agenda-todo-yesterday "org-agenda" (&optional arg)) (declare-function org-agenda-list "org-agenda" (&optional arg start-day span with-hour)) @@ -11516,7 +11517,7 @@ headlines matching this string." (org-map-continue-from nil) tags-list rtn rtn1 level category txt todo marker priority - ts-date ts-date-type ts-date-pair) + ts-date ts-date-type ts-date-pair ts-datetime) (unless (or (member action '(agenda sparse-tree)) (functionp action)) (setq action (list 'lambda nil action))) (save-excursion @@ -11533,9 +11534,10 @@ headlines matching this string." tags-list (org-get-tags el) org-scanner-tags tags-list) (when (eq action 'agenda) - (setq ts-date-pair (org-agenda-entry-get-agenda-timestamp el) - ts-date (car ts-date-pair) - ts-date-type (cdr ts-date-pair))) + (setq ts-date-pair (org-agenda-entry-get-agenda-datestamp el) + ts-date (car ts-date-pair) + ts-date-type (cdr ts-date-pair) + ts-datetime (car (org-agenda-entry-get-agenda-timestamp el)))) (catch :skip (when (and @@ -11596,6 +11598,7 @@ headlines matching this string." 'org-marker marker 'org-hd-marker marker 'org-category category 'todo-state todo 'ts-date ts-date + 'ts-datetime ts-datetime 'priority priority 'urgency priority 'type (concat "tagsmatch" ts-date-type)) diff --git a/testing/examples/agenda-file3.org b/testing/examples/agenda-file3.org new file mode 100644 index 000000000..4e1adfd41 --- /dev/null +++ b/testing/examples/agenda-file3.org @@ -0,0 +1,11 @@ +* TODO three point five +SCHEDULED: <2024-01-17 Wed 17:00> + +* TODO zero +SCHEDULED: <2024-01-17 Wed 08:00> + +* TODO four point five +SCHEDULED: <2024-03-14 Thu 01:00> + +* TODO two point five +SCHEDULED: <2024-01-17 Wed 12:00> diff --git a/testing/lisp/test-org-agenda.el b/testing/lisp/test-org-agenda.el index e59461bdb..8e81f93e4 100644 --- a/testing/lisp/test-org-agenda.el +++ b/testing/lisp/test-org-agenda.el @@ -216,7 +216,7 @@ "all todo" (goto-char (point-min)) (search-forward "TODO Todo and will appear in agenda" nil t))) - + ;; All todo keywords, including not done. (org-todo-list "*") (should @@ -277,7 +277,37 @@ (progn "NEXT|TODO" (goto-char (point-min)) - (search-forward "TODO Todo and will appear in agenda" nil t)))))) + (search-forward "TODO Todo and will appear in agenda" nil t)))) + (org-test-agenda--kill-all-agendas))) + +(ert-deftest test-org-agenda/todo-list-timestamp-sort () + "Test agenda-sortation of scheduled headlines." + (cl-assert (not org-agenda-sticky) nil "precondition violation") + (cl-assert (not (org-test-agenda--agenda-buffers)) + nil "precondition violation") + (let ((org-agenda-files `(,(expand-file-name "examples/agenda-file2.org" + org-test-dir) + ,(expand-file-name "examples/agenda-file3.org" + org-test-dir))) + (org-agenda-sorting-strategy '(scheduled-up)) + (org-agenda-sort-notime-is-late t)) + (org-todo-list) + (set-buffer org-agenda-buffer-name) + (print (buffer-substring-no-properties (point-min) (point-max))) + (goto-char (point-min)) + (should (search-forward "TODO zero" nil t)) + (should (search-forward "TODO one" nil t)) + (should (search-forward "TODO two" nil t)) + (should (search-forward "TODO two point five" nil t)) + (should (search-forward "TODO three" nil t)) + (should (search-forward "TODO three point five" nil t)) + (should (search-forward "TODO four" nil t)) + (should (search-forward "TODO four point five" nil t)) + ;; The timestamp in TODO five is not SCHEDULED; with + ;; notime-is-late, it should appear last in scheduled-up even + ;; though TODO four point five is scheduled two months later. + (should (search-forward "TODO five" nil t)) + (org-test-agenda--kill-all-agendas))) (ert-deftest test-org-agenda/scheduled-non-todo () "One informative line in the agenda from scheduled non-todo-keyword-item." -- 2.34.1