提问人:Niko Fohr 提问时间:10/17/2023 最后编辑:FObersteinerNiko Fohr 更新时间:10/18/2023 访问量:53
如何检查日期时间值是否有效(而不是在 DST 更改引起的时间间隔内)?
How to check if a datetime value is valid (and not in time gap caused by DST change)?
问:
夏令时 (DST) 更改通常每年发生两次。在秋季,时钟向后移动,这会产生折叠,这意味着给定的当地时间具有两种可能的含义。这在 PEP-495 中有所涵盖。在春季,时钟向前移动,这在可能的本地时间值中产生间隙,通常长度为一小时。
例如,在“欧洲/伦敦”时区中,2023-03-26 接近 DST 更改的可能分钟数为:00:58、00:59、02:00、02:01;值 01:00 到 01:59 不存在。
例
以下是应检测为有效(可能)和不可能的情况示例:
import datetime as dt
from zoneinfo import ZoneInfo
timestamp_possible = dt.datetime.strptime(
"2023-03-26 00:30:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
timestamp_impossible = dt.datetime.strptime(
"2023-03-26 01:30:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
以下是我如何使用 pandas.to_datetime 来做到这一点,但我不完全确定它为什么有效,以及这是否是 pandas 的实现细节(可能会改变?)或保证行为。
def is_valid(timestamp):
return pd.to_datetime(timestamp).to_pydatetime(timestamp) == timestamp
这给了
>>> is_valid(timestamp_possible)
True
>>> is_valid(timestamp_impossible)
False
问题
检测给定对象是否有效或是否达到“不存在的时间值”差距的最简单方法是什么?在 Python 标准库中有什么方法可以做到这一点吗?datetime.datetime
答:
1赞
Niko Fohr
10/17/2023
#1
我仔细研究了 PEP-495,在“严格无效时间检查”部分实际上有一个非常有用的功能:
def utcoffset(dt, raise_on_gap=True, raise_on_fold=False):
u = dt.utcoffset()
v = dt.replace(fold=not dt.fold).utcoffset()
if u == v:
return u
if (u < v) == dt.fold:
if raise_on_fold:
raise AmbiguousTimeError
else:
if raise_on_gap:
raise MissingTimeError
return u
修改它很容易,使其返回一个标志,该标志告诉时间戳是否在间隙处:
import enum
class DstFlag(enum.IntEnum):
NONE = 0
FOLD = 1
GAP = 2
def get_dst_flag(timestamp: dt.datetime) -> DstFlag:
u = timestamp.utcoffset()
v = timestamp.replace(fold=not timestamp.fold).utcoffset()
if u == v:
return DstFlag.NONE
if (u < v) == timestamp.fold:
return DstFlag.FOLD
return DstFlag.GAP
这看起来不是很容易理解,但无论如何它看起来运行良好。一些测试值:(u < v) == timestamp.fold
# Spring DST Change 2023-03-26 01:00 UTC+0 (01:00 local time)
# 01:00 (UTC+) -> 02:00 (UTC+1)
dt_normal1 = dt.datetime.strptime(
"2023-03-26 00:00:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
dt_withgap = dt.datetime.strptime(
"2023-03-26 01:30:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
dt_normal2 = dt.datetime.strptime(
"2023-03-26 03:00:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
# Fall DST Change 2023-10-29 01:00 UTC+0 (02:00 local time)
# 02:00 (UTC+1) -> 01:00 (UTC+0)
dt_normal3 = dt.datetime.strptime(
"2023-10-29 00:00:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
dt_withfold = dt.datetime.strptime(
"2023-10-29 01:30:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
dt_normal4 = dt.datetime.strptime(
"2023-10-29 03:00:00", "%Y-%m-%d %H:%M:%S"
).replace(tzinfo=ZoneInfo("Europe/London"))
和测试返回值:
>>> get_dst_flag(dt_normal1)
<DstFlag.NONE: 0>
>>> get_dst_flag(dt_withgap)
<DstFlag.GAP: 2>
>>> get_dst_flag(dt_normal2)
<DstFlag.NONE: 0>
>>> get_dst_flag(dt_normal3)
<DstFlag.NONE: 0>
>>> get_dst_flag(dt_withfold)
<DstFlag.FOLD: 1>
>>> get_dst_flag(dt_normal4)
<DstFlag.NONE: 0>
评论