feat(timeago): add format_time_html returning a <time> element
Wrap humanize_delta in an HTML <time> element with datetime= and title= attributes carrying the precise UTC value, so hovering surfaces the exact timestamp regardless of the relative label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
237f26e5cb
commit
1926fe895c
2 changed files with 51 additions and 1 deletions
|
|
@ -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'<time datetime="{escape(iso)}" title="{escape(title)}">'
|
||||
f"{escape(label)}</time>"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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("<time ")
|
||||
assert out.endswith("</time>")
|
||||
assert 'datetime="2026-05-16T14:32:11+00:00"' in out
|
||||
assert 'title="2026-05-16 14:32:11 UTC"' in out
|
||||
assert ">5 minutes ago<" in out
|
||||
|
||||
|
||||
def test_format_time_html_label_matches_humanize_delta():
|
||||
then = NOW - timedelta(hours=2)
|
||||
label = humanize_delta(then, now=NOW)
|
||||
out = str(format_time_html(then, now=NOW))
|
||||
assert f">{label}<" in out
|
||||
|
||||
|
||||
def test_format_time_html_normalises_naive_input_to_utc():
|
||||
then_naive = datetime(2026, 5, 16, 14, 32, 11)
|
||||
now = datetime(2026, 5, 16, 14, 37, 11, tzinfo=UTC)
|
||||
out = str(format_time_html(then_naive, now=now))
|
||||
assert 'datetime="2026-05-16T14:32:11+00:00"' in out
|
||||
assert 'title="2026-05-16 14:32:11 UTC"' in out
|
||||
|
|
|
|||
Loading…
Reference in a new issue