我目前正在尝试将代码库从使用
pytz
迁移到使用 Python 的 zoneinfo
库。与 zoneinfo
的处理方式相比,我遇到了 pytz
如何处理夏令时转换的问题。
假设我有一个天真的
datetime
对象:
>>> import datetime
>>> start = datetime.datetime(2021, 10, 30, 23, 0)
使用
pytz
使该对象具有时区感知的方法是使用 localize
,如下所示:
>>> 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>)
请注意,
start_cet
已经非常接近 2021 年中欧夏令时结束了。如果我在这个时间上加上 5 个小时,我会得到以下结果:
>>> end = start_cet + datetime.timedelta(hours=5)
>>> end
datetime.datetime(2021, 10, 31, 4, 0, tzinfo=<DstTzInfo 'Europe/Copenhagen' CEST+2:00:00 DST>)
现在,实际上,在
start_cet
上增加 5 个小时实际上应该会让我们到达 2021 年 10 月 31 日的 3:00,因为离开 DST 会让我们倒退一小时,从 CEST 转到 CET。我们可以通过将 astimezone
与我们的 pytz
时区一起应用来获得正确的值:
>>> end.astimezone(local_timezone)
datetime.datetime(2021, 10, 31, 3, 0, tzinfo=<DstTzInfo 'Europe/Copenhagen' CET+1:00:00 STD>)
问题:使用
zoneinfo
时查找夏令时实际时间的等效方法是什么?
这是我尝试过的。
zoneinfo
没有localize
方法,建议我们简单地使用replace
:
>>> 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
比 pytz
更有优势,添加 timedelta
将成功地将我们转换到正确的时区(如 utcoffset
结果所示:
>>> 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)
尽管如此,对于
zoneinfo
时区,似乎无法获得在 start_cet
上添加 5 小时后的实际时间。如上所示,使用
pytz
我可以使用 astimezone
,但是使用 zoneinfo
,这不会改变任何东西:
>>> end.astimezone(tz=local_timezone)
datetime.datetime(2021, 10, 31, 4, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Europe/Copenhagen'))
除了@deceze的评论(CET是UTC偏移量,而不是时区)之外,请注意Python中的timedelta算术是wall time算术。
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 小时。