Python pytz、zoneinfo 和夏令时

Python pytz, zoneinfo and daylight savings time

提问人:Paul 提问时间:8/8/2023 最后编辑:FObersteinerPaul 更新时间:8/8/2023 访问量:165

问:

我目前正在尝试将代码库从使用 Python 库迁移到使用 Python 库。与如何处理夏令时转换相比,我遇到了如何处理夏令时转换的问题。pytzzoneinfozoneinfopytz

假设我有一个幼稚的对象:datetime

>>> import datetime
>>> start = datetime.datetime(2021, 10, 30, 23, 0)

用于使此对象具有时区感知的方法是使用 ,如下所示:pytzlocalize

>>> local_timezone = pytz.timezone('Europe/Copenhagen')
>>> start_cet = local_timezone.localize(start)
>>> start_cet
datetime.datetime(2021, 10, 30, 23, 0, tzinfo=<DstTzInfo 'Europe/Copenhagen' CEST+2:00:00 DST>)

请注意,这非常接近 CEST 2021 的结束。如果我在这个时间上加上 5 个小时,我会得到以下结果:start_cet

>>> end = start_cet + datetime.timedelta(hours=5)
>>> end
datetime.datetime(2021, 10, 31, 4, 0, tzinfo=<DstTzInfo 'Europe/Copenhagen' CEST+2:00:00 DST>)

现在,实际上,增加 5 小时实际上应该让我们在 2021 年 10 月 31 日 3:00 降落,因为离开 DST 会让我们回到一个小时,从 CEST 转移到 CET。我们可以通过应用我们的时区来获得正确的值:start_cetastimezonepytz

>>> end.astimezone(local_timezone)
datetime.datetime(2021, 10, 31, 3, 0, tzinfo=<DstTzInfo 'Europe/Copenhagen' CET+1:00:00 STD>)

问:使用 zoneinfo 时,在 DST 中移动时查找实际时间的等效方法是什么?

这是我尝试过的。 没有方法,而是建议我们简单地使用:zoneinfolocalizereplace

>>> local_timezone = zoneinfo.ZoneInfo('Europe/Copenhagen')
>>> start_cet = start.replace(tzinfo=local_timezone)
>>> start_cet
datetime.datetime(2021, 10, 30, 23, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Europe/Copenhagen'))
>>> start_cet.utcoffset()
datetime.timedelta(seconds=7200)

zoneinfo这样做的好处是,添加 a 将成功地将我们过渡到正确的时区(从结果中可以看出:pytztimedeltautcoffset

>>> end = start_cet + datetime.timedelta(hours=5)
>>> end
datetime.datetime(2021, 10, 31, 4, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Europe/Copenhagen'))
>>> end.utcoffset()
datetime.timedelta(seconds=3600)

尽管如此,对于时区,似乎无法获得将 5 小时添加到 上的实际时间。如上所示,使用 I 可以使用 ,但使用 ,这不会改变任何内容:zoneinfostart_cetpytzastimezonezoneinfo

>>> end.astimezone(tz=local_timezone)
datetime.datetime(2021, 10, 31, 4, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Europe/Copenhagen'))

python datetime dst pytz zoneinfo

评论

1赞 deceze 8/8/2023
使用“CET”相当错误。在欧洲地区,冬季使用CET,夏季使用CEST。您应该使用一个具体的基于位置的时区,例如 ,这将使库在 CET 和 CEST 之间正确转换。由于不同的国家(可能)在不同的时间过渡,因此本身没有“CET结束”。Europe/Berlin
0赞 Paul 8/8/2023
你是对的,我太笼统地使用“CET”了。我将更新问题以反映这一点。当我使用具体时区时,同样的问题仍然存在。

答:

1赞 FObersteiner 8/8/2023 #1

除了 @deceze 的评论(CET 指定 UTC 偏移量,而不是时区)之外,请注意 Python 中的时间增量算术是墙时间算术。

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

eu_berlin_tz = ZoneInfo("Europe/Berlin")

start = datetime(2021, 10, 30, 23, 0, tzinfo=eu_berlin_tz)

dur = timedelta(hours=5)

print(start)
# 2021-10-30 23:00:00+02:00 => CEST

print(start + dur)
# 2021-10-31 04:00:00+01:00 => CET

2021-10-30T23:00:00+02:00 至 2021-10-31T04:00:00+01:00 是挂钟上的 5 小时,尽管由于 DST 变为非活动状态,实际上已经过去了 6 小时。

如果以 UTC 为单位添加持续时间,然后转换回指定的时区,则结果更符合您的预期 IIUC:

def aware_add(dt, duration):
    return (dt.astimezone(ZoneInfo("UTC")) + duration).astimezone(dt.tzinfo)

print(aware_add(start, dur))
# 2021-10-31 03:00:00+01:00

2021-10-30T23:00:00+02:00 至 2021-10-31T03:00:00+01:00 在挂钟上是 4 小时,但实际上是 5 小时。

评论

1赞 Paul 8/8/2023
美丽的答案,正是我需要的。谢谢!!