On 2025-04-20 13:27, Ihor Radchenko wrote: > Although, I would add some comments to the code, decrypting what 5 and 7 > refer to.
Done, and a bit more. > Not that hard. All you need is to re-implement your reproducer in pure > Elisp. Even a single test will do. Here I could not resist to add more general test coverage for `org-timestamp-change'. Basically to tweak timestamps at every item position up and down, both with and w/o time and both with and w/o weekdays. Please check the attached patch and let me know what you think. Thanks.
From 33b25f91a50101f67e8a998c370e007831a35b5d Mon Sep 17 00:00:00 2001 From: Jens Schmidt <jschmidt4...@vodafonemail.de> Date: Sat, 26 Apr 2025 21:33:46 +0200 Subject: [PATCH] Improve support for weekday-less timestamps * lisp/org.el (org-ts-regexp1, org-timestamp-formats): Doc fixes. (org-timestamp-change): Test for presence of optional groups before accessing them. * testing/lisp/test-org.el (test-org/at-timestamp-p): Add tests for timestamps with time and without weekday name. (test-org/org-timestamp-change): New test. Link: https://list.orgmode.org/5bfeb020-821b-49a4-9380-1927adc05...@vodafonemail.de/ --- lisp/org.el | 28 +++++++++++++++--- testing/lisp/test-org.el | 64 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 78e8c0692..b5f4f3b89 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -448,7 +448,16 @@ This one does not require the space after the date, so it can be used on a string that terminates immediately after the date.") (defconst org-ts-regexp1 "\\(\\([0-9]\\{4\\}\\)-\\([0-9]\\{2\\}\\)-\\([0-9]\\{2\\}\\)\\(?: *\\([^]+0-9>\r\n -]+\\)\\)?\\( \\([0-9]\\{1,2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)" - "Regular expression matching time strings for analysis.") + "Regular expression matching time strings for analysis. +This regular expression provides the following groups: + 1: everything (required for embedding) + 2: year + 3: month + 4: day + 5: weekday name (optional) + 6: time part (optional) + 7: hour + 8: minute") (defconst org-ts-regexp2 (concat "<" org-ts-regexp1 "[^>\n]\\{0,16\\}>") "Regular expression matching time stamps, with groups.") @@ -479,6 +488,13 @@ The time stamps may be either active or inactive.") "Regular expression for specifying repeated events. After a match, group 1 contains the repeat expression.") +;; The weekday name "%a" is considered semi-optional in these formats, +;; see https://list.orgmode.org/87fricxatw.fsf@localhost/. It is +;; "optional" because the `org-timestamp-*' functions work alright on +;; weekday-less timestamps in paragraphs when one omits the "%a". But +;; it is only "semi"-optional since Org cannot process properly +;; timestamps in CLOCK, DEADLINE, and SCHEDULED lines when one omits +;; the "%a". (defvaralias 'org-time-stamp-formats 'org-timestamp-formats) (defconst org-timestamp-formats '("%Y-%m-%d %a" . "%Y-%m-%d %a %H:%M") "Formats for `format-time-string' which are used for time stamps. @@ -15472,9 +15488,13 @@ When SUPPRESS-TMP-DELAY is non-nil, suppress delays like (looking-at org-ts-regexp3) (goto-char (pcase origin-cat - ;; `day' category ends before `hour' if any, or at the end - ;; of the day name. - (`day (min (or (match-beginning 7) (1- (match-end 5))) origin)) + ;; `day' category ends at the end of the weekday name if + ;; any (group 5), or before `hour' if any (group 7), or at + ;; the end of the timestamp (group 1). + (`day (min (cond ((match-end 5) (1- (match-end 5))) + ((match-beginning 7)) + (t (1- (match-end 1)))) + origin)) (`hour (min (match-end 7) origin)) (`minute (min (1- (match-end 8)) origin)) ((pred integerp) (min (1- (match-end 0)) origin)) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index eefc9b692..af307f70b 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -8993,6 +8993,14 @@ CLOSED: %s (eq 'day (org-test-with-temp-text "<2012-03-29 T<point>hu>" (org-at-timestamp-p)))) + (should + (eq 'hour + (org-test-with-temp-text "<2012-03-29 Thu <point>12:34>" + (org-at-timestamp-p)))) + (should + (eq 'minute + (org-test-with-temp-text "<2012-03-29 Thu 12:<point>34>" + (org-at-timestamp-p)))) (should (wholenump (org-test-with-temp-text "<2012-03-29 Thu +2<point>y>" @@ -9005,6 +9013,23 @@ CLOSED: %s (eq 'after (org-test-with-temp-text "<2012-03-29 Thu><point>ยป" (org-at-timestamp-p)))) + ;; Test optional weekday name. + (should + (eq 'day + (org-test-with-temp-text "<2012-03-2<point>9>" + (org-at-timestamp-p)))) + (should + (eq 'day + (org-test-with-temp-text "<2012-03-29<point> 12:34>" + (org-at-timestamp-p)))) + (should + (eq 'hour + (org-test-with-temp-text "<2012-03-29 <point>12:34>" + (org-at-timestamp-p)))) + (should + (eq 'minute + (org-test-with-temp-text "<2012-03-29 12:<point>34>" + (org-at-timestamp-p)))) ;; Test `inactive' optional argument. (should (org-test-with-temp-text "[2012-03-29 Thu]" @@ -9067,6 +9092,45 @@ CLOSED: %s (org-test-with-temp-text "# [2012-03-29 Thu]<point>" (org-at-timestamp-p 'lax)))) +(ert-deftest test-org/org-timestamp-change () + "Test `org-timestamp-change' specifications." + (let ((now (current-time)) now-ts point) + (message "Testing with timestamps <%s> and <%s>" + (format-time-string (car org-timestamp-formats) now) + (format-time-string (cdr org-timestamp-formats) now)) + ;; loop over regular timestamp formats and weekday-less timestamp + ;; formats + (dolist (org-timestamp-formats + (list org-timestamp-formats + (cons (replace-regexp-in-string + " %a" "" (car org-timestamp-formats)) + (replace-regexp-in-string + " %a" "" (cdr org-timestamp-formats))))) + ;; loop over timestamps that do not and do contain time + (dolist (format (list (car org-timestamp-formats) + (cdr org-timestamp-formats))) + (setq now-ts + (concat "<" (format-time-string format now) ">")) + (org-test-with-temp-text now-ts + (forward-char 1) + (while (not (eq (char-after) ?>)) + (skip-syntax-forward "-") + ;; change the timestamp unit at point one down, two up, + ;; one down, which should give us the original timestamp + ;; again. However, point can move backward during that + ;; operation, so take care of that. *Not* using + ;; `save-excursion', which fails to restore point since + ;; the timestamp gets completely replaced. + (setq point (point)) + (org-timestamp-change -1 nil nil nil) + (org-timestamp-change 2 nil nil nil) + (org-timestamp-change -1 nil nil nil) + (goto-char point) + (should (string= + (buffer-substring (point-min) (point-max)) + now-ts)) + (forward-char 1))))))) + (ert-deftest test-org/timestamp () "Test `org-timestamp' specifications." ;; Insert chosen time stamp at point. -- 2.39.5