`datetime.utcnow()` 现在已被弃用

Python 决定弃用 `datetime.utcnow()`,再次点燃了关于编程语言应如何表示时间、时区,以及“朴素”与“感知” datetime 的长期争论。评论者争论的是:所有时间戳是否都应始终携带明确的 UTC 或时区信息,如何建模本地与全局事件(如店铺营业时间或日历约会),以及这项变更是否值得为大型代码库,尤其是金融领域,带来向后兼容和迁移上的痛苦。许多人都认为 Python 的 datetime API 多年来一直容易出错,但他们在这是否应通过弃用并最终移除来修复,还是会造成不必要的破坏性变更这一点上意见分歧很大。

朴素与时区感知的 datetime

  • 许多人都同意,Python 中“朴素”与“感知”之间的划分很容易出错,尤其是在不知不觉地混用它们时。
  • 核心抱怨是:utcnow() 听起来像是返回一个带 UTC 时区感知的 datetime,但实际上返回的是没有时区信息的、具有本地语义的数据;这已经引发了微妙的 bug。
  • 有人认为,从概念上说,“所有真实的 datetime 都应该有时区”,朴素 datetime 应该很少使用,或者应当是明确分离的类型。
  • 也有人坚持,朴素 UTC 是一种有效且高效的内部约定,被广泛使用(例如在金融领域),弃用它会带来破坏性影响。

需要本地 / 朴素时间的使用场景

  • 周期性或本地事件:在“本地时间上午 8 点”响的闹钟、“欧洲/伦敦时间上午 10 点”的未来约会、“本地时间 9:30”的店铺营业时间、重复会议、证券交易所开盘时间。
  • 这些场景无法干净地映射到某一个 UTC 瞬间,尤其是在政府在排程之后更改夏令时规则或偏移量时。
  • 有人希望有不同的类型:时间戳/瞬时点、本地日期/时间、循环规则,以防止误用。

存储和交换时间

  • 很多人强烈支持“把过去事件存成 UTC,展示时再转换”;但对未来事件则存在分歧。
  • 对最佳存储方式也有分歧:
    • Epoch 整数:简单且快速,但需要隐含知道 epoch、单位和 UTC;如果缺少元数据就会有歧义。
    • ISO-8601 字符串:自描述且人类可读,但更慢、体积更大。
    • 数据库类型:有人警告某些数据库中的“TIMESTAMP WITH TIME ZONE”其实只存 UTC,丢弃原始时区;也有人认为它仍然有用。

DST、偏移量与奇怪的时区

  • 参与者列举了许多棘手现实:半小时和 45 分钟偏移、历史上奇怪的偏移、频繁的政治变动、夏令时切换、重复的小时。
  • 有人指出,UTC↔本地时间转换对于未来日期来说并不是一个稳定的纯函数;你需要时区名称和最新的 tz 数据库。

除了 utcnow 还能用什么 & API 设计

  • 推荐模式:datetime.now(timezone.utc)(或等价写法),并且可能只在序列化边界处去掉 tzinfo。
  • 有些人希望 Python 能像 Java、Rust、Elixir 那样,为带时区与不带时区的 datetime 提供分离的类型,并且从一开始就把“aware”作为默认值。
  • 也有人认为 Python 本来就有动态特性和 mock,像 .NET 的 time provider 那样的重型抽象是过度设计。

向后兼容性与生态影响

  • 人们担心这次弃用会影响大量沿用朴素 UTC 的遗留代码库和金融代码库。
  • 反方观点是:先弃用、后移除,比悄悄改变语义更安全;静态和运行时分析可以找到调用点。
  • 更广泛的争论还包括:Python 在 3.x 时代是否愿意打破 API,以及 C、Java 那种“永不破坏”的文化是否合理,还有渐进式改动是否比“Python 4”式的大破坏更好。