浅谈Python3中datetime不同时区转换介绍与踩坑


Posted in Python onAugust 02, 2021

最近的项目需要根据用户所属时区制定一些特定策略,学习、应用了若干python3的时区转换相关知识,这里整理一部分记录下来。

下面涉及的几个概念及知识点:

GMT时间:Greenwich Mean Time, 格林尼治平均时间

UTC时间:Universal Time Coordinated 世界协调时,可以认为是更精准的GMT时间,但两者误差极小,在1s以内,一般可视为等同

LMT:Local Mean Time, 当地标准时间

Python中的北京时间:Python的标准timezone中信息中并没有Asia/Beijing,原因要追溯到国民政府期间上报给国际标准的五个时区城市没有北京,因此一般使用Asia/Shanghai获取东8区时间

Python使用到的时间相关函数及概念:

包含时区信息的datetime称为: offset-aware datetime,反之称为offset-naive datetime

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

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

datetime(...tzinfo=tz) : 直接指定year/month/day/hour/second+时区信息生成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-navie 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小时(64800s):

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 相关文章推荐
Python的Django框架下管理站点的基本方法
Jul 17 Python
Python如何获取系统iops示例代码
Sep 06 Python
Java及python正则表达式详解
Dec 27 Python
python实现贪吃蛇游戏
Mar 21 Python
Django模板语言 Tags使用详解
Sep 09 Python
Python3标准库之dbm UNIX键-值数据库问题
Mar 24 Python
Pytorch框架实现mnist手写库识别(与tensorflow对比)
Jul 20 Python
python Gabor滤波器讲解
Oct 26 Python
python图片合成的示例
Nov 09 Python
Django中使用Celery的方法步骤
Dec 07 Python
学会用Python实现滑雪小游戏,再也不用去北海道啦
May 20 Python
Python实现猜拳与猜数字游戏的方法详解
Apr 06 Python
python数字转对应中文的方法总结
Aug 02 #Python
Python List remove()实例用法详解
Aug 02 #Python
Python中基础数据类型 set集合知识点总结
Aug 02 #Python
python unittest单元测试的步骤分析
Aug 02 #Python
python元组打包和解包过程详解
Aug 02 #Python
python字典进行运算原理及实例分享
Aug 02 #Python
Python中可变和不可变对象的深入讲解
You might like
Terran历史背景
2020/03/14 星际争霸
PHP在线生成二维码(google api)的实现代码详解
2013/06/04 PHP
php格式输出文件var_export函数实例
2014/11/15 PHP
php实现水印文字和缩略图的方法示例
2016/12/29 PHP
php脚本守护进程原理与实现方法详解
2017/07/20 PHP
phpinfo无法显示的原因及解决办法
2019/02/15 PHP
Swoole4.4协程抢占式调度器详解
2019/05/23 PHP
win10下 php安装seaslog扩展的详细步骤
2020/12/04 PHP
解析js原生方法创建表格效率测试
2013/07/08 Javascript
JavaScript对内存分配及管理机制详细解析
2013/11/11 Javascript
JS获取当前脚本文件的绝对路径
2016/03/02 Javascript
Jquery Easyui验证组件ValidateBox使用详解(20)
2016/12/18 Javascript
详解js的异步编程技术的方法
2017/02/09 Javascript
JavaScript简介_动力节点Java学院整理
2017/06/26 Javascript
详解利用jsx写vue组件的方法示例
2017/07/17 Javascript
JavaScript中各数制转换全面总结
2017/08/21 Javascript
Vue-Router进阶之滚动行为详解
2017/09/13 Javascript
JavaScript中toLocaleString()和toString()的区别实例分析
2018/08/14 Javascript
Nuxt.js实现校验访问浏览器类型的中间件
2018/08/24 Javascript
JS判断数组里是否有重复元素的方法小结
2019/05/21 Javascript
微信小程序实现音频文件播放进度的实例代码
2020/03/02 Javascript
微信小程序云函数添加数据到数据库的方法
2020/03/04 Javascript
使用webpack5从0到1搭建一个react项目的实现步骤
2020/12/16 Javascript
[14:51]DOTA2 HEROS教学视频教你分分钟做大人-卓尔游侠
2014/06/13 DOTA
Python实现方便使用的级联进度信息实例
2015/05/05 Python
python 2.6.6升级到python 2.7.x版本的方法
2016/10/09 Python
Tensorflow加载预训练模型和保存模型的实例
2018/07/27 Python
Python实现处理逆波兰表达式示例
2018/07/30 Python
PyQt5实现让QScrollArea支持鼠标拖动的操作方法
2019/06/19 Python
使用pyinstaller逆向.pyc文件
2019/12/20 Python
python 代码实现k-means聚类分析的思路(不使用现成聚类库)
2020/06/01 Python
树莓派升级python的具体步骤
2020/07/05 Python
python调用私有属性的方法总结
2020/07/24 Python
运动会表扬稿大全
2014/01/16 职场文书
高校教师个人工作总结2014
2014/12/17 职场文书
Java使用HttpClient实现文件下载
2022/08/14 Java/Android