emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
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
Date: Fri, 07 Jul 2023 14:24:42 +0700	[thread overview]
Message-ID: <87edlkyyit.fsf@gmail.com> (raw)
In-Reply-To: <87wmzi4s6n.fsf@localhost>

[-- 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


  reply	other threads:[~2023-07-07  7:26 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87edlkyyit.fsf@gmail.com \
    --to=ichernyshovvv@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    --cc=yantar92@posteo.net \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).