不列颠哥伦比亚省、时区和 Postgres
在2026年3月8日,不列颠哥伦比亚省将其时钟调整为全年实施的太平洋夏令时。在3月,他们将时钟向前拨快一小时至 UTC-7,但不会在11月回退至 UTC-8。往后看,亚美利加/温哥华时区的 UTC 偏移量将永久为 UTC-7。让我们借此机会讨论一下日期和时区存储。在最基本的例子中,默认的做法是存储 UTC 值,然后相对于 UTC 计算本地时间。然而,使用日历系统的人们是以本地时间(即墙上时钟时间)来思考的,从不考虑 UTC。在修改时区数据之后,来自 UTC 的这些时间计算对于一个地区将与用户输入的值不同。如果您在基于 UTC 的列中存储了2026年及以后不列颠哥伦比亚省的预约时间戳,您的11月至3月的预约可能会错过一个小时!请注意,timestamptz 列不存储本地时间。它们存储的是 UTC 时间,时区仅用于在插入和查询时转换回 UTC。如果您将未来的预约存储为在亚美利加/温哥华时区的 timestamptz,则它是在存储时根据当时的规则转换为 UTC 的。当您稍后查询该预约时,它将使用当前规则转换回本地时间。如果存储到查询的规则发生了变化,您得到的本地时间可能与用户最初的意图不符。如果您没有更新您的 tzdata 包,那么 Postgres 不知道这一变化,它将继续使用旧规则进行转换。Ubuntu 中 tzdata 包的更新频率是多少?令人惊讶的是,每几个月更新一次。如果您的列存储在 timestamptz 列类型中,并与不列颠哥伦比亚省的客户合作,请使用以下 SQL 查询来确定 tzdata 包是否已更新: 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 已更新。这并不像听上去那么好,因为它将需要翻阅日志才能知道未来的预约是在时区调整之前还是之后创建的。如果值为 18:00:00 +00,则好消息!您的 tzdata 没有更新,您没有数据分散在更新之间。时区变化的示例:今年早些时候,一位用户预定了2026年11月10日的上午10点的预约。在数据库中,您将其存储为 timestamptz:INSERT INTO appointments (patient_id, starts_at) VALUES (42, '2026-11-10T10:00:00-08:00'); -- 存储为:2026-11-10 18:00:00+00 (UTC) 在2026年4月,tzdata 更新发布以推送新的时区规则。2026年11月10日,患者在他们日历中记录的当地时间上午10点到达。但当您查询预约时,它显示他们的预约是在当地时间上午11点:SELECT starts_at AT TIME ZONE 'America/Vancouver' AS local_time FROM appointments WHERE patient_id = 42; -- 返回:2026-11-10 11:00:00 请注意,这比最初输入的时间晚了一个小时。一种能够经受住时区变化的模式:双列模式 顾名思义,双列模式在两列中(实际上是三列)存储数据:本地时间戳 本地时区 UTC 时间戳 UTC 时间戳列应该是一个计算列。使用时间戳和时区来计算 UTC。那个计算出的 UTC 值也应该被存储和查询,以便后台作业发送通知以及简化约束检查,例如预约冲突。当本地意图具有权威性时,双列模式是必要的:在某一时间和地点的人或交付、法律截止日期、日历事件等。不要过于复杂。然而,当事件发生在过去,或者确切的 UTC 时刻是权威性的(日志条目、财务交易、传感器读数),请使用普通的 timestamptz。双列模式增加了成本和复杂性,只有在必须保留未来本地意图时才值得付出。详细的模式应如下所示:CREATE TABLE appointments ( id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY, local_time timestamp NOT NULL, -- 墙上时钟的值 timezone_name text NOT NULL, -- IANA 名称:'America/Vancouver' starts_at_utc timestamptz NOT NULL -- 通过触发器计算 ... ); local_time 和 timezone_name 一起回答“用户想要什么?”通过存储墙日历/墙钟值/墙钟位置。这些值只应在用户请求时更改。它们将用于计算 starts_at_utc。starts_at_utc 可以是您索引、查询和用于约束的列。它回答“这个预约现在对应哪个 UTC 时刻?”拥有一个计算的、存储的 UTC 值应该使使用当前的 UTC 值变得简单。可以通过几种方法计算 starts_at_utc,包括应用程序或数据库。虽然计算的 UTC 列将是生成列的一个很好的例子,但 Postgres 不允许 timestamp w
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡