qinghuan
← 回到知识探索

计算机趣闻 / 趣味知识

“明天同一时间”和“过二十四小时”,为什么可能不同?

用明确的时区偏移算一遍夏令时切换:日历走了一天,经过的小时数却可能是 23 或 25。

展开本文目录
  1. 先问:你要的是日历上的下一天,还是一段时长?
  2. 切换的那天,钟面会漏一段或重复一段
  3. 把偏移写出来,再统一到 UTC 计算
  4. 存时间时,把人的意图也一起留下
  5. 记住这三点
  6. 别把例子用过头
  7. 自己试一小步
  8. 查原始资料
  9. 找一份资料继续练
开始前,会这些就够了
  • 会运行一小段 Python,认识字符串、列表和条件判断即可
遇到不熟悉的词,先看这 3 个解释
时间戳
时间戳是用来标记某个时间点的数据。常见的一种做法是选定起点,再记录从起点算起的秒数或毫秒数;交换数据时,双方必须知道起点、单位和采用的时间规则。
时区
时区规则把同一个时间点换成当地钟表上的日期和时间。有些地区还会按日期调整时钟,因此偏移量不一定全年相同。地区名称与固定的几个小时偏移,不能随意互换。
重复当地时间的 fold 标记
当地时钟往回拨时,同一天的某个钟点可能出现两次。只写日期和钟点,无法分清是哪次;Python 用 fold 这个标记补充说明:在这样的重复区间,0 指较早的那次,1 指较晚的那次。
打开完整术语小抄 ↗

“每天上午九点提醒我”和“每隔二十四小时提醒我”,大部分时候听起来一样。在会切换夏令时的地区,这两个要求却可能在某一天分开:钟表跳过一小时,或把一小时显示两遍。

这不是程序运行得忽快忽慢,也不是本站另一篇介绍的系统时钟校时问题。我们讨论的是地方日历的规定,以及把这些日历时间放到同一条时间线上以后,相隔多少小时。

先问:你要的是日历上的下一天,还是一段时长?

“明天同一时间”先按当地日历找下一天,再按照那一天的时区规则解释。“再过二十四小时”则从当前时刻沿时间线向后走固定长度。这两种要求都合理,提醒、营业日和定时刷新各自可能需要不同的一种。

时区也不只是一个永久不变的加减小时数。America/New_York 这样的地区名称关联一组历史和转换规则;某一个时刻写出的 −05:00 只是当时相对于 UTC 的偏移。把这个偏移长期当成整个地区,可能跨不过季节切换。

切换的那天,钟面会漏一段或重复一段

按美国当前规则,在实行夏令时的地区,春季转换时本地 02:00 跳到 03:00,秋季则从 02:00 回到 01:00。因此相邻两个当地午夜之间,可能经过二十三或二十五小时。不是全球都切换,也不能把一地的规则套给别处。

秋天那两次 01:30 看起来一样,却可对应两个时刻;春天某些钟面时间则根本没有出现。只存“日期和时分”有时不足以确定发生时刻。实际预约系统需要明确地区和遇到重复、缺失时间时采用的处理规则。

把偏移写出来,再统一到 UTC 计算

下面使用纽约在 2024 年两次转换日的明确偏移。代码只需 datetime 标准库,不加载你电脑的时区数据库。我们手工提供的是已确定的时刻,程序不会根据纽约名称自动推算未来哪天换时。

每组都先计算日历日期差,再把两个带偏移的时刻转换到 UTC 后相减。这样刻意分开“日期走了几天”和“时间线上过了几小时”。不要通过去掉时区信息,假装它们已经变成 UTC。

小实验 · python
from datetime import datetime, timezone

pairs = [
    ("spring", "2024-03-10T00:00:00-05:00", "2024-03-11T00:00:00-04:00"),
    ("autumn", "2024-11-03T00:00:00-04:00", "2024-11-04T00:00:00-05:00"),
]
for name, start_text, end_text in pairs:
    start = datetime.fromisoformat(start_text)
    end = datetime.fromisoformat(end_text)
    days = (end.date() - start.date()).days
    hours = (end.astimezone(timezone.utc) - start.astimezone(timezone.utc)).total_seconds() / 3600
    print(name, "calendar_days", days, "elapsed_hours", int(hours))

怎样运行:保存为 calendar_day_demo.py,在一个自己的练习目录中运行 python calendar_day_demo.py。使用 Python 3.9 或更高版本,仅用标准库。

运行后对照结果
spring calendar_days 1 elapsed_hours 23
autumn calendar_days 1 elapsed_hours 25

存时间时,把人的意图也一起留下

记录一次已经发生的事件,可以保存明确的时间线时刻,并按需要保留展示用的地区。安排“每个当地工作日上午九点”的未来任务,则还需保留日历规则和地区名称,仅保存本次换算出来的 UTC 时刻不等于保存了整个重复要求。

Python 的 zoneinfo 可根据 IANA 数据处理地区规则,但它需要可用的时区数据,有些 Windows 环境需要额外提供数据包。不同日期库的加法语义也需要查文档。本篇固定案例不模拟未来政策、闰秒或所有时区,只用两次转换说明“一天”必须写清含义。

记住这三点

  • 一个当地日历日不保证恰好经过二十四小时。
  • 地区时区规则与某一时刻的 UTC 偏移不是同一东西。
  • 固定时长和日历重复任务,应按各自意图计算。

别把例子用过头

  • 不能把所有地区都描述为每年有二十三和二十五小时的一天。
  • 删除 tzinfo 只是去掉信息,不是完成时区转换。

自己试一小步

为“每天九点”和“每隔二十四小时”各写一条清楚的提醒需求。

  1. 写明使用哪个地区的当地时间,以及起始日期。
  2. 选择一个夏令时转换附近的已知时刻,用明确偏移画出时间线。
  3. 说明遇到不存在或重复的当地时间时,需要产品决定什么。

做到这些,就算完成

  • 没有把某个固定 UTC 偏移直接当作永久地区规则。
  • 分别写出了日历差和经过时长。
  • 区分本例硬编码历史偏移与使用时区数据库计算的能力。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。