fix(time): session start in the system prompt uses its own date's UTC offset
_session_start_like localised the naive session-id stamp with datetime.now().astimezone().tzinfo, which is a fixed offset for today. A stamp from the other DST half got the wrong offset: under Europe/London on a summer day, session 20260115_003000_* rendered as "Conversation started: Wednesday, January 14, 2026". dt.astimezone() applies the offset in force at the stamp itself, as gateway/message_timestamps.py already does. Also replace the two remaining datetime.utcfromtimestamp() calls (deprecated since Python 3.12) with fromtimestamp(..., timezone.utc). The output strings are unchanged. tui_gateway/methods_session.py runs with tui_gateway/server.py's globals, so server.py now imports timezone.
This commit is contained in:
committed by
Siddharth Balyan
parent
57d7a15c28
commit
8a2fb31fbc
@@ -195,7 +195,8 @@ def _session_start_like(agent: Any, now: Any) -> Any:
|
||||
def _to_display_tz(dt: Any) -> Any:
|
||||
if dt.tzinfo is None:
|
||||
try:
|
||||
dt = dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
|
||||
# The offset in force at dt, not today's: a stamp from the other DST half differs by an hour.
|
||||
dt = dt.astimezone()
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
if getattr(now, "tzinfo", None) is not None and dt.tzinfo is not None:
|
||||
|
||||
@@ -171,7 +171,7 @@ def _format_timestamp_ms(value: Any) -> str:
|
||||
ts_ms = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return "-"
|
||||
return dt.datetime.utcfromtimestamp(ts_ms / 1000).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
return dt.datetime.fromtimestamp(ts_ms / 1000, dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
|
||||
|
||||
def _compact_number(value: Any, decimals: int = 2) -> str:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Tests for agent/system_prompt.py — context-file cwd wiring."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
@@ -824,6 +826,31 @@ class TestSessionStartLike:
|
||||
start = _session_start_like(agent, now)
|
||||
assert start.strftime("%Y-%m-%d") == "2026-01-01"
|
||||
|
||||
@pytest.mark.skipif(not hasattr(time, "tzset"), reason="needs a POSIX process timezone switch")
|
||||
def test_stamp_from_other_dst_half_keeps_its_own_offset(self):
|
||||
"""A naive stamp takes the UTC offset in force at that stamp, not today's:
|
||||
a January 00:30 London session read on a summer day rendered as January 14.
|
||||
Both halves are checked so the test bites whichever season it runs in."""
|
||||
from agent.system_prompt import _session_start_like
|
||||
|
||||
london = ZoneInfo("Europe/London")
|
||||
original_tz = os.environ.get("TZ")
|
||||
try:
|
||||
os.environ["TZ"] = "Europe/London"
|
||||
time.tzset()
|
||||
for sid, now, expected in (
|
||||
("20260115_003000_jan", datetime(2026, 7, 16, 9, 0, tzinfo=london), "2026-01-15T00:30:00+00:00"),
|
||||
("20260715_003000_jul", datetime(2026, 12, 16, 9, 0, tzinfo=london), "2026-07-15T00:30:00+01:00"),
|
||||
):
|
||||
start = _session_start_like(SimpleNamespace(session_id=sid), now)
|
||||
assert start.isoformat() == expected
|
||||
finally:
|
||||
if original_tz is None:
|
||||
os.environ.pop("TZ", None)
|
||||
else:
|
||||
os.environ["TZ"] = original_tz
|
||||
time.tzset()
|
||||
|
||||
|
||||
def test_conversation_start_uses_session_start_not_build_time(monkeypatch):
|
||||
"""Regression: a session that started on Jan 1 must still read
|
||||
|
||||
@@ -2381,7 +2381,7 @@ def _(rid, params: dict) -> dict:
|
||||
started_at, label = params.get("started_at"), str(params.get("label") or "")
|
||||
finished_at = float(params.get("finished_at") or time.time())
|
||||
d = _spawn_tree_session_dir(session_id or "default")
|
||||
path = d / f"{datetime.utcfromtimestamp(finished_at).strftime('%Y%m%dT%H%M%S')}.json"
|
||||
path = d / f"{datetime.fromtimestamp(finished_at, timezone.utc).strftime('%Y%m%dT%H%M%S')}.json"
|
||||
meta = {"session_id": session_id, "started_at": float(started_at) if started_at else None,
|
||||
"finished_at": finished_at, "label": label}
|
||||
try:
|
||||
|
||||
@@ -15,7 +15,7 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone # noqa: F401 (timezone: split modules)
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, NamedTuple, Optional # noqa: F401 (Callable: split modules)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user