emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
@ 2023-02-19 12:25 Ilya Chernyshov
  2023-02-19 14:11 ` Ilya Chernyshov
  2023-02-20 11:07 ` Ihor Radchenko
  0 siblings, 2 replies; 13+ messages in thread
From: Ilya Chernyshov @ 2023-02-19 12:25 UTC (permalink / raw)
  To: emacs-orgmode

Hello, guys

`org-element-timestamp-interpreter' function returns string of the 
form
"<2023-02-19 Sun 10:00>--<2023-02-19 Sun 10:30>" for a timestamp 
object
passed to it. The better result would be "<2023-02-19 Sun 
10:00-10:30>".

This function is also used for interpreting clock entries, which 
only
have the format of "[2023-02-19 Sun 10:00]--[2023-02-19 Sun 
10:30]".

So, an option is needed that controls whether to return a 
daterange
(even it's possible to return a timerange) or a timerange (if the 
dates
in the range are equal).

I wrote a patch that handles this problem, could you please review 
the
code and give some advice to improve it?

-- 
Best,
Ilya


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-02-19 12:25 [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil Ilya Chernyshov
@ 2023-02-19 14:11 ` Ilya Chernyshov
  2023-02-20 11:07 ` Ihor Radchenko
  1 sibling, 0 replies; 13+ messages in thread
From: Ilya Chernyshov @ 2023-02-19 14:11 UTC (permalink / raw)
  To: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 34 bytes --]


I forgot to attach the patch :)


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-org-element-timestamp-interpreter-Return-daterange-a.patch --]
[-- Type: text/x-patch, Size: 13338 bytes --]

From b47324bc804e64e6cef482ba4897a457252e803a Mon Sep 17 00:00:00 2001
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
Date: Sat, 18 Feb 2023 14:55:39 +0700
Subject: [PATCH] org-element-timestamp-interpreter: Return daterange anyway,
 if DATERANGE is non-nil

* lisp/org-element (org-element-timestamp-interpreter): Replace
silenced argument with optional argument DATERANGE which controls
whether to return daterange (even if dates in the range are equal) or
timerange (if it's possible). Refactor the code.

* lisp/org-element (org-element-planning-interpreter): Call
`org-element-timestamp-interpreter' without second argument.

* lisp/org-element (org-element-clock-interpreter): Call
`org-element-timestamp-interpreter' with DATERANGE set to t, because
clock entries only support daterange format.

* lisp/org-element (org-element-interpret-data): Call
`org-element-timestamp-interpreter' on timestamp object with DATERANGE
set to t, if it's inactive-range.

* testing/lisp/test-org-element
(test-org-element/timestamp-interpreter): Expect timerange returned
for timestamp of type active-range from
`org-element-timestamp-interpreter' and `org-test-parse-and-interpret'
calls. Add tests for `org-element-timestamp-interpreter' with
DATERANGE argument.
---
 lisp/org-element.el              | 180 ++++++++++++++-----------------
 testing/lisp/test-org-element.el |  28 ++++-
 2 files changed, 106 insertions(+), 102 deletions(-)

diff --git a/lisp/org-element.el b/lisp/org-element.el
index d7847a678..9649c6951 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -2042,7 +2042,7 @@ (defun org-element-clock-interpreter (clock _)
   "Interpret CLOCK element as Org syntax."
   (concat "CLOCK: "
 	  (org-element-timestamp-interpreter
-	   (org-element-property :value clock) nil)
+	   (org-element-property :value clock) t)
 	  (let ((duration (org-element-property :duration clock)))
 	    (and duration
 		 (concat " => "
@@ -2673,15 +2673,15 @@ (defun org-element-planning-interpreter (planning _)
 	 (list (let ((deadline (org-element-property :deadline planning)))
 		 (when deadline
 		   (concat org-element-deadline-keyword " "
-			   (org-element-timestamp-interpreter deadline nil))))
+			   (org-element-timestamp-interpreter deadline))))
 	       (let ((scheduled (org-element-property :scheduled planning)))
 		 (when scheduled
 		   (concat org-element-scheduled-keyword " "
-			   (org-element-timestamp-interpreter scheduled nil))))
+			   (org-element-timestamp-interpreter scheduled))))
 	       (let ((closed (org-element-property :closed planning)))
 		 (when closed
 		   (concat org-element-closed-keyword " "
-			   (org-element-timestamp-interpreter closed nil))))))
+			   (org-element-timestamp-interpreter closed))))))
    " "))
 
 
@@ -4020,100 +4020,79 @@ (defun org-element-timestamp-parser ()
 		     repeater-props
 		     warning-props))))))
 
-(defun org-element-timestamp-interpreter (timestamp _)
-  "Interpret TIMESTAMP object as Org syntax."
-  (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"))))
-	 (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"))))
-	 (build-ts-string
-	  ;; Build an Org timestamp string from TIME.  ACTIVEP is
-	  ;; non-nil when time stamp is active.  If WITH-TIME-P is
-	  ;; non-nil, add a time part.  HOUR-END and MINUTE-END
-	  ;; specify a time range in the timestamp.  REPEAT-STRING is
-	  ;; the repeater string, if any.
-	  (lambda (time activep &optional with-time-p hour-end minute-end)
-	    (let ((ts (format-time-string
-                       (org-time-stamp-format with-time-p)
-		       time)))
-	      (when (and hour-end minute-end)
-		(string-match "[012]?[0-9]:[0-5][0-9]" ts)
-		(setq ts
-		      (replace-match
-		       (format "\\&-%02d:%02d" hour-end minute-end)
-		       nil nil ts)))
-	      (unless activep (setq ts (format "[%s]" (substring ts 1 -1))))
-	      (dolist (s (list repeat-string warning-string))
-		(when (org-string-nw-p s)
-		  (setq ts (concat (substring ts 0 -1)
-				   " "
-				   s
-				   (substring ts -1)))))
-	      ;; Return value.
-	      ts)))
-	 (type (org-element-property :type timestamp)))
-    (pcase type
-      ((or `active `inactive)
-       (let* ((minute-start (org-element-property :minute-start timestamp))
-	      (minute-end (org-element-property :minute-end timestamp))
-	      (hour-start (org-element-property :hour-start timestamp))
-	      (hour-end (org-element-property :hour-end timestamp))
-	      (time-range-p (and hour-start hour-end minute-start minute-end
-				 (or (/= hour-start hour-end)
-				     (/= minute-start minute-end)))))
-	 (funcall
-	  build-ts-string
-	  (org-encode-time 0
-                           (or minute-start 0)
-                           (or hour-start 0)
-                           (org-element-property :day-start timestamp)
-                           (org-element-property :month-start timestamp)
-                           (org-element-property :year-start timestamp))
-	  (eq type 'active)
-	  (and hour-start minute-start)
-	  (and time-range-p hour-end)
-	  (and time-range-p minute-end))))
-      ((or `active-range `inactive-range)
-       (let ((minute-start (org-element-property :minute-start timestamp))
-	     (minute-end (org-element-property :minute-end timestamp))
-	     (hour-start (org-element-property :hour-start timestamp))
-	     (hour-end (org-element-property :hour-end timestamp)))
-	 (concat
-	  (funcall
-	   build-ts-string (org-encode-time
-			    0
-			    (or minute-start 0)
-			    (or hour-start 0)
-			    (org-element-property :day-start timestamp)
-			    (org-element-property :month-start timestamp)
-			    (org-element-property :year-start timestamp))
-	   (eq type 'active-range)
-	   (and hour-start minute-start))
-	  "--"
-	  (funcall build-ts-string
-		   (org-encode-time
-                    0
-                    (or minute-end 0)
-                    (or hour-end 0)
-                    (org-element-property :day-end timestamp)
-                    (org-element-property :month-end timestamp)
-                    (org-element-property :year-end timestamp))
-		   (eq type 'active-range)
-		   (and hour-end minute-end)))))
-      (_ (org-element-property :raw-value timestamp)))))
-
+(defun org-element-timestamp-interpreter (timestamp &optional daterange)
+  "Interpret TIMESTAMP object as Org syntax.
+
+If DATERANGE is non-nil, create daterange even if dates in the
+range are equal."
+  (if (member
+       (org-element-property :type timestamp)
+       '(active inactive inactive-range active-range))
+      (let((start-date 
+            (format-time-string
+             (org-time-stamp-format nil 'no-brackets)
+	     (org-encode-time
+              0 0 0
+              (org-element-property :day-start timestamp)
+              (org-element-property :month-start timestamp)
+              (org-element-property :year-start timestamp)))))
+        (when start-date
+          (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"))))
+                (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))
+                (start-time
+	         (and hour-start minute-start
+		      (format "%02d:%02d" hour-start minute-start)))
+                (hour-end (org-element-property :hour-end timestamp))
+                (minute-end (org-element-property :minute-end timestamp))
+                (end-time (and hour-end minute-end
+                               (format "%02d:%02d" hour-end minute-end)))
+                (day-end (org-element-property :day-end timestamp))
+                (month-end (org-element-property :month-end timestamp))
+                (year-end (org-element-property :year-end timestamp))
+                (time-range-p (and start-time end-time
+                                   (not (string= start-time end-time))))
+                (end-date
+                 (and year-end month-end day-end
+                      (format-time-string
+                       (org-time-stamp-format nil 'no-brackets)
+                       (org-encode-time
+                        0 0 0 day-end month-end year-end))))
+                (date-range-p (or (and daterange time-range-p)
+                                  (and end-date (not (string= end-date start-date)))))
+                (ts
+	         (concat
+                  "<" start-date (and start-time (concat " " start-time))
+                  (if date-range-p
+                      (concat ">--<" end-date (and end-time (concat " " end-time)))
+                    (if time-range-p (concat "-" end-time)))
+                  ">")))
+	    (dolist (s (list repeat-string warning-string))
+	      (when (org-string-nw-p s)
+	        (setq ts (string-replace ">" (concat " " s ">") ts))))
+	    (pcase (org-element-property :type timestamp)
+              ((or `active `active-range)
+               ts)
+              ((or `inactive `inactive-range)
+               (string-replace
+                "<" "["
+                (string-replace ">" "]" ts)))))))
+    (org-element-property :raw-value timestamp)))
 
 ;;;; Underline
 
@@ -5050,7 +5029,10 @@ (defun org-element-interpret-data (data)
 		       ((stringp data) data)
 		       ;; Element or object without contents.
 		       ((not (org-element-contents data))
-			(funcall interpret data nil))
+			(cond ((eq type 'timestamp)
+                               (funcall interpret data
+                                        (eq (org-element-property :type data) 'inactive-range)))
+                              (t (funcall interpret data nil))))
 		       ;; Element or object with contents.
 		       (t
 			(funcall
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index 43f1d860f..fd865f38b 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -3235,17 +3235,28 @@ (ert-deftest test-org-element/timestamp-interpreter ()
 	      :hour-start 16 :minute-start 40)) nil)))
   ;; Active range.
   (should
-   (string-match "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
+   (string-match "<2012-03-29 .* 16:40-16:41>"
 		 (org-test-parse-and-interpret
 		  "<2012-03-29 thu. 16:40>--<2012-03-29 thu. 16:41>")))
   (should
    (string-match
-    "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
+    "<2012-03-29 .* 16:40-16:41>"
     (org-element-timestamp-interpreter
      '(timestamp
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41))
+     t)))
+  
   ;; Inactive range.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
@@ -3258,7 +3269,18 @@ (ert-deftest test-org-element/timestamp-interpreter ()
      '(timestamp
        (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
-	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+	      :day-end 29 :hour-end 16 :minute-end 41)) t)))
+
+  (should
+   (string-match
+    "\\[2012-03-29 .* 16:40-16:41\\]"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41))
+     nil)))
+  
   ;; Diary.
   (should (equal (org-test-parse-and-interpret "<%%diary-float t 4 2>")
 		 "<%%diary-float t 4 2>\n"))
-- 
2.39.0


[-- Attachment #3: Type: text/plain, Size: 16 bytes --]


-- 
Best,
Ilya

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-02-19 12:25 [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil Ilya Chernyshov
  2023-02-19 14:11 ` Ilya Chernyshov
@ 2023-02-20 11:07 ` Ihor Radchenko
  2023-02-20 16:36   ` Ilya Chernyshov
  1 sibling, 1 reply; 13+ messages in thread
From: Ihor Radchenko @ 2023-02-20 11:07 UTC (permalink / raw)
  To: Ilya Chernyshov; +Cc: emacs-orgmode

Ilya Chernyshov <ichernyshovvv@gmail.com> writes:

> So, an option is needed that controls whether to return a 
> daterange
> (even it's possible to return a timerange) or a timerange (if the 
> dates
> in the range are equal).
>
> I wrote a patch that handles this problem, could you please review 
> the
> code and give some advice to improve it?

What about recording the type of timestamp range in the parser?
Then, interpreter can simply examine the range type and emit the correct
timestamp string.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
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>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-02-20 11:07 ` Ihor Radchenko
@ 2023-02-20 16:36   ` Ilya Chernyshov
  2023-02-22 11:21     ` Ihor Radchenko
  0 siblings, 1 reply; 13+ messages in thread
From: Ilya Chernyshov @ 2023-02-20 16:36 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 982 bytes --]

You suggest to split timestamp types active/inactive-range to active/inactive-timerange, active/inactive-daterange?


On February 20, 2023 6:07:19 PM GMT+07:00, Ihor Radchenko <yantar92@posteo.net> wrote:
>Ilya Chernyshov <ichernyshovvv@gmail.com> writes:
>
>> So, an option is needed that controls whether to return a 
>> daterange
>> (even it's possible to return a timerange) or a timerange (if the 
>> dates
>> in the range are equal).
>>
>> I wrote a patch that handles this problem, could you please review 
>> the
>> code and give some advice to improve it?
>
>What about recording the type of timestamp range in the parser?
>Then, interpreter can simply examine the range type and emit the correct
>timestamp string.
>
>-- 
>Ihor Radchenko // yantar92,
>Org mode contributor,
>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>

[-- Attachment #2: Type: text/html, Size: 1152 bytes --]

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-02-20 16:36   ` Ilya Chernyshov
@ 2023-02-22 11:21     ` Ihor Radchenko
  2023-07-01 19:47       ` Ilya Chernyshov
  0 siblings, 1 reply; 13+ messages in thread
From: Ihor Radchenko @ 2023-02-22 11:21 UTC (permalink / raw)
  To: Ilya Chernyshov; +Cc: emacs-orgmode

Ilya Chernyshov <ichernyshovvv@gmail.com> writes:

> You suggest to split timestamp types active/inactive-range to active/inactive-timerange, active/inactive-daterange?

I suggest introducing a new timestamp property :range-type. It can be
nil, timerange, or daterange.

Changing :type values will not be backwards-compatible.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
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>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-02-22 11:21     ` Ihor Radchenko
@ 2023-07-01 19:47       ` Ilya Chernyshov
  2023-07-02  8:46         ` Ihor Radchenko
  0 siblings, 1 reply; 13+ messages in thread
From: Ilya Chernyshov @ 2023-07-01 19:47 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode


[-- Attachment #1.1: Type: text/plain, Size: 399 bytes --]

Hello!

In the new patch I added :range-type timestamp property, adjusted
interpreter,
parser functions, added tests for the property.



On Wed, Feb 22, 2023 at 11:21 AM Ihor Radchenko <yantar92@posteo.net> wrote:

> I suggest introducing a new timestamp property :range-type. It can be
> nil, timerange, or daterange.
>
> Changing :type values will not be backwards-compatible.
>

[-- Attachment #1.2: Type: text/html, Size: 783 bytes --]

[-- Attachment #2: 0001-New-timestamp-property-range-type.patch --]
[-- Type: text/x-patch, Size: 16460 bytes --]

From d95418110897bdde85a74734bb5b5fa7a3c77b92 Mon Sep 17 00:00:00 2001
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
Date: Sat, 18 Feb 2023 14:55:39 +0700
Subject: [PATCH] New timestamp property :range-type

* lisp/org-element (org-element-timestamp-interpreter): For ranges:
When :range-type is nil or `timerange', return timerange (<YYYY-mm-DD
HH:MM-HH:MM>) if date-start and date-end are equal; return
daterange(<...>--<...>) otherwise. When :range-type is `daterange',
return daterange anyway. Refactor the code.

* lisp/org-element (org-element-timestamp-parser): Add :range-type property

* testing/lisp/test-org-element
(test-org-element/timestamp-interpreter): Add new tests.
(test-org-element/timestamp-parser): Add testing for :range-type
property.
---
 lisp/org-element.el              | 168 ++++++++++++++-----------------
 testing/lisp/test-org-element.el |  86 +++++++++++++---
 2 files changed, 149 insertions(+), 105 deletions(-)

diff --git a/lisp/org-element.el b/lisp/org-element.el
index bfb1d206e..13dd41e5d 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -4043,7 +4043,7 @@ Assume point is at the target."
   "Parse time stamp at point, if any.
 
 When at a time stamp, return a new syntax node of `timestamp' type
-containing `:type', `:raw-value', `:year-start', `:month-start',
+containing `:type', `:range-type', `:raw-value', `:year-start', `:month-start',
 `:day-start', `:hour-start', `:minute-start', `:year-end',
 `:month-end', `:day-end', `:hour-end', `:minute-end',
 `:repeater-type', `:repeater-value', `:repeater-unit',
@@ -4077,6 +4077,10 @@ Assume point is at the beginning of the timestamp."
 			 (activep 'active)
 			 ((or date-end time-range) 'inactive-range)
 			 (t 'inactive)))
+             (range-type (cond
+                          (date-end 'daterange)
+                          (time-range 'timerange)
+                          (t nil)))
 	     (repeater-props
 	      (and (not diaryp)
 		   (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)"
@@ -4123,6 +4127,7 @@ Assume point is at the beginning of the timestamp."
 	(org-element-create
          'timestamp
 	 (nconc (list :type type
+                      :range-type range-type
 		      :raw-value raw-value
 		      :year-start year-start
 		      :month-start month-start
@@ -4142,98 +4147,75 @@ Assume point is at the beginning of the timestamp."
 
 (defun org-element-timestamp-interpreter (timestamp _)
   "Interpret TIMESTAMP object as Org syntax."
-  (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"))))
-	 (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"))))
-	 (build-ts-string
-	  ;; Build an Org timestamp string from TIME.  ACTIVEP is
-	  ;; non-nil when time stamp is active.  If WITH-TIME-P is
-	  ;; non-nil, add a time part.  HOUR-END and MINUTE-END
-	  ;; specify a time range in the timestamp.  REPEAT-STRING is
-	  ;; the repeater string, if any.
-	  (lambda (time activep &optional with-time-p hour-end minute-end)
-	    (let ((ts (format-time-string
-                       (org-time-stamp-format with-time-p)
-		       time)))
-	      (when (and hour-end minute-end)
-		(string-match "[012]?[0-9]:[0-5][0-9]" ts)
-		(setq ts
-		      (replace-match
-		       (format "\\&-%02d:%02d" hour-end minute-end)
-		       nil nil ts)))
-	      (unless activep (setq ts (format "[%s]" (substring ts 1 -1))))
-	      (dolist (s (list repeat-string warning-string))
-		(when (org-string-nw-p s)
-		  (setq ts (concat (substring ts 0 -1)
-				   " "
-				   s
-				   (substring ts -1)))))
-	      ;; Return value.
-	      ts)))
-	 (type (org-element-property :type timestamp)))
-    (pcase type
-      ((or `active `inactive)
-       (let* ((minute-start (org-element-property :minute-start timestamp))
-	      (minute-end (org-element-property :minute-end timestamp))
-	      (hour-start (org-element-property :hour-start timestamp))
-	      (hour-end (org-element-property :hour-end timestamp))
-	      (time-range-p (and hour-start hour-end minute-start minute-end
-				 (or (/= hour-start hour-end)
-				     (/= minute-start minute-end)))))
-	 (funcall
-	  build-ts-string
-	  (org-encode-time 0
-                           (or minute-start 0)
-                           (or hour-start 0)
-                           (org-element-property :day-start timestamp)
-                           (org-element-property :month-start timestamp)
-                           (org-element-property :year-start timestamp))
-	  (eq type 'active)
-	  (and hour-start minute-start)
-	  (and time-range-p hour-end)
-	  (and time-range-p minute-end))))
-      ((or `active-range `inactive-range)
-       (let ((minute-start (org-element-property :minute-start timestamp))
-	     (minute-end (org-element-property :minute-end timestamp))
-	     (hour-start (org-element-property :hour-start timestamp))
-	     (hour-end (org-element-property :hour-end timestamp)))
-	 (concat
-	  (funcall
-	   build-ts-string (org-encode-time
-			    0
-			    (or minute-start 0)
-			    (or hour-start 0)
-			    (org-element-property :day-start timestamp)
-			    (org-element-property :month-start timestamp)
-			    (org-element-property :year-start timestamp))
-	   (eq type 'active-range)
-	   (and hour-start minute-start))
-	  "--"
-	  (funcall build-ts-string
-		   (org-encode-time
-                    0
-                    (or minute-end 0)
-                    (or hour-end 0)
-                    (org-element-property :day-end timestamp)
-                    (org-element-property :month-end timestamp)
-                    (org-element-property :year-end timestamp))
-		   (eq type 'active-range)
-		   (and hour-end minute-end)))))
-      (_ (org-element-property :raw-value timestamp)))))
-
+  (if (member
+       (org-element-property :type timestamp)
+       '(active inactive inactive-range active-range))
+      (let((start-date 
+            (format-time-string
+             (org-time-stamp-format nil 'no-brackets)
+	     (org-encode-time
+              0 0 0
+              (org-element-property :day-start timestamp)
+              (org-element-property :month-start timestamp)
+              (org-element-property :year-start timestamp)))))
+        (when start-date
+          (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"))))
+                (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))
+                (start-time
+	         (and hour-start minute-start
+		      (format "%02d:%02d" hour-start minute-start)))
+                (hour-end (org-element-property :hour-end timestamp))
+                (minute-end (org-element-property :minute-end timestamp))
+                (end-time (and hour-end minute-end
+                               (format "%02d:%02d" hour-end minute-end)))
+                (day-end (org-element-property :day-end timestamp))
+                (month-end (org-element-property :month-end timestamp))
+                (year-end (org-element-property :year-end timestamp))
+                (time-range-p (and start-time end-time
+                                   (not (string= start-time end-time))))
+                (end-date
+                 (and year-end month-end day-end
+                      (format-time-string
+                       (org-time-stamp-format nil 'no-brackets)
+                       (org-encode-time
+                        0 0 0 day-end month-end year-end))))
+                (date-range-p (or (eq range-type 'daterange)
+                                  (and end-date (not (string= end-date start-date)))))
+                (ts
+	         (concat
+                  "<" start-date (and start-time (concat " " start-time))
+                  (if date-range-p
+                      (concat ">--<" end-date (and end-time (concat " " end-time)))
+                    (if time-range-p (concat "-" end-time)))
+                  ">")))
+	    (dolist (s (list repeat-string warning-string))
+	      (when (org-string-nw-p s)
+	        (setq ts (string-replace ">" (concat " " s ">") ts))))
+	    (pcase (org-element-property :type timestamp)
+              ((or `active `active-range)
+               ts)
+              ((or `inactive `inactive-range)
+               (string-replace
+                "<" "["
+                (string-replace ">" "]" ts)))))))
+    (org-element-property :raw-value timestamp)))
 
 ;;;; Underline
 
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index 283ade10f..09a20491d 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -3081,27 +3081,31 @@ Outside list"
   (should
    (org-test-with-temp-text "<2012-03-29 16:40>"
      (eq (org-element-property :type (org-element-context)) 'active)))
-  (should-not
+  (should
    (org-test-with-temp-text "<2012-03-29 Thu>"
      (let ((timestamp (org-element-context)))
-       (or (org-element-property :hour-start timestamp)
-	   (org-element-property :minute-start timestamp)))))
+       (and
+        (not
+         (or (org-element-property :hour-start timestamp)
+	     (org-element-property :minute-start timestamp)))
+        (null (org-element-property :range-type timestamp))))))
   (should
-   (equal '(2012 3 29 16 40)
+   (equal '(2012 3 29 16 40 nil)
 	  (org-test-with-temp-text "<2012-03-29 Thu 16:40>"
 	    (let ((object (org-element-context)))
 	      (list (org-element-property :year-start object)
 		    (org-element-property :month-start object)
 		    (org-element-property :day-start object)
 		    (org-element-property :hour-start object)
-		    (org-element-property :minute-start object))))))
+		    (org-element-property :minute-start object)
+                    (org-element-property :range-type object))))))
   ;; Inactive timestamp.
   (should
    (org-test-with-temp-text "[2012-03-29 Thu 16:40]"
      (eq (org-element-property :type (org-element-context)) 'inactive)))
   ;; Time range.
   (should
-   (equal '(2012 3 29 16 40 7 30)
+   (equal '(2012 3 29 16 40 7 30 timerange)
 	  (org-test-with-temp-text "<2012-03-29 Thu 7:30-16:40>"
 	    (let ((object (org-element-context)))
 	      (list (org-element-property :year-end object)
@@ -3110,7 +3114,8 @@ Outside list"
 		    (org-element-property :hour-end object)
 		    (org-element-property :minute-end object)
 		    (org-element-property :hour-start object)
-		    (org-element-property :minute-start object))))))
+		    (org-element-property :minute-start object)
+                    (org-element-property :range-type object))))))
   (should
    (eq 'active-range
        (org-test-with-temp-text "<2012-03-29 Thu 7:30-16:40>"
@@ -3118,12 +3123,17 @@ Outside list"
   ;; Date range.
   (should
    (org-test-with-temp-text "[2012-03-29 Thu 16:40]--[2012-03-29 Thu 16:41]"
-     (eq (org-element-property :type (org-element-context)) 'inactive-range)))
-  (should-not
+     (let((timestamp (org-element-context)))
+       (and (eq (org-element-property :type timestamp) 'inactive-range)
+            (eq (org-element-property :range-type timestamp) 'daterange)))))
+  (should
    (org-test-with-temp-text "[2011-07-14 Thu]--[2012-03-29 Thu]"
      (let ((timestamp (org-element-context)))
-       (or (org-element-property :hour-end timestamp)
-	   (org-element-property :minute-end timestamp)))))
+       (and
+        (not
+         (or (org-element-property :hour-end timestamp)
+	     (org-element-property :minute-end timestamp)))
+        (eq (org-element-property :range-type timestamp) 'daterange)))))
   ;; With repeater, warning delay and both.
   (should
    (eq 'catch-up
@@ -3697,31 +3707,83 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type inactive :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40)) nil)))
   ;; Active range.
+
+  ;; range-type: daterange; parse-and-interpret
   (should
    (string-match "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
 		 (org-test-parse-and-interpret
 		  "<2012-03-29 thu. 16:40>--<2012-03-29 thu. 16:41>")))
+
+  ;; range-type: daterange; interpreter
   (should
    (string-match
     "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :range-type daterange :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  
+  ;; range-type: timerange; parse-and-interpret
+  (should
+   (string-match "<2012-03-29 .* 16:40-16:41>"
+		 (org-test-parse-and-interpret
+		  "<2012-03-29 thu. 16:40-16:41>")))
+
+  ;; range-type: timerange; interpreter
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40-16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :range-type timerange :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  
+  ;; range-type: nil; date-start and date-end are equal; interpreter
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40-16:41>"
     (org-element-timestamp-interpreter
      '(timestamp
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+
+  ;; range-type: nil; date-start and date-end aren't equal; interpreter
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-30 .* 16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 30 :hour-end 16 :minute-end 41)) nil)))
+
   ;; Inactive range.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
 		 (org-test-parse-and-interpret
 		  "[2012-03-29 thu. 16:40]--[2012-03-29 thu. 16:41]")))
+
+  (should
+   (string-match
+    "\\[2012-03-29 .* 16:40-16:41\\]"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type inactive-range :range-type timerange :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+
   (should
    (string-match
-    "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
+    "\\[2012-03-29 .* 16:40-16:41\\]"
     (org-element-timestamp-interpreter
      '(timestamp
        (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  
   ;; Diary.
   (should (equal (org-test-parse-and-interpret "<%%diary-float t 4 2>")
 		 "<%%diary-float t 4 2>\n"))
-- 
2.40.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-01 19:47       ` Ilya Chernyshov
@ 2023-07-02  8:46         ` Ihor Radchenko
  2023-07-07  7:24           ` Ilya Chernyshov
  0 siblings, 1 reply; 13+ messages in thread
From: Ihor Radchenko @ 2023-07-02  8:46 UTC (permalink / raw)
  To: Ilya Chernyshov; +Cc: emacs-orgmode

Ilya Chernyshov <ichernyshovvv@gmail.com> writes:

> In the new patch I added :range-type timestamp property, adjusted
> interpreter,
> parser functions, added tests for the property.

Thanks!

Some general stylistic comments:

1. You left some whitespace-only blank lines and spaces at the end of
   lines. Please, clean them up.
2. Please, use double space between sentences in the commit message and
   link to this thread. See
   https://orgmode.org/worg/org-contribute.html#commit-messages
3. I noticed some (let((...) forms. Please put spaces between sexps like
   (let ((...)
       ^ space here

> * lisp/org-element (org-element-timestamp-parser): Add :range-type property

Dot is missing at the end of the sentence here.

>  (defun org-element-timestamp-interpreter (timestamp _)
>    "Interpret TIMESTAMP object as Org syntax."
> ...
> +	         (concat
> +                  "<" start-date (and start-time (concat " " start-time))
> +                  (if date-range-p
> +                      (concat ">--<" end-date (and end-time (concat " " end-time)))
> +                    (if time-range-p (concat "-" end-time)))
> +                  ">")))

Here, you are manually constructing time part of the timestamp,
bypassing `org-time-stamp-format' and `org-timestamp-formats'. Please,
use `org-time-stamp-format' for times as well.
If necessary, feed free to extend `org-time-stamp-format' and the value
of `org-timestamp-formats' constant.

> +	    (dolist (s (list repeat-string warning-string))
> +	      (when (org-string-nw-p s)
> +	        (setq ts (string-replace ">" (concat " " s ">") ts))))
> +	    (pcase (org-element-property :type timestamp)
> +              ((or `active `active-range)
> +               ts)
> +              ((or `inactive `inactive-range)
> +               (string-replace
> +                "<" "["
> +                (string-replace ">" "]" ts)))))))

`string-replace' is fragile here. If we ever need to put "<" or ">"
inside timestamp, random breakages may happen. Please, rewrite.



>    (should
>     (string-match
> -    "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
> +    "\\[2012-03-29 .* 16:40-16:41\\]"
>      (org-element-timestamp-interpreter
>       '(timestamp
>         (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
>  	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
>  	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
> +  
>    ;; Diary.
>    (should (equal (org-test-parse-and-interpret "<%%diary-float t 4 2>")
>  		 "<%%diary-float t 4 2>\n"))
> -- 
> 2.40.1

> * lisp/org-element (org-element-timestamp-interpreter): For ranges:
> When :range-type is nil or `timerange', return timerange (<YYYY-mm-DD
> HH:MM-HH:MM>) if date-start and date-end are equal; return
> daterange(<...>--<...>) otherwise. When :range-type is `daterange',
> return daterange anyway. Refactor the code.

Interpreting timestamps with :time-range nil and
:day-end/:year-end/:month-end non-nil as timerange is a breaking change.
Let's avoid it.

With the new version of the parser, the test example will never happen,
but the tested AST structure might happen in the wild - we do not want
to break anything when it is not necessary.

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
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>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-02  8:46         ` Ihor Radchenko
@ 2023-07-07  7:24           ` Ilya Chernyshov
  2023-07-08  8:35             ` Ihor Radchenko
  0 siblings, 1 reply; 13+ messages in thread
From: Ilya Chernyshov @ 2023-07-07  7:24 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 1403 bytes --]


Ihor Radchenko <yantar92@posteo.net> writes:

> Some general stylistic comments:
> 1. You left some whitespace-only blank lines and spaces at the end of
>    lines. Please, clean them up.
> 2. Please, use double space between sentences in the commit message and
>    link to this thread. See
>    https://orgmode.org/worg/org-contribute.html#commit-messages
> 3. I noticed some (let((...) forms. Please put spaces between sexps like
>    (let ((...)
>        ^ space here

I'm sorry for incorrect formatting. Fixed.

> Here, you are manually constructing time part of the timestamp,
> bypassing `org-time-stamp-format' and `org-timestamp-formats'. Please,
> use `org-time-stamp-format' for times as well.
> If necessary, feed free to extend `org-time-stamp-format' and the value
> of `org-timestamp-formats' constant.

I used `org-time-stamp-format' for start time, but I didn't come with the
idea on how to format end time in the current context, so I just used
`(format "-%02d:%02d" hour-end minute-end)'.

> `string-replace' is fragile here. If we ever need to put "<" or ">"
> inside timestamp, random breakages may happen. Please, rewrite.

Fixed.

> Interpreting timestamps with :time-range nil and
> :day-end/:year-end/:month-end non-nil as timerange is a breaking change.
> Let's avoid it.

Timestamp objects (ranges) with :range-type nil are now interpreted as
dateranges, as it was before.



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-lisp-org-element.el-Add-new-timestamp-property-range.patch --]
[-- Type: text/x-patch, Size: 17626 bytes --]

From 4ee8b8bc065c208374d7a516a7c8dc9fb7d0fe5e Mon Sep 17 00:00:00 2001
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
Date: Sat, 18 Feb 2023 14:55:39 +0700
Subject: [PATCH] lisp/org-element.el: Add new timestamp property :range-type

* lisp/org-element (org-element-timestamp-interpreter): Take into
account :range-type value when interpreting ranges.  When start and
end dates are equal and :range-type is `timerange', return a
timerange (<YYYY-mm-DD HH:MM-HH:MM>).  If :range-type is `daterange'
always return a daterange (<...>--<...>).  When :range-type is nil,
return a daterange for any range (as it was before).
(org-element-timestamp-parser): Add :range-type property.

* testing/lisp/test-org-element
(test-org-element/timestamp-interpreter): Add new tests.
(test-org-element/timestamp-parser): Add testing for :range-type
property.

* etc/ORG-NEWS (Major changes and additions to Org API): Add news about this property.
---
 etc/ORG-NEWS                     |   7 ++
 lisp/org-element.el              | 177 +++++++++++++++----------------
 testing/lisp/test-org-element.el | 121 ++++++++++++++++++++-
 3 files changed, 210 insertions(+), 95 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index d04e92275..d8d2275d9 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -200,6 +200,13 @@ a newly created one.
 Previously, one had to use
 
 : (apply #'org-element-create 'section nil (org-element-contents node))
+**** New property ~:range-type~ for org-element timestamp object
+
+~org-element-timestamp-parser~ now adds =:range-type= property to each
+timestamp object.  Possible values: ~timerange~, ~daterange~, ~nil~.
+
+~org-element-timestamp-interpreter~ takes into account this property
+and returns approptiate timestamp string.
 
 *** ~org-priority=show~ command no longer adjusts for scheduled/deadline
 
diff --git a/lisp/org-element.el b/lisp/org-element.el
index bfb1d206e..c6b2d81a1 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -4043,7 +4043,7 @@ Assume point is at the target."
   "Parse time stamp at point, if any.
 
 When at a time stamp, return a new syntax node of `timestamp' type
-containing `:type', `:raw-value', `:year-start', `:month-start',
+containing `:type', `:range-type', `:raw-value', `:year-start', `:month-start',
 `:day-start', `:hour-start', `:minute-start', `:year-end',
 `:month-end', `:day-end', `:hour-end', `:minute-end',
 `:repeater-type', `:repeater-value', `:repeater-unit',
@@ -4077,6 +4077,10 @@ Assume point is at the beginning of the timestamp."
 			 (activep 'active)
 			 ((or date-end time-range) 'inactive-range)
 			 (t 'inactive)))
+             (range-type (cond
+                          (date-end 'daterange)
+                          (time-range 'timerange)
+                          (t nil)))
 	     (repeater-props
 	      (and (not diaryp)
 		   (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)"
@@ -4123,6 +4127,7 @@ Assume point is at the beginning of the timestamp."
 	(org-element-create
          'timestamp
 	 (nconc (list :type type
+                      :range-type range-type
 		      :raw-value raw-value
 		      :year-start year-start
 		      :month-start month-start
@@ -4142,98 +4147,84 @@ Assume point is at the beginning of the timestamp."
 
 (defun org-element-timestamp-interpreter (timestamp _)
   "Interpret TIMESTAMP object as Org syntax."
-  (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"))))
-	 (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"))))
-	 (build-ts-string
-	  ;; Build an Org timestamp string from TIME.  ACTIVEP is
-	  ;; non-nil when time stamp is active.  If WITH-TIME-P is
-	  ;; non-nil, add a time part.  HOUR-END and MINUTE-END
-	  ;; specify a time range in the timestamp.  REPEAT-STRING is
-	  ;; the repeater string, if any.
-	  (lambda (time activep &optional with-time-p hour-end minute-end)
-	    (let ((ts (format-time-string
-                       (org-time-stamp-format with-time-p)
-		       time)))
-	      (when (and hour-end minute-end)
-		(string-match "[012]?[0-9]:[0-5][0-9]" ts)
-		(setq ts
-		      (replace-match
-		       (format "\\&-%02d:%02d" hour-end minute-end)
-		       nil nil ts)))
-	      (unless activep (setq ts (format "[%s]" (substring ts 1 -1))))
-	      (dolist (s (list repeat-string warning-string))
-		(when (org-string-nw-p s)
-		  (setq ts (concat (substring ts 0 -1)
-				   " "
-				   s
-				   (substring ts -1)))))
-	      ;; Return value.
-	      ts)))
-	 (type (org-element-property :type timestamp)))
-    (pcase type
-      ((or `active `inactive)
-       (let* ((minute-start (org-element-property :minute-start timestamp))
-	      (minute-end (org-element-property :minute-end timestamp))
-	      (hour-start (org-element-property :hour-start timestamp))
-	      (hour-end (org-element-property :hour-end timestamp))
-	      (time-range-p (and hour-start hour-end minute-start minute-end
-				 (or (/= hour-start hour-end)
-				     (/= minute-start minute-end)))))
-	 (funcall
-	  build-ts-string
-	  (org-encode-time 0
-                           (or minute-start 0)
-                           (or hour-start 0)
-                           (org-element-property :day-start timestamp)
-                           (org-element-property :month-start timestamp)
-                           (org-element-property :year-start timestamp))
-	  (eq type 'active)
-	  (and hour-start minute-start)
-	  (and time-range-p hour-end)
-	  (and time-range-p minute-end))))
-      ((or `active-range `inactive-range)
-       (let ((minute-start (org-element-property :minute-start timestamp))
-	     (minute-end (org-element-property :minute-end timestamp))
-	     (hour-start (org-element-property :hour-start timestamp))
-	     (hour-end (org-element-property :hour-end timestamp)))
-	 (concat
-	  (funcall
-	   build-ts-string (org-encode-time
-			    0
-			    (or minute-start 0)
-			    (or hour-start 0)
-			    (org-element-property :day-start timestamp)
-			    (org-element-property :month-start timestamp)
-			    (org-element-property :year-start timestamp))
-	   (eq type 'active-range)
-	   (and hour-start minute-start))
-	  "--"
-	  (funcall build-ts-string
-		   (org-encode-time
-                    0
-                    (or minute-end 0)
-                    (or hour-end 0)
-                    (org-element-property :day-end timestamp)
-                    (org-element-property :month-end timestamp)
-                    (org-element-property :year-end timestamp))
-		   (eq type 'active-range)
-		   (and hour-end minute-end)))))
-      (_ (org-element-property :raw-value timestamp)))))
-
+  (if (member
+       (org-element-property :type timestamp)
+       '(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)))
+        (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"))))
+                 (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))
+                 (hour-end (or (org-element-property :hour-end timestamp) hour-start))
+                 (minute-end (or (org-element-property :minute-end timestamp) minute-start))
+                 (day-end (or (org-element-property :day-end timestamp) day-start))
+                 (month-end (or (org-element-property :month-end timestamp) month-start))
+                 (year-end (or (org-element-property :year-end timestamp) year-start))
+                 (time-range-p (and hour-start minute-start minute-end hour-end
+                                    (or (/= hour-start hour-end)
+				        (/= minute-start minute-end))))
+                 (date-range-p (or (and (eq range-type nil) time-range-p)
+                                   (and day-end month-end year-end
+                                        (or
+                                         (/= day-start day-end)
+                                         (/= month-start month-end)
+                                         (/= year-start year-end)))))
+                 (brackets
+                  (if (member
+                       (org-element-property :type timestamp)
+                       '(inactive inactive-range))
+                      (cons "[" "]")
+                    (cons "<" ">"))))
+            (concat
+             (car brackets)
+             (format-time-string
+	      (org-time-stamp-format (and (integerp minute-start) (integerp hour-start)) 'no-brackets)
+	      (org-encode-time
+	       0
+	       (or minute-start 0)
+	       (or hour-start 0)
+	       day-start
+	       month-start
+	       year-start))
+             (cond
+              ((or date-range-p (eq range-type 'daterange))
+               (concat
+                (and (org-string-nw-p repeat-string) (concat " " repeat-string))
+                (and (org-string-nw-p warning-string) (concat " " warning-string))
+                (cdr brackets)
+                "--" (car brackets)
+                (format-time-string
+	         (org-time-stamp-format (and (integerp minute-end) (integerp hour-end)) 'no-brackets)
+	         (org-encode-time
+	          0
+	          (or minute-end 0)
+	          (or hour-end 0)
+	          day-end
+	          month-end
+	          year-end))))
+              ((and time-range-p (eq range-type 'timerange)) (format "-%02d:%02d" hour-end minute-end)))
+             (and (org-string-nw-p repeat-string) (concat " " repeat-string))
+             (and (org-string-nw-p warning-string) (concat " " warning-string))
+             (cdr brackets)))))
+    (org-element-property :raw-value timestamp)))
 
 ;;;; Underline
 
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index 283ade10f..1c083628d 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -3138,8 +3138,73 @@ Outside list"
 	  (org-test-with-temp-text "<2012-03-29 Thu +1y -1y>"
 	    (let ((ts (org-element-context)))
 	      (list (org-element-property :repeater-type ts)
-		    (org-element-property :warning-type ts)))))))
-
+		    (org-element-property :warning-type ts))))))
+  ;; :range-type property
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    nil))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    nil))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00-13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'timerange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00-12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'timerange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>--<2023-07-03 Mon>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun 12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-03 Mon 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-03 Mon>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00 +5d>--<2023-07-02 Sun 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange)))
 
 ;;;; Underline
 
@@ -3697,10 +3762,40 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type inactive :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40)) nil)))
   ;; Active range.
+
+  ;; range-type: daterange; parse-and-interpret
   (should
    (string-match "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
 		 (org-test-parse-and-interpret
 		  "<2012-03-29 thu. 16:40>--<2012-03-29 thu. 16:41>")))
+
+  ;; range-type: daterange; interpreter
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :range-type daterange :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  
+  ;; range-type: timerange; parse-and-interpret
+  (should
+   (string-match "<2012-03-29 .* 16:40-16:41>"
+		 (org-test-parse-and-interpret
+		  "<2012-03-29 thu. 16:40-16:41>")))
+
+  ;; range-type: timerange; interpreter
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40-16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :range-type timerange :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  
+  ;; range-type: nil; date-start and date-end are equal; interpreter
   (should
    (string-match
     "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
@@ -3709,11 +3804,32 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+
+  ;; range-type: nil; date-start and date-end aren't equal; interpreter
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-30 .* 16:41>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 30 :hour-end 16 :minute-end 41)) nil)))
+
   ;; Inactive range.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
 		 (org-test-parse-and-interpret
 		  "[2012-03-29 thu. 16:40]--[2012-03-29 thu. 16:41]")))
+
+  (should
+   (string-match
+    "\\[2012-03-29 .* 16:40-16:41\\]"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type inactive-range :range-type timerange :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+
   (should
    (string-match
     "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
@@ -3722,6 +3838,7 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  
   ;; Diary.
   (should (equal (org-test-parse-and-interpret "<%%diary-float t 4 2>")
 		 "<%%diary-float t 4 2>\n"))
-- 
2.40.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-07  7:24           ` Ilya Chernyshov
@ 2023-07-08  8:35             ` Ihor Radchenko
  2023-07-10 18:19               ` Ilya Chernyshov
  0 siblings, 1 reply; 13+ messages in thread
From: Ihor Radchenko @ 2023-07-08  8:35 UTC (permalink / raw)
  To: Ilya Chernyshov; +Cc: emacs-orgmode

Ilya Chernyshov <ichernyshovvv@gmail.com> writes:

>> Interpreting timestamps with :time-range nil and
>> :day-end/:year-end/:month-end non-nil as timerange is a breaking change.
>> Let's avoid it.
>
> Timestamp objects (ranges) with :range-type nil are now interpreted as
> dateranges, as it was before.

I still have questions about this part.

> +                 (hour-start (org-element-property :hour-start timestamp))
> +                 (minute-start (org-element-property :minute-start timestamp))
> +                 (hour-end (or (org-element-property :hour-end timestamp) hour-start))
> +                 (minute-end (or (org-element-property :minute-end timestamp) minute-start))
> +                 (day-end (or (org-element-property :day-end timestamp) day-start))
> +                 (month-end (or (org-element-property :month-end timestamp) month-start))
> +                 (year-end (or (org-element-property :year-end timestamp) year-start))

I note that *-end is never going to be nil when the corresponding
*-start is non-nil. This will remove some original information about
timestamp that can cause edge cases as I describe below.

> +                 (time-range-p (and hour-start minute-start minute-end hour-end

So, this (and ...), copied from the old version of the code looks suspicious.

> +                                    (or (/= hour-start hour-end)
> +				        (/= minute-start minute-end))))

> +                 (date-range-p (or (and (eq range-type nil) time-range-p)

What about [2023-11-12 12:00-13:00] represented as Elisp AST with :range-type nil?

> +                                   (and day-end month-end year-end
> +                                        (or
> +                                         (/= day-start day-end)
> +                                         (/= month-start month-end)
> +                                         (/= year-start year-end)))))

[2023-11-12 12:00]--[2023-11-12 13:00]?

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
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>


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-08  8:35             ` Ihor Radchenko
@ 2023-07-10 18:19               ` Ilya Chernyshov
  2023-07-11  9:02                 ` Ihor Radchenko
  0 siblings, 1 reply; 13+ messages in thread
From: Ilya Chernyshov @ 2023-07-10 18:19 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 275 bytes --]


Hi.

Preserved old behavior for `org-element-timestamp-interpreter'
function for :range-type set to `nil'. The new function

The new function takes into account :range-type value when interpreting
ranges and throws error when :range-type set for `active'/`inactive'
types.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-lisp-org-element.el-Add-new-timestamp-property-range.patch --]
[-- Type: text/x-patch, Size: 22184 bytes --]

From aa21a84fa38324f455cb17ac32e656e1242c2135 Mon Sep 17 00:00:00 2001
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
Date: Sat, 18 Feb 2023 14:55:39 +0700
Subject: [PATCH] lisp/org-element.el: Add new timestamp property :range-type

* lisp/org-element (org-element-timestamp-interpreter): Preserve old
behavior when :range-type is `nil'.  Take into account :range-type
value when interpreting ranges.  When :range-type is `timerange',
return a timerange (<YYYY-mm-DD HH:MM-HH:MM>).  If :range-type is
`daterange' return a daterange (<...>--<...>).  When :range-type is
nil, return a daterange (as it was before).  When :range-type is
`daterange' or `timerange' and :type is `active'/`inactive', throw an
error.
(org-element-timestamp-parser): Add :range-type property.

* testing/lisp/test-org-element
(test-org-element/timestamp-interpreter): Add new tests.
(test-org-element/timestamp-parser): Add tests for :range-type
property.

* etc/ORG-NEWS (Major changes and additions to Org API): Add news about this property.
---
 etc/ORG-NEWS                     |   7 +
 lisp/org-element.el              | 171 +++++++++++--------------
 testing/lisp/test-org-element.el | 211 ++++++++++++++++++++++++++++++-
 3 files changed, 290 insertions(+), 99 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index d04e92275..d8d2275d9 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -200,6 +200,13 @@ a newly created one.
 Previously, one had to use
 
 : (apply #'org-element-create 'section nil (org-element-contents node))
+**** New property ~:range-type~ for org-element timestamp object
+
+~org-element-timestamp-parser~ now adds =:range-type= property to each
+timestamp object.  Possible values: ~timerange~, ~daterange~, ~nil~.
+
+~org-element-timestamp-interpreter~ takes into account this property
+and returns approptiate timestamp string.
 
 *** ~org-priority=show~ command no longer adjusts for scheduled/deadline
 
diff --git a/lisp/org-element.el b/lisp/org-element.el
index bfb1d206e..5cbc57aab 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -4043,7 +4043,7 @@ Assume point is at the target."
   "Parse time stamp at point, if any.
 
 When at a time stamp, return a new syntax node of `timestamp' type
-containing `:type', `:raw-value', `:year-start', `:month-start',
+containing `:type', `:range-type', `:raw-value', `:year-start', `:month-start',
 `:day-start', `:hour-start', `:minute-start', `:year-end',
 `:month-end', `:day-end', `:hour-end', `:minute-end',
 `:repeater-type', `:repeater-value', `:repeater-unit',
@@ -4077,6 +4077,10 @@ Assume point is at the beginning of the timestamp."
 			 (activep 'active)
 			 ((or date-end time-range) 'inactive-range)
 			 (t 'inactive)))
+             (range-type (cond
+                          (date-end 'daterange)
+                          (time-range 'timerange)
+                          (t nil)))
 	     (repeater-props
 	      (and (not diaryp)
 		   (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)"
@@ -4123,6 +4127,7 @@ Assume point is at the beginning of the timestamp."
 	(org-element-create
          'timestamp
 	 (nconc (list :type type
+                      :range-type range-type
 		      :raw-value raw-value
 		      :year-start year-start
 		      :month-start month-start
@@ -4142,99 +4147,77 @@ Assume point is at the beginning of the timestamp."
 
 (defun org-element-timestamp-interpreter (timestamp _)
   "Interpret TIMESTAMP object as Org syntax."
-  (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"))))
-	 (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"))))
-	 (build-ts-string
-	  ;; Build an Org timestamp string from TIME.  ACTIVEP is
-	  ;; non-nil when time stamp is active.  If WITH-TIME-P is
-	  ;; non-nil, add a time part.  HOUR-END and MINUTE-END
-	  ;; specify a time range in the timestamp.  REPEAT-STRING is
-	  ;; the repeater string, if any.
-	  (lambda (time activep &optional with-time-p hour-end minute-end)
-	    (let ((ts (format-time-string
-                       (org-time-stamp-format with-time-p)
-		       time)))
-	      (when (and hour-end minute-end)
-		(string-match "[012]?[0-9]:[0-5][0-9]" ts)
-		(setq ts
-		      (replace-match
-		       (format "\\&-%02d:%02d" hour-end minute-end)
-		       nil nil ts)))
-	      (unless activep (setq ts (format "[%s]" (substring ts 1 -1))))
-	      (dolist (s (list repeat-string warning-string))
-		(when (org-string-nw-p s)
-		  (setq ts (concat (substring ts 0 -1)
-				   " "
-				   s
-				   (substring ts -1)))))
-	      ;; Return value.
-	      ts)))
-	 (type (org-element-property :type timestamp)))
-    (pcase type
-      ((or `active `inactive)
-       (let* ((minute-start (org-element-property :minute-start timestamp))
-	      (minute-end (org-element-property :minute-end timestamp))
-	      (hour-start (org-element-property :hour-start timestamp))
-	      (hour-end (org-element-property :hour-end timestamp))
-	      (time-range-p (and hour-start hour-end minute-start minute-end
-				 (or (/= hour-start hour-end)
-				     (/= minute-start minute-end)))))
-	 (funcall
-	  build-ts-string
-	  (org-encode-time 0
-                           (or minute-start 0)
-                           (or hour-start 0)
-                           (org-element-property :day-start timestamp)
-                           (org-element-property :month-start timestamp)
-                           (org-element-property :year-start timestamp))
-	  (eq type 'active)
-	  (and hour-start minute-start)
-	  (and time-range-p hour-end)
-	  (and time-range-p minute-end))))
-      ((or `active-range `inactive-range)
-       (let ((minute-start (org-element-property :minute-start timestamp))
-	     (minute-end (org-element-property :minute-end timestamp))
-	     (hour-start (org-element-property :hour-start timestamp))
-	     (hour-end (org-element-property :hour-end timestamp)))
-	 (concat
-	  (funcall
-	   build-ts-string (org-encode-time
-			    0
-			    (or minute-start 0)
-			    (or hour-start 0)
-			    (org-element-property :day-start timestamp)
-			    (org-element-property :month-start timestamp)
-			    (org-element-property :year-start timestamp))
-	   (eq type 'active-range)
-	   (and hour-start minute-start))
-	  "--"
-	  (funcall build-ts-string
-		   (org-encode-time
-                    0
-                    (or minute-end 0)
-                    (or hour-end 0)
-                    (org-element-property :day-end timestamp)
-                    (org-element-property :month-end timestamp)
-                    (org-element-property :year-end timestamp))
-		   (eq type 'active-range)
-		   (and hour-end minute-end)))))
-      (_ (org-element-property :raw-value timestamp)))))
-
-
+  (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)))
+          (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"))))
+                   (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
+               (car brackets)
+               (format-time-string
+	        (org-time-stamp-format (when (and minute-start hour-start) t) 'no-brackets)
+	        (org-encode-time
+	         0 (or minute-start 0) (or hour-start 0)
+	         day-start month-start year-start))
+               (let((hour-end (org-element-property :hour-end timestamp))
+                    (minute-end (org-element-property :minute-end timestamp)))
+                 (pcase type
+                   ((or `active `inactive)
+                    (pcase range-type
+                      (`nil
+                       (when (and hour-start hour-end minute-start minute-end
+				  (or (/= hour-start hour-end)
+				      (/= minute-start minute-end)))
+                         (format "-%02d:%02d" hour-end minute-end)))
+                      ((or `timerange `daterange)
+                       (error "`:range-type' must be `nil' for `active'/`inactive' type"))))
+                   ((or `active-range `inactive-range)
+                    (pcase range-type
+                      (`timerange (format "-%02d:%02d" (or hour-end hour-start) (or minute-end minute-start)))
+                      ((or `nil `daterange)
+                       (concat
+                        timestamp-end
+                        "--" (car brackets)
+                        (format-time-string
+	                 (org-time-stamp-format (when (and minute-end hour-end) t) 'no-brackets)
+	                 (org-encode-time
+	                  0 (or minute-end 0) (or hour-end 0)
+	                  (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)))))))))
+               timestamp-end))))
+      (org-element-property :raw-value timestamp))))
 ;;;; Underline
 
 (defun org-element-underline-parser ()
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index 283ade10f..1a1f7aa75 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -3138,8 +3138,73 @@ Outside list"
 	  (org-test-with-temp-text "<2012-03-29 Thu +1y -1y>"
 	    (let ((ts (org-element-context)))
 	      (list (org-element-property :repeater-type ts)
-		    (org-element-property :warning-type ts)))))))
-
+		    (org-element-property :warning-type ts))))))
+  ;; :range-type property
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    nil))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    nil))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00-13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'timerange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00-12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'timerange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>--<2023-07-03 Mon>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun 12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-03 Mon 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-03 Mon>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00 +5d>--<2023-07-02 Sun 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange)))
 
 ;;;; Underline
 
@@ -3685,6 +3750,14 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
 		  '(timestamp
 		    (:type active :year-start 2012 :month-start 3 :day-start 29
 			   :hour-start 16 :minute-start 40)) nil)))
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 40)) nil)))
   ;; Inactive.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]"
@@ -3696,11 +3769,35 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
      '(timestamp
        (:type inactive :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40)) nil)))
-  ;; Active range.
+  ;; Active daterange.
   (should
    (string-match "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
 		 (org-test-parse-and-interpret
 		  "<2012-03-29 thu. 16:40>--<2012-03-29 thu. 16:41>")))
+  ;;; No end time, dates are not equal
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-30 .*>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 30)) nil)))
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:40>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 40)) nil)))
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-29 .*>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40)) nil)))
   (should
    (string-match
     "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
@@ -3709,7 +3806,7 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
-  ;; Inactive range.
+  ;; Inactive daterange.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
 		 (org-test-parse-and-interpret
@@ -3722,6 +3819,11 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  ;; Active timerange
+  (should
+   (string-match "<2012-03-29 .* 16:40-16:41>"
+		 (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"))
@@ -3767,7 +3869,106 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :year-end 2012 :month-end 3 :day-end 30 :repeater-type cumulate
 	      :repeater-value 1 :repeater-unit year))
-     nil))))
+     nil)))
+  ;; Tests for :range-type property
+  ;;; Errors
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type timerange
+                   :type active
+                   :year-start 2023 :month-start 7 :day-start 10
+                   :year-end 2023 :month-end 7 :day-end 10
+                   :hour-start 17 :minute-start 30
+                   :hour-end 17 :minute-end 30))
+    nil))
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type daterange
+                   :type active :year-start 2023 :month-start 7 :day-start 10
+                   :hour-start 17 :minute-start 30)) nil))
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type timerange
+                   :type inactive
+                   :year-start 2023 :month-start 7 :day-start 10
+                   :year-end 2023 :month-end 7 :day-end 10
+                   :hour-start 17 :minute-start 30
+                   :hour-end 17 :minute-end 30))
+    nil))
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type daterange
+                   :type inactive :year-start 2023 :month-start 7 :day-start 10
+                   :hour-start 17 :minute-start 30)) nil))
+  
+  ;;; End part is nil
+  (should
+   (string-match "<2023-07-10 .*>--<2023-07-10 .*>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type daterange
+                                 :type active-range :year-start 2023 :month-start 7 :day-start 10)) nil)))
+  (should
+   (string-match "<2023-07-10 .* 17:30-17:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type timerange
+                                 :type active-range :year-start 2023 :month-start 7 :day-start 10
+		                 :hour-start 17 :minute-start 30)) nil)))
+  (should
+   (string-match "<2023-07-10 .* 17:30>--<2023-07-10 .*>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type daterange
+                                 :type active-range :year-start 2023 :month-start 7 :day-start 10
+		                 :hour-start 17 :minute-start 30)) nil)))
+  ;;; End is equal to start
+  (should
+   (string-match "<2023-07-10 .* 17:30-17:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type timerange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+                                 :year-end 2023 :month-end 7 :day-end 10
+		                 :hour-start 17 :minute-start 30
+                                 :hour-end 17 :minute-end 30)) nil)))
+  (should
+   (string-match "<2023-07-10 .* 17:30>--<2023-07-10 .* 17:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type daterange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+                                 :year-end 2023 :month-end 7 :day-end 10
+		                 :hour-start 17 :minute-start 30
+                                 :hour-end 17 :minute-end 30)) nil)))
+  ;;;; End date is not equal to start date, but interpret the object as a timerange (:range-type 'timerange)
+  (should
+   (string-match "<2023-07-10 .* 17:30-18:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type timerange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+                                 :year-end 2023 :month-end 8 :day-end 10
+                                 :hour-start 17 :minute-start 30
+                                 :hour-end 18 :minute-end 30)) nil)))
+  ;;;; End date is not equal to start date, interpret the object as a daterange (:range-type 'daterange)
+  (should
+   (string-match "<2023-07-10 .* 17:30>--<2023-08-10 .* 18:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type daterange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+		                 :year-end 2023 :month-end 8 :day-end 10
+                                 :hour-start 17 :minute-start 30
+                                 :hour-end 18 :minute-end 30)) nil))))
 
 (ert-deftest test-org-element/verse-block-interpreter ()
   "Test verse block interpretation."
-- 
2.40.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-10 18:19               ` Ilya Chernyshov
@ 2023-07-11  9:02                 ` Ihor Radchenko
  2023-07-11 13:16                   ` Ilya Chernyshov
  0 siblings, 1 reply; 13+ messages in thread
From: Ihor Radchenko @ 2023-07-11  9:02 UTC (permalink / raw)
  To: Ilya Chernyshov; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 542 bytes --]

Ilya Chernyshov <ichernyshovvv@gmail.com> writes:

> Preserved old behavior for `org-element-timestamp-interpreter'
> function for :range-type set to `nil'. The new function
>
> The new function takes into account :range-type value when interpreting
> ranges and throws error when :range-type set for `active'/`inactive'
> types.

Thanks!

The patch looks good in general, but there is at least one test failing
when I run make test. May you please check?

Also, see the attached diff where I suggest some comments to explain the
code logic.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: extra-comments.diff --]
[-- Type: text/x-patch, Size: 5822 bytes --]

diff --git a/lisp/org-element.el b/lisp/org-element.el
index 203f45a33..baa605548 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -4152,6 +4152,8 @@ (defun org-element-timestamp-interpreter (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 (and day-start month-start year-start)
             (let* ((repeat-string
 	            (concat
@@ -4184,39 +4186,82 @@ (defun org-element-timestamp-interpreter (timestamp _)
                      (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-format (when (and minute-start hour-start) t) 'no-brackets)
+                ;; `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))
-               (let((hour-end (org-element-property :hour-end timestamp))
-                    (minute-end (org-element-property :minute-end timestamp)))
+               ;; 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' use 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' will never assign end times here.
+                       ;; End time will always imply `active-range' or `inactive-range' TYPE.
+                       ;; But manually built timestamps may contain
+                       ;; anything, 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)))
-                      ((or `nil `daterange)
+                      ;; 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-format (when (and minute-end hour-end) t) 'no-brackets)
+                         ;; `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))))
 ;;;; Underline
 

[-- Attachment #3: Type: text/plain, Size: 335 bytes --]


And please provide an additional patch for WORG:
https://orgmode.org/worg/dev/org-element-api.html#org6ae377e

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
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>

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-11  9:02                 ` Ihor Radchenko
@ 2023-07-11 13:16                   ` Ilya Chernyshov
  2023-07-12  8:16                     ` Ihor Radchenko
  0 siblings, 1 reply; 13+ messages in thread
From: Ilya Chernyshov @ 2023-07-11 13:16 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: emacs-orgmode

[-- Attachment #1: Type: text/plain, Size: 822 bytes --]

Ihor Radchenko <yantar92@posteo.net> writes:

> The patch looks good in general, but there is at least one test failing
> when I run make test. May you please check?

Inside `org-timestamp-split-range', there was an attempt to intepret a
timestamp object with :type `active'/`inactive' and :range-type
`daterange'/`timerange' which caused an error. Fixed by setting
:range-type to nil before `(org-element-interpret-data split-ts)'.


> Also, see the attached diff where I suggest some comments to explain the
> code logic.

Looks good to me, I just changed a note about
`org-element-timestamp-parser' setting nil for end part if :type is
`active'/`inactive'. That's not true. The actual behavior is that it
sets end date/time to the same values as start date/time even if it's
`active'/`inactive'. So here is the patch:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-lisp-org-element.el-Add-new-timestamp-property-range.patch --]
[-- Type: text/x-patch, Size: 26559 bytes --]

From 823e7f39d33977854605485fcae814af0a3fdefe Mon Sep 17 00:00:00 2001
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
Date: Sat, 18 Feb 2023 14:55:39 +0700
Subject: [PATCH] lisp/org-element.el: Add new timestamp property :range-type

* lisp/org-element.el (org-element-timestamp-interpreter): Preserve old
behavior when :range-type is `nil'.  Take into account :range-type
value when interpreting ranges.  When :range-type is `timerange',
return a timerange (<YYYY-mm-DD HH:MM-HH:MM>).  If :range-type is
`daterange' return a daterange (<...>--<...>).  When :range-type is
nil, return a daterange (as it was before).  When :range-type is
`daterange' or `timerange' and :type is `active'/`inactive', throw an
error.
(org-element-timestamp-parser): Add :range-type property.

* lisp/org.el (org-timestamp-split-range): Make sure that :range-type
is nil for a split timestamp.

* testing/lisp/test-org-element.el
(test-org-element/timestamp-interpreter): Add new tests.
(test-org-element/timestamp-parser): Add tests for :range-type
property.

* etc/ORG-NEWS (Major changes and additions to Org API): Add news about this property.
---
 etc/ORG-NEWS                     |   7 +
 lisp/org-element.el              | 215 +++++++++++++++-------------
 lisp/org.el                      |   1 +
 testing/lisp/test-org-element.el | 231 ++++++++++++++++++++++++++++++-
 4 files changed, 355 insertions(+), 99 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 973a97a2f..a4725ae8c 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -200,6 +200,13 @@ a newly created one.
 Previously, one had to use
 
 : (apply #'org-element-create 'section nil (org-element-contents node))
+**** New property ~:range-type~ for org-element timestamp object
+
+~org-element-timestamp-parser~ now adds =:range-type= property to each
+timestamp object.  Possible values: ~timerange~, ~daterange~, ~nil~.
+
+~org-element-timestamp-interpreter~ takes into account this property
+and returns an appropriate timestamp string.
 
 *** ~org-priority=show~ command no longer adjusts for scheduled/deadline
 
diff --git a/lisp/org-element.el b/lisp/org-element.el
index 1c9707573..075f64920 100644
--- a/lisp/org-element.el
+++ b/lisp/org-element.el
@@ -4043,7 +4043,7 @@ Assume point is at the target."
   "Parse time stamp at point, if any.
 
 When at a time stamp, return a new syntax node of `timestamp' type
-containing `:type', `:raw-value', `:year-start', `:month-start',
+containing `:type', `:range-type', `:raw-value', `:year-start', `:month-start',
 `:day-start', `:hour-start', `:minute-start', `:year-end',
 `:month-end', `:day-end', `:hour-end', `:minute-end',
 `:repeater-type', `:repeater-value', `:repeater-unit',
@@ -4077,6 +4077,10 @@ Assume point is at the beginning of the timestamp."
 			 (activep 'active)
 			 ((or date-end time-range) 'inactive-range)
 			 (t 'inactive)))
+             (range-type (cond
+                          (date-end 'daterange)
+                          (time-range 'timerange)
+                          (t nil)))
 	     (repeater-props
 	      (and (not diaryp)
 		   (string-match "\\([.+]?\\+\\)\\([0-9]+\\)\\([hdwmy]\\)"
@@ -4123,6 +4127,7 @@ Assume point is at the beginning of the timestamp."
 	(org-element-create
          'timestamp
 	 (nconc (list :type type
+                      :range-type range-type
 		      :raw-value raw-value
 		      :year-start year-start
 		      :month-start month-start
@@ -4142,99 +4147,121 @@ Assume point is at the beginning of the timestamp."
 
 (defun org-element-timestamp-interpreter (timestamp _)
   "Interpret TIMESTAMP object as Org syntax."
-  (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"))))
-	 (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"))))
-	 (build-ts-string
-	  ;; Build an Org timestamp string from TIME.  ACTIVEP is
-	  ;; non-nil when time stamp is active.  If WITH-TIME-P is
-	  ;; non-nil, add a time part.  HOUR-END and MINUTE-END
-	  ;; specify a time range in the timestamp.  REPEAT-STRING is
-	  ;; the repeater string, if any.
-	  (lambda (time activep &optional with-time-p hour-end minute-end)
-	    (let ((ts (format-time-string
-                       (org-time-stamp-format with-time-p)
-		       time)))
-	      (when (and hour-end minute-end)
-		(string-match "[012]?[0-9]:[0-5][0-9]" ts)
-		(setq ts
-		      (replace-match
-		       (format "\\&-%02d:%02d" hour-end minute-end)
-		       nil nil ts)))
-	      (unless activep (setq ts (format "[%s]" (substring ts 1 -1))))
-	      (dolist (s (list repeat-string warning-string))
-		(when (org-string-nw-p s)
-		  (setq ts (concat (substring ts 0 -1)
-				   " "
-				   s
-				   (substring ts -1)))))
-	      ;; Return value.
-	      ts)))
-	 (type (org-element-property :type timestamp)))
-    (pcase type
-      ((or `active `inactive)
-       (let* ((minute-start (org-element-property :minute-start timestamp))
-	      (minute-end (org-element-property :minute-end timestamp))
-	      (hour-start (org-element-property :hour-start timestamp))
-	      (hour-end (org-element-property :hour-end timestamp))
-	      (time-range-p (and hour-start hour-end minute-start minute-end
-				 (or (/= hour-start hour-end)
-				     (/= minute-start minute-end)))))
-	 (funcall
-	  build-ts-string
-	  (org-encode-time 0
-                           (or minute-start 0)
-                           (or hour-start 0)
-                           (org-element-property :day-start timestamp)
-                           (org-element-property :month-start timestamp)
-                           (org-element-property :year-start timestamp))
-	  (eq type 'active)
-	  (and hour-start minute-start)
-	  (and time-range-p hour-end)
-	  (and time-range-p minute-end))))
-      ((or `active-range `inactive-range)
-       (let ((minute-start (org-element-property :minute-start timestamp))
-	     (minute-end (org-element-property :minute-end timestamp))
-	     (hour-start (org-element-property :hour-start timestamp))
-	     (hour-end (org-element-property :hour-end timestamp)))
-	 (concat
-	  (funcall
-	   build-ts-string (org-encode-time
-			    0
-			    (or minute-start 0)
-			    (or hour-start 0)
-			    (org-element-property :day-start timestamp)
-			    (org-element-property :month-start timestamp)
-			    (org-element-property :year-start timestamp))
-	   (eq type 'active-range)
-	   (and hour-start minute-start))
-	  "--"
-	  (funcall build-ts-string
-		   (org-encode-time
-                    0
-                    (or minute-end 0)
-                    (or hour-end 0)
-                    (org-element-property :day-end timestamp)
-                    (org-element-property :month-end timestamp)
-                    (org-element-property :year-end timestamp))
-		   (eq type 'active-range)
-		   (and hour-end minute-end)))))
-      (_ (org-element-property :raw-value timestamp)))))
-
-
+  (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"))))
+                   (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))))
 ;;;; Underline
 
 (defun org-element-underline-parser ()
diff --git a/lisp/org.el b/lisp/org.el
index 62278ec77..590f8ab96 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -20043,6 +20043,7 @@ Return a new timestamp object."
 	;; Set new type.
 	(org-element-put-property
 	 split-ts :type (if (eq type 'active-range) 'active 'inactive))
+        (org-element-put-property split-ts :range-type nil)
 	;; Copy start properties over end properties if END is
 	;; non-nil.  Otherwise, copy end properties over `start' ones.
 	(let ((p-alist '((:minute-start . :minute-end)
diff --git a/testing/lisp/test-org-element.el b/testing/lisp/test-org-element.el
index 283ade10f..2e3a249ab 100644
--- a/testing/lisp/test-org-element.el
+++ b/testing/lisp/test-org-element.el
@@ -3138,8 +3138,73 @@ Outside list"
 	  (org-test-with-temp-text "<2012-03-29 Thu +1y -1y>"
 	    (let ((ts (org-element-context)))
 	      (list (org-element-property :repeater-type ts)
-		    (org-element-property :warning-type ts)))))))
-
+		    (org-element-property :warning-type ts))))))
+  ;; :range-type property
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    nil))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    nil))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00-13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'timerange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00-12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'timerange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun>--<2023-07-03 Mon>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun 12:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-03 Mon 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-03 Mon>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00>--<2023-07-02 Sun>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange))
+  (should
+   (eq
+    (org-test-with-temp-text "<2023-07-02 Sun 12:00 +5d>--<2023-07-02 Sun 13:00>"
+      (org-element-property :range-type (org-element-timestamp-parser)))
+    'daterange)))
 
 ;;;; Underline
 
@@ -3685,6 +3750,14 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
 		  '(timestamp
 		    (:type active :year-start 2012 :month-start 3 :day-start 29
 			   :hour-start 16 :minute-start 40)) nil)))
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 40)) nil)))
   ;; Inactive.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]"
@@ -3696,11 +3769,45 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
      '(timestamp
        (:type inactive :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40)) nil)))
-  ;; Active range.
+  ;; Active daterange.
   (should
    (string-match "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
 		 (org-test-parse-and-interpret
 		  "<2012-03-29 thu. 16:40>--<2012-03-29 thu. 16:41>")))
+  ;;; No end time, dates are not equal
+  (should
+   ;; Expected result: "<2012-03-29 Thu 16:40>--<2012-03-30 Fri>"
+   (string=
+    (format
+     "<%s>--<%s>"
+     (format-time-string (cdr org-time-stamp-formats) (org-encode-time 0 40 16 29 03 2012))
+     (format-time-string (car org-time-stamp-formats) (org-encode-time 0 0 0 30 03 2012)))
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 30)) nil)))
+  ;;; No start time, dates are not equal
+  (should
+   ;; Expected result: "<2012-03-29 Thu>--<2012-03-30 Fri 16:40>"
+   (string=
+    (format
+     "<%s>--<%s>"
+     (format-time-string (car org-time-stamp-formats) (org-encode-time 0 0 0 29 03 2012))
+     (format-time-string (cdr org-time-stamp-formats) (org-encode-time 0 40 16 30 03 2012)))
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-end 16 :minute-end 40 :year-end 2012 :month-end 3
+	      :day-end 30)) nil)))
+  (should
+   (string-match
+    "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:40>"
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:type active-range :year-start 2012 :month-start 3 :day-start 29
+	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
+	      :day-end 29 :hour-end 16 :minute-end 40)) nil)))
   (should
    (string-match
     "<2012-03-29 .* 16:40>--<2012-03-29 .* 16:41>"
@@ -3709,7 +3816,7 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
-  ;; Inactive range.
+  ;; Inactive daterange.
   (should
    (string-match "\\[2012-03-29 .* 16:40\\]--\\[2012-03-29 .* 16:41\\]"
 		 (org-test-parse-and-interpret
@@ -3722,6 +3829,11 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type inactive-range :year-start 2012 :month-start 3 :day-start 29
 	      :hour-start 16 :minute-start 40 :year-end 2012 :month-end 3
 	      :day-end 29 :hour-end 16 :minute-end 41)) nil)))
+  ;; Active timerange
+  (should
+   (string-match "<2012-03-29 .* 16:40-16:41>"
+		 (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"))
@@ -3767,7 +3879,116 @@ DEADLINE: <2012-03-29 thu.> SCHEDULED: <2012-03-29 thu.> CLOSED: [2012-03-29 thu
        (:type active-range :year-start 2012 :month-start 3 :day-start 29
 	      :year-end 2012 :month-end 3 :day-end 30 :repeater-type cumulate
 	      :repeater-value 1 :repeater-unit year))
-     nil))))
+     nil)))
+  ;; Tests for :range-type property
+  ;;; Errors
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type timerange
+                   :type active
+                   :year-start 2023 :month-start 7 :day-start 10
+                   :year-end 2023 :month-end 7 :day-end 10
+                   :hour-start 17 :minute-start 30
+                   :hour-end 17 :minute-end 30))
+    nil))
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type daterange
+                   :type active :year-start 2023 :month-start 7 :day-start 10
+                   :hour-start 17 :minute-start 30)) nil))
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type timerange
+                   :type inactive
+                   :year-start 2023 :month-start 7 :day-start 10
+                   :year-end 2023 :month-end 7 :day-end 10
+                   :hour-start 17 :minute-start 30
+                   :hour-end 17 :minute-end 30))
+    nil))
+  (should-error
+   (org-element-timestamp-interpreter
+    '(timestamp
+      (:range-type daterange
+                   :type inactive :year-start 2023 :month-start 7 :day-start 10
+                   :hour-start 17 :minute-start 30)) nil))
+  
+  ;;; End part is nil
+  (should
+   ;; Expected result: "<2023-07-10 Mon>--<2023-07-10 Mon>"
+   (string=
+    (format
+     "<%s>--<%s>"
+     (format-time-string (car org-time-stamp-formats) (org-encode-time 0 0 0 10 7 2023))
+     (format-time-string (car org-time-stamp-formats) (org-encode-time 0 0 0 10 7 2023)))
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:range-type daterange
+                    :type active-range :year-start 2023 :month-start 7 :day-start 10)) nil)))
+  (should
+   (string-match "<2023-07-10 .* 17:30-17:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type timerange
+                                 :type active-range :year-start 2023 :month-start 7 :day-start 10
+		                 :hour-start 17 :minute-start 30)) nil)))
+  (should
+   ;; Expected result: "<2023-07-10 Mon 17:30>--<2023-07-10 Mon>"
+   (string=
+    (format
+     "<%s>--<%s>"
+     (format-time-string (cdr org-time-stamp-formats) (org-encode-time 0 30 17 10 7 2023))
+     (format-time-string (car org-time-stamp-formats) (org-encode-time 0 0 0 10 7 2023)))
+    (org-element-timestamp-interpreter
+     '(timestamp
+       (:range-type daterange
+                    :type active-range :year-start 2023 :month-start 7 :day-start 10
+		    :hour-start 17 :minute-start 30)) nil)))
+  ;;; End is equal to start
+  (should
+   (string-match "<2023-07-10 .* 17:30-17:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type timerange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+                                 :year-end 2023 :month-end 7 :day-end 10
+		                 :hour-start 17 :minute-start 30
+                                 :hour-end 17 :minute-end 30)) nil)))
+  (should
+   (string-match "<2023-07-10 .* 17:30>--<2023-07-10 .* 17:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type daterange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+                                 :year-end 2023 :month-end 7 :day-end 10
+		                 :hour-start 17 :minute-start 30
+                                 :hour-end 17 :minute-end 30)) nil)))
+  ;;;; End date is not equal to start date, but interpret the object as a timerange (:range-type 'timerange)
+  (should
+   (string-match "<2023-07-10 .* 17:30-18:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type timerange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+                                 :year-end 2023 :month-end 8 :day-end 10
+                                 :hour-start 17 :minute-start 30
+                                 :hour-end 18 :minute-end 30)) nil)))
+  ;;;; End date is not equal to start date, interpret the object as a daterange (:range-type 'daterange)
+  (should
+   (string-match "<2023-07-10 .* 17:30>--<2023-08-10 .* 18:30>"
+		 (org-element-timestamp-interpreter
+		  '(timestamp
+		    (:range-type daterange
+                                 :type active-range
+                                 :year-start 2023 :month-start 7 :day-start 10
+		                 :year-end 2023 :month-end 8 :day-end 10
+                                 :hour-start 17 :minute-start 30
+                                 :hour-end 18 :minute-end 30)) nil))))
 
 (ert-deftest test-org-element/verse-block-interpreter ()
   "Test verse block interpretation."
-- 
2.40.1


[-- Attachment #3: Type: text/plain, Size: 123 bytes --]


> And please provide an additional patch for WORG:
> https://orgmode.org/worg/dev/org-element-api.html#org6ae377e

Sure.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0001-dev-org-element-api.org-Add-range-type-for-timestamp.patch --]
[-- Type: text/x-patch, Size: 874 bytes --]

From ea809755deef50cf91f41d50241ab86a0787f2cf Mon Sep 17 00:00:00 2001
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
Date: Tue, 11 Jul 2023 19:00:40 +0700
Subject: [PATCH] dev/org-element-api.org: Add :range-type for timestamp
 objects

---
 dev/org-element-api.org | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/dev/org-element-api.org b/dev/org-element-api.org
index baf7e8a1..ffcda274 100644
--- a/dev/org-element-api.org
+++ b/dev/org-element-api.org
@@ -708,6 +708,8 @@ Object.
   (integer or ~nil~).
 - ~:type~ :: Type of timestamp (symbol: ~active~, ~active-range~,
   ~diary~, ~inactive~, ~inactive-range~).
+- ~:range-type~ :: Type of range (symbol: ~daterange~, ~timerange~ or
+  ~nil~).
 - ~:warning-type~ :: Type of warning, if any (symbol: ~all~, ~first~
   or ~nil~)
 - ~:warning-unit~ :: Unit of delay, if one is defined (symbol: ~year~,
-- 
2.40.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil
  2023-07-11 13:16                   ` Ilya Chernyshov
@ 2023-07-12  8:16                     ` Ihor Radchenko
  0 siblings, 0 replies; 13+ messages in thread
From: Ihor Radchenko @ 2023-07-12  8:16 UTC (permalink / raw)
  To: Ilya Chernyshov; +Cc: emacs-orgmode

Ilya Chernyshov <ichernyshovvv@gmail.com> writes:

> From 823e7f39d33977854605485fcae814af0a3fdefe Mon Sep 17 00:00:00 2001
> From: Ilya Chernyshov <ichernyshovvv@gmail.com>
> Date: Sat, 18 Feb 2023 14:55:39 +0700
> Subject: [PATCH] lisp/org-element.el: Add new timestamp property :range-type

Thanks!
Applied, onto main, with slight amendments.
I re-filled the modified comment, added a link to this thread, and
mentioned why we need the new property.

https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=c74c4ab18

I also applied the patch for WORG.
https://git.sr.ht/~bzg/worg/commit/1f049c5e

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
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>


^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2023-07-12  8:18 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-02-19 12:25 [PATCH] org-element-timestamp-interpreter: Return daterange anyway, if DATERANGE is non-nil Ilya Chernyshov
2023-02-19 14:11 ` Ilya Chernyshov
2023-02-20 11:07 ` Ihor Radchenko
2023-02-20 16:36   ` Ilya Chernyshov
2023-02-22 11:21     ` Ihor Radchenko
2023-07-01 19:47       ` Ilya Chernyshov
2023-07-02  8:46         ` Ihor Radchenko
2023-07-07  7:24           ` Ilya Chernyshov
2023-07-08  8:35             ` Ihor Radchenko
2023-07-10 18:19               ` Ilya Chernyshov
2023-07-11  9:02                 ` Ihor Radchenko
2023-07-11 13:16                   ` Ilya Chernyshov
2023-07-12  8:16                     ` Ihor Radchenko

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).