Here is a patch for timestamp sortation issues in todo-list, as
documented in the following recent threads:

[BUG] Agenda sortation generally ignores time-of-day
https://list.orgmode.org/caku+9yxmuxgzg_lxosbgudkb6rtsenmtnctpnxw5z8kbbwf...@mail.gmail.com/

Sorting a TODO agenda across multiple files
https://orgmode.org/list/16ea261a-b94c-43b2-91e0-a1ff01d0b...@kuba-orlik.name

For ease of discussion, here is the exposition in the patch msg:

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, and retrieves
it by inspecting the headline in its original file, instead of relying
on the population of text properties.  This is probably a little more
computationally expensive -- we do this inspection step during *every*
call to the comparison function, instead of caching its value in a
text property.  In my personal experience using this across about 50
agenda files containing a total of about 500 headlines, this has not
resulted in any noticeable lag.

In addition, I felt it was prudent to make a change to this function,
which is only ever called in the context of agenda sorting, rather
than to redesign and repair the assignment of text properties that
happens in org.el:org-scan-tags.  A more proper solution would be to
make a fix there.  As mentioned, I believe the root cause to be that
some properties (like time-of-day) are assigned in the weekly/daily
agenda view and not in any of the other views, and surely
`org-agenda-sorting-strategy' is not the only functionality affected
by this discrepancy.

PTAL,
Charles
From 9d2656dcee19af9f8231aeda3da7c755ad0a6a4d Mon Sep 17 00:00:00 2001
From: Charles Tam <m...@charlest.net>
Date: Thu, 17 Apr 2025 15:19:48 -0400
Subject: [PATCH] lisp/org-agenda.el: Account for timestamps in agenda sort
 methods

* org-agenda.el (org-cmp-ts, org-agenda-sorting-strategy): Account for
  full datetime when comparing timestamps.
(org-cmp-ts): When figuring out what timestamp to use for headline
  comparison, visit the headline directly.  The original behavior is
  to rely on text properties that do not fully populate, or populate
  with truncated values, in todo-list mode.  (This is why
  sorting-strategy may already behave correctly in the main agenda
  view.)
(org-agenda-sorting-strategy): Update docstring to reflect new
  behavior.

* 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, and retrieves
it by inspecting the headline in its original file, instead of relying
on the population of text properties.  This is probably a little more
computationally expensive -- we do this inspection step during *every*
call to the comparison function, instead of caching its value in a
text property.  In my personal experience using this across about 50
agenda files containing a total of about 500 headlines, this has not
resulted in any noticeable lag.

In addition, I felt it was prudent to make a change to this function,
which is only ever called in the context of agenda sorting, rather
than to redesign and repair the assignment of text properties that
happens in org.el:org-scan-tags.  A more proper solution would be to
make a fix there.  As mentioned, I believe the root cause to be that
some properties (like time-of-day) are assigned in the weekly/daily
agenda view and not in any of the other views, and surely
`org-agenda-sorting-strategy' is not the only functionality affected
by this discrepancy.

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                | 34 +++++++++++++++----------------
 testing/examples/agenda-file3.org | 11 ++++++++++
 testing/lisp/test-org-agenda.el   | 34 +++++++++++++++++++++++++++++--
 3 files changed, 60 insertions(+), 19 deletions(-)
 create mode 100644 testing/examples/agenda-file3.org

diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index 87a9a462a..45030ffa5 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.
@@ -7587,13 +7587,13 @@ 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))
-		 def))
-	 (tb (or (and (string-match type (or (get-text-property 1 'type b) ""))
-		      (get-text-property 1 'ts-date b))
-		 def)))
+  (let* ((def (if org-agenda-sort-notime-is-late 99999999999 -1))
+         (time-string-a (and (string-match type (or (get-text-property 1 'type a) ""))
+                             (org-entry-get (get-text-property 1 'org-hd-marker a) (upcase type))))
+         (ta (if time-string-a (org-time-string-to-seconds time-string-a) def))
+         (time-string-b (and (string-match type (or (get-text-property 1 'type b) ""))
+		             (org-entry-get (get-text-property 1 'org-hd-marker b) (upcase type))))
+	 (tb (if time-string-b (org-time-string-to-seconds time-string-b) def)))
     (cond ((if ta (and tb (< ta tb)) tb) -1)
 	  ((if tb (and ta (< tb ta)) ta) +1))))

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 06d5abc43..58dde3c5f 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

Reply via email to