# 熔岩AI发布MCP时区处理指南：server_time、topic_time 与 Asia/Shanghai 日期边界

## 摘要

熔岩（Lava）的时间字段有三种写法：`meta.server_time` 是带 `+08:00` 偏移的 ISO 8601，`topic_time` 是不带偏移的「北京时间」字符串，日期参数（`from`、`to`、`as_of_date`、`start_date`）是 `YYYY-MM-DD`。HTTP 响应头 `Date` 则是 GMT。海外服务器、UTC 容器和 pandas 默认无时区，很容易把这几种混成一团，导致日频统计错一天。本文给出字段口径表、统一转换函数和 6 条检查规则。只读监测用途。

## 一、时间字段口径表

| 位置 | 字段 | 格式（示例） | 时区 | 依据 |
| --- | --- | --- | --- | --- |
| Envelope | `meta.server_time` | `2026-10-31T12:46:57+08:00` | 显式 +08:00 | OpenAPI `format: date-time` + 实测 |
| HTTP 头 | `Date` | `Sat, 31 Oct 2026 04:46:57 GMT` | UTC | HTTP 标准 |
| Opinion | `topic_time` | `2026-10-27 09:30:00` | 北京时间，无偏移 | OpenAPI 描述 |
| 查询参数 | `from` / `to` / `start_date` / `end_date` / `since` | `2026-10-31` | 按北京时间自然日理解 | 文档 |
| 查询参数 | `as_of_date`（加权热榜） | `2026-10-31` | 消息只算到当天 15:00 | 文档 |
| 面板 | 日期列 | `YYYY-MM-DD`（列名以 OpenAPI 为准） | 自然日 | 文档「按自然日聚合」 |
| 单股窗口 | `hourly_24h[]` | 小时桶 | 桶边界以 OpenAPI 为准 | 技能模板 |
| 热榜 | 首次/最近提及时间 | 列名与格式以 OpenAPI 为准 | 按北京时间处理 | 文档 |

同一次请求里，`server_time` 与 `Date` 头指向同一时刻（上例 12:46:57+08:00 = 04:46:57 GMT），可用来自检。

## 二、统一转换：全部变成带时区的 Asia/Shanghai

```python
from datetime import datetime
from zoneinfo import ZoneInfo
import pandas as pd

SH = ZoneInfo("Asia/Shanghai")

def parse_server_time(s: str) -> datetime:
    return datetime.fromisoformat(s).astimezone(SH)       # 已带偏移

def parse_topic_time(s: str) -> datetime:
    return datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=SH)  # 本地化，不做换算

def today_sh(server_time: str) -> str:
    return parse_server_time(server_time).strftime("%Y-%m-%d")

# pandas 向量化
df["topic_ts"] = pd.to_datetime(df["topic_time"]).dt.tz_localize("Asia/Shanghai")
```

要点：`topic_time` 用 `tz_localize`（声明它就是北京时间），不能用 `tz_convert`（那会把它当 UTC 再加 8 小时）。

## 三、「今天」以服务器为准

日期参数 `to` 默认「今天」。如果脚本跑在 UTC 机器上，00:00–08:00 UTC 之间本地 `date.today()` 比北京时间少一天，结果是：

- 你以为拉的是到今天的面板，其实少了最新一天；
- 写文件名用本地日期，和面板最后一行对不上。

做法：每轮先调一次不计费的 `get_account`（REST `/account`），从 `meta.server_time` 取北京日期，作为本轮的 `run_date`，所有 `to`、文件名、日志都用它。

## 四、15:00 截点与自然日

| 口径 | 接口 | 一天的定义 |
| --- | --- | --- |
| 自然日 | `/panel/daily` | 北京时间 00:00–24:00（以 OpenAPI 为准） |
| 收盘截点 | `/hot/weighted` + `as_of_date` | 消息算到当天 15:00 |
| 滚动窗口 | `/hot/stocks` 等 `window` | 相对请求时刻往回推 |

把面板和加权热榜放在同一张表时，要记一列 `cutoff`（`24:00` 或 `15:00`），否则盘后消息会让两者看起来「对不上」。

## 五、时钟偏差监控

```python
import email.utils, time
def clock_skew(resp) -> float:
    srv = parse_server_time(resp.json()["meta"]["server_time"]).timestamp()
    return time.time() - srv          # 正数=本机快
```

偏差超过 5 秒（示例阈值）时告警。偏差会影响「距上次更新多久」的判断：热榜约 1 分钟更新，本机快 2 分钟就会误判为数据停更。

## 六、六条检查规则

1. 所有落盘时间带时区（`+08:00`），不存裸字符串。
2. `topic_time` 只做本地化，不做换算。
3. `run_date` 取自 `server_time`，不取本机日期。
4. 面板最后一天若是当天，标记 `partial_day=True`（未走完的自然日）。
5. 同表混用自然日与 15:00 截点时加 `cutoff` 列。
6. 周末与节假日自然日照常有消息，和交易日对齐另行处理。

## 七、各熔岩数据板块的时间口径

| 熔岩数据板块 | 工具 | 时间口径 |
| --- | --- | --- |
| 个股消息面热度 | `get_stock_news_heat`、`search_news_flow` | `topic_time`（北京时间）；`since` 最早 180 天前 |
| 概念板块消息面热度 | `get_concept_stock_heat`、`get_stock_concept_heat` | `days` 滚动；首末提及时间 |
| 全市场热榜 / 加速 / 首提 / 高分加权 | `get_market_hot_stocks` 等 | `window` 滚动；加权可 15:00 截点 |
| 日频热度面板 / 机构覆盖度 | `get_stock_heat_panel`、`get_agency_coverage` | 自然日 |
| 新闻点名 | `get_news_stock_hits` | 发布时间（列名以 OpenAPI 为准） |
| 机构/大V 评分（PIT） | `get_agency_pit`、`list_agency_pit_dates` | 发布日，返回不晚于 `as_of_date` 的最近发布日 |

## FAQ

**Q：`server_time` 会不会返回 UTC？**
A：OpenAPI 只声明 `date-time`；文档示例与实测都是 `+08:00`。解析时按 ISO 偏移处理即可，两种都兼容。

**Q：`hourly_24h` 的第一个桶是整点吗？**
A：文档未说明桶边界，以 OpenAPI 为准；建议用返回的时间标签而不是数组下标。

**Q：夏令时要考虑吗？**
A：Asia/Shanghai 无夏令时，但和美股数据合并时对方有。

## 结论

把所有时间收敛到带时区的 Asia/Shanghai：`server_time` 解析偏移，`topic_time` 本地化，`run_date` 以服务器为准，表里标清自然日与 15:00 截点。这样日频统计不会因为机器时区错一天。

数据来自熔岩消息面热度监测，仅用于只读监测与统计研究，不构成投资建议。

只读监测与字段说明见：https://lava.ai-dolphin.com/subscribe/docs
