DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
雨云;给项目一台云服务器
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

dateutil 解析成功,转时间戳却报 offset 必须在 24 小时内

dateutil 的通用 parse 可把非法偏移带进 datetime。ISO 契约改用对应入口并拒绝错误偏移,保留原输入,避免归一化后丢失非法分钟。

dateutil.parser.parse() 已经返回 datetime,直到写日志、调用 isoformat() 或 timestamp() 才报:

ValueError: offset must be strictly between -24h and +24h

先检查输入里的时区偏移。例如 +2400 可能通过通用解析阶段,却产生无法正常使用的偏移;+0060 更隐蔽,可能被合成为一小时,直接改变时间含义。

这条表现出现在 python-dateutil 2.9.0.post0。2026 年 10 月发布的 3.0.0 正式源码中,通用 parser 的对应分支仍直接把小时、分钟合成秒;ISO parser 则分别校验分钟不大于 59、小时不大于 23。不能用“已经升到 3.0”排除它。

ISO 输入用对应入口,不靠宽松解析纠错

如果接口契约本来就是 ISO 8601,可在入口使用 isoparse(),并将失败作为输入错误处理:

from dateutil.parser import isoparse

def read_iso_timestamp(text: str):
    try:
        value = isoparse(text)
    except ValueError:
        raise ValueError("invalid ISO timestamp") from None
    if value.tzinfo is None:
        raise ValueError("timestamp must include an offset")
    return value

示例函数的业务要求是必须带偏移,因此拒绝无时区值;不需要这一要求的业务应保留自己的契约。isoparse 支持的 ISO 形式比某些接口约定更宽,若协议只接受一种精确格式,还需单独限制允许的日期、时间及小数秒形式。

不要在失败后无条件回退到宽松的 parse()。那样会让刚被拒绝的 +00:60 或其他不合法形式重新进入原来的路径。也不要把 60 分钟改成 59、截掉超范围小时,制造另一个看似可用的时刻。

支持自由格式时,偏移原文必须参与判断

需要保留通用 parser 的其他日期写法时,应在已有输入格式规则中校验偏移部分,明确小时、分钟允许范围,再进入解析。不能只对解析后的 utcoffset() 做检查:+0060 已经被归一成一小时,原来的非法分钟信息已经丢失。

也不要简单搜索字符串尾部的几个数字,误把日期、秒或其他文本当作偏移。自由格式和命名时区应分别定义支持范围,无法判定的输入保留为错误,不假定是 UTC。

旧代码若只捕获 ParserError,注意失败可能出现在之后的 datetime 操作,类型是普通 ValueError。应把解析、必要的偏移校验和首次使用放在清楚的输入错误边界里,不能只包住 parse() 一行便认为数据有效。

检查拒绝路径,也检查时刻有没有变

保留带 +00:00、合法正负偏移、非法分钟、超范围小时和缺失偏移的样本。有效样本转换为 UTC 后应代表同一时刻,无效样本则应在进入调度、存储或过期判断前失败。

对已经按非法偏移归一化保存的数据,改解析入口不会自动恢复原始含义。保留原始输入与旧记录,根据真实数据契约另行修正;没有原始偏移就不能靠猜测减回一小时。