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.tzinfoNone,这将使用您的系统本地时区来确定如何进行绝对加法,并返回一个感知时区。在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 也适用于 pytzdateutil,因为它使用适用于所有时区库的操作。

【讨论】:

我以前遇到过这个讨论,感谢您的复习^^ - 所以 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)

连接大型 CSV 文件中单词的最有效方法:pandas 还是 Python 标准库? [复制]

python哪些标准库