python datetime 时区陷阱
datetime 时区陷阱
问题
- 在用 python 开发的时候,我们经常用 datetime 模块的 datetime 函数来构造时;
- 例如:我们需要构造一个 '2022-12-09 12:00:00' 的时间对象,则可以这样:
t = datetime.datetime(2022, 12, 9, 12, 0, 0)
; - datetime 构造的时间真的没有问题,真的对吗?
答案
直接告诉你答案:
- 答案是:datetime 函数构造的时间不一定对,以北京时间为例,datetime 要快 6 分钟
- 解决办法:使用 pytz.timezone 的 localize 方法进行处理即可
错误示范:
from pytz import timezone
from datetime import datetime
tz = timezone('Asia/Shanghai')
t = datetime.datetime(2022, 12, 9, 12, 0, 0, tzinfo=tz)
print(t)
正确示范:
from pytz import timezone
from datetime import datetime
tz = timezone('Asia/Shanghai')
t = tz.localize(datetime.datetime(2022, 12, 9, 12, 0, 0))
print(t)
分析经过(可忽略)
情况一:不设置时区
依赖于 python 配置里的默认时区,程序里不设置时区
from datetime import datetime
now = datetime.now()
manual_now = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second, now.microsecond)
print(now)
print(manual_now)
diff = manual_now.timestamp() - now.timestamp()
print(f'datetime构造时间与默认时间差:{diff} 秒')
输出:
2022-12-09 11:34:24.450903
2022-12-09 11:34:24.450903
datetime构造时间与默认时间差:0.0 秒
注:可见在不设置时区时,datetime 函数构造的时间和默认时间没有区别,是对的
情况二:设置时区(以北京时间为例)
设置时区需要用到 pytz 模块,未安装的可以先安装一下
pip install pytz
1)使用 datetime 函数的 tzinfo 参数
from pytz import timezone
from datetime import datetime
tz = timezone('Asia/Shanghai')
now = datetime.now(tz)
print(now)
manual_now = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second, now.microsecond, tzinfo=tz)
print(manual_now)
diff = manual_now.timestamp() - now.timestamp()
print(f'datetime构造时间与北京时间差:{diff} 秒')
输出
2022-12-09 11:36:20.493618+08:00
2022-12-09 11:36:20.493618+08:06
datetime构造时间与北京时间差:-360.0 秒
注1:打印两个时间的时候,表面上是一样的,但要注意后面的+08:06。通过比例两者的时间戳,直接使用 tzinfo 参数构造出来的时间,比实际时间快 360 秒(即 6 分钟)
注2:其本质是因为时区的设置只有 'Asia/Shanghai'(即上海时间),而没有 'Asia/Beijing'(即北京时间),上海时间实际上是比北京时间快 6 分钟的
注3:如果你再换成 'Asia/Hong_Kong'(即香港时间),会发现香港时间比北京时间慢 23 分钟
2)时区本地化
严格意义上,设置时区后的结果并没有错,但生活上我们都是使用的北京时间(大部分系统也是使用的北京时间),所有我们还是需要对时间进行“本地化”
使用 tzinfo.localize 方法对 datetime 进行本地化处理:
from pytz import timezone
from datetime import datetime
tz = timezone('Asia/Shanghai')
now = datetime.now(tz)
print(now)
true_now = tz.localize(datetime(now.year, now.month, now.day, now.hour, now.minute, now.second, now.microsecond))
print(true_now)
diff = true_now.timestamp() - now.timestamp()
print(f'使用tz.localize处理datetime与北京时间差:{diff} 秒')
输出:
2022-12-09 11:45:10.528836+08:00
2022-12-09 11:45:10.528836+08:00
使用tz.localize处理datetime与北京时间差:0.0 秒
注:经过 localize 函数处理后的时间为真正的北京时间
回归问题
回归一开始的问题:如果我们需要构造一个 '2022-12-09 12:00:00' 的时间对象,正确的做法为:
from pytz import timezone
from datetime import datetime
tz = timezone('Asia/Shanghai')
bj_time = tz.localize(datetime(2022, 12, 9, 12, 0, 0))
print(bj_time)