Python 3.9:使用标准库构造 DST 有效时间戳
Posted
技术标签:
【中文标题】Python 3.9:使用标准库构造 DST 有效时间戳【英文标题】:Python 3.9: Construct DST valid timestamp using standard library 【发布时间】:2021-02-02 23:28:09 【问题描述】:我想仅使用 Python 3.9 中的标准库来构造 DST 有效的时间戳,并希望此版本可以实现。在我的时区“欧洲/柏林”中,DST 2020 年的过境点是: 2020-03-29 02:00 时钟切换到 03:00(没有小时 2!) 2020 年 10 月 25 日 03:00 时钟切换回 02:00(小时 2 存在两次!) 我的脚本产生以下输出: 三月 2020-03-29 01:59:00+01:00 CET 加 1 小时:2020-03-29 02:59:00+01:00 CET (应该是 03:59:00 CEST,因为没有第 2 小时!) 十月 2020-10-25 02:00:00+02:00 CEST 加 1 小时:2020-10-25 03:00:00+01:00 CET (似乎可以 - 编辑:应该是欧洲中部时间 02:00 !!!)
下面提供了示例代码。 Windows 用户可能需要“pip install tzdata”才能使其工作。 任何建议将不胜感激!
'''
Should work out of the box with Python 3.9
Got a fallback import statement.
BACKPORT (3.6+)
pip install backports.zoneinfo
WINDOWS (TM) needs:
pip install tzdata
'''
from datetime import datetime, timedelta
from time import tzname
try:
from zoneinfo import ZoneInfo
except ImportError:
from backports import zoneinfo
ZoneInfo = zoneinfo.ZoneInfo
tz = ZoneInfo("Europe/Berlin")
hour = timedelta(hours=1)
print("MARCH")
dt_01 = datetime(2020, 3, 29, 1, 59, tzinfo=tz)
dt_02 = dt_01 + hour
print(f"dt_01 dt_01.tzname() plus 1 h: dt_02 dt_02.tzname()")
print("\nOCTOBER")
dt_01 = datetime(2020, 10, 25, 2, 0, tzinfo=tz)
dt_02 = dt_01 + hour
print(f"dt_01 dt_01.tzname() plus 1 h: dt_02 dt_02.tzname()")
【问题讨论】:
【参考方案1】:虽然有悖常理,但这是意料之中的。有关日期时间算法如何工作的更多详细信息,请参阅this blog post。这样做的原因是,将 timedelta
添加到 datetime
应该被认为是“将日历/时钟提前 X 量”,而不是“经过这段时间后日历/时钟会说什么”。请注意,第一个问题可能会导致在本地时区中甚至不会出现的时间!
如果您愿意,“datetime
表示在此 timedelta
表示的时间量过去之后将是什么时间?” (您似乎这样做了),您应该做一些相当于转换为 UTC 并返回的事情,如下所示:
from datetime import datetime, timedelta, timezone
def absolute_add(dt: datetime, td: timedelta) -> datetime:
utc_in = dt.astimezone(timezone.utc) # Convert input to UTC
utc_out = utc_in + td # Do addition in UTC
civil_out = utc_out.astimezone(dt.tzinfo) # Back to original tzinfo
return civil_out
我相信您可以创建一个覆盖__add__
的timedelta
子类来为您执行此操作(如果可以的话,我想将类似的东西引入标准库)。
请注意,如果dt.tzinfo
是None
,这将使用您的系统本地时区来确定如何进行绝对加法,并返回一个感知时区。在America/New_York
中运行:
>>> absolute_add(datetime(2020, 11, 1, 1), timedelta(hours=1))
datetime.datetime(2020, 11, 1, 1, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=68400), 'EST'))
如果您希望它对天真的日期时间进行民事加法,并对有意识的日期时间进行绝对加法,您可以在函数中检查它是否是天真的:
def absolute_add_nolocal(dt: datetime, td: timedelta) -> datetime:
if dt.tzinfo is None:
return dt + td
return absolute_add(dt, td)
另外,需要明确的是,这与zoneinfo
无关。这一直是 Python 中日期时间的语义,我们无法以向后兼容的方式对其进行更改。 pytz
的工作方式略有不同,因为添加 pytz
-aware 日期时间会做错事,并且在算术发生后需要 normalize
步骤,而 pytz
作者决定 normalize
应该使用绝对-时间语义。
absolute_add
也适用于 pytz
和 dateutil
,因为它使用适用于所有时区库的操作。
【讨论】:
我以前遇到过这个讨论,感谢您的复习^^ - 所以 Python 的 timedelta 试图表示持续时间的重载概念,例如DST 过渡日有 23/25 小时以及作为绝对数量的持续时间(1 天 = 86400 秒)?如果这导致“挂钟”(调整为 DST 的那个)上甚至不存在日期时间,那只会让我感到困惑,不仅违反直觉...... @Paul:非常彻底和乐于助人!非常感谢您的努力! @MrFuppes timedelta 始终表示理想的线性时间轴上的偏移量。主要问题是它与生成它的上下文脱节,因此不同区域减法会导致“这是在 UTC 中应用的偏移量”,而同区减法会导致“这是您的日历时间的差异当地时间”。 "从上下文中离婚" - 好吧,这是另一种说法,但我仍然看不到到达 当地时间 的意义'不存在(或仅在您忘记在 DST 转换后更改时钟的情况下......)。顺便提一句。我认为pandas
默认情况下会在 UTC 中进行加法/减法。所以 - timedelta 一开始就应该没有结婚,我的意思是停留在两种不同的类型中,“duration”(没有重载)和“timezonetimedelta”之类的东西?
我的意思是,“点”就是算术的定义方式?这是一个有效的定义,即使很多人不会觉得它有用。例如,如果您想要添加挂墙时间,但要防止到达无效的本地时间,dt + td + td
可能不等于 dt + 2 * td
,如果 dt + td
会给出一个虚构的本地时间。实现时区和其他日期时间的许多低级数学也依赖于当前的数学属性。以上是关于Python 3.9:使用标准库构造 DST 有效时间戳的主要内容,如果未能解决你的问题,请参考以下文章
Python 中给定 TimeZone 的标准 UTC 偏移量(无 DST)
Python 时间库:如何使用 strptime 和 strftime 保存 dst
Python3.9标准库math中的函数汇总介绍(53个函数和5个常数)
Python标准库:内置函数dict(mapping, **kwarg)