From e40724a0fe92bb975ed0bf456ac66f5e23a4bd3b Mon Sep 17 00:00:00 2001 From: busya Date: Tue, 30 Jun 2026 18:11:42 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20timezone=20handling=20across=20fullstack?= =?UTF-8?q?=20=E2=80=94=20UTC=20parsing=20+=20display=20in=20configured=20?= =?UTF-8?q?TZ?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: backend returned naive ISO datetimes (no Z/offset) → JS parsed them as browser local time → 3h drift for MSK users → '3ч' instead of 'только что'. Backend: - schemas/agent.py: add field_serializer('Z' suffix) for ConversationItem.updated_at and MessageItem.created_at — naive datetimes serialized as UTC - routes/agent_conversations.py: datetime.utcnow() → datetime.now(timezone.utc) (3x) Frontend: - New: stores/timezone.svelte.ts — global reactive appTimezone store - dateFormat.ts: add parseDateUTC() (appends 'Z' to naive ISO), all format*() functions now use parseDateUTC + { timeZone: appTimezone.current } - ~25 files: replace new Date(apiString) → parseDateUTC(apiString), add timeZone: appTimezone.current to toLocaleString()/toLocaleDateString() - SystemSettings.svelte + HealthCenterModel sync appTimezone to global store - ConversationList.svelte: fix relativeTime() and date grouping (the '3ч' bug) Verified: backend schema test, frontend 2501 tests pass, build succeeds, browser validation on /agent and /settings. --- backend/src/api/routes/agent_conversations.py | 8 +-- backend/src/schemas/agent.py | 13 ++++- .../DashboardMaintenanceBadge.svelte | 6 ++- .../components/MaintenanceEventsTable.svelte | 6 ++- .../assistant/AssistantChatPanel.svelte | 3 +- .../assistant/ConversationList.svelte | 36 ++++++++++--- .../lib/components/backups/BackupList.svelte | 4 +- .../lib/components/git/CommitHistory.svelte | 4 +- .../lib/components/layout/TaskDrawer.svelte | 4 +- .../lib/components/tasks/LogEntryRow.svelte | 5 +- .../lib/components/tasks/TaskHistory.svelte | 4 +- .../components/translate/RunTabContent.svelte | 11 ++-- .../translate/ScheduleConfig.svelte | 4 +- .../translate/TranslationRunResult.svelte | 3 +- .../models/Dashboards.FiltersModel.svelte.ts | 3 +- .../lib/models/HealthCenterModel.svelte.ts | 3 ++ frontend/src/lib/stores/timezone.svelte.ts | 29 ++++++++++ frontend/src/lib/utils/dateFormat.ts | 54 ++++++++++++++++--- .../dashboards/[id]/validation/+page.svelte | 6 +-- .../routes/dashboards/dashboard-helpers.ts | 4 +- .../src/routes/dashboards/health/+page.svelte | 3 +- .../src/routes/datasets/[id]/+page.svelte | 6 ++- .../src/routes/settings/SystemSettings.svelte | 11 +++- .../routes/settings/automation/+page.svelte | 4 +- frontend/src/routes/translate/+page.svelte | 4 +- .../src/routes/translate/history/+page.svelte | 10 ++-- .../src/routes/validation-tasks/+page.svelte | 4 +- .../validation-tasks/[policyId]/+page.svelte | 8 +-- .../[policyId]/runs/[runId]/+page.svelte | 10 ++-- .../validation-tasks/history/+page.svelte | 6 +-- 30 files changed, 215 insertions(+), 61 deletions(-) create mode 100644 frontend/src/lib/stores/timezone.svelte.ts diff --git a/backend/src/api/routes/agent_conversations.py b/backend/src/api/routes/agent_conversations.py index adf60b82..e1017f00 100644 --- a/backend/src/api/routes/agent_conversations.py +++ b/backend/src/api/routes/agent_conversations.py @@ -2,7 +2,7 @@ # #region AgentChat.Api.Conversations [C:3] [TYPE Module] [SEMANTICS agent-chat,api,rest] # @defgroup AgentChat REST routes for conversation lifecycle. -from datetime import datetime +from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session @@ -170,10 +170,10 @@ async def save_conversation( id=body.conversation_id, user_id=user_id, title=body.title or "", - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) db.add(conv) - conv.updated_at = datetime.utcnow() + conv.updated_at = datetime.now(timezone.utc) if body.title: conv.title = body.title @@ -196,7 +196,7 @@ async def save_conversation( state=msg_data.get("state"), tool_calls=msg_data.get("tool_calls"), attachments=msg_data.get("attachments"), - created_at=datetime.utcnow(), + created_at=datetime.now(timezone.utc), ) db.add(msg) db.flush() diff --git a/backend/src/schemas/agent.py b/backend/src/schemas/agent.py index 8d2cdb43..1bd8723e 100644 --- a/backend/src/schemas/agent.py +++ b/backend/src/schemas/agent.py @@ -4,7 +4,14 @@ from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer + + +def _serialize_datetime(v: datetime) -> str: + """Serialize datetime as ISO 8601 with Z suffix. Naive datetimes are treated as UTC.""" + if v.tzinfo is None: + return v.strftime("%Y-%m-%dT%H:%M:%S") + "Z" + return v.isoformat() # #region Schemas.Agent.ConversationItem [C:1] [TYPE Class] [SEMANTICS agent,schema,conversation] @@ -18,6 +25,8 @@ class ConversationItem(BaseModel): has_tool_calls: bool = False # conversation includes tool usage has_error: bool = False # conversation has error state risk_level: str | None = None # "safe" | "guarded" | "dangerous" + + _serialize_updated_at = field_serializer("updated_at")(_serialize_datetime) # #endregion Schemas.Agent.ConversationItem @@ -63,6 +72,8 @@ class MessageItem(BaseModel): tool_calls: list[ToolCall] | None = None attachments: list[AttachmentMeta] | None = None created_at: datetime + + _serialize_created_at = field_serializer("created_at")(_serialize_datetime) # #endregion Schemas.Agent.MessageItem diff --git a/frontend/src/lib/components/DashboardMaintenanceBadge.svelte b/frontend/src/lib/components/DashboardMaintenanceBadge.svelte index 402552c2..a8ade7f0 100644 --- a/frontend/src/lib/components/DashboardMaintenanceBadge.svelte +++ b/frontend/src/lib/components/DashboardMaintenanceBadge.svelte @@ -10,6 +10,8 @@