11-01 温哥华不回拨:用 60 行 Python 查你的工单系统会不会差一小时(KeepRobots 工程笔记)

如果你的工单、值班或 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。
  • 美国 :按 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。

有两个细节,写代码时要注意:

  1. IANA 把 BC 的变化记在 2026-11-01 02:00,而不是法律上已经生效的 3 月 (BC 修订法规 3 月 8 日生效,IANA 说明里写作 2026-03-09)。2026b 的说明里写得很清楚,这是为了照顾 CLDR 当时的限制做的临时处理。所以在新库里,America/Vancouver 11-01 之前的 isdst 仍然是 1,缩写仍然显示 PDT,之后才变成 MST、isdst=0。偏移量始终是 -7,只有标签会变。
  2. 缩写不要拿来做逻辑判断 。新库里温哥华 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 之前的最低动作

  1. 在每个运行环境(服务器、容器、App、现场设备)里跑一次偏移检查,有哪个城市就查哪个城市。
  2. 未来预约改为存当地时间 + 时区名,存量数据重新换算一次。
  3. 值班表按城市排,不要按「太平洋时间」排。11-01 之后,温哥华和西雅图不在同一个时间上。
  4. 对外沟通写清楚城市名和 UTC 偏移,比如「温哥华 16:00(UTC-7)」。

想看不写代码、只讲流程的版本,比如工单怎么写才能一天一个来回就闭环,可以读我们网站上的这篇:机器人海外技术支持怎么跨时区协同。如果你们在北美需要一个按小时付费、需要时才上门的现场服务点,KeepRobots 的合作方式写在这里:北美现场服务合作。


来源(2026-10-24 核对)

相关推荐
xiao77mwq1 小时前
你以为 OCR 慢?实测 81.3 s 里全是故障代价:如何把「定格态」消掉,让耗时从 6.5 s 到 13 倍差距显出原形
python
深圳市益普科技有限公司1 小时前
半导体良率管理YMS:良率与设备参数的相关性,先拉哪几组数据
人工智能·python·算法
“AI国潮设计-小江”2 小时前
【AIGC实战】Python+SDXL打造“潮汕英歌舞IP”万能立牌插件,批量生成“发财元宝蛋糕”视觉营销方案(附源码)
开发语言·人工智能·python·prompt·aigc
砚底藏山河3 小时前
python量化实操:冷热分层存储
java·开发语言·python·金融
十五年专注C++开发3 小时前
pybind11:一个跨C++和Python平台的编程库
开发语言·c++·python·pybind11
炘爚3 小时前
数据结构算法总结
数据结构·python·算法
信誓旦旦的程序猿3 小时前
【Python量化实战宝典 #18】MACD 金叉死叉手册:DIF、DEA、柱体全拆解(Python 可跑)
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
Patrick在香港3 小时前
一个加 1 的动作,把最大值变成了最小值
python·numpy·数据类型·整数溢出·数据精度
weixin_BYSJ19873 小时前
电竞先锋网站---附源码02032
java·vue.js·spring boot·python·django·tomcat·php