Python 的zoneinfo模块是在 Python 3.9 正式加入标准库的。在那之前,处理时区问题基本靠第三方库pytz,或者更古老的dateutil。很多人已经习惯安装pytz了,但标准库内置这个功能之后,有些事情确实不一样了。
先说说它到底是什么。简单点讲,zoneinfo就是一套让 Python 的datetime对象真正“懂时区”的工具。Python 自带的datetime对象有两个非常让人头疼的状态:naive 和 aware。naive 的意思是这个时间对象不知道自己属于哪个时区,就像一个没有标注地点的时钟,你说它现在是几点,其实没有意义。aware 则是带着时区信息的,比如你知道这个时间是“北京时间 2025 年 6 月 1 日 14:00”。zoneinfo的作用就是帮你把 naive 的时间变成 aware,或者在不同时区之间来回转换。
它能做的事情其实很集中:加载 IANA 时区数据库里的时区名称,比如"Asia/Shanghai"、"America/New_York",然后给datetime对象附加上时区信息,或者做时区间的转换。举个例子,你有一个用户在美国纽约,他的系统时间记录的是纽约当地时间,你想把它转成北京时间存到数据库里。用zoneinfo就能很干净地完成这个转换。
使用方式上,我一般这样写:
fromdatetimeimportdatetimefromzoneinfoimportZoneInfo shanghai_tz=ZoneInfo("Asia/Shanghai")ny_tz=ZoneInfo("America/New_York")# 创建一个带时区的北京时间dt_shanghai=datetime(2025,6,1,14,0,0,tzinfo=shanghai_tz)# 转换成纽约时间dt_ny=dt_shanghai.astimezone(ny_tz)print(dt_shanghai)# 2025-06-01 14:00:00+08:00print(dt_ny)# 2025-06-01 02:00:00-04:00看到+08:00和-04:00这两个偏移量了吗?这就是zoneinfo自动从数据库中查到的。不需要你去手算夏令时、冬令时,它全都帮你处理好了。
说到最佳实践,我得提几件容易踩坑的事。第一件:尽量不要用datetime.utcnow()。这个函数返回的是 naive 的 UTC 时间,很容易和真正带时区的 UTC 时间搞混。建议直接用datetime.now(ZoneInfo("UTC")),或者 Python 3.11 之后可以用datetime.now(datetime.UTC)。第二件:存数据库的时候,最好统一用 UTC 时间,前端展示的时候再转成用户的本地时间。这样数据库里的时间就不会因为夏令时变化而出现歧义。第三件:如果你的系统需要支持 Python 3.8 及以下版本,那就还得用pytz,因为zoneinfo是 3.9 才有的。但如果是新项目,直接上zoneinfo就好。
至于和同类技术对比,主要是和pytz比。pytz有两个历史包袱:一是它的时区对象不能直接作为tzinfo参数传给datetime构造函数,得用localize和normalize方法,代码写起来比较绕。二是它对夏令时转换的处理有点隐晦,不小心就会踩坑。举个例子,用pytz创建一个上海时间:
importpytz shanghai_tz=pytz.timezone("Asia/Shanghai")dt=datetime(2025,6,1,14,0,0)dt_aware=shanghai_tz.localize(dt)# 必须用 localize而zoneinfo可以直接写tzinfo=ZoneInfo("Asia/Shanghai"),直观得多。另外zoneinfo用的是操作系统的时区数据库(Linux 和 macOS 通常自带),而pytz自己维护了一份数据库副本。在更新频率上,更新系统时区数据库比更新pytz包要方便一些。但pytz也有它的优势:它支持更多历史时区数据,如果你在处理非常古老的时间(比如 1900 年以前的),pytz可能更可靠。不过绝大多数业务场景用不到这么深的历史数据。
还有个技术叫dateutil,它也有时区功能,但dateutil更像是瑞士军刀——功能很多,但专门用来做时区处理的话,性能不如zoneinfo和pytz。
总体来看,Python 3.9+ 的新项目,建议直接用zoneinfo,少装一个包是一方面,更重要的是代码写出来更干净。如果还在用旧版本,pytz也能用,只是一些细节写法上需要注意区别。