From a0925cb0c88fa3841a33387e042809127b22a10e Mon Sep 17 00:00:00 2001 From: kevin9327 <5299031+kevin9327@users.noreply.github.com> Date: Wed, 9 Sep 2026 07:10:55 +0900 Subject: [PATCH] =?UTF-8?q?fix(reports):=20AI=20=E6=8A=A5=E5=91=8A=20creat?= =?UTF-8?q?ed=5Fat=20=E6=94=B9=E7=94=A8=E5=8C=97=E4=BA=AC=E5=A2=99?= =?UTF-8?q?=E9=92=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三类 AI 报告(财务分析 / 个股分析 / 大盘复盘)共用的 JsonReportStore 用 datetime.now() 补 created_at, 取的是宿主机时钟。容器默认 UTC 时写入的是 UTC 墙钟, 前端 fmtRelative 再按浏览器本地时区解析这串 naive 时间, 刚生成 的报告被显示成「8 小时前」; stockAnalysisStore 的「今天是否已生成过报告」 判定(created_at 前 10 位 == 浏览器今天)在北京 00:00-08:00 也会误判。 改用 app.market_time.cn_now(), 保持原有 naive 秒精度格式不变。 --- backend/app/services/json_report_store.py | 11 +++-- .../tests/test_json_report_store_timezone.py | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 backend/tests/test_json_report_store_timezone.py diff --git a/backend/app/services/json_report_store.py b/backend/app/services/json_report_store.py index 95570db..171f5ec 100644 --- a/backend/app/services/json_report_store.py +++ b/backend/app/services/json_report_store.py @@ -21,6 +21,8 @@ import threading import time from pathlib import Path +from app.market_time import cn_now + logger = logging.getLogger(__name__) @@ -121,6 +123,9 @@ class JsonReportStore: @staticmethod def _now_iso() -> str: - """当前本地时间 ISO 字符串(带秒精度,前端 toLocaleString 友好)。""" - from datetime import datetime - return datetime.now().isoformat(timespec="seconds") + """当前北京时间 ISO 字符串(带秒精度,前端 toLocaleString 友好)。 + + 用北京墙钟而非宿主机时钟: 容器默认 UTC 时, 前端把这串 naive 时间按浏览器 + 本地时区解析, 刚生成的报告会显示成「8 小时前」。 + """ + return cn_now().replace(tzinfo=None).isoformat(timespec="seconds") diff --git a/backend/tests/test_json_report_store_timezone.py b/backend/tests/test_json_report_store_timezone.py new file mode 100644 index 0000000..42b4721 --- /dev/null +++ b/backend/tests/test_json_report_store_timezone.py @@ -0,0 +1,42 @@ +"""AI 报告 created_at 时区测试 — 必须是北京墙钟, 不随服务器时区漂移。 + +三类 AI 报告(财务分析 / 个股分析 / 大盘复盘)共用 JsonReportStore 补 created_at, +前端 fmtRelative 用 `new Date(created_at)` 按浏览器本地时区解析这串 naive 时间。 +服务端若用宿主机时钟(Docker 镜像默认 UTC), 刚生成的报告会被显示成「8 小时前」, +「今天是否已生成过报告」的判定(created_at 前 10 位 == 浏览器今天)也会误判。 +""" +from __future__ import annotations + +from datetime import datetime, timedelta, timezone + +import pytest + +from app.services.json_report_store import JsonReportStore + +CN_TZ = timezone(timedelta(hours=8)) + + +@pytest.fixture +def store(tmp_path, monkeypatch): + s = JsonReportStore("ai_reports.json", 20, id_prefix="rpt") + monkeypatch.setattr(s, "_path", lambda: tmp_path / "ai_reports.json") + return s + + +def test_created_at_follows_beijing_clock(store): + """created_at 与北京墙钟一致(宿主机时区非 UTC+8 时旧实现偏移整时区差)。""" + saved = store.save_report({"symbol": "600519.SH", "content": "正文"}) + + got = datetime.fromisoformat(saved["created_at"]) + expected = datetime.now(CN_TZ).replace(tzinfo=None) + assert abs((got - expected).total_seconds()) < 5 + + +def test_created_at_keeps_naive_second_precision_format(store): + """格式不变: 秒精度、无时区后缀, 历史记录与前端解析保持兼容。""" + saved = store.save_report({"symbol": "600519.SH", "content": "正文"}) + + created_at = saved["created_at"] + assert len(created_at) == 19 + assert created_at[10] == "T" + assert datetime.fromisoformat(created_at).tzinfo is None