From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <emacs-orgmode-bounces+larch=yhetil.org@gnu.org>
Received: from mp11.migadu.com ([2001:41d0:403:478a::])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))
	by ms9.migadu.com with LMTPS
	id AFj2GpRWrWSl8QAASxT56A
	(envelope-from <emacs-orgmode-bounces+larch=yhetil.org@gnu.org>)
	for <larch@yhetil.org>; Tue, 11 Jul 2023 15:18:12 +0200
Received: from aspmx1.migadu.com ([2001:41d0:403:478a::])
	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))
	by mp11.migadu.com with LMTPS
	id GNozG5RWrWR5DAEA9RJhRA
	(envelope-from <emacs-orgmode-bounces+larch=yhetil.org@gnu.org>)
	for <larch@yhetil.org>; Tue, 11 Jul 2023 15:18:12 +0200
Received: from lists.gnu.org (lists.gnu.org [209.51.188.17])
	(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
	(No client certificate requested)
	by aspmx1.migadu.com (Postfix) with ESMTPS id B289B4B6C9
	for <larch@yhetil.org>; Tue, 11 Jul 2023 15:18:11 +0200 (CEST)
Authentication-Results: aspmx1.migadu.com;
	dkim=pass header.d=gmail.com header.s=20221208 header.b=G5gjIKcg;
	spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org";
	dmarc=pass (policy=none) header.from=gmail.com
ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org;
	s=key1; t=1689081492;
	h=from:from:sender:sender:reply-to:subject:subject:date:date:
	 message-id:message-id:to:to:cc:cc:mime-version:mime-version:
	 content-type:content-type:in-reply-to:in-reply-to:
	 references:references:list-id:list-help:list-unsubscribe:
	 list-subscribe:list-post:dkim-signature;
	bh=8VYZfgQonoaUu3D+jPNAi++DHExQNKkCt6l3yBjeACI=;
	b=scAPitEXiLPqPIDlD8FLEL1saFJPLmY9GTGnVFeZ6LM0ZqNMa6zbsIA7bLF9ATbQL3ifPr
	Jjg9E5J0MwATpFJdynGLV2mv1189e1+SZ0jAJ5ONn47ejzkBPEuoeB/AhBDDm0jlw/d4Da
	rTcTftCSOkUgxQfmw6F3u/5ffr9sAxISRfsO9Vjn63UNaHld41IAx7NjZDQnjOMpV5Pd37
	6/b1GoLD4+m23cswQw6D8uJR0BIw8lNH2S0oYTVkMTsMih07NXlPURoDKQo4n2TnZ5aCpy
	oF1j/eglFd4Q1CtlZkRCx2TO1LpQx9QGZ9l8xBiwE5nAVewyYbEEoJL8Op0tUA==
ARC-Authentication-Results: i=1;
	aspmx1.migadu.com;
	dkim=pass header.d=gmail.com header.s=20221208 header.b=G5gjIKcg;
	spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org";
	dmarc=pass (policy=none) header.from=gmail.com
ARC-Seal: i=1; s=key1; d=yhetil.org; t=1689081492; a=rsa-sha256; cv=none;
	b=G7CtEZYV2gArtGjJWzIjfo8oVs09+TK25n3uM4zQRijjFfspqGNtPjeo7g5yw5SIpwKPlQ
	qywhkTs43yoL1LkxBVQKqdFnwwQ/KFHMh6o1Z+28NWnahKp4nCUmrGnKDNnV6ES3gmdmDL
	8D+vuLfUv8X/9FGduyVrEMMkFUcgNclVKqRq/E7NaQ9o+1IuAcN51Y3JSnGA+XcQ7T6OEY
	mflIK84e3vNNcWlmcWOJXwHBinAEI70yZgq/C8cpZkXOlARTUrsdCZUK3xrd3k7ofExmAu
	depheAtJ8OrDy3eQGOTdVWcKOoC6oDt9b/OusHObPzO4wx8qp6+6hOojgkmbng==
Received: from localhost ([::1] helo=lists1p.gnu.org)
	by lists.gnu.org with esmtp (Exim 4.90_1)
	(envelope-from <emacs-orgmode-bounces@gnu.org>)
	id 1qJDEq-0001YZ-AX; Tue, 11 Jul 2023 09:17:08 -0400
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <ichernyshovvv@gmail.com>)
 id 1qJDEm-0001YL-9S
 for emacs-orgmode@gnu.org; Tue, 11 Jul 2023 09:17:04 -0400
Received: from mail-lf1-x136.google.com ([2a00:1450:4864:20::136])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.90_1) (envelope-from <ichernyshovvv@gmail.com>)
 id 1qJDEg-0005iG-Vl
 for emacs-orgmode@gnu.org; Tue, 11 Jul 2023 09:17:03 -0400
Received: by mail-lf1-x136.google.com with SMTP id
 2adb3069b0e04-4fa48b5dc2eso8979825e87.1
 for <emacs-orgmode@gnu.org>; Tue, 11 Jul 2023 06:16:58 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20221208; t=1689081416; x=1691673416;
 h=mime-version:message-id:date:references:in-reply-to:subject:cc:to
 :from:from:to:cc:subject:date:message-id:reply-to;
 bh=8VYZfgQonoaUu3D+jPNAi++DHExQNKkCt6l3yBjeACI=;
 b=G5gjIKcgbnMNoazQYG1U1nM1Y0ofnCr/xCC300EyQhom3rc6jfKrP4lEofwRSedUez
 a/LnaZFvrmYRUL9oheCckle1+eGK+DBsOujcPrtMmlen88pqOHnuCxUSln9rHk6Wbh4C
 G/GMLQ45Ah5B4DxqBh4IBeS2JSXewPZKWqWfkuAVHgB9Dy0SFTcGgHAfwsVRVIY8XE0X
 VQ032jk3j9vCc0bmMDG/1YcScsBLTHXlgN5SXNKoXI7xbjYdH8+zbbOalVXZbdkI+76B
 a+Cw0E2rzFteBOjV+bftHM8gPtYclSGcduVmbD/rO+RuekiCr/3yGmFpBS1guI8TGDAV
 wW/w==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20221208; t=1689081416; x=1691673416;
 h=mime-version:message-id:date:references:in-reply-to:subject:cc:to
 :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;
 bh=8VYZfgQonoaUu3D+jPNAi++DHExQNKkCt6l3yBjeACI=;
 b=emzKMIcjp609stf31QqjgQoxwnGKGWv7G5Rma9DNi4D/wx4+J3gPtgYrl7bWB1tZfB
 EMW00rWe0FLJ9vl0yurBcTLXUEI/gKnjNGfhlrWPN/Sr6rpnCOrdbnhQC2saVE/j+oSK
 1JXR5RENTlhjnqH7pIvTyWPuS6qUcbQXzMeb+PNTyvC8mCxUk729GewFvfzk8oXTdpXq
 YpA4pITHMskWkruHxSyfLSf/4QFsDegrrV9HzTUjTBeApsvoYQaciefkV1MntikQaJ9P
 PbvkOo+4BzHIGAuuwgoDj/s6rSwFELAmhXxrjj6tNXFpZ+SJ+rgMTy9qTvyjr7X/40Rn
 lYBQ==
X-Gm-Message-State: ABy/qLYhkBJwnkdadE0VDMvEaTSLjzA6X/LRmEGia3mp2p1HG9vtDaht
 mRYhm6EMxpN25o7i59xL34JlQtTmCrY=
X-Google-Smtp-Source: APBJJlGO/vArSNqDwsw7MMux4hCjD6RVg9UG6XwSGwKCUJThGCH0Ftl0sF+oUWx24kWyOvfHuWOpMQ==
X-Received: by 2002:a05:6512:15a3:b0:4fb:89f2:594c with SMTP id
 bp35-20020a05651215a300b004fb89f2594cmr15056588lfb.56.1689081415589; 
 Tue, 11 Jul 2023 06:16:55 -0700 (PDT)
Received: from sonyvaio ([92.127.245.54]) by smtp.gmail.com with ESMTPSA id
 j14-20020ac253ae000000b004fb915e8b9csm318090lfh.53.2023.07.11.06.16.54
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Tue, 11 Jul 2023 06:16:54 -0700 (PDT)
From: Ilya Chernyshov <ichernyshovvv@gmail.com>
To: Ihor Radchenko <yantar92@posteo.net>
Cc: emacs-orgmode <emacs-orgmode@gnu.org>
Subject: Re: [PATCH] org-element-timestamp-interpreter: Return daterange
 anyway, if DATERANGE is non-nil
In-Reply-To: <87o7kiom6i.fsf@localhost>
References: <87y1ot6dqz.fsf@gmail.com> <87wn4cegt4.fsf@localhost>
 <9E22693E-7D20-470A-B57B-EA17BBE9160F@gmail.com>
 <87a616c5do.fsf@localhost>
 <CAGEwbGU9bmg=jAAX5OTrcrjV0q67Nd2yR3aVM4Z-yOUv1Pz1sQ@mail.gmail.com>
 <87wmzi4s6n.fsf@localhost> <87edlkyyit.fsf@gmail.com>
 <87ttueajih.fsf@localhost> <87edlfljcx.fsf@gmail.com>
 <87o7kiom6i.fsf@localhost>
Date: Tue, 11 Jul 2023 20:16:50 +0700
Message-ID: <87wmz6ziyl.fsf@gmail.com>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=-=-="
Received-SPF: pass client-ip=2a00:1450:4864:20::136;
 envelope-from=ichernyshovvv@gmail.com; helo=mail-lf1-x136.google.com
X-Spam_score_int: -20
X-Spam_score: -2.1
X-Spam_bar: --
X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,
 DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001,
 RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001,
 T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-BeenThere: emacs-orgmode@gnu.org
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: "General discussions about Org-mode." <emacs-orgmode.gnu.org>
List-Unsubscribe: <https://lists.gnu.org/mailman/options/emacs-orgmode>,
 <mailto:emacs-orgmode-request@gnu.org?subject=unsubscribe>
List-Archive: <https://lists.gnu.org/archive/html/emacs-orgmode>
List-Post: <mailto:emacs-orgmode@gnu.org>
List-Help: <mailto:emacs-orgmode-request@gnu.org?subject=help>
List-Subscribe: <https://lists.gnu.org/mailman/listinfo/emacs-orgmode>,
 <mailto:emacs-orgmode-request@gnu.org?subject=subscribe>
Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org
Sender: emacs-orgmode-bounces+larch=yhetil.org@gnu.org
X-Migadu-Country: US
X-Migadu-Flow: FLOW_IN
X-Migadu-Queue-Id: B289B4B6C9
X-Migadu-Spam-Score: -6.47
X-Migadu-Scanner: mx0.migadu.com
X-Spam-Score: -6.47
X-TUID: 62nsNflrW+re

--=-=-=
Content-Type: text/plain

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:


--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
 filename=0001-lisp-org-element.el-Add-new-timestamp-property-range.patch

>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


--=-=-=
Content-Type: text/plain


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

Sure.


--=-=-=
Content-Type: text/x-patch
Content-Disposition: attachment;
 filename=0001-dev-org-element-api.org-Add-range-type-for-timestamp.patch

>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


--=-=-=--