Python 中时区感知的日期时间对象的时间算术不一致

问题描述 投票:0回答:1

令人惊讶的是,Python 时区感知对象并未按预期处理时间算术。例如考虑这个 在 2022-10-30 02:00 创建时区感知对象的代码片段。

from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo


zone = ZoneInfo("Europe/Madrid")

HOUR = timedelta(hours=1)

u0 = datetime(2022, 10, 30, 2, tzinfo=zone)

2:59 时钟回到 2:00,标志着夏令时结束。 这使得 2022-10-30 02:00 含糊不清。 2022 年 10 月 30 日,时钟两次显示 2:00。拳头来了 DST 实例

2022-10-30 02:00:00+02:00
随后是冬季时间实例
 2022-10-30 02:00:00+01:00
,此时时区从 CEST 转换为 CET。 Python 通过选择
u0
作为两个实例中的第一个(DST 中的一个)来解决歧义 间隔。通过打印
u0
及其时区名称来验证这一点:

>>> print(u0)
2022-10-30 02:00:00+02:00
>>> u0.tzname()
'CEST'

这是中欧夏令时区。

如果我们向

u0
添加 1 小时,则可以正确检测到
CET
(即中欧冬季时区)。

>>> u1 = u0 + HOUR
>>> u1.tzname()
'CET'

但是,时间并没有像预期那样折回到 2:00:

>>> print(u1)
2022-10-30 03:00:00+01:00
'CET'

所以,加上1小时的间隔,看起来好像过去了2个小时。由于墙上时间从 2:00 改为 1 小时 3:00 和另一次由于时区的变化(向 UTC 移动 1 小时)而导致的另一次。相反,人们会期望

u1
打印为
2022-10-30 02:00:00+01:00
。通过将
u0
u1
转换为 UTC 来验证这 2 小时的轮班:

>>> u0.astimezone(timezone.utc)
datetime.datetime(2022, 10, 30, 0, 0, tzinfo=datetime.timezone.utc)
>>> u1.astimezone(timezone.utc)
datetime.datetime(2022, 10, 30, 2, 0, tzinfo=datetime.timezone.utc)

更糟糕的是,

u1
u0
之间的时间间隔计算不一致,具体取决于 选择的时区。一方面我们有:

>>> u1 - u0
datetime.timedelta(seconds=3600)

相当于1小时间隔。 另一方面,如果我们在 UTC 中进行相同的计算:

>>> u1.astimezone(timezone.utc) - u0.astimezone(timezone.utc)
datetime.timedelta(seconds=7200)

计算出的间隔为2h

总之,Python 在时区感知日期时间中对 timedelta 的处理似乎强调本地时钟时间而不是一致的逻辑间隔,导致跨越 DST 边界时可能出现差异。

在我看来,这可能会产生误导,因为

zoneinfo
库的存在给人的印象是此类问题已经存在 已经解决了。

有吗 有人知道这是否是错误或预期行为?有其他人遇到过这个问题吗?您如何处理应用程序中的这些差异?如果这是预期的行为,也许 Python 文档应该提供有关使用时区感知对象执行时间算术的更清晰的指导或警告。

编辑

我已经使用 python 3.11 和 3.12 验证了所描述的行为。其他时区也获得了类似的结果,例如“欧洲/雅典”,但我没有对所有时区进行广泛的检查。

python datetime timezone
1个回答
0
投票

除了我的评论之外,这里还有一个使用

fold
的示例,也许可以帮助澄清问题:

from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo

zone = ZoneInfo("Europe/Madrid")
HOUR = timedelta(hours=1)
u0 = datetime(2022, 10, 30, 1, 59, 59, tzinfo=zone)

print(u0+HOUR) # one hour later on a *wall clock*...
2022-10-30 02:59:59+02:00 # CEST

print((u0+HOUR).replace(fold=1)) # specify on which side of the "DST fold" we want to be...
2022-10-30 02:59:59+01:00 # CET

您可以使用

fold
指定日期时间是否应落在 DST“折叠”的特定一侧。

墙上的时间算术仍然不在乎:

print((u0+HOUR).replace(fold=1)-u0)
1:00:00

print((u0+HOUR)-u0)
1:00:00

因此,如果您希望 timedelta 算术是绝对时间算术,请使用 UTC(如 OP 中所示)。

© www.soinside.com 2019 - 2024. All rights reserved.