datetime.fromisoformat() 不支持 Z 后缀和带冒号时区(如+08:00),Python 3.11+ 支持 Z 但仍不支持冒号;推荐用 dateutil.parser.parse() 或预处理后配合 strptime() 解析。
datetime.fromisoformat() 是 Python 3.7+ 提供的便捷方法,但它**不支持 Z 后缀或带冒号的时区偏移(如 +08:00)**。直接调用会抛出 ValueError: Invalid isoformat string。
这是最常踩的坑:以为 ISO 就是 fromisoformat() 的全部覆盖范围,其实它只支持「简化 ISO 8601」——即不含 Z、不含冒号分隔的时区(如 +0800 才勉强可解析,+08:00 不行)。
实操建议:
Z 或 +08:00),别用 fromisoformat(),改用 datetime.fromisoformat() 的替代方案Z 替换为 +00:00,再统一用 datetime.strptime() 或第三方库解析fromisoformat() 已支持 Z,但依然不支持 +08:00 中的冒号 —— 所以兼容性仍受限datetime.strptime() 灵活但需手动指定格式,关键在时区部分的匹配:
Z 对应 %z,但 %z **不接受字面量 Z**,只接受 +0000 形式;所以得先替换:s.replace('Z', '+0000')
+08:00 中的冒号无法被 %z 直接识别(%z 要求 +0800),需先去掉冒号:re.sub(r'([+-]\d{2}):(\d{2})', r'\1\2', s)
from datetime import datetime
import re
s = "2025-04-05T12:30:45.123Z"
s_clean = s.replace('Z
', '+0000')
dt = datetime.strptime(s_clean, '%Y-%m-%dT%H:%M:%S.%f%z')
s2 = "2025-04-05T12:30:45.123+08:00"
s2_clean = re.sub(r'([+-]\d{2}):(\d{2})', r'\1\2', s2)
dt2 = datetime.strptime(s2_clean, '%Y-%m-%dT%H:%M:%S.%f%z')
注意:%f 只匹配 6 位微秒,若输入毫秒(3 位)或更长,strptime 会失败 —— 建议用正则先截断或补零
绝大多数实际项目中,输入格式不可控(Z、+08:00、+0800、甚至无时区),硬写 strptime 易出错且维护成本高。dateutil.parser.parse() 是更鲁棒的选择:
Z、+08:00、-05:30 等所有常见 ISO 变体tzinfo 完整的 datetime 对象(不是 naive 时间)pip install python-dateutil
from dateutil import parser; dt = parser.parse("2025-04-05T12:30:45.123+08:00")
strptime,但对非高频解析场景无感;若需极致性能,再考虑缓存或预编译无论用哪种方式解析,结果是否带 tzinfo 决定了后续操作是否安全:
strptime(...%z) 或 dateutil.parser.parse() 得到的是 aware datetime(含时区),可直接比较、转换、转 UTCfromisoformat() 解析成功(比如输入恰巧是 +0800)但没注意它返回的是 aware 对象,而你代码里又混用了 naive 时间,会导致 TypeError: can't compare offset-naive and offset-aware datetimes
dt.replace(tzinfo=None),但务必确认这是有意为之,而非疏忽真正复杂的地方不在解析语法,而在后续所有时间运算都依赖这个 aware/naive 判断 —— 一个没注意,就可能在跨时区服务里埋下数据偏差隐患