Python3 datetime时区转换详解:LMT陷阱与Etc/GMT规范

唐可
唐可
发布时间:2026-10-14| 5人浏览过
原创

本文深入解析Python3中datetime的时区处理机制,重点剖析使用Asia/Shanghai等时区时因LMT(当地平均时间)导致的分钟级误差陷阱。通过对比offset-aware与naive datetime,详解astimezone与replace的区别,并推荐采用Etc/GMT系列时区字符串以确保UTC/GMT标准时间的准确性,避免跨时区计算偏差。

近期项目中需依据用户所在时区执行特定策略,在此过程中学习并应用了Python3的时区转换技术,现将部分核心经验整理如下。

下文将涉及以下关键概念与知识点:

GMT(Greenwich Mean Time):格林尼治平均时间。

UTC(Universal Time Coordinated):世界协调时,可视为精度更高的GMT时间。两者误差极小(小于1秒),通常可视为等同。

LMT(Local Mean Time):当地标准时间。

关于Python中的北京时间:Python标准timezone库中并未包含Asia/Beijing,这源于历史原因——国民政府时期上报给国际标准的五个时区城市中未包含北京。因此,通常使用Asia/Shanghai来获取东8区时间。

Python中涉及的时间相关函数及概念如下:

包含时区信息的datetime对象称为offset-aware datetime,反之则为offset-naive datetime。

pytz.timezone(x):pytz包中预定义的时区相关对象,可通过命令python3 -m pip install pytz进行安装。

datetime(...):直接指定year/month/day/hour/second参数,生成naive datetime。

datetime(...tzinfo=tz):直接指定year/month/day/hour/second及tzinfo参数,生成offset-aware datetime。

datetime.now():生成当前默认时区的naive datetime。

datetime.now(tzinfo=tz):生成指定时区的offset-aware datetime。

datetime.strptime(string, format):生成由string和format表示的、当前默认时区的naive datetime。

datetime.replace(tzinfo=tz):直接替换datetime的时区信息为tz,生成offset-aware datetime。注意:此操作不进行实际的时区转换。

datetime.astimezone(tz):将时间转换为新的tz时区的offset-aware datetime。

在以下代码示例中,由于云主机位于日本,默认时区为东9区(Asia/Tokyo)。

Python中获取当前时刻时间的方法:


In [1]: import pytz

In [2]: from datetime import datetime, timedelta

In [3]: datetime.now() # 默认时区当前时间
Out[3]: datetime.datetime(2021, 8, 1, 18, 36, 8, 352873)

In [4]: datetime.now(pytz.timezone('Asia/Tokyo')) # 指定Tokyo时区当前时间
Out[4]: datetime.datetime(2021, 8, 1, 18, 36, 25, 421048, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)

观察可知,datetime.now()未指定时区时,返回的是offset-naive datetime;而指定时区后返回的是offset-aware datetime。Naive和aware类型的datetime无法直接进行比较或相减操作,只有同类型的datetime才能计算时间差或比较大小,示例如下:


In [5]: datetime.now() - datetime.now(pytz.timezone('Asia/Tokyo'))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-8b6c111dc5de> in <module>
----> 1 datetime.now() - datetime.now(pytz.timezone('Asia/Tokyo'))

TypeError: can't subtract offset-naive and offset-aware datetimes

In [6]: datetime.now() - datetime.now() # 只有同样的offset-naive datetime才能求差值
Out[6]: datetime.timedelta(days=-1, seconds=86399, microseconds=999991)
In [8]: datetime.now(pytz.timezone('Asia/Tokyo')) - datetime.now(pytz.timezone('Asia/Tokyo')) # 同样的offset-aware datetime才能求差值
Out[8]: datetime.timedelta(days=-1, seconds=86399, microseconds=999976)

这里遇到第一个常见陷阱。例如,若想获取北京时间2021年1月1日0点的datetime,并转换为东京时间,直觉上可能会编写如下代码:


In [19]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')) # 这里获取北京时间20210101 0点的datetime
Out[19]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' LMT+8:06:00 STD>) # 注意获取的是LMT时间
In [21]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Asia/Shanghai')).astimezone(pytz.timezone('Asia/Tokyo')) # 将北京时转换为东京时间
Out[21]: datetime.datetime(2021, 1, 1, 0, 54, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>) # 获取的是日本标准时间JST+9
In [22]: datetime.now(pytz.timezone('Asia/Shanghai')) # 示例获取当前时刻北京时间
Out[22]: datetime.datetime(2021, 8, 1, 18, 11, 6, 706727, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>) # 获取的是中国标准时间(CST+8)

仔细分析发现,北京时间0点转换为东京时间后显示为0:54,相差54分钟而非预期的1小时。进一步查看tzinfo信息,发现其值为LMT+8:06:00 STD,这表示获取的是LMT(当地平均时间),相比UTC快8小时6分钟,而非东8区标准时间。而通过astimezone方法转换后得到的是日本标准时间(东9区),因此两者差值并非整1小时。

该陷阱的根本原因在于,通过datetime(..tzinfo=..)指定时区获取的是LMT时间,而datetime.now(tz)和datetime.astimezone(tz)获取的是UTC(GMT)标准时间。LMT与GMT标准时间之间可能存在甚至十分钟级的差值,这足以影响程序逻辑的正确性。

因此,为确保获取标准时区时间,建议避免使用Asia/Shanghai、Asia/Tokyo这类大洲/城市字符串表示时间,而应使用GMT、UTC等无歧义的标准时区标识,如下所示:


In [45]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-9'))
Out[45]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-9'>) # 东9区应使用GMT-9

这里出现第二个陷阱。由于历史原因,Python中timezone的表示规则是“西正东负”,这与ISO标准相反。因此,东9区应表示为Etc/GMT-9,而Etc/GMT+9实际上表示的是西9区。可通过以下代码验证:GMT-9与JST(日本标准时间)相差0,而GMT+9与JST相差18小时(64800秒):


In [50]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-9')) - datetime(2021, 1, 1).astimezone(pytz.timezone('Asia/Tokyo'))
Out[50]: datetime.timedelta(0)

In [51]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT+9')) - datetime(2021, 1, 1).astimezone(pytz.timezone('Asia/Tokyo'))
Out[51]: datetime.timedelta(seconds=64800)

最后,以北京时间为例,获取指定时区2021年1月1日datetime的正确方式如下:


In [56]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8'))
Out[56]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-8'>)
In [58]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT-8')).astimezone(pytz.timezone('Asia/Shanghai'))
Out[58]: datetime.datetime(2021, 1, 1, 0, 0, tzinfo=<DstTzInfo 'Asia/Shanghai' CST+8:00:00 STD>) # 可见GMT-8和东八区标准时间(CST+8)一致

若需进一步获取指定时区零点的时间戳,方法则更为简单:


In [44]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT0')).timestamp() # 获取格林尼治时区2021年1月1日0点时间戳
Out[44]: 1609459200.0

另外两种获取指定时区时刻的方法,这三种方式在结果上是等价的:


In [51]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT0')) == datetime(2021, 1, 1).replace(tzinfo=pytz.timezone('Etc/GMT0'))
Out[51]: True
In [53]: datetime(2021, 1, 1, tzinfo=pytz.timezone('Etc/GMT0')) == datetime.strptime('20210101', '%Y%m%d').replace(tzinfo=pytz.timezone('Etc/GMT0'))

至此,关于Python3中datetime不同时区转换的介绍与常见陷阱已阐述完毕。如需了解更多Python3 datetime时区转换相关内容,请查阅本站往期文章或继续浏览后续文章,感谢支持!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。