mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
Merge pull request #291 from kevin9327/fix/ext-data-latest-sync-beijing-clock
fix(ext-data): 卡片「最新」时间按北京墙钟, 不再随服务端时区漂移
This commit is contained in:
@@ -16,6 +16,7 @@ import polars as pl
|
||||
from fastapi import APIRouter, File, HTTPException, Query, Request, UploadFile
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.market_time import CN_TZ
|
||||
from app.services.ext_data import (
|
||||
ExtConfig,
|
||||
ExtConfigStore,
|
||||
@@ -255,10 +256,14 @@ def _with_instrument_name(df: pl.DataFrame, data_dir: Path) -> pl.DataFrame:
|
||||
|
||||
|
||||
def _latest_sync_date(config: ExtConfig, data_dir: Path) -> str | None:
|
||||
"""扫描数据文件,返回该扩展配置的最新同步时间(含时分秒)。
|
||||
"""扫描数据文件,返回该扩展配置的最新同步时间(北京墙钟, 含时分秒)。
|
||||
|
||||
- snapshot: 直接取 ext_data/{id}/part.parquet 的 mtime
|
||||
- timeseries: 扫描 ext_data/{id}/timeseries/date=xxx 分区目录
|
||||
|
||||
用北京时间而非宿主机时钟: 前端 ExtDataStatCard 原样展示这串裸时间,
|
||||
容器默认 UTC 时会比同一页拉取面板里的 pull.last_run(带时区 ISO,
|
||||
浏览器按本地时区渲染)整整差一个时区。
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
@@ -266,7 +271,7 @@ def _latest_sync_date(config: ExtConfig, data_dir: Path) -> str | None:
|
||||
# 快照: part.parquet 与 config.json 同级
|
||||
p = data_dir / "ext_data" / config.id / "part.parquet"
|
||||
if p.exists():
|
||||
ts = datetime.fromtimestamp(p.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
|
||||
ts = datetime.fromtimestamp(p.stat().st_mtime, tz=CN_TZ).strftime("%Y-%m-%d %H:%M:%S")
|
||||
return ts
|
||||
# 兼容旧路径
|
||||
old = data_dir / "instruments_ext"
|
||||
@@ -285,7 +290,7 @@ def _latest_sync_date(config: ExtConfig, data_dir: Path) -> str | None:
|
||||
|
||||
|
||||
def _latest_sync_from_partitions(base: Path) -> str | None:
|
||||
"""从 date=xxx 分区目录中找到最新分区的修改时间。"""
|
||||
"""从 date=xxx 分区目录中找到最新分区的修改时间 (北京墙钟)。"""
|
||||
from datetime import datetime
|
||||
latest_ts: float = 0
|
||||
latest_date: str | None = None
|
||||
@@ -297,7 +302,7 @@ def _latest_sync_from_partitions(base: Path) -> str | None:
|
||||
latest_ts = mtime
|
||||
latest_date = d.name[5:]
|
||||
if latest_date and latest_ts > 0:
|
||||
ts = datetime.fromtimestamp(latest_ts).strftime("%H:%M:%S")
|
||||
ts = datetime.fromtimestamp(latest_ts, tz=CN_TZ).strftime("%H:%M:%S")
|
||||
return f"{latest_date} {ts}"
|
||||
return latest_date
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"""扩展数据卡片的「最新」时间必须是北京墙钟, 不随服务端时区漂移。
|
||||
|
||||
latest_sync_date 由 parquet 的 mtime 格式化而来, 前端 ExtDataStatCard 原样
|
||||
展示这串裸时间。用宿主机时钟格式化, 容器默认 UTC 时同一次同步在卡片上显示
|
||||
成 8 小时前, 而同一页拉取面板里的 pull.last_run(带时区的 ISO, 前端按浏览器
|
||||
时区渲染)显示的是正确时间 —— 一个页面两个时间对不上。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import types
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api.ext_data import _latest_sync_date
|
||||
from app.market_time import CN_TZ
|
||||
|
||||
# 固定 epoch 秒 = 北京时间 2026-09-04 22:13:20
|
||||
_MTIME = 1_788_531_200
|
||||
|
||||
|
||||
def _beijing(fmt: str) -> str:
|
||||
return datetime.fromtimestamp(_MTIME, tz=CN_TZ).strftime(fmt)
|
||||
|
||||
|
||||
def _touch(path, mtime: int) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(b"")
|
||||
os.utime(path, (mtime, mtime))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def snapshot_config():
|
||||
return types.SimpleNamespace(id="ext_gn_ths", mode="snapshot")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def timeseries_config():
|
||||
return types.SimpleNamespace(id="ext_ts_demo", mode="timeseries")
|
||||
|
||||
|
||||
def test_snapshot_latest_sync_is_beijing_wall_clock(tmp_path, snapshot_config):
|
||||
_touch(tmp_path / "ext_data" / snapshot_config.id / "part.parquet", _MTIME)
|
||||
|
||||
got = _latest_sync_date(snapshot_config, tmp_path)
|
||||
|
||||
assert got == _beijing("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def test_timeseries_latest_sync_is_beijing_wall_clock(tmp_path, timeseries_config):
|
||||
base = tmp_path / "ext_data" / timeseries_config.id / "timeseries"
|
||||
_touch(base / "date=2026-09-03" / "part.parquet", _MTIME - 86400)
|
||||
_touch(base / "date=2026-09-04" / "part.parquet", _MTIME)
|
||||
|
||||
got = _latest_sync_date(timeseries_config, tmp_path)
|
||||
|
||||
assert got == f"2026-09-04 {_beijing('%H:%M:%S')}"
|
||||
|
||||
|
||||
def test_latest_sync_is_none_without_any_data(tmp_path, snapshot_config):
|
||||
assert _latest_sync_date(snapshot_config, tmp_path) is None
|
||||
Reference in New Issue
Block a user