最近的项目需要根据用户所属时区制定一些特定策略,学习、应用了若干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时区转换介绍与踩坑的更多相关文章

  1. java中的时区转换

    目录 java中的时区转换 一.时区的说明 二.时间的表示 三.时间戳 四.Date类和时间戳 五.java中的时区转换 java中的时区转换 一.时区的说明 地球表面按经线从东到西,被划成一个个区域 ...

  2. datetime时区转换

    http://www.dannysite.com/blog/122/ Python标准库中的datetime模块提供了各种对日期和时间的处理方法.从本文的主题着手,首先利用datetime中提供的ut ...

  3. python datetime时区转换

    from pytz import timezone def datetime_as_timezone(date_time, time_zone): tz = timezone(time_zone) u ...

  4. 彻底解决Odoo8.0单时区应用中的时区问题

    原文地址:http://shine-it.net/index.php/topic,17001.0.html 由于数据库中存储的是UTC时区,默认情况下数据导出和group by都存在时区问题.彻底解决 ...

  5. [置顶] xamarin android toolbar(踩坑完全入门详解)

    网上关于toolbar的教程有很多,很多新手,在使用toolbar的时候踩坑实在太多了,不好好总结一下,实在浪费.如果你想学习toolbar,你肯定会去去搜索androd toolbar,既然你能看到 ...

  6. C#中UnixTime和DateTime的转换(转载)

    由于在API请求中返回回来的时间格式为UNIX形式,需要转换成正常的显示方式,在网上找到了这么一个例子. 使用是在C#中使用的,所以WP8开发应该也可以. 转载源地址:http://blog.linu ...

  7. Python中模块之time&datetime的功能介绍

    time&datetime的功能介绍 1. time模块 1. 时间的分类 1. 时间戳:以秒为单位的整数 2. 时间字符格式化:常见的年月日时分秒 3. 时间元祖格式:9大元素,每个元素对应 ...

  8. SQL中DateTime转换成Varchar样式

    SQL中DateTime转换成Varchar样式语句及查询结果:Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AMSelect ...

  9. Python3中内置类型bytes和str用法及byte和string之间各种编码转换,python--列表,元组,字符串互相转换

    Python3中内置类型bytes和str用法及byte和string之间各种编码转换 python--列表,元组,字符串互相转换 列表,元组和字符串python中有三个内建函数:,他们之间的互相转换 ...

随机推荐

  1. jdk,jre.jvm三者的关系

    jdk>jre>jvm jdk=jre+java的开发工具(包括java.exe,javac.exe.javadoc.exe) jre=jvm+java核心类库

  2. Django(65)jwt认证原理

    前言 带着问题学习是最有目的性的,我们先提出以下几个问题,看看通过这篇博客的讲解,能解决问题吗? 什么是JWT? 为什么要用JWT?它有什么优势? JWT的认证流程是怎样的? JWT的工作原理? 我们 ...

  3. 解Bug之路-ZooKeeper集群拒绝服务

    解Bug之路-ZooKeeper集群拒绝服务 前言 ZooKeeper作为dubbo的注册中心,可谓是重中之重,线上ZK的任何风吹草动都会牵动心弦.最近笔者就碰到线上ZK Leader宕机后,选主无法 ...

  4. 【模板】 RMQ求区间最值

    RMQ RMQ简单来说就是求区间的最大值(最小值) 核心算法:动态规划 RMQ(以下以求最大值为例) F[i,j]表示 从 i 开始 到i+2j -1这个区间中的最大值 状态转移方程 F[i,j]=m ...

  5. char与varchar2字符类型的区别

    1.实验: 1)创建一个表test,包含三个字段,数据类型分别是varchar2,number,char create table test(name varchar2(10),id number(1 ...

  6. SpringCloud(6)Eureka架构与CAP原则与取舍策略

    一:Eureka架构 Register(服务注册):把自己的 IP 和端口注册给 Eureka. Renew(服务续约):发送心跳包,每 30 秒发送一次,告诉 Eureka 自己还活着.如果 90 ...

  7. ES6学习笔记之 this 详解

    1.非箭头函数下的 this var obj = { x: 0, f1: function () { console.log(this.x); } } var f1 = obj.f1; var x = ...

  8. Docker_CICD笔记

    1 环境说明 1.1 机器配置 主机名称 IP地址 系统版本/内存/cpu核数/硬盘 安装软件 controlnode 172.16.1.70/24 centos7.4/4/2/60 docker.d ...

  9. 如何使用Jdk查看源码?

    先看再点赞,给自己一点思考的时间,如果对自己有帮助,微信搜索[程序职场]关注这个执着的职场程序员.我有什么:职场规划指导,技能提升方法,讲不完的职场故事,个人成长经验. 最近,有很多读者问我,有没有j ...

  10. 认识Java中String与StringBuffer以及StringBuilder

    String(引用数据类型) String对象一经创建就不会发生变化(在常量池里),即便是赋予新的值,也不是在原来的基础上改变,而是创建一个新的字符串对象,将引用指向这个对象,会造成空间的浪费: St ...