【问题标题】:Python 3.9: Construct DST valid timestamp using standard libraryPython 3.9:使用标准库构造 DST 有效时间戳
【发布时间】: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()}")

【问题讨论】:

    标签: python-3.x timezone dst timedelta zoneinfo


    【解决方案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 会给出一个虚构的本地时间。实现时区和其他日期时间的许多低级数学也依赖于当前的数学属性。
    猜你喜欢
    • 2013-08-01
    • 2015-04-20
    • 2011-10-23
    • 1970-01-01
    • 2015-08-22
    • 2010-10-09
    • 1970-01-01
    • 2014-05-01
    • 1970-01-01
    相关资源
    最近更新 更多