Attached are the updated versions of my patches. Thank you for your
suggestions.
On 7/13/25 14:00, Ihor Radchenko wrote:
for <time ...> tag, right?
Exactly. I've tried to improve the wording.
What is the motivation behind changing -- to EN DASH?
This is not really a change. Previously, `org-html-timestamp' would
perform this replacement, but not `org-html-planning' nor
`org-html-clock'. When refactoring the code to use a common function
between these three functions, I decided to move the
`replace-regexp-in-string` into the common `org-html--format-timestamp'
rather than keep this replacement exclusive to `org-html-timestamp'. I
could revert this, but think it's better this way for consistency.
I've tried to clarify what changed in the updated commit message, too.
From 94fc03eb61fcfa61c0654e28a8921f84e416f8c1 Mon Sep 17 00:00:00 2001
From: Lukas Epple <em...@lukasepple.de>
Date: Wed, 25 Jun 2025 15:45:43 +0200
Subject: [PATCH 5/5] lisp/ox-html.el: Use time element for timestamps in fancy
html5
* ox-html.el (org-html--format-timestamp): Use time html element and its
datetime attribute when `org-html--html5-fancy-p'.
(org-html-datetime-formats): Add custom value that allows the user to
modify how `org-html--format-timestamp' renders the timestamp in the
HTML5 datetime attribute without affecting how it is displayed.
* testing/lisp/test-ox-html.el (ox-html/html5-fancy-timestamps): Add
simple test for the changed behavior (which only covers the behavior of
`org-html-timestamp').
* ORG-NEWS: Describe changed behavior of timestamp formatting with fancy
HTML5 rendering.
---
etc/ORG-NEWS | 10 ++++++++++
lisp/ox-html.el | 37 ++++++++++++++++++++++++++++++------
testing/lisp/test-ox-html.el | 18 ++++++++++++++++++
3 files changed, 59 insertions(+), 6 deletions(-)
diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 4cb7561a8..a89e40fe7 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -658,6 +658,16 @@ will be defined as empty and not produce any metadata if their
corresponding ~org-latex-with-author~, ~org-latex-with-title~, or
~org-latex-with-creator~ option is set to ~nil~.
+*** Fancy HTML5 export uses ~<time>~ element for timestamps
+Previously, timestamps would always be rendered inside a ~<span
+class="timestamp">~. Now, if both ~org-html-doctype~ is ~html5~ and
+~org-html-html5-fancy~ is enabled, org will now use the semantically
+appropriate ~<time>~ element. This will also use the ~timestamp~ class,
+but additionally set the ~datetime~ attribute with a machine readable
+variant of the timestamp. The format used for the attribute can be
+customized using ~org-html-datetime-attribute-date-timestamp-format~
+and ~org-html-datetime-attribute-full-timestamp-format~.
+
* Version 9.7
** Important announcements and breaking changes
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index 9eeb6a1a7..be66f6bb3 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1168,6 +1168,19 @@ See `format-time-string' for more information on its components."
:package-version '(Org . "8.0")
:type 'string)
+(defcustom org-html-datetime-formats '("%F" . "%FT%T")
+ "Formats used for the timestamp added as metadata to the time HTML element.
+This only has an effect when `org-html-html5-fancy' is enabled, but
+does not affect how the timestamp is displayed. The format in CAR
+represents the timestamp used for timestamps without a time component,
+CDR the one for the full date and time. Note that the HTML standard
+restricts what timestamp formats are considered valid for the datetime
+attribute. See `format-time-string' for more information on its
+components."
+ :type '(cons string string)
+ :group 'org-export-html
+ :package-version '(Org . "9.8"))
+
;;;; Template :: Mathjax
(defcustom org-html-mathjax-options
@@ -1811,12 +1824,24 @@ a value to `org-html-standalone-image-predicate'."
"Format given TIMESTAMP for inclusion in an HTML document.
INFO is a plist used as a communication channel. Formatted timestamp
will be wrapped in an element with class timestamp."
- (replace-regexp-in-string
- "--"
- "–"
- (format "<span class=\"timestamp\">%s</span>"
- (org-html-plain-text (org-timestamp-translate timestamp)
- info))))
+ (let ((html-tag (if (org-html--html5-fancy-p info) "time" "span"))
+ (html-attrs (concat "class=\"timestamp\""
+ (when (org-html--html5-fancy-p info)
+ (format " datetime=\"%s\""
+ (org-format-timestamp
+ timestamp
+ (if (org-timestamp-has-time-p timestamp)
+ (cdr org-html-datetime-formats)
+ (car org-html-datetime-formats))))))))
+ (replace-regexp-in-string
+ "--"
+ "–"
+ (format "<%s %s>%s</%s>"
+ html-tag
+ html-attrs
+ (org-html-plain-text (org-timestamp-translate timestamp)
+ info)
+ html-tag))))
;;;; Table
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
index 3d786629a..15b09a31f 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -954,6 +954,24 @@ SCHEDULED: <2025-03-26 Wed> DEADLINE: <2025-03-27 Thu 13:00> CLOSED: [2025-03-25
"<span class=\"timestamp-kwd\">DEADLINE:</span> <span class=\"timestamp\"><2025-03-27 Thu 13:00> </span>"
"<span class=\"timestamp-kwd\">SCHEDULED:</span> <span class=\"timestamp\"><2025-03-26 Wed> </span>"))))))
+(ert-deftest ox-html/html5-fancy-timestamps ()
+ "Test rendering of timestamps with fancy HTML5 enabled"
+ (org-test-with-temp-text "
+[2025-06-25 Wed]
+<2025-06-25 Wed 19:10>
+"
+ (let ((export-buffer "*Test HTML Export")
+ (org-export-show-temporary-buffer nil)
+ (org-html-doctype "html5")
+ (org-html-html5-fancy t))
+ (org-export-to-buffer 'html export-buffer
+ nil nil nil t)
+ (with-current-buffer export-buffer
+ (mapc (lambda (s)
+ (should (= 1 (how-many (rx-to-string s)))))
+ '("<span class=\"timestamp-wrapper\"><time class=\"timestamp\" datetime=\"2025-06-25\">[2025-06-25 Wed]</time></span>"
+ "<span class=\"timestamp-wrapper\"><time class=\"timestamp\" datetime=\"2025-06-25T19:10:00\"><2025-06-25 Wed 19:10></time></span>"))))))
+
;;; Postamble Format
--
2.50.0
From 55843e0c99793da3bb52a3145f785abeb5b5da8d Mon Sep 17 00:00:00 2001
From: Lukas Epple <em...@lukasepple.de>
Date: Fri, 21 Feb 2025 17:12:39 +0100
Subject: [PATCH 4/5] lisp/ox-html.el: Unify timestamp formatting
* lisp/ox-html.el (org-html--format-timestamp): Add new function for
handling the creation of <span class="timestamp"> elements.
(org-html-clock): Use `org-html--format-timestamp', except for the
duration string which can't use the same code.
(org-html-planning, org-html-timestamp): Use `org-html--format-timestamp'.
* lisp/testing/test-ox-html.el (ox-html/clock): Update expected output
since `org-html--format-timestamp' replaces -- with – in all cases
now, matching the previous behavior of `org-html-timestamp', but not
`org-html-clock'.
---
lisp/ox-html.el | 43 ++++++++++++++++++++++--------------
testing/lisp/test-ox-html.el | 2 +-
2 files changed, 27 insertions(+), 18 deletions(-)
diff --git a/lisp/ox-html.el b/lisp/ox-html.el
index f60bda02b..9eeb6a1a7 100644
--- a/lisp/ox-html.el
+++ b/lisp/ox-html.el
@@ -1807,6 +1807,17 @@ is meant to be used as a predicate for `org-export-get-ordinal' or
a value to `org-html-standalone-image-predicate'."
(org-element-property :caption element))
+(defun org-html--format-timestamp (timestamp info)
+ "Format given TIMESTAMP for inclusion in an HTML document.
+INFO is a plist used as a communication channel. Formatted timestamp
+will be wrapped in an element with class timestamp."
+ (replace-regexp-in-string
+ "--"
+ "–"
+ (format "<span class=\"timestamp\">%s</span>"
+ (org-html-plain-text (org-timestamp-translate timestamp)
+ info))))
+
;;;; Table
(defun org-html-htmlize-region-for-paste (beg end)
@@ -2651,17 +2662,17 @@ holding contextual information."
;;;; Clock
-(defun org-html-clock (clock _contents _info)
+(defun org-html-clock (clock _contents info)
"Transcode a CLOCK element from Org to HTML.
CONTENTS is nil. INFO is a plist used as a communication
channel."
(format "<p>
<span class=\"timestamp-wrapper\">
-<span class=\"timestamp-kwd\">%s</span> <span class=\"timestamp\">%s</span>%s
+<span class=\"timestamp-kwd\">%s</span> %s%s
</span>
</p>"
org-clock-string
- (org-timestamp-translate (org-element-property :value clock))
+ (org-html--format-timestamp (org-element-property :value clock) info)
(let ((time (org-element-property :duration clock)))
(and time (format " <span class=\"timestamp\">(%s)</span>" time)))))
@@ -3577,10 +3588,9 @@ channel."
(when timestamp
(let ((string (car pair)))
(format "<span class=\"timestamp-kwd\">%s</span> \
-<span class=\"timestamp\">%s</span> "
+%s "
string
- (org-html-plain-text (org-timestamp-translate timestamp)
- info))))))
+ (org-html--format-timestamp timestamp info))))))
`((,org-closed-string . ,(org-element-property :closed planning))
(,org-deadline-string . ,(org-element-property :deadline planning))
(,org-scheduled-string . ,(org-element-property :scheduled planning)))
@@ -3932,17 +3942,16 @@ information."
"Transcode a TIMESTAMP object from Org to HTML.
CONTENTS is nil. INFO is a plist holding contextual
information."
- (let* (
- ;; Strip :post-blank
- ;; It will be handled as a part of generic transcoder code
- ;; so we should avoid double-counting post-blank.
- (timestamp-no-blank
- (org-element-put-property
- (org-element-copy timestamp t)
- :post-blank 0))
- (value (org-html-plain-text (org-timestamp-translate timestamp-no-blank) info)))
- (format "<span class=\"timestamp-wrapper\"><span class=\"timestamp\">%s</span></span>"
- (replace-regexp-in-string "--" "–" value))))
+ (let (
+ ;; Strip :post-blank
+ ;; It will be handled as a part of generic transcoder code
+ ;; so we should avoid double-counting post-blank.
+ (timestamp-no-blank
+ (org-element-put-property
+ (org-element-copy timestamp t)
+ :post-blank 0)))
+ (format "<span class=\"timestamp-wrapper\">%s</span>"
+ (org-html--format-timestamp timestamp-no-blank info))))
;;;; Underline
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
index 55f457054..3d786629a 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -934,7 +934,7 @@ CLOCK: [2025-02-21 Fri 17:43]--[2025-02-21 Fri 17:48] => 0:05
nil nil nil t)
(with-current-buffer export-buffer
(should (search-forward
- "<span class=\"timestamp-kwd\">CLOCK:</span> <span class=\"timestamp\">[2025-02-21 Fri 17:43]--[2025-02-21 Fri 17:48] </span> <span class=\"timestamp\">(0:05)</span>"
+ "<span class=\"timestamp-kwd\">CLOCK:</span> <span class=\"timestamp\">[2025-02-21 Fri 17:43]–[2025-02-21 Fri 17:48] </span> <span class=\"timestamp\">(0:05)</span>"
nil t))))))
(ert-deftest ox-html/planning ()
--
2.50.0
From 4c390d45809a1ef62160752a85b9efae7ce45799 Mon Sep 17 00:00:00 2001
From: Lukas Epple <em...@lukasepple.de>
Date: Tue, 25 Mar 2025 19:30:34 +0100
Subject: [PATCH 3/5] testing/lisp/test-ox-html.el: Add test for rendering of
planning
---
testing/lisp/test-ox-html.el | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
index c64dacae1..55f457054 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -937,6 +937,23 @@ CLOCK: [2025-02-21 Fri 17:43]--[2025-02-21 Fri 17:48] => 0:05
"<span class=\"timestamp-kwd\">CLOCK:</span> <span class=\"timestamp\">[2025-02-21 Fri 17:43]--[2025-02-21 Fri 17:48] </span> <span class=\"timestamp\">(0:05)</span>"
nil t))))))
+(ert-deftest ox-html/planning ()
+ "Test rendering of timestamps in planning elements"
+ (org-test-with-temp-text "
+* Some Item
+SCHEDULED: <2025-03-26 Wed> DEADLINE: <2025-03-27 Thu 13:00> CLOSED: [2025-03-25 Tue 19:09]
+"
+ (let ((export-buffer "*Test HTML Export")
+ (org-export-show-temporary-buffer nil)
+ (org-export-with-planning t))
+ (org-export-to-buffer 'html export-buffer
+ nil nil nil t)
+ (with-current-buffer export-buffer
+ (mapc (lambda (s) (should (search-forward s nil t)))
+ '("<span class=\"timestamp-kwd\">CLOSED:</span> <span class=\"timestamp\">[2025-03-25 Tue 19:09]</span>"
+ "<span class=\"timestamp-kwd\">DEADLINE:</span> <span class=\"timestamp\"><2025-03-27 Thu 13:00> </span>"
+ "<span class=\"timestamp-kwd\">SCHEDULED:</span> <span class=\"timestamp\"><2025-03-26 Wed> </span>"))))))
+
;;; Postamble Format
--
2.50.0
From 7c963398c341ee82a0a1c9302c34f84235c9c992 Mon Sep 17 00:00:00 2001
From: Lukas Epple <em...@lukasepple.de>
Date: Tue, 25 Mar 2025 19:06:51 +0100
Subject: [PATCH 2/5] testing/lisp/test-ox-html.el: Add test for clock
rendering
---
testing/lisp/test-ox-html.el | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
index d969a5669..c64dacae1 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -918,6 +918,25 @@ $x$"
"<span class=\"timestamp\"><2025-02-18 Tue 23:59></span>"
"<span class=\"timestamp\">[2025-02-17 Mon 17:00]–[2025-02-17 Mon 19:00]</span>"))))))
+(ert-deftest ox-html/clock ()
+ "Test rendering of clock elements"
+ (org-test-with-temp-text "
+* Test
+:LOGBOOK:
+CLOCK: [2025-02-21 Fri 17:43]--[2025-02-21 Fri 17:48] => 0:05
+:END:
+"
+ (let ((export-buffer "*Test HTML Export")
+ (org-export-show-temporary-buffer nil)
+ (org-export-with-drawers t)
+ (org-export-with-clocks t))
+ (org-export-to-buffer 'html export-buffer
+ nil nil nil t)
+ (with-current-buffer export-buffer
+ (should (search-forward
+ "<span class=\"timestamp-kwd\">CLOCK:</span> <span class=\"timestamp\">[2025-02-21 Fri 17:43]--[2025-02-21 Fri 17:48] </span> <span class=\"timestamp\">(0:05)</span>"
+ nil t))))))
+
;;; Postamble Format
--
2.50.0
From 72aad0c90723727979d0953e6926972c45742cf5 Mon Sep 17 00:00:00 2001
From: Lukas Epple <em...@lukasepple.de>
Date: Fri, 21 Feb 2025 19:20:09 +0100
Subject: [PATCH 1/5] testing/lisp/test-ox-html.el: Add test for timestamp
rendering
---
testing/lisp/test-ox-html.el | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/testing/lisp/test-ox-html.el b/testing/lisp/test-ox-html.el
index ec8a7b18f..d969a5669 100644
--- a/testing/lisp/test-ox-html.el
+++ b/testing/lisp/test-ox-html.el
@@ -894,6 +894,29 @@ $x$"
(with-current-buffer export-buffer
(libxml-parse-xml-region (point-min) (point-max))))))))
+
+;;; Rendering Timestamps
+
+(ert-deftest ox-html/plain-timestamps ()
+ "Test rendering of timestamps (outside of clock/planning)"
+ (org-test-with-temp-text "
+- [2025-01-31 Fri]
+- [2025-01-31 Fri 14:00]
+- <2025-02-18 Tue>
+- <2025-02-18 Tue 23:59>
+- [2025-02-17 Tue 17:00]--[2025-02-17 Fri 19:00]
+"
+ (let ((export-buffer "*Test HTML Export")
+ (org-export-show-temporary-buffer nil))
+ (org-export-to-buffer 'html export-buffer
+ nil nil nil t)
+ (with-current-buffer export-buffer
+ (mapc (lambda (s) (should (search-forward s nil t)))
+ '("<span class=\"timestamp\">[2025-01-31 Fri]</span>"
+ "<span class=\"timestamp\">[2025-01-31 Fri 14:00]</span>"
+ "<span class=\"timestamp\"><2025-02-18 Tue></span>"
+ "<span class=\"timestamp\"><2025-02-18 Tue 23:59></span>"
+ "<span class=\"timestamp\">[2025-02-17 Mon 17:00]–[2025-02-17 Mon 19:00]</span>"))))))
;;; Postamble Format
--
2.50.0