時間不會為任何人停止……即便是你。
数据库的时间存储
数据库应储存UTC是黄金标准,最佳实践。
核心原则:存储绝对时间,而非相对时间
- UTC(协调世界时) 是一个全球统一的、不受夏令时影响的绝对时间点。
2025-10-27 11:00:00 UTC在地球上任何地方,指向的都是同一个时刻。 - 本地时间 是相对的。
2025-10-27 11:00:00这个字符串本身是模糊的。是北京时间的11点,还是纽约时间的11点?两者相差12或13个小时。如果纽约在11月第一个周日结束夏令时,这个时间点还会“跳变”。 - 数据库的职责 是忠实地、无歧义地记录事件发生的 那个瞬间。
你现在看到的时间格式大概是这样的:
Plain12025-10-27T11:00:00.634Z
其中:
z表示UTC(零时区)- 不依赖服务器部署地区
- 不受夏令时影响
- 跨时区数据可直接对比
时间戳
对于时间戳通常有两总理解:
A、不带时间的时间戳
例如 MySQL 的 TIMESTAMP 类型(注意:MySQL 的 TIMESTAMP 实际是带时区的,但这里容易混淆)或 DATETIME,PostgreSQL 的 timestamp(不带时区)。
- 它只存了
YYYY-MM-DD HH:MM:SS,没有时区信息
B、带时区的时间戳
例如 PostgreSQL 的 timestamptz,SQL Server 的 datetimeoffset,Oracle 的 TIMESTAMP WITH TIME ZONE。
- 工作原理:当你存入一个带时区的时间(如
2025-10-27 11:00:00+08:00)时,数据库通常会将其内部转换为 UTC 存储。当你查询时,它会根据你客户端的时区设置,转换回对应的本地时间显示。 - 存储带时区的时间戳,其底层存储的就是 UTC。UTC 是它的“存储格式”,带时区信息是它的“输入/输出接口”。
两种“时间戳”
我们常说的时间戳不是那种1767752710到秒或是毫秒的么,为什么2025-10-27 11:00:00+08:00也是时间戳?这其实是两种类型:
| 类型 | 正式名称 | 常见叫法 | 例子 | 特点 |
|---|---|---|---|---|
| Unix时间戳 | Unix时间/Epoch时间 | 时间戳、时间戳数字、秒数 | 1767752710 | 整数,表示从1970-01-01 00:00:00 UTC开始的秒数 |
| ISO 8601时间戳 | 日期时间字符串 | 时间戳、ISO时间戳、时间字符串 | 2025-10-27 11:00:00+08:00 | 字符串,包含完整的日期时间和时区信息 |
语境决定含义
- 开发/系统层面:说“时间戳”通常指
1767752710 - 数据库/SQL层面:说“时间戳”通常指带时区的日期时间类型
- 日志/API文档:说“时间戳”通常指
2025-10-27T11:00:00Z
无论哪种形式,数据库最终存储的都是一个绝对时间点
时间戳转换
我的用户在UTC+8,我需要在接口层转换成UTC+8吗?
并不推荐,如果在接口转成UTC+8,就会失去时区的中立性,而多时区的用户也会变得难以支持。
那么我应该在什么时候做时区转换呢?
前端,仅有一个场景:展示给用户
比如:
TypeScript1new Date("2025-12-30T09:43:39.634Z").toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })
或者使用dayjs:
TypeScript1dayjs.utc(time).local().format("YYYY-MM-DD HH:mm:ss")
什么时候需要考虑用户时区?
⚠️当业务中出现自然日语义时,例如:
- 今天/昨天
- 本周/本月
这些概念,会依赖用户所在时区,当遇到这一类情况时,依旧靠前端解决,使用用户时区计算时间边界,传递时间戳:
TypeScript1const tz = "Asia/Shanghai" 2 3const start = dayjs().tz(tz).startOf("day").valueOf() 4const end = dayjs().tz(tz).endOf("day").valueOf() 5 6api.get("/consume", { start, end })
这样做优点在于,后端逻辑极简,完全不用考虑时间上的转换,不考虑用户在哪,更保证了时间的绝对。