时间字段的问题很少是某一行代码“写错了”,更多是约定没有写清楚:单位靠猜、时区靠默认、精度靠运气。下面这份清单按数据流排列,可以逐项对照现有接口。
01 单位与字段名
- 字段名是否明确了单位?
created_at_ms比created_at更不容易误解。 - 文档是否写了“秒”或“毫秒”,而不只是“时间戳”?
- 整数是否可能超过 JavaScript 安全整数范围?超过 13 位时优先用字符串。
- 同一个接口里是否存在秒和毫秒混用?如果无法避免,字段名必须区分。
- 负数时间戳是否在业务范围内?如果是,序列化和展示是否支持?
02 格式与序列化
整数、ISO 字符串和带偏移的 RFC 3339 字符串各有适用场景。关键不是选哪一种,而是全链路统一,并在边界处显式转换。
THREE COMMON FORMATS
// 1. 秒级整数:短,但必须约定单位
{ "created_at": 1767225600 }
// 2. UTC ISO 字符串:无歧义,适合 JSON
{ "created_at": "2026-01-01T00:00:00.000Z" }
// 3. 带偏移的 RFC 3339:保留本地时间信息
{ "created_at": "2026-01-01T08:00:00+08:00" }如果接口同时返回时间戳和格式化字符串,要确认两者指向同一时刻。曾经出现过的典型问题是:时间戳按 UTC 生成,字符串却按服务器本地时区格式化,前端无论用哪个都会得到不一致的结果。
03 时区约定
- 存储层是否统一使用 UTC 或带时区类型?
- 数据库会话时区是否固定?连接池、迁移任务和报表任务是否一致?
- 应用服务器、容器和定时任务是否都设置了明确的
TZ? - 前端格式化是否显式传入时区,而不是依赖浏览器默认值?
- 面向用户的页面是否标注了“北京时间”或具体城市,而不是只显示裸时间?
跨时区业务还应对夏令时做一次专项检查。北京时间没有夏令时,但纽约、伦敦、巴黎和悉尼有;固定偏移的代码在切换日会偏差一小时。
04 存储与精度
- 秒级字段是否使用
BIGINT或等价宽类型,而不是 32 位INT? - 需要毫秒精度时,数据库列是否真的保留了小数秒?
- 读取和写入是否使用同一种时间类型?是否发生了隐式时区转换?
- 备份、数据仓库和导出文件是否也完成了字段升级?
- 索引和排序是否按绝对时间进行,而不是按格式化字符串?
字符串排序在 ISO 8601 且统一带 UTC 的情况下通常可行,但一旦混入不同偏移或本地格式,顺序就会出错。排序和比较优先使用时间戳或数据库时间类型。
05 前端展示
前端至少需要回答三个问题:用哪个时区显示?显示到秒还是毫秒?空值和异常值怎么呈现?
EXPLICIT DISPLAY / 显式时区
const formatter = new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
});
formatter.format(new Date(1767225600 * 1000));
// 2026/01/01 08:00:00不要在组件里散落“减 8 小时”的魔法数字。所有时区处理应收敛到一个工具函数或日期库配置中,避免不同页面各自实现一套。
06 监控与回归
- 日志是否同时记录原始时间戳、单位和格式化结果?
- 是否对异常年份(例如 1970、1901、2100 以后)设置告警?
- 是否有跨时区、跨夏令时、跨年和闰日的固定测试用例?
- 发布后是否抽查一条真实数据,从接口到页面完整走通?
回归测试不必很复杂。保留一组固定的时间戳与期望日期,覆盖秒/毫秒、UTC/北京时间、正常值与边界值,就足以拦住大部分低级错误。
07 上线前 12 项清单
| # | 检查项 | 通过标准 |
|---|---|---|
| 1 | 单位 | 字段名和文档明确秒或毫秒 |
| 2 | 类型 | 时间戳使用 64 位或字符串,不溢出 |
| 3 | 格式 | JSON 中无歧义,带 Z 或偏移 |
| 4 | 存储 | 统一 UTC 或带时区类型 |
| 5 | 会话时区 | 应用与数据库设置一致且可查 |
| 6 | 精度 | 毫秒/微秒按需保留,未被截断 |
| 7 | 显示 | 显式时区,标注城市 |
| 8 | 跨日 | 目标城市日期差异有提示 |
| 9 | 夏令时 | 切换日测试通过 |
| 10 | 边界 | 2038 前后与远期日期可用 |
| 11 | 异常 | 空值、负值、超大值有兜底 |
| 12 | 监控 | 异常年份与格式有告警 |
这份清单可以直接复制到团队的发布检查表里。遇到具体值时,用单个转换核对,用批量转换抽查一列数据,再用时区换算确认两端城市的日期是否一致。