branch: externals/org
commit 5da0eb6ea77742bb8dbff82d105f79e829eaa415
Author: Ihor Radchenko <yanta...@posteo.net>
Commit: Ihor Radchenko <yanta...@posteo.net>

    org-element-timestamp-parser: Allow time in diary sexp timestamps
    
    * lisp/org-agenda.el (org-agenda-get-timestamps):
    * lisp/org-element.el (org-element--timestamp-regexp): Adjust
    timestamp regexp.
    (org-element-timestamp-parser): Support the new syntax for diary sexp
    timestamps.  The diary sexp is now stored in :diary-sexp property and
    the time/time range is stored as usual.
    (org-element-timestamp-interpreter): Interpret diary timestamp
    according to its building blocks rather than raw value.
    * testing/lisp/test-org-agenda.el (test-org-agenda/diary-timestamp):
    New test checking for agenda support of times in diary timestamps.
    *
    testing/lisp/test-org-element.el (test-org-element/timestamp-interpreter):
    Add parser tests.
    * doc/org-manual.org (Timestamps): Add an example of the new syntax to
    the manual.
    * etc/ORG-NEWS (Diary type timestamps now support optional
    time/timerange): Document the Org syntax addition.
    
    This syntax modification is fixing an omission in org-element.el.  In
    the past, org-agenda had explicit support for diary timestamps with
    time/timerange, but that support was ad-hoc.  Now, after org-agenda
    switched to use parser, we must modify Org syntax to fix the feature
    regression.
---
 doc/org-manual.org               |   2 +-
 etc/ORG-NEWS                     |  38 ++++-
 lisp/org-agenda.el               |   2 +-
 lisp/org-element.el              | 300 +++++++++++++++++++++------------------
 testing/lisp/test-org-agenda.el  |  88 +++++++-----
 testing/lisp/test-org-element.el |  27 +++-
 6 files changed, 283 insertions(+), 174 deletions(-)

diff --git a/doc/org-manual.org b/doc/org-manual.org
index d66d95a22b..80d41dfd2e 100644
--- a/doc/org-manual.org
+++ b/doc/org-manual.org
@@ -6156,7 +6156,7 @@ the agenda (see [[*Weekly/daily agenda]]).  We 
distinguish:
 
   #+begin_example
   ,* 22:00-23:00 The nerd meeting on every 2nd Thursday of the month
-    <%%(diary-float t 4 2)>
+    <%%(diary-float t 4 2) 22:00-23:00>
   #+end_example
 
 - Time range ::
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 87ebed751d..99dd8839cc 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -339,7 +339,43 @@ Now, ~org-store-link~ moves the stored link to front of 
the list of
 stored links.  This way, the link will show up first in the completion
 and when inserting all the stored links with ~org-insert-all-links~.
 
-*** Major changes and additions to Org API
+*** Major changes and additions to Org element API
+**** Diary type timestamps now support optional time/timerange
+
+Previously, diary type timestamps could not specify time.
+Now, it is allowed to add a time or time range:
+
+: <%%(diary-float t 4 2) 22:00-23:00>
+: <%%(diary-float t 4 2) 10:30>
+
+The parsed representation of such timestamps will have ~:hour-start~,
+~:minute-start~, ~:hour-end~, ~:minute-end~, and ~:range-type~
+properties set appropriately.  In addition, a new ~:diary-sexp~
+property will store the diary sexp value.
+
+For example,
+
+: <%%(diary-float t 4 2) 22:00-23:00>
+
+will have the following properties
+
+#+begin_src emacs-lisp
+:type: diary
+:range-type: timerange
+:raw-value: "<%%(diary-float t 4 2) 22:00-23:00>"
+:year-start: nil
+:month-start: nil
+:day-start: nil
+:hour-start: 22
+:minute-start: 0
+:year-end: nil
+:month-end: nil
+:day-end: nil
+:hour-end: 23
+:minute-end: 0
+:diary-sexp: "(diary-float t 4 2)"
+#+end_src
+
 **** New term: "syntax node"
 
 To reduce confusion with "element" referring to both "syntax element"
diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index 7a1b6521a5..93c6acef25 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -5831,7 +5831,7 @@ displayed in agenda view."
             (org-encode-time   ; DATE bound by calendar
              0 0 0 (nth 1 date) (car date) (nth 2 date))))
           "\\|\\(<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[hdwmy]>\\)"
-          "\\|\\(<%%\\(([^>\n]+)\\)>\\)"))
+          "\\|\\(<%%\\(([^>\n]+)\\)\\([^\n>]*\\)>\\)"))
         timestamp-items)
     (goto-char (point-min))
     (while (re-search-forward regexp nil t)
diff --git a/lisp/org-element.el b/lisp/org-element.el
index ccca973d00..cf0982f18c 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -4282,7 +4282,7 @@ Assume point is at the target."
          "\\|"
          "\\(?:<[0-9]+-[0-9]+-[0-9]+[^>\n]+?\\+[0-9]+[dwmy]>\\)"
          "\\|"
-         "\\(?:<%%\\(?:([^>\n]+)\\)>\\)")
+         "\\(?:<%%\\(?:([^>\n]+)\\)\\([^\n>]*\\)>\\)")
   "Regexp matching any timestamp type object.")
 
 (defconst org-element--timestamp-raw-value-regexp
@@ -4300,8 +4300,8 @@ containing `:type', `:range-type', `:raw-value', 
`:year-start',
 `:year-end', `:month-end', `:day-end', `:hour-end', `:minute-end',
 `:repeater-type', `:repeater-value', `:repeater-unit',
 `:repeater-deadline-value', `:repeater-deadline-unit', `:warning-type',
-`:warning-value', `:warning-unit', `:begin', `:end' and `:post-blank'
-properties.  Otherwise, return nil.
+`:warning-value', `:warning-unit', `:diary-sexp', `:begin', `:end' and
+`:post-blank' properties.  Otherwise, return nil.
 
 Assume point is at the beginning of the timestamp."
   (when (looking-at-p org-element--timestamp-regexp)
@@ -4312,19 +4312,29 @@ Assume point is at the beginning of the timestamp."
              (progn
                (looking-at org-element--timestamp-raw-value-regexp)
                (match-string-no-properties 0)))
-            (date-start (match-string-no-properties 1))
-            (date-end (match-string-no-properties 3))
             (diaryp (match-beginning 2))
+             diary-sexp
+            (date-start (if diaryp
+                             ;; Only consider part after sexp for
+                             ;; diary timestamps.
+                             (save-match-data
+                               (looking-at org-element--timestamp-regexp)
+                               (setq diary-sexp
+                                     (buffer-substring-no-properties
+                                      (+ 3 (match-beginning 0))
+                                      (match-beginning 2)))
+                               (match-string 2))
+                           (match-string-no-properties 1)))
+            (date-end (match-string-no-properties 3))
             (post-blank (progn (goto-char (match-end 0))
                                (skip-chars-forward " \t")))
             (end (point))
             (time-range
-             (and (not diaryp)
-                  (string-match
-                   
"[012]?[0-9]:[0-5][0-9]\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)"
-                   date-start)
-                  (cons (string-to-number (match-string 2 date-start))
-                        (string-to-number (match-string 3 date-start)))))
+             (when (string-match
+                    
"[012]?[0-9]:[0-5][0-9]\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)"
+                    date-start)
+               (cons (string-to-number (match-string 2 date-start))
+                     (string-to-number (match-string 3 date-start)))))
             (type (cond (diaryp 'diary)
                         ((and activep (or date-end time-range)) 'active-range)
                         (activep 'active)
@@ -4395,6 +4405,17 @@ Assume point is at the beginning of the timestamp."
                  day-end (or (nth 3 date) day-start)
                  hour-end (or (nth 2 date) (car time-range) hour-start)
                  minute-end (or (nth 1 date) (cdr time-range) minute-start))))
+        ;; Diary timestamp with time.
+        (when (and diaryp
+                   (string-match 
"\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\(-\\([012]?[0-9]\\):\\([0-5][0-9]\\)\\)?" 
date-start))
+          (setq hour-start (match-string 1 date-start)
+                minute-start (match-string 2 date-start)
+                hour-end (match-string 4 date-start)
+                minute-end (match-string 5 date-start))
+          (when hour-start (setq hour-start (string-to-number hour-start)))
+          (when minute-start (setq minute-start (string-to-number 
minute-start)))
+          (when hour-end (setq hour-end (string-to-number hour-end)))
+          (when minute-end (setq minute-end (string-to-number minute-end))))
        (org-element-create
          'timestamp
         (nconc (list :type type
@@ -4413,137 +4434,144 @@ Assume point is at the beginning of the timestamp."
                      :begin begin
                      :end end
                      :post-blank post-blank)
+                (and diary-sexp (list :diary-sexp diary-sexp))
                repeater-props
                warning-props))))))
 
 (defun org-element-timestamp-interpreter (timestamp _)
   "Interpret TIMESTAMP object as Org syntax."
   (let((type (org-element-property :type timestamp)))
-    (if (member type '(active inactive inactive-range active-range))
-        (let ((day-start (org-element-property :day-start timestamp))
-              (month-start (org-element-property :month-start timestamp))
-              (year-start (org-element-property :year-start timestamp)))
-          ;; Return nil when start date is not available.  Could also
-          ;; throw an error, but the current behavior is historical.
-          (when (and day-start month-start year-start)
-            (let* ((repeat-string
-                   (concat
-                    (pcase (org-element-property :repeater-type timestamp)
-                      (`cumulate "+") (`catch-up "++") (`restart ".+"))
-                    (let ((val (org-element-property :repeater-value 
timestamp)))
-                      (and val (number-to-string val)))
-                    (pcase (org-element-property :repeater-unit timestamp)
-                      (`hour "h") (`day "d") (`week "w") (`month "m") (`year 
"y"))
-                     (when-let ((repeater-deadline-value
-                                 (org-element-property 
:repeater-deadline-value timestamp))
-                                (repeater-deadline-unit
-                                 (org-element-property :repeater-deadline-unit 
timestamp)))
-                       (concat
-                        "/"
-                        (number-to-string repeater-deadline-value)
-                        (pcase repeater-deadline-unit
-                          (`hour "h") (`day "d") (`week "w") (`month "m") 
(`year "y"))))))
-                   (range-type (org-element-property :range-type timestamp))
-                   (warning-string
-                   (concat
-                    (pcase (org-element-property :warning-type timestamp)
-                      (`first "--") (`all "-"))
-                    (let ((val (org-element-property :warning-value 
timestamp)))
-                      (and val (number-to-string val)))
-                    (pcase (org-element-property :warning-unit timestamp)
-                      (`hour "h") (`day "d") (`week "w") (`month "m") (`year 
"y"))))
-                   (hour-start (org-element-property :hour-start timestamp))
-                   (minute-start (org-element-property :minute-start 
timestamp))
-                   (brackets
-                    (if (member
-                         type
-                         '(inactive inactive-range))
-                        (cons "[" "]")
-                      (cons "<" ">")))
-                   (timestamp-end
-                    (concat
-                     (and (org-string-nw-p repeat-string) (concat " " 
repeat-string))
-                     (and (org-string-nw-p warning-string) (concat " " 
warning-string))
-                     (cdr brackets))))
-              (concat
-               ;; Opening backet: [ or <
-               (car brackets)
-               ;; Starting date/time: YYYY-MM-DD DAY[ HH:MM]
-               (format-time-string
-                ;; `org-time-stamp-formats'.
-               (org-time-stamp-format
-                 ;; Ignore time unless both HH:MM are available.
-                 ;; Ignore means (car org-timestamp-formats).
-                 (and minute-start hour-start)
-                 'no-brackets)
-               (org-encode-time
-                0 (or minute-start 0) (or hour-start 0)
-                day-start month-start year-start))
-               ;; Range: -HH:MM or TIMESTAMP-END--[YYYY-MM-DD DAY HH:MM]
-               (let ((hour-end (org-element-property :hour-end timestamp))
-                     (minute-end (org-element-property :minute-end timestamp)))
-                 (pcase type
-                   ((or `active `inactive)
-                    ;; `org-element-timestamp-parser' uses this type
-                    ;; when no time/date range is provided.  So,
-                    ;; should normally return nil in this clause.
-                    (pcase range-type
-                      (`nil
-                       ;; `org-element-timestamp-parser' assigns end
-                       ;; times for `active'/`inactive' TYPE if start
-                       ;; time is not nil.  But manually built
-                       ;; timestamps may not contain end times, so
-                       ;; check for end times anyway.
-                       (when (and hour-start hour-end minute-start minute-end
-                                 (or (/= hour-start hour-end)
-                                     (/= minute-start minute-end)))
-                         ;; Could also throw an error.  Return range
-                         ;; timestamp nevertheless to preserve
-                         ;; historical behavior.
-                         (format "-%02d:%02d" hour-end minute-end)))
-                      ((or `timerange `daterange)
-                       (error "`:range-type' must be `nil' for 
`active'/`inactive' type"))))
-                   ;; Range must be present.
-                   ((or `active-range `inactive-range)
-                    (pcase range-type
-                      ;; End time: -HH:MM.
-                      ;; Fall back to start time if end time is not defined 
(arbitrary historical choice).
-                      ;; Error will be thrown if both end and begin time is 
not defined.
-                      (`timerange (format "-%02d:%02d" (or hour-end 
hour-start) (or minute-end minute-start)))
-                      ;; End date: TIMESTAMP-END--[YYYY-MM-DD DAY HH:MM
-                      ((or `daterange
-                           ;; Should never happen in the output of 
`org-element-timestamp-parser'.
-                           ;; Treat as an equivalent of `daterange' 
arbitrarily.
-                           `nil)
-                       (concat
-                        ;; repeater + warning + closing > or ]
-                        ;; This info is duplicated in date ranges.
-                        timestamp-end
-                        "--" (car brackets)
-                        (format-time-string
-                         ;; `org-time-stamp-formats'.
-                        (org-time-stamp-format
-                          ;; Ignore time unless both HH:MM are available.
-                          ;; Ignore means (car org-timestamp-formats).
-                          (and minute-end hour-end)
-                          'no-brackets)
-                        (org-encode-time
-                          ;; Closing HH:MM missing is a valid scenario.
-                         0 (or minute-end 0) (or hour-end 0)
-                          ;; YEAR/MONTH/DAY-END will always be present
-                          ;; for `daterange' range-type, as parsed by
-                          ;; `org-element-timestamp-parser'.
-                          ;; For manually constructed timestamp
-                          ;; object, arbitrarily fall back to starting
-                          ;; date.
-                         (or (org-element-property :day-end timestamp) 
day-start)
-                         (or (org-element-property :month-end timestamp) 
month-start)
-                         (or (org-element-property :year-end timestamp) 
year-start)))))))))
-               ;; repeater + warning + closing > or ]
-               ;; This info is duplicated in date ranges.
-               timestamp-end))))
-      ;; diary type.
-      (org-element-property :raw-value timestamp))))
+    (let ((day-start (org-element-property :day-start timestamp))
+          (month-start (org-element-property :month-start timestamp))
+          (year-start (org-element-property :year-start timestamp)))
+      ;; Return nil when start date is not available.  Could also
+      ;; throw an error, but the current behavior is historical.
+      (when (or (and day-start month-start year-start)
+                (eq type 'diary))
+        (let* ((repeat-string
+               (concat
+                (pcase (org-element-property :repeater-type timestamp)
+                  (`cumulate "+") (`catch-up "++") (`restart ".+"))
+                (let ((val (org-element-property :repeater-value timestamp)))
+                  (and val (number-to-string val)))
+                (pcase (org-element-property :repeater-unit timestamp)
+                  (`hour "h") (`day "d") (`week "w") (`month "m") (`year "y"))
+                 (when-let ((repeater-deadline-value
+                             (org-element-property :repeater-deadline-value 
timestamp))
+                            (repeater-deadline-unit
+                             (org-element-property :repeater-deadline-unit 
timestamp)))
+                   (concat
+                    "/"
+                    (number-to-string repeater-deadline-value)
+                    (pcase repeater-deadline-unit
+                      (`hour "h") (`day "d") (`week "w") (`month "m") (`year 
"y"))))))
+               (range-type (org-element-property :range-type timestamp))
+               (warning-string
+               (concat
+                (pcase (org-element-property :warning-type timestamp)
+                  (`first "--") (`all "-"))
+                (let ((val (org-element-property :warning-value timestamp)))
+                  (and val (number-to-string val)))
+                (pcase (org-element-property :warning-unit timestamp)
+                  (`hour "h") (`day "d") (`week "w") (`month "m") (`year 
"y"))))
+               (hour-start (org-element-property :hour-start timestamp))
+               (minute-start (org-element-property :minute-start timestamp))
+               (brackets
+                (if (member
+                     type
+                     '(inactive inactive-range))
+                    (cons "[" "]")
+                  ;; diary as well
+                  (cons "<" ">")))
+               (timestamp-end
+                (concat
+                 (and (org-string-nw-p repeat-string) (concat " " 
repeat-string))
+                 (and (org-string-nw-p warning-string) (concat " " 
warning-string))
+                 (cdr brackets))))
+          (concat
+           ;; Opening backet: [ or <
+           (car brackets)
+           ;; Starting date/time: YYYY-MM-DD DAY[ HH:MM]
+           (if (eq type 'diary)
+               (concat
+                "%%"
+                (org-element-property :diary-sexp timestamp)
+                (when (and minute-start hour-start)
+                  (format " %02d:%02d" hour-start minute-start)))
+             (format-time-string
+              ;; `org-time-stamp-formats'.
+             (org-time-stamp-format
+               ;; Ignore time unless both HH:MM are available.
+               ;; Ignore means (car org-timestamp-formats).
+               (and minute-start hour-start)
+               'no-brackets)
+             (org-encode-time
+              0 (or minute-start 0) (or hour-start 0)
+              day-start month-start year-start)))
+           ;; Range: -HH:MM or TIMESTAMP-END--[YYYY-MM-DD DAY HH:MM]
+           (let ((hour-end (org-element-property :hour-end timestamp))
+                 (minute-end (org-element-property :minute-end timestamp)))
+             (pcase type
+               ((or `active `inactive)
+                ;; `org-element-timestamp-parser' uses this type
+                ;; when no time/date range is provided.  So,
+                ;; should normally return nil in this clause.
+                (pcase range-type
+                  (`nil
+                   ;; `org-element-timestamp-parser' assigns end
+                   ;; times for `active'/`inactive' TYPE if start
+                   ;; time is not nil.  But manually built
+                   ;; timestamps may not contain end times, so
+                   ;; check for end times anyway.
+                   (when (and hour-start hour-end minute-start minute-end
+                             (or (/= hour-start hour-end)
+                                 (/= minute-start minute-end)))
+                     ;; Could also throw an error.  Return range
+                     ;; timestamp nevertheless to preserve
+                     ;; historical behavior.
+                     (format "-%02d:%02d" hour-end minute-end)))
+                  ((or `timerange `daterange)
+                   (error "`:range-type' must be `nil' for `active'/`inactive' 
type"))))
+               ;; Range must be present.
+               ((or `active-range `inactive-range
+                    (and `diary (guard (eq 'timerange range-type))))
+                (pcase range-type
+                  ;; End time: -HH:MM.
+                  ;; Fall back to start time if end time is not defined 
(arbitrary historical choice).
+                  ;; Error will be thrown if both end and begin time is not 
defined.
+                  (`timerange (format "-%02d:%02d" (or hour-end hour-start) 
(or minute-end minute-start)))
+                  ;; End date: TIMESTAMP-END--[YYYY-MM-DD DAY HH:MM
+                  ((or `daterange
+                       ;; Should never happen in the output of 
`org-element-timestamp-parser'.
+                       ;; Treat as an equivalent of `daterange' arbitrarily.
+                       `nil)
+                   (concat
+                    ;; repeater + warning + closing > or ]
+                    ;; This info is duplicated in date ranges.
+                    timestamp-end
+                    "--" (car brackets)
+                    (format-time-string
+                     ;; `org-time-stamp-formats'.
+                    (org-time-stamp-format
+                      ;; Ignore time unless both HH:MM are available.
+                      ;; Ignore means (car org-timestamp-formats).
+                      (and minute-end hour-end)
+                      'no-brackets)
+                    (org-encode-time
+                      ;; Closing HH:MM missing is a valid scenario.
+                     0 (or minute-end 0) (or hour-end 0)
+                      ;; YEAR/MONTH/DAY-END will always be present
+                      ;; for `daterange' range-type, as parsed by
+                      ;; `org-element-timestamp-parser'.
+                      ;; For manually constructed timestamp
+                      ;; object, arbitrarily fall back to starting
+                      ;; date.
+                     (or (org-element-property :day-end timestamp) day-start)
+                     (or (org-element-property :month-end timestamp) 
month-start)
+                     (or (org-element-property :year-end timestamp) 
year-start)))))))))
+           ;; repeater + warning + closing > or ]
+           ;; This info is duplicated in date ranges.
+           timestamp-end))))))
 ;;;; Underline
 
 (defun org-element-underline-parser ()
diff --git a/testing/lisp/test-org-agenda.el b/testing/lisp/test-org-agenda.el
index 3348db473b..d4f7e71b4f 100644
--- a/testing/lisp/test-org-agenda.el
+++ b/testing/lisp/test-org-agenda.el
@@ -690,43 +690,65 @@ Sunday      7 January 2024
 (ert-deftest test-org-agenda/skip-deadline-prewarning-if-scheduled ()
   "Test `org-agenda-skip-deadline-prewarning-if-scheduled'."
   (org-test-at-time
-   "2024-01-15"
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled t))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
-      (org-agenda-list nil nil 1)
-      (should-not (search-forward "In " nil t))))
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled 10))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
-      (org-agenda-list nil nil 1)
-      (should (search-forward "In " nil t))))
-   ;; Custom prewarning cookie "-3d", so there should be no warning anyway.
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled 10))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat -3d> SCHEDULED: <2024-01-19 Fri>"
-      (org-agenda-list nil nil 1)
-      (should-not (search-forward "In " nil t))))
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled 3))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
+      "2024-01-15"
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled t))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
+       (org-agenda-list nil nil 1)
+       (should-not (search-forward "In " nil t))))
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled 10))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
+       (org-agenda-list nil nil 1)
+       (should (search-forward "In " nil t))))
+    ;; Custom prewarning cookie "-3d", so there should be no warning anyway.
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled 10))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat -3d> SCHEDULED: <2024-01-19 Fri>"
+       (org-agenda-list nil nil 1)
+       (should-not (search-forward "In " nil t))))
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled 3))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
+       (org-agenda-list nil nil 1)
+       (should-not (search-forward "In " nil t))))
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled nil))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
+       (org-agenda-list nil nil 1)
+       (should (search-forward "In " nil t))))
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-16 Tue>"
+       (org-agenda-list nil nil 1)
+       (should-not (search-forward "In " nil t))))
+    (let ((org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled))
+      (org-test-agenda-with-agenda
+       "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-15 Mon>"
+       (org-agenda-list nil nil 1)
+       (should (search-forward "In " nil t))))))
+
+(ert-deftest test-org-agenda/diary-timestamp ()
+  "Test diary timestamp handling."
+  (org-test-at-time
+      "2024-01-15"
+    (org-test-agenda-with-agenda
+        "* TODO foo\n<%%(diary-date 01 15 2024)>"
       (org-agenda-list nil nil 1)
-      (should-not (search-forward "In " nil t))))
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled nil))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-19 Fri>"
+      (should (search-forward "foo" nil t)))
+    (org-test-agenda-with-agenda
+        "* TODO foo\n<%%(diary-date 02 15 2024)>"
       (org-agenda-list nil nil 1)
-      (should (search-forward "In " nil t))))
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-16 Tue>"
+      (should-not (search-forward "foo" nil t)))
+    ;; Test time and time ranges in diary timestamps.
+    (org-test-agenda-with-agenda
+        "* TODO foo\n<%%(diary-date 01 15 2024) 12:00>"
       (org-agenda-list nil nil 1)
-      (should-not (search-forward "In " nil t))))
-   (let ((org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled))
-     (org-test-agenda-with-agenda
-      "* TODO foo\nDEADLINE: <2024-01-20 Sat> SCHEDULED: <2024-01-15 Mon>"
+      (should (search-forward "12:00" nil t)))
+    (org-test-agenda-with-agenda
+        "* TODO foo\n<%%(diary-date 01 15 2024) 12:00-14:00>"
       (org-agenda-list nil nil 1)
-      (should (search-forward "In " nil t))))))
+      (should (search-forward "12:00-14:00" nil t)))))
 
 
 ;; agenda redo
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index ad4ff1ce9b..ad96c3106d 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -3989,8 +3989,31 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> 
CLOSED: [2012-03-29 thu
                 (org-test-parse-and-interpret
                  "<2012-03-29 thu. 16:40-16:41>")))
   ;; Diary.
-  (should (equal (org-test-parse-and-interpret "<%%diary-float t 4 2>")
-                "<%%diary-float t 4 2>\n"))
+  (should (equal (org-test-parse-and-interpret "<%%(diary-float t 4 2)>")
+                "<%%(diary-float t 4 2)>\n"))
+  ;; Diary with time.
+  (should (equal (org-test-parse-and-interpret "<%%(diary-float t 4 2) 12:00>")
+                "<%%(diary-float t 4 2) 12:00>\n"))
+  (should (equal (org-test-parse-and-interpret "<%%(diary-cyclic 1 1 1 2020) 
12:00-14:00>")
+                "<%%(diary-cyclic 1 1 1 2020) 12:00-14:00>\n"))
+  (org-test-with-temp-text "<%%(diary-float t 4 2) 12:00>"
+    (let ((ts (org-element-context)))
+      (should (org-element-type-p ts 'timestamp))
+      (should (eq 'diary (org-element-property :type ts)))
+      (should (eq nil (org-element-property :range-type ts)))
+      (should (equal 12 (org-element-property :hour-start ts)))
+      (should (equal 0 (org-element-property :minute-start ts)))
+      (should-not (org-element-property :hour-end ts))
+      (should-not (org-element-property :minute-end ts))))
+  (org-test-with-temp-text "<%%(diary-float t 4 2) 12:00-14:01>"
+    (let ((ts (org-element-context)))
+      (should (org-element-type-p ts 'timestamp))
+      (should (eq 'diary (org-element-property :type ts)))
+      (should (eq 'timerange (org-element-property :range-type ts)))
+      (should (equal 12 (org-element-property :hour-start ts)))
+      (should (equal 0 (org-element-property :minute-start ts)))
+      (should (equal 14 (org-element-property :hour-end ts)))
+      (should (equal 1 (org-element-property :minute-end ts)))))
   ;; Timestamp with repeater interval, repeater deadline, with delay, with 
combinations.
   (should
    (string-match "<2012-03-29 .* \\+1y>"

Reply via email to