
如果你的工单、值班或 SLA 系统里有 America/Vancouver,现在跑一遍单元测试,大概率全绿。问题是今天测不出来:10 月里温哥华本来就是 UTC-7,新旧时区库算出来的结果完全一样。差别要到 2026-11-01 凌晨 2 点才出现。到那天,旧库会让温哥华「回拨」到 UTC-8,而当地墙上的钟不会动。
我们是 KeepRobots,在温哥华给别家生产的机器人做部署、售后和预防性维护,日常要和国内工程师跨时区对工单。这篇只讲一件事:怎么在 11-01 之前确认你的系统没有把温哥华时间算错一小时。下面有一段能直接跑的检测脚本,还有一份按技术栈整理的升级清单。
一、到底变了什么
先把事实对齐(来源附在文末):
- BC 省 :2026 年 3 月 8 日的拨快是最后一次换时。之后全年固定 UTC-7,官方名称叫 Pacific time,缩写 PCT,取代原来的 PST/PDT。2026-11-01 不回拨。
- BC 省内有例外 :东北部(Dawson Creek、Fort St. John 一带)本来就全年 MST(UTC-7),时间跟全省一样。东南部 East Kootenay(Cranbrook 等)跟着 Alberta 走,全年 UTC-6。Golden 在 11-01 最后换一次时,之后全年 UTC-7。
- 写代码要注意:IANA 没有单独的 Golden 时区,
zone.tab里 BC 东部整体挂在America/Edmonton下(截至 2026e)。Edmonton 在 2026c 之后是全年 -6,而 Golden 11-01 之后是 -7。现场在 Golden 的话,11-01 之后的时间按America/Vancouver算才对,别照着 zone.tab 选 Edmonton。
- 写代码要注意:IANA 没有单独的 Golden 时区,
- 美国 :按 15 U.S.C. §260a,夏令时在「11 月第一个周日」凌晨 2 点结束,也就是 2026-11-01。西雅图、洛杉矶回到 UTC-8。
- 加拿大其他地方:多伦多(安大略)照常回拨到 UTC-5。
结果就是,从 11-01 到明年 3 月,温哥华比西雅图快一小时。北京和温哥华之间全年都差 15 小时,北京和多伦多则从 12 小时变成 13 小时。
二、时区库从哪个版本开始知道这件事
这条变化要靠 IANA 时区库(tzdata)。几乎所有语言和操作系统的时区计算都来自它。我们核到的版本线是这样的:
| IANA 版本 | 发布日期 | 加拿大相关变化 | Python tzdata 包 |
|---|---|---|---|
| 2026a | 2026-03-01 | 不含 BC(早于 BC 3-02 的公告) | 2026.1 |
| 2026b | 2026-04-22 | BC 全年 -07 | 2026.2 |
| 2026c | 2026-07-08 | Alberta 全年 -06(America/Edmonton) | 2026.3 |
| 2026d | 2026-09-11 | 西北地区 America/Inuvik 全年 -06 | 2026.4 |
| 2026e | 2026-09-29 | Manitoba 全年 -05(America/Winnipeg) | 2026.5 |
Python 包这一列不是推测出来的。我们把 PyPI 上 2026.1 到 2026.5 的 wheel 都下载下来,逐个读了包里的 IANA_VERSION。
有两个细节,写代码时要注意:
- IANA 把 BC 的变化记在 2026-11-01 02:00,而不是法律上已经生效的 3 月 (BC 修订法规 3 月 8 日生效,IANA 说明里写作 2026-03-09)。2026b 的说明里写得很清楚,这是为了照顾 CLDR 当时的限制做的临时处理。所以在新库里,
America/Vancouver11-01 之前的isdst仍然是 1,缩写仍然显示 PDT,之后才变成 MST、isdst=0。偏移量始终是 -7,只有标签会变。 - 缩写不要拿来做逻辑判断 。新库里温哥华 11-01 之后的缩写是
MST,政府叫它PCT,有些 UI 还在显示PDT。用偏移量和 IANA 时区名,别用三个字母的缩写。
判断规则只有一条:取 2026-11-02 12:00 America/Vancouver 的 UTC 偏移,是 -7 就对,是 -8 就说明你的时区库早于 2026b。
三、检测脚本
脚本做两件事:(1) 检查温哥华、Edmonton、Winnipeg、多伦多在 2026-11-02 的偏移,算错时提示最低需要哪个版本;(2) 把现场当地的工作时间换算成北京时间,对比 11-01 前后和北京工作窗口重叠了几个小时。只用标准库,Python 3.9 及以上可以直接跑(Windows 没有系统时区库,要先 pip install tzdata)。
python
#!/usr/bin/env python3
"""检查本机时区库是否包含 2026 年加拿大时区变化,并对比 11-01 前后北京与北美现场的重叠窗口。
用法: python3 vancouver_tz_check.py [--site 08:00-17:00] [--cn 09:00-18:00 --cn 07:00-23:00]"""
import argparse, os, zoneinfo
from datetime import datetime, timedelta, timezone
BJ = timezone(timedelta(hours=8))
# 2026-11-02 当地正午的"应有"UTC 偏移,以及最早包含该变化的 IANA 版本
EXPECT = {"America/Vancouver": (-7, "2026b"), "America/Edmonton": (-6, "2026c"),
"America/Winnipeg": (-5, "2026e"), "America/Toronto": (-5, "不变")}
def tz_source():
try:
import tzdata
pkg = f"tzdata 包 {tzdata.IANA_VERSION}"
except ImportError:
pkg = "未装 tzdata 包"
ver = next((open(os.path.join(p, "+VERSION")).read().strip()
for p in zoneinfo.TZPATH if os.path.exists(os.path.join(p, "+VERSION"))), "未知")
return f"TZPATH={list(zoneinfo.TZPATH)} | 系统库版本 {ver} | {pkg}"
def offset_h(zone, day):
dt = datetime.fromisoformat(f"{day}T12:00").replace(tzinfo=zoneinfo.ZoneInfo(zone))
return dt.utcoffset().total_seconds() / 3600
def hm(s):
a, b = s.split("-")
return [tuple(map(int, x.split(":"))) for x in (a, b)]
def overlap_h(site_start, site_end, cn_win):
(h1, m1), (h2, m2) = cn_win
total = 0.0
for d in (-1, 0, 1): # 北京侧取前后三天的工作窗口,处理跨日
day = site_start.astimezone(BJ).date() + timedelta(days=d)
a = datetime(day.year, day.month, day.day, h1, m1, tzinfo=BJ)
b = datetime(day.year, day.month, day.day, h2, m2, tzinfo=BJ)
total += max(0, (min(b, site_end) - max(a, site_start)).total_seconds())
return total / 3600
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--site", default="08:00-17:00", help="现场当地工作时间")
ap.add_argument("--cn", action="append", help="北京工作窗口,可多次")
args = ap.parse_args()
cn_wins = args.cn or ["09:00-18:00", "07:00-23:00"]
print("时区来源:", tz_source())
print("\n[1] 2026-11-02 12:00 当地 UTC 偏移")
for zone, (want, since) in EXPECT.items():
got = offset_h(zone, "2026-11-02")
flag = "OK" if got == want else f"错!应为 {want:+.0f},需 >= {since}"
print(f" {zone:<18} {got:+.0f} {flag}")
(sh, sm), (eh, em) = hm(args.site)
print(f"\n[2] 现场 {args.site} 换算成北京时间,及与北京窗口的重叠(小时)")
print(" 现场 日期 偏移 现场工作时段=北京时间 " + " ".join(f"北京{w}" for w in cn_wins))
for city, zone in (("温哥华", "America/Vancouver"), ("多伦多", "America/Toronto")):
for day in ("2026-10-30", "2026-11-02"): # 回拨前的周五、回拨后的周一
y, mo, dd = map(int, day.split("-"))
z = zoneinfo.ZoneInfo(zone)
s = datetime(y, mo, dd, sh, sm, tzinfo=z)
e = datetime(y, mo, dd, eh, em, tzinfo=z)
bs, be = s.astimezone(BJ), e.astimezone(BJ)
span = f"{bs:%m-%d %H:%M}--{be:%H:%M}" + ("(次日)" if be.date() > bs.date() else "")
ov = "".join(f"{overlap_h(s, e, hm(w)):>13.1f}" for w in cn_wins)
print(f" {city:<6} {day} {s.utcoffset().total_seconds()/3600:+.0f} {span:<22}{ov}")
if __name__ == "__main__":
main()
本机实测输出(2026-10-24,macOS 26.6.2,系统自带 Python 3.9.6)
text
时区来源: TZPATH=['/usr/share/zoneinfo', '/usr/lib/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'] | 系统库版本 2026d | 未装 tzdata 包
[1] 2026-11-02 12:00 当地 UTC 偏移
America/Vancouver -7 OK
America/Edmonton -6 OK
America/Winnipeg -6 错!应为 -5,需 >= 2026e
America/Toronto -5 OK
[2] 现场 08:00-17:00 换算成北京时间,及与北京窗口的重叠(小时)
现场 日期 偏移 现场工作时段=北京时间 北京09:00-18:00 北京07:00-23:00
温哥华 2026-10-30 -7 10-30 23:00--08:00(次日) 0.0 1.0
温哥华 2026-11-02 -7 11-02 23:00--08:00(次日) 0.0 1.0
多伦多 2026-10-30 -4 10-30 20:00--05:00(次日) 0.0 3.0
多伦多 2026-11-02 -5 11-02 21:00--06:00(次日) 0.0 2.0
这台 Mac 的系统时区库是 2026d:温哥华算对了,Winnipeg 却是错的 ,因为 Manitoba 的变化要到 2026e 才进库。我们原本以为系统更新到位就没事,这一行输出说明不能这么想。每加一个城市,都要按这个城市再查一遍。
换成旧库会怎样
我们用 pip install --target 装了 tzdata==2026.1(IANA 2026a),再设 PYTHONTZPATH="" 强制 zoneinfo 只读这个包,跑同一个脚本:
text
时区来源: TZPATH=[] | 系统库版本 未知 | tzdata 包 2026a
[1] 2026-11-02 12:00 当地 UTC 偏移
America/Vancouver -8 错!应为 -7,需 >= 2026b
America/Edmonton -7 错!应为 -6,需 >= 2026c
America/Winnipeg -6 错!应为 -5,需 >= 2026e
America/Toronto -5 OK
[2] 现场 08:00-17:00 换算成北京时间,及与北京窗口的重叠(小时)
现场 日期 偏移 现场工作时段=北京时间 北京09:00-18:00 北京07:00-23:00
温哥华 2026-10-30 -7 10-30 23:00--08:00(次日) 0.0 1.0
温哥华 2026-11-02 -8 11-03 00:00--09:00 0.0 2.0
多伦多 2026-10-30 -4 10-30 20:00--05:00(次日) 0.0 3.0
多伦多 2026-11-02 -5 11-02 21:00--06:00(次日) 0.0 2.0
注意 10-30 那一行,新旧库的结果一模一样。这就是为什么今天的测试抓不到这个问题。

四、读这张表:真正受影响的是延长班
先说一个可能出乎意料的结论:**北京 09:00--18:00 和温哥华、多伦多现场 08:00--17:00 的重叠,回拨前后都是 0 小时。**两边能实时说上话,全靠北京早班和晚班。
下面是一个虚构的场景,用来说明影响有多大。北京二线支持排了 07:00--08:00 的早班,专门接温哥华现场下班前(16:00--17:00)的升级问题。
- 时区库正确:11-01 之后,北京 07:00 仍然是温哥华 16:00,早班照常接得上。
- 时区库过旧:系统以为 11-01 之后温哥华是 UTC-8,现场 08:00--17:00 被算成北京 00:00--09:00。按「对齐现场最后一小时」自动排班的系统会把早班挪到 08:00--09:00,这时温哥华已经 17:00 下班;按现场上班时间起算的 SLA 计时器也会晚开一小时。系统每一步都按规则在走,但每一步都差一小时。
多伦多是真的变了:和北京晚上的重叠从 3 小时缩到 2 小时。这是现实里的变化,不是 bug,排班要跟着调整。
五、升级清单:每种技术栈怎么查
核心思路:不要只看版本号,直接算 2026-11-02 12:00 America/Vancouver 的偏移。
| 栈 | 时区数据来自哪里 | 怎么查 |
|---|---|---|
Python zoneinfo |
先读系统 TZPATH,找不到才用 tzdata 包 |
跑上面的脚本;python -c "import tzdata;print(tzdata.IANA_VERSION)"。Windows 或精简镜像没有系统库时,tzdata>=2026.2 |
Python pytz |
自带一份数据,不读系统库 | python -c "import pytz;print(pytz.OLSON_VERSION)",单独升级 |
| Java | JDK 自带 tzdb.dat | ZoneRulesProvider.getVersions("America/Vancouver") 看版本;也可以直接算 ZonedDateTime.of(2026,11,2,12,0,0,0,ZoneId.of("America/Vancouver")).getOffset()。来不及升级 JDK 就用 Oracle 的 tzupdater 工具 |
| Node.js / 浏览器 | ICU 自带时区数据 | node -p process.versions.tz。本机 Node v24.21.0 输出 2026c,偏移正确,但 toLocaleString 的缩写显示 PDT,又一个不能信缩写的例子 |
| PostgreSQL | 系统 tzdata 或自带一份,看编译方式 | SET TIME ZONE 'UTC'; SELECT '2026-12-01 10:00'::timestamp AT TIME ZONE 'America/Vancouver'; 结果是 17:00 就对,18:00 就是旧库 |
| MySQL | mysql.time_zone* 表,需要手动导入 |
SELECT CONVERT_TZ('2026-11-02 19:00:00','UTC','America/Vancouver'); 应该返回 12:00。不对就重新跑 mysql_tzinfo_to_sql |
| 容器镜像 | 镜像里的 tzdata 包 | head -1 /usr/share/zoneinfo/tzdata.zi(Debian/Ubuntu/Alpine 一般有),或 apt list --installed tzdata、apk info -v tzdata。基础镜像不重新 build 就不会更新 |
| Android | 系统时区模块,另外还有 App 自己打包的库 | 在设备上直接算偏移。用了 Joda-Time、ThreeTenABP 的,要单独升级 App 里的数据 |
| iOS / macOS | 跟着系统更新走 | 在设备上直接算偏移,不要靠系统版本号推断 |
| 机器人本体 / 平板 | 固件里的 Linux tzdata | 同容器的查法。离线设备最容易漏,建议日志一律记 UTC |
**有一个坑,升级时区库也修不好:已经存进数据库的未来时间。**比如在旧库下录了一个「11-05 温哥华 10:00 上门」的预约,存成 UTC 就是 18:00Z。升级后再显示,就变成了 11:00。未来的预约要存「当地时间 + IANA 时区名」,到要用的时候再换算成 UTC。升级之后,把 11-01 以后所有北美的未来预约、值班、SLA 截止时间重新算一遍。
六、11-01 之前的最低动作
- 在每个运行环境(服务器、容器、App、现场设备)里跑一次偏移检查,有哪个城市就查哪个城市。
- 未来预约改为存当地时间 + 时区名,存量数据重新换算一次。
- 值班表按城市排,不要按「太平洋时间」排。11-01 之后,温哥华和西雅图不在同一个时间上。
- 对外沟通写清楚城市名和 UTC 偏移,比如「温哥华 16:00(UTC-7)」。
想看不写代码、只讲流程的版本,比如工单怎么写才能一天一个来回就闭环,可以读我们网站上的这篇:机器人海外技术支持怎么跨时区协同。如果你们在北美需要一个按小时付费、需要时才上门的现场服务点,KeepRobots 的合作方式写在这里:北美现场服务合作。
来源(2026-10-24 核对)
- B.C. 政府新闻稿(2026-03-02):news.gov.bc.ca/releases/20...
- B.C. 政府 Daylight saving time 页面(PCT、各地区例外):www2.gov.bc.ca/gov/content...
- IANA tz 2026b 发布公告(BC 变化,记在 2026-11-01 02:00):lists.iana.org/hyperkitty/...
- IANA 各版本发布日期:www.iana.org/time-zones/... (2026c Alberta、2026d Inuvik、2026e Manitoba 见各版本页)
- PyPI tzdata 版本历史:pypi.org/project/tzd... (版本与 IANA 的对应关系由我们读 wheel 内
IANA_VERSION核实) - 15 U.S.C. §260a:www.law.cornell.edu/uscode/text...
- PostgreSQL 检查方法参考 Crunchy Data:crunchydata.com/blog/britis...