# 时间与日期规范

业务时间统一使用 Unix epoch 毫秒，格式化字符串只用于显示和外部协议适配。

百积木平台控制的业务时间和系统时间统一使用 **Unix epoch 毫秒整数**。这一规则覆盖 API、模块方法、Connector 业务载荷、事件 payload、工作流、任务、缓存和数据库。

## 标准表示

| 位置                            | 类型              | 示例                                           |
| ----------------------------- | --------------- | -------------------------------------------- |
| JSON / Connector / 事件 payload | integer         | `1785984572000`                              |
| CModel `DataType`             | `integer`       | `{ "@type": "DataType", "type": "integer" }` |
| Java                          | `long` / `Long` | `1785984572000L`                             |
| Rust                          | `i64`           | `1785984572000_i64`                          |
| JavaScript / TypeScript       | 安全整数 `number`   | `1785984572000`                              |
| PostgreSQL / MySQL            | `BIGINT`        | `1785984572000`                              |

字段名使用业务语义，例如 `createdAt`、`updatedAt`、`lastTimestamp`、`deliveryTime`。单位由全局契约确定，不需要在每个时间点字段后追加 `Millis`。持续时间不是时间点，必须显式标明单位，例如 `timeoutMs`、`durationMs`。

## 禁止的做法

- 不得在同一字段中同时接受秒、毫秒和 RFC 3339 字符串。
- 不得根据数值位数或字符串形态静默猜测单位并自动兼容。
- 不得把 `Date`、`Instant`、`DateTime` 或数据库时间类型的默认 JSON 序列化结果当成公共契约。
- 不得同时提供 `lastTimestamp` 和 `lastOccurredAt` 两套同义字段。
- 不得在后端和数据库保存格式化后的本地时间字符串。

入参类型或单位错误时应立即拒绝，并返回 CModel 结构化校验错误。只接受现代业务时间的接口还应按业务范围校验，防止秒级值误入；历史日期场景则应使用适合该领域的范围，不能用一个全局阈值排除合法历史时间。

## 为什么数据库也存 BIGINT

数据库使用 `TIMESTAMP`/`TIMESTAMPTZ` 虽然便于日期函数计算，但 ORM 和驱动通常会把它序列化为格式化字符串，导致服务边界出现多种表示、隐式时区转换和精度差异。使用 `BIGINT` 可以让持久化、消息和接口保持同一份原始值，避免来回转换后改变语义。

排序、范围过滤和索引对 `BIGINT` 同样有效。确实需要按自然日、月份或时区做分析时，在查询或分析视图中显式转换，例如 PostgreSQL：

```sql
to_timestamp(created_at / 1000.0)
```

显示时区属于调用方或报表层。数据库中的原始时间点不带显示时区；格式化时必须显式选择用户或工作区时区。

## 日期和显示文本

不是所有看起来像时间的字段都是时间点：

- 仅表示自然日的 `reportDate`、`birthday` 可以使用 ISO 日期字符串 `YYYY-MM-DD`，其 CModel 语义应明确为 date-only。
- 面向人阅读的“明天下午”“预计半小时内”等内容是显示文本，应使用 `etaText`、`scheduleDisplayText` 等名称，不能命名为 `etaAt`。
- UI 的 `datetime-local`、中文日期和 RFC 3339 都是显示或输入格式。前端必须在组件边界转换为毫秒，提交给服务后不再携带格式化副本。
- 外部标准强制使用 RFC 3339 时，只能在边界适配器转换；进入内部模型后立即恢复为毫秒。

## 迁移顺序

存量字段迁移必须一次完成生产者、消费者和数据：

1. 发布结构化 CModel 契约和新主版本。
2. 将数据库列确定性转换为 `BIGINT`，保留空值和毫秒精度。
3. 更新服务模型、SQL 默认值和响应 envelope。
4. 更新 Connector、前端、工作流和 Bundle Manifest。
5. 部署后验证毫秒输入成功，秒级与字符串输入返回结构化错误。

不要长期保留“秒或毫秒都接受”的兼容分支。它会把契约错误隐藏到更深层，并让同一数据在不同链路产生不同含义。
