Python 的 datetime 模块最容易出问题的地方,不是语法,而是:
“它很多行为看起来正常,但实际在跨时区、夏令时、时间比较时会 silently 出错。”
尤其是:
日志系统
爬虫
定时任务
数据库存储
分布式系统
Selenium 自动化
API 时间戳转换
这些场景里,时区坑非常多。
1. naive datetime 与 aware datetime 混用
最经典的问题:
from datetime import datetime, timezone
a = datetime.now()
b = datetime.now(timezone.utc)
print(a > b)
直接报错:
TypeError: can't compare offset-naive and offset-aware datetimes
因为:
a 没有时区(naive)
b 有 UTC 时区(aware)
Python 禁止直接比较。
隐蔽问题
很多代码:
datetime.now()
默认得到:
2026-05-24 10:00:00
你以为这是“本地时间”。
实际上:
它只是“没有时区信息的时间”。
Python 并不知道它属于:
UTC
台北
东京
纽约
这会导致后续转换全部出错。
2. replace(tzinfo=...) 并不会转换时区
很多人这样写:
dt = datetime.now()
dt = dt.replace(tzinfo=timezone.utc)
这是错误用法。
它不是:
“把本地时间转成 UTC”
而是:
“强行声明这个时间本来就是 UTC”
例如:
你电脑本地时间:
2026-05-24 20:00:00 (台北)
直接:
replace(tzinfo=UTC)
会变成:
2026-05-24 20:00:00+00:00
等于时间瞬间偏移了 8 小时。
正确做法
应该先声明原时区:
from zoneinfo import ZoneInfo
dt = datetime.now(ZoneInfo("Asia/Taipei"))
再转换:
utc_dt = dt.astimezone(timezone.utc)
3. datetime.utcnow() 是大坑
很多老代码:
datetime.utcnow()
看起来像:
UTC 时间
但实际上:
返回的是 naive datetime。
即:
2026-05-24 12:00:00
没有 tzinfo。
因此:
datetime.utcnow().timestamp()
在某些系统下会被当成本地时间解释。
导致:
时间戳错误
提前 8 小时
延后 8 小时
推荐写法
datetime.now(timezone.utc)
得到:
2026-05-24 12:00:00+00:00
这是真正安全的 UTC 时间。
4. timestamp() 会受本地时区影响
例如:
dt = datetime(2026, 1, 1)
print(dt.timestamp())
很多人以为:
固定 Unix 时间戳
实际上:
naive datetime 会被当成本地时间。
如果你在:
台湾
美国
日本
结果都可能不同。
5. 夏令时(DST)最危险
例如美国:
2026-03-08 02:00
时间会直接跳到:
03:00
所以:
from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime(2026, 3, 8, 2, 30,
tzinfo=ZoneInfo("America/New_York"))
这个时间:
实际不存在。
更恐怖的是重复时间
秋季回拨:
01:30 出现两次
Python 无法自动判断:
第一次 01:30
第二次 01:30
因此:
fold=0
fold=1
机制被引入。
但大量程序员根本不知道。
6. timedelta(days=1) 不一定等于 24 小时
例如:
dt + timedelta(days=1)
跨夏令时后:
可能是:
23 小时
25 小时
因为:
“一天” 是日历概念,不是固定秒数。
7. pytz 的 localize 陷阱
老项目经常:
import pytz
tz = pytz.timezone("Asia/Taipei")
dt = tz.localize(datetime.now())
但很多人写成:
datetime.now(tz)
在 pytz 里:
某些 DST 场景会出错。
这是:
pytz
历史设计问题。
Python 3.9+ 推荐
直接使用:
zoneinfo
替代:
pytz
8. 数据库存储时间的大坑
很多数据库:
MySQL DATETIME
SQLite
根本不存时区。
例如:
2026-05-24 18:00:00
你无法知道:
UTC?
北京?
东京?
因此:
跨服务器后会乱。
推荐方案
统一:
数据库永远存 UTC
展示时:
再转本地时区
9. ISO 格式不一定安全
例如:
dt.isoformat()
输出:
2026-05-24T18:00:00
没有:
+08:00
时区信息。
因此:
API 接收方可能按本地时间解释。
正确格式
2026-05-24T18:00:00+08:00
必须带 offset。
10. fromtimestamp() 默认本地时区
例如:
datetime.fromtimestamp(0)
得到:
1970-01-01 08:00:00
(东八区)
而:
datetime.utcfromtimestamp(0)
得到:
1970-01-01 00:00:00
但它又是 naive datetime。
最安全的现代写法(Python 3.9+)
推荐统一:
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
获取 UTC
now_utc = datetime.now(timezone.utc)
获取本地时区时间
taipei = ZoneInfo("Asia/Taipei")
dt = datetime.now(taipei)
时区转换
utc_dt = dt.astimezone(timezone.utc)
实战建议
永远不要:
datetime.utcnow()
永远不要:
replace(tzinfo=...)
用于转换时区。
永远不要:
is naive datetime
参与跨系统传输。
最稳方案
统一:
内部 UTC
显示本地化
这是:
AWS
Docker
Kubernetes
PostgreSQL
常见实践。