资讯文章 资讯 · NEWS
凌晨发的条目显示成昨天:目录站时间字段的时区,从数据库到页面这一段
2026-10-23 约 1,673 字 预计阅读 4 分钟 yiyunkj
凌
有个站长问我,为什么晚上十一点发的条目,列表里显示的是昨天。我看了一眼他的数据库,时间没错,页面渲染时少了八小时。这类问题不难修,难的是它会在很多地方同时出现,而且每一处看起来都像独立的小毛病。

图片来源:网络,版权归原作者所有,侵权请联系删除
时间会在三个地方各走一遍
一个条目从创建到显示,时间字段至少经过三层:数据库写入、应用层读取格式化、前端展示。这三层各自有一个默认时区。
数据库的默认时区往往是服务器操作系统的,操作系统装的时候可能是 UTC,也可能是本地时间。PHP 或 Python 的默认时区又是另一个配置。前端如果再按浏览器时区渲染,就出现第三套。
| 环节 | 建议存什么 | 常见错法 |
|---|---|---|
| 数据库 | UTC,或用带时区的时间类型 | 存本地时间字符串,跨时区无法换算 |
| 应用层 | 统一一个时区常量 | 每处调用各自取服务器默认值 |
| 页面展示 | 固定站点时区 | 按浏览器时区渲染,同一页两个日期 |
| sitemap / 结构化数据 | ISO 8601 带偏移 | 只写日期,丢掉时区与时刻 |
为什么展示层不该按浏览器时区
听起来「按用户本地时间显示」很体贴,实际上会带来一串麻烦。同一个页面,北京的用户看到 10 月 22 日,洛杉矶的用户看到 10 月 21 日,页面缓存只要不是按用户维度切,就必然串味。搜索引擎抓到的版本、你截图存档的版本、用户投诉时说的版本,可能是三个日期。
目录站的时间字段用来做什么?排序、判断新鲜度、显示「今天新增」。这三件事都需要一个全站统一的参照系。所以固定成站点所在时区,页面上标清楚,比讨好跨时区用户更实际。
如果做出海站,多语言版本可以各用一个时区,但要按语言目录分开,而不是按访问者的 IP 或浏览器设置动态切。
输出格式:带偏移,别只写日期
机器读的时间必须带时区信息,否则解析方只能猜。ISO 8601 的写法是 2026-10-23T11:37:33+08:00,末尾也能用 Z 表示 UTC。
三个地方要用同一种格式:页面上的可见时间(人看的可以只到分钟)、结构化数据里的发布日期与修改日期、sitemap 里的最后修改时间。sitemap 用的是 W3C Datetime,本质是 ISO 8601 的一个子集,允许只写日期,但只要写了时刻就必须有 T 分隔符和时区。
不合法的时间会被静默丢弃,这一点很坑。写成 2026-10-23 11:37:33(缺 T)或者 2026/10/23,不会报错,但那个值等于没写。
排序与「今天」是两个不同的判断
按时间排序要按时间戳,不能按格式化后的字符串。这个错误在混用两种时区的数据里特别明显:字符串比较会得到「2026-10-23 09:00」排在「2026-10-22T23:00Z」前面的结果,而实际后者更新。
「今天新增」的边界要用站点时区算。凌晨 0 点到 1 点之间新增的条目,如果按 UTC 判断,会被算进前一天,首页的「今日新增」就成了空的。这个 bug 只在半夜出现,负责的同事白天测不出来。
还有一个容易忽略的点:条目页显示的「收录时间」和「最后更新时间」要用同一个参照系。一个按本地时间、一个按 UTC,页面上就会出现「最后更新早于收录」这种荒唐组合。
夏令时与不存在的时刻
国内没有夏令时,所以本地站不用担心这块。做出海站就要处理两个边界:春季跳表那天有 60 分钟不存在(02:30 这类时间无法表示),秋季回拨那天有 60 分钟重复(同一个 01:30 出现两次)。
定时任务尤其容易在这里出错。按本地时间设的「每天 02:00 跑一次」,在跳表那天要么不跑要么跑两次。稳妥的做法是定时任务用 UTC 表达式,业务逻辑里再换算成当地时间判断。
三条能立刻执行的自检
- 随便打开一个条目页,把页面上的时间和数据库里的原始值对一遍,差额是整小时(通常是 8 小时)就说明某层没对齐。
- 把 sitemap 里某条 URL 的 lastmod 和页面上显示的更新时间比,两者应该能对上,差 8 小时同样是时区问题。
- 用
TZ=UTC和TZ=Asia/Shanghai各请求一次同一个页面,如果返回的内容不一样,说明展示层在跟着环境变量走。
时区错误的特点是「大部分时候看起来正常」。它只在整点前后、跨日边界、跨时区用户身上暴露,所以值得在模板层一次性定死,而不是每处单独调。