浅谈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脚本使得web页面上的代码高亮显示
Apr 24 Python
Python打印输出数组中全部元素
Mar 13 Python
python查询mysql,返回json的实例
Mar 26 Python
Python基于多线程操作数据库相关问题分析
Jul 11 Python
influx+grafana自定义python采集数据和一些坑的总结
Sep 17 Python
对python捕获ctrl+c手工中断程序的两种方法详解
Dec 26 Python
对python pandas读取剪贴板内容的方法详解
Jan 24 Python
python中实现控制小数点位数的方法
Jan 24 Python
python selenium循环登陆网站的实现
Nov 04 Python
pycharm中使用request和Pytest进行接口测试的方法
Jul 31 Python
Pycharm学生免费专业版安装教程的方法步骤
Sep 24 Python
python tkinter实现定时关机
Apr 21 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
深入了解PHP类Class的概念
2012/06/14 PHP
php带抄送和密件抄送的邮件发送方法
2015/03/20 PHP
PHP中的事务使用实例
2015/05/26 PHP
10个对初学者非常有用的PHP技巧
2016/04/06 PHP
PHP实现微信小程序人脸识别刷脸登录功能
2018/05/24 PHP
javascript入门·动态的时钟,显示完整的一些方法,新年倒计时
2007/10/01 Javascript
基于PHP+Jquery制作的可编辑的表格的代码
2011/04/10 Javascript
jquery的幻灯片图片切换效果代码分享
2015/09/07 Javascript
JS及PHP代码编写八大排序算法
2016/07/12 Javascript
JavaScript事件用法浅析
2016/10/31 Javascript
AngularJS使用ng-app自动加载bootstrap框架问题分析
2017/01/04 Javascript
vue+mockjs模拟数据实现前后端分离开发的实例代码
2017/08/08 Javascript
nodejs对项目下所有空文件夹创建gitkeep的方法
2019/08/02 NodeJs
jQuery实现的记住帐号密码功能完整示例
2019/08/03 jQuery
vue实现移动端拖动排序
2020/08/21 Javascript
js+html+css实现手动轮播和自动轮播
2020/12/30 Javascript
[02:50]【扭转乾坤,只此一招】DOTA2全新版本永雾林渊开启新篇章
2020/12/24 DOTA
Python实现批量把SVG格式转成png、pdf格式的代码分享
2014/08/21 Python
python 实现对文件夹内的文件排序编号
2018/04/12 Python
python 列表,数组和矩阵sum的用法及区别介绍
2018/06/28 Python
python opencv对图像进行旋转且不裁剪图片的实现方法
2019/07/09 Python
python numpy实现多次循环读取文件 等间隔过滤数据示例
2020/03/14 Python
使用keras框架cnn+ctc_loss识别不定长字符图片操作
2020/06/29 Python
Python requests及aiohttp速度对比代码实例
2020/07/16 Python
CSS3的常见transformation图形变化用法小结
2016/05/13 HTML / CSS
html5页面结构_动力节点Java学院整理
2017/07/10 HTML / CSS
泰国演唱会订票网站:StubHub泰国
2018/02/26 全球购物
意大利中国电子产品购物网站:Geekmall.com
2019/09/30 全球购物
澳大利亚排名第一的露营和户外设备在线零售商:Outbax
2020/05/06 全球购物
数据保密承诺书
2014/06/03 职场文书
2014年最新大专生职业生涯规划书范文
2014/09/13 职场文书
领导干部作风建设工作总结
2014/10/23 职场文书
导游词之嵊泗列岛
2019/10/30 职场文书
祝福语集锦:送给毕业同学祝福语
2019/11/21 职场文书
CSS3实现的文字弹出特效
2021/04/16 HTML / CSS
动画电影《擅长捉弄人的高木同学》6月10日上映!
2022/03/20 日漫