Skip to content

Commit 2ae2fd3

Browse files
committed
fix: parse UTC metric timestamps on Python 3.10
1 parent 4e153b6 commit 2ae2fd3

3 files changed

Lines changed: 25 additions & 0 deletions

File tree

‎src/pg_diag/metric_engine.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,10 @@ def _chart_epoch_status(previous: dict[str, Any], current: dict[str, Any]) -> st
14881488

14891489

14901490
def _seconds_between(start: str, end: str) -> float:
1491+
# json_safe serializes UTC datetimes with Z; Python 3.10 requires an
1492+
# explicit offset when parsing those source timestamps with fromisoformat.
1493+
start = start[:-1] + "+00:00" if start.endswith("Z") else start
1494+
end = end[:-1] + "+00:00" if end.endswith("Z") else end
14911495
try:
14921496
return max((datetime.fromisoformat(end) - datetime.fromisoformat(start)).total_seconds(), 0.0)
14931497
except ValueError:

‎tests/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,5 @@ counts and changing top-N wait groups must retain their diagnostic evidence.
281281
The pytest wrapper passes explicit test file paths for compatibility with Node
282282
18 and 22. Architecture tests also cover event metadata, writer-pressure counts,
283283
and wait-profile shares with unequal sample counts.
284+
`test_metric_engine.py` verifies that UTC timestamps emitted by `json_safe`
285+
produce valid delta and rate intervals on Python 3.10 as well as newer versions.

‎tests/unit/test_metric_engine.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from datetime import datetime, timedelta, timezone
34
from pathlib import Path
45

56
import pytest
@@ -15,6 +16,7 @@
1516
evaluate_metric_table_findings,
1617
)
1718
from pg_diag.planner import PlannedItem
19+
from pg_diag.security import json_safe
1820
from pg_diag.versioning import select_query_variant
1921

2022

@@ -43,6 +45,23 @@
4345
}
4446

4547

48+
@pytest.mark.parametrize(("transform", "expected"), [("delta", 120), ("rate", 4.0)])
49+
def test_chart_intervals_accept_serialized_utc_timestamps(transform: str, expected: float) -> None:
50+
start = datetime(2026, 9, 5, tzinfo=timezone.utc)
51+
samples = json_safe([
52+
{"timestamp": start, "rows": [{"value": 10}]},
53+
{"timestamp": start + timedelta(seconds=30), "rows": [{"value": 130}]},
54+
])
55+
assert all(sample["timestamp"].endswith("Z") for sample in samples)
56+
result = build_chart_result(
57+
{"series": [{"name": "count", "value_ref": "value", "transform": transform}]},
58+
samples,
59+
{},
60+
)
61+
assert result["series"][0]["points"][-1]["value"] == expected
62+
assert result["interval_coverage"]["comparable"] == 1
63+
64+
4665
@pytest.mark.parametrize("entry", [
4766
{"name": "rx", "sample_count": 2, "missing_count": -1},
4867
{"name": "rx", "sample_count": True, "missing_count": 0},

0 commit comments

Comments
 (0)