在 NocoBase 社区中,有一类 Bug 报告反复出现,尤其是在中文论坛:“我所有的日期时间都偏移了 8 小时”或“日期显示为前一天”。中国使用 UTC+8,因此偏移量为 8 小时。我在 UTC+9 的环境中运行实例,果然——偏移量为 9 小时。无论你的偏移量是多少,偏移量就等于这个数值。
这个模式强烈暗示这不是随机的损坏,而是某种机制。我用 PostgreSQL 和 MySQL 分别搭建了 NocoBase 2.x 实例,并测量了实际存储的内容以及如何重新解释,直到谜团有了明确的答案。
测试环境: NocoBase 2.0.51 和 2.1.23(官方 Docker 镜像)× PostgreSQL 16 和 MySQL 8.4。所有数据通过 REST API 写入和读取,服务器时区通过容器的
TZ环境变量控制。我故意忽略浏览器端的渲染——这里只讨论服务器存储的内容以及如何解释它。
背景:2.x 提供了四种日期时间字段类型
NocoBase 2.x 的数据表提供了四种日期时间字段类型(官方列表——虽然部分类型的详情页仍显示“待补充”,这也是我选择实测的原因):
| 类型 | 适用场景 |
|---|---|
| 带时区的日期时间 | 绝对时刻——事件开始时间、日志 |
| 不带时区的日期时间 | 需要按原样保留的墙上时钟时间 |
| 仅日期 | 生日、截止日期、纪念日 |
| Unix 时间戳 | 系统集成 |
测量 1:每种类型实际存储的内容
我通过 xlsx 导入“2026-07-12 09:00”,并查看每个数据库中的原始值(2.0.51 和 2.1.23 版本完全一致):
| 字段类型 | PostgreSQL | MySQL |
|---|---|---|
| 带时区的日期时间 |
timestamptz → 2026-07-12 09:00:00+09(包含偏移量的绝对时刻) |
DATETIME → 2026-07-12 09:00:00(仅墙上时钟——不包含偏移量信息) |
| 不带时区的日期时间 |
timestamp → 09:00:00
|
DATETIME → 09:00:00
|
| 仅日期 |
date → 2026-07-12
|
date → 2026-07-12
|
第一行说明了全部问题。同样的字段类型——“带时区的日期时间”——在 PostgreSQL 中存储为绝对时刻,但在 MySQL 中存储为纯墙上时钟值。MySQL 的 DATETIME 根本没有地方存放偏移量。记住这一点,我们继续下一个实验。
测量 2:更改服务器 TZ,存储的数据含义也会改变(MySQL)
在 MySQL 环境中,我在服务器 TZ=UTC 时写入“2026-07-12 09:00”。然后将应用容器的 TZ 改为 Asia/Tokyo 并重启。仅此而已。
# MySQL 中的原始值(字节未发生任何改变)
dt_tz: 2026-07-12 09:00:00
# API 返回的内容
TZ=UTC 时 → "2026-07-12T09:00:00.000Z" (= UTC+9 时的 18:00)
TZ=Asia/Tokyo 时 → "2026-07-12T00:00:00.000Z" (= UTC+9 时的 09:00)
进入全屏模式 退出全屏模式
磁盘上的数据字节完全相同,但作为绝对时刻的含义却移动了 9 小时。因为 MySQL 的 DATETIME 没有记录“09:00”属于哪个时区,NocoBase 每次读写都必须通过服务器的 TZ 来解释。如果事后更改 TZ,数据库中所有已存储的日期时间都会立即改变含义。
这种机制解释了大多数反复出现的“8 小时偏移”报告:实例最初启动时 TZ 未设置(= UTC),后来有人将其设置为本地时间——或者 staging 与 production 的 TZ 不一致。没有数据受损,只是解释方式不同。
PostgreSQL 呢?我进行了相同的实验,数值没有移动。 timestamptz 存储了偏移量,因此无需重新解释。
由此得出两条规则:
-
在第一天就选择服务器
TZ并永不更改——尤其是 MySQL。在 compose 文件中明确固定,并在所有环境(开发、测试、生产)中保持一致。 - 如果可以选择数据库,PostgreSQL 在结构上免疫这一类事故。
另一件事:导入日期 Bug(真实存在且已修复)
并非所有日期偏移都源于此机制。在 2.x 时代还存在一个真正的 Bug:在 v2.0.44–2.0.51 版本中,CSV/Excel 导入可能将日期存储为前一天,即使数据库、应用和服务器时区完全一致。该问题已被确认为缺陷,并于 2026 年 5 月中旬修复(论坛帖子 t/12426、t/12494)。
值得一提的是,我在 2.0.51 版本通过 API 导入 xlsx/CSV 时无法复现该问题——两个数据库都能正确存储日期。报告涉及的是 UI 导入路径,因此可能涉及其他路由或环境因素。无论如何,当前版本已修复此问题。
实际经验:当日期出现偏移时,要区分“这是机制问题?”还是“这是我当前版本的已知 Bug?”理解测量 1 和 2 可以回答第一个问题;快速搜索论坛(包括中文分类——这是最活跃的部分)可以回答第二个问题。
检查清单
- 根据含义匹配字段类型。生日和截止日期应使用“仅日期”。绝对时刻应使用“带时区的日期时间”。将“带时区的日期时间”用于仅日期数据是导致日期渲染偏移一天的常见原因。
-
在第一天就在 compose 文件中固定服务器
TZ,之后永不更改(尤其是 MySQL)。UTC 或本地时区均可;只需选择一个并在所有地方使用。 - 导入后打开一条记录检查日期时间。这类偏移会均匀影响每一行——一条记录就能告诉你全部情况。
- 如果出现异常:检查版本,然后搜索论坛——包括中文分类,那里包含了大多数真实世界的报告。
总结
- NocoBase 2.x 的“带时区的日期时间”在 PostgreSQL 中是绝对时刻,但在 MySQL 中是墙上时钟值 + 基于
TZ的解释(已实测)。 - 这就是为什么在 MySQL 上事后更改服务器
TZ会改变所有已存储日期时间的含义——这是反复出现“8 小时偏移”(或 9 小时,或其他偏移量)的原因。 - 导入日期偏移一天的 Bug 真实存在但已修复;区分机制问题与已知 Bug 可以快速诊断。
(在 2.0.51 / 2.1.23 × PostgreSQL 16 / MySQL 8.4 上实测。未来版本的行为可能发生变化。)
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.