diff --git a/l4d2web/l4d2web/services/timeago.py b/l4d2web/l4d2web/services/timeago.py
index c3face6..eb329fe 100644
--- a/l4d2web/l4d2web/services/timeago.py
+++ b/l4d2web/l4d2web/services/timeago.py
@@ -1,5 +1,7 @@
from datetime import UTC, datetime
+from markupsafe import Markup, escape
+
_MONTHS = (
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
@@ -51,3 +53,18 @@ def humanize_delta(then: datetime, now: datetime | None = None) -> str:
return _format_date(then, now)
return _relative_label(abs_seconds, past=(delta_seconds > 0))
+
+
+def format_time_html(then: datetime, now: datetime | None = None) -> Markup:
+ if now is None:
+ now = datetime.now(UTC)
+ then_utc = _ensure_utc(then).astimezone(UTC)
+ now = _ensure_utc(now)
+
+ label = humanize_delta(then_utc, now=now)
+ iso = then_utc.isoformat()
+ title = then_utc.strftime("%Y-%m-%d %H:%M:%S UTC")
+ return Markup(
+ f'"
+ )
diff --git a/l4d2web/tests/test_timeago.py b/l4d2web/tests/test_timeago.py
index 274cbab..e02d6ed 100644
--- a/l4d2web/tests/test_timeago.py
+++ b/l4d2web/tests/test_timeago.py
@@ -1,8 +1,9 @@
from datetime import UTC, datetime, timedelta
import pytest
+from markupsafe import Markup
-from l4d2web.services.timeago import humanize_delta
+from l4d2web.services.timeago import format_time_html, humanize_delta
NOW = datetime(2026, 5, 16, 12, 0, 0, tzinfo=UTC)
@@ -82,3 +83,35 @@ def test_humanize_delta_year_boundary_includes_year_when_years_differ():
now = datetime(2026, 1, 15, 12, 0, 0, tzinfo=UTC)
then = datetime(2025, 11, 15, 12, 0, 0, tzinfo=UTC)
assert humanize_delta(then, now=now) == "15 Nov 2025"
+
+
+def test_format_time_html_returns_markup():
+ then = NOW - timedelta(minutes=5)
+ out = format_time_html(then, now=NOW)
+ assert isinstance(out, Markup)
+
+
+def test_format_time_html_contains_time_element_with_attrs():
+ then = datetime(2026, 5, 16, 14, 32, 11, tzinfo=UTC)
+ now = then + timedelta(minutes=5)
+ out = str(format_time_html(then, now=now))
+ assert out.startswith("