← 返回信息流
AI 资讯Hacker News·2 小时前

不列颠哥伦比亚时区与Postgres数据库

原标题:British Columbia, Time Zones, and Postgres

速览

本文分析了不列颠哥伦比亚省(British Columbia)的时区配置问题,并探讨了其与Postgres数据库之间的关联。内容涉及时区处理在数据库系统中的具体应用及潜在影响。

AI 深度解读

不列颠哥伦比亚省、时区与 Postgres:当夏令时规则永久改变时,你的数据库该怎么办?

背景

2026年3月8日,加拿大不列颠哥伦比亚省(British Columbia,简称 BC)宣布了一项重大的时区政策变更:该地区将永久实行太平洋夏令时间(Pacific Daylight Savings Time, PDT)。这意味着在2026年3月,时钟将向前拨快一小时,变为 UTC-7;但在随后的11月,时钟将不再向后拨回一小时(即不再恢复至标准时间 UTC-8)。

从今往后,America/Vancouver 时区的 UTC 偏移量将永久固定为 UTC-7。这一政治决策直接改变了该地区的日历时间(Wall Clock Time)与协调世界时(UTC)之间的转换规则。对于依赖数据库存储未来预约、事件或法律截止日期的应用而言,这是一个潜在的数据一致性陷阱。

核心内容

在大多数基础应用中,存储日期和时间的默认做法是存储 UTC 值,然后在展示时根据用户所在的时区计算出本地时间。然而,用户通常基于本地时间(即挂钟时间)进行思考,而不会考虑 UTC。当某个地区的时区规则发生修改后,基于 UTC 重新计算出的本地时间可能与用户最初输入的预期时间产生偏差。

Postgres timestamptz 的工作机制

Postgres 中的 timestamptz(带时区的时间戳)列并不存储本地时间,它只存储 UTC 时间。时区信息仅在插入和查询时用于在 UTC 和本地时间之间进行转换。

如果在 2026 年及以后,你将一个基于不列颠哥伦比亚省的预约存储为 timestamptz 类型,且该预约发生在 11 月至次年 3 月之间,你的数据可能会出现一小时的时间偏差。

具体逻辑如下:

  1. 存储时:如果你在未来某个时间点(例如 2026 年 4 月,时区规则已更新为永久 PDT)存储一个预约,系统会根据当时的时区规则将用户输入的本地时间转换为 UTC 存储。
  2. 查询时:当你稍后查询该预约时,系统会根据当前的时区规则将存储的 UTC 时间转换回本地时间。
  3. 冲突点:如果存储规则和查询规则不一致(即规则在存储和查询之间发生了变化),你得到的本地时间将不是用户最初意图的时间。

tzdata 包的更新频率

如果数据库服务器没有更新 tzdata 包,Postgres 将不知道时区规则的变更,并继续使用旧规则进行转换。令人惊讶的是,Ubuntu 等系统中的 tzdata 包更新频率相当高,大约每几个月就会更新一次。

如何检测 tzdata 是否已更新

如果你的列使用 timestamptz 类型且服务于 BC 地区的客户,可以使用以下 SQL 查询来检测 tzdata 是否已包含 2026 年后的新规则:

SELECT
  to_char(
    '2026-12-01 10:00:00'::timestamp AT TIME ZONE 'America/Vancouver',
    'HH24:MI:SS OF'
  ) AS november_2026_vancouver_offset;
  • 如果返回 17:00:00 +00:说明 tzdata 已更新。这意味着系统知道 2026 年 12 月 1 日 BC 省处于 UTC-7(即 UTC 时间 17:00)。但这并非好消息,因为这意味着如果你之前存储的数据是基于旧规则(UTC-8)计算的,现在查询时会被重新解释,导致数据出现“分裂”——部分记录基于旧规则,部分基于新规则。你需要通过日志来追溯每条记录是在更新前还是更新后创建的。
  • 如果返回 18:00:00 +00:说明 tzdata 未更新。系统仍认为 2026 年 12 月 1 日 BC 省处于 UTC-8(即 UTC 时间 18:00)。在这种情况下,虽然数据暂时“安全”(没有因规则变更导致的分裂),但长远来看,一旦更新 tzdata,所有未来的预约时间都会发生偏移。

时区偏移的实际案例

假设用户在 2026 年 11 月 10 日预订了温哥华当地时间上午 10:00 的预约。

场景 A:tzdata 未更新(旧规则 UTC-8)

INSERT INTO appointments (patient_id, starts_at)
VALUES (42, '2026-11-10T10:00:00-08:00');
-- 存储为 UTC: 2026-11-10 18:00:00+00

当患者在 11 月 10 日上午 10:00 到达时,查询结果:

SELECT starts_at AT TIME ZONE 'America/Vancouver' AS local_time
FROM appointments
WHERE patient_id = 42;
-- 返回: 2026-11-10 10:00:00 (正确,因为查询和存储都基于 UTC-8)

场景 B:tzdata 已更新(新规则 UTC-7) 假设在 2026 年 4 月,tzdata 更新,引入了永久 PDT 规则。

  1. 存储:如果用户在 2026 年 4 月之后存储了上述预约(假设用户输入的是 2026-11-10T10:00:00-08:00 或者系统自动转换),系统会根据新规则(UTC-7)进行转换吗?
    • 注意:如果用户输入的是明确的偏移量 -08:00,Postgres 会将其转换为 UTC 18:00:00+00
    • 如果用户输入的是本地时间 2026-11-10 10:00:00 并指定时区 America/Vancouver,在 2026 年 4 月(规则已变),系统会认为 11 月 10 日属于 PDT (UTC-7)。因此,2026-11-10 10:00:00 会被转换为 2026-11-10 17:00:00+00
  2. 查询:当在 2026 年 11 月查询时,系统再次使用新规则(UTC-7)将 17:00:00+00 转换回本地时间,结果是 2026-11-10 10:00:00

然而,真正的风险在于“混合”情况或用户意图的丢失: 如果用户是在规则变更(例如 2025 年)存储了一个未来的预约(2026 年 11 月),当时系统认为 11 月是标准时间(UTC-8)。

  • 存储时 (2025年):用户输入 2026-11-10 10:00:00。系统使用旧规则 (UTC-8) 转换为 UTC 18:00:00+00
  • 查询时 (2026年11月,tzdata已更新):系统使用新规则 (UTC-7) 将 18:00:00+00 转换回本地时间。
  • 结果18:00:00+00 在 UTC-7 下是 `11:
查看原文 →crunchydata.com