python中的时区问题

问题背景

使用 Python 进行了许久的开发,一直没有踩到时区的坑,最近新的业务中引入了比较多的服务,而且使用 grpc 进行数据通讯,不幸踩到了时区的坑,果然偷的懒最终还是会有报应的,于是梳理下对应的时区问题,同时发现系统中之前的数据库 Mongo 中的时区问题,一起整理如下。

基础概念

几个时间概念

首先是几个常见的时间概念

  • GMT 时间:格林威治时间,基准时间
  • UTC 时间:Coordinated Universal Time,全球协调时间,更精准的基准时间,与 GMT 基本等同
  • CST 中国基准时间:为 UTC 时间 + 8 小时,即 UTC 时间的 0 点对应于中国基准时间的 8 点,即为一般称为东八区的时间

ISO 8601

一种标准化的时间表示方法,表示格式为 :YYYY-MM-DDThh:mm:ss ± timezone,可以表示不同时区的时间,时区部分用Z 表示为 UTC 标准时区。两个例子:

  • 1997-07-16T08:20:30Z 表示的是 UTC 时间的 1997 年 7 月 16 号 8:20:30
  • 1997-07-16T19:20:30+08:00 表示的是东八区时间的 1997 年 7 月 16 号 19:20:30

时间戳

1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0,当前的时间戳即为从 epoch time 到现在的秒数,一般叫做 timestamp,因此一个时间戳一定对应于一个特定的 UTC 时间,同时也对应于其他时区的一个确定的时间。因此时间戳可以认为是一个相对安全的时间表示方法。

datetime 实践

datetime 是 python 中最基础的一个时间管理包,下面分别利用 datetime 去实践下对应的时区概念

datetime 类型

datetime 分成两种类型:

  • naive,本地类型的时间,当 datetime 中没有指定时区信息时就是这种类型,此类型的时区是根据运行环境确定对应的时区。因此这种类型的时间会因为运行环境的不同而得到不同时间戳
  • aware,带有时区类型的时间,这种类型的时间对象由于时间和时区都是确定的,因此对应于确定的时间戳

举例如下:

from datetime import datetime, timezone

now = datetime.now()
now.tzinfo   # None
utc_now = datetime.now(timezone.utc)
utc_now.tzinfo # UTC

可以看到上面的例子中,now 没有指定时区,为 naive 类型的时间,其时区与运行环境相关。而 utc_now 指定了 UTC 时区,为 aware 类型的时间。

获取当前时间

  • datetime.now() 可用于获取当前时间,支持设置对应的时区,如果不设置时区默认获取的是本地的时间,根据是否指定时区可能穿件出 naive 类型的时间或者 aware 类型的时间,但是对应的时间戳都是符合预期的。
  • datetime.utcnow() 谨慎使用 获取是当前 UTC 对应的时间,但是生成的 datetime 对象是没有指定时区的,因此使用的是本地时区,创建的是 naive 类型的时间。因此如果运行环境为东八区,得到的时间是 UTC 对应的时间,但是时区是东八区,最终得到的时间会比预期早 8 个小时,转化得到时间戳也是不符合预期的。

举例如下:

from datetime import datetime
now = datetime.now()
now.timestamp() # 1610035129.323702 unow = datetime.utcnow()
unow.timestamp() # 1610006329.323797

最终在 2021-01-07 23:58:49 在东八区环境下运行上面的代码,now.timestamp() 得到时间戳转化为对应的时间为东八区的 2021-01-07 23:58:49,但是 unow.timestamp() 得到的时间戳对应的时间为东八区的 2021-01-07 15:58:49,对应于 UTC 时间 2021-01-07 07:58:49,和 UTC 的当前时间完全对不上。

时间戳操作

  • datetime.timestamp() 生成当前时间对应的时间戳
  • datetime.fromtimestamp() 根据时间戳生成运行环境时区对应的时间
  • datetime.utcfromtimestamp() 谨慎使用 根据时间戳生成对应的 UTC 时间,由于生成的 datetime 是没有指定时区的,因此获取时间戳看起来得到的是 8 个小时之前时间的时间戳

对于上面的例子,我们使用前面得到的当前时间戳 1610035129 进行测试如下:

from datetime import datetime

timestamp = 1610035129
d1 = datetime.fromtimestamp(timestamp) # 2021-01-07 23:58:49 d2 = datetime.utcfromtimestamp(timestamp) # 2021-01-07 15:58:49

最终得到 d1 是本地时区正确的时间,但是 d2 是 UTC 的是啊金,但是没有指定的时区,因此看起来就是就是本地 8 个小时前的时间了

时区设置

默认构建的 datetime 是没有时区信息的,可以通过 datetime.replace() 为时间设置上时区,但是这样必须保证对应的时间与时区信息匹配,否则就会导致错误的时区的时间,一个简单例子就是:

from datetime import datetime, timedelta, timezone
tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00,即东八区对应的时区 now = datetime.now() # 默认构建的时间无时区 dt = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00

设置上对应的时区后,对应的日期与时间是不变的,但是由于设置了全新的时区,如果与之前的时区不同,那么对应的时间戳就会改变,使用此方法时要谨慎

时区转换

可以将一个带有时区信息的时间转换为另一个时区的时间,通过 datetime.astimezone() 可以实现,一个简单的例子是:

from datetime import datetime, timedelta, timezone
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc) # 构建了 UTC 的当前时间 bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8))) # 将时区转化为东八区的时间

通过 astimezone() 进行转换后,虽然时间变化了,但是对应的是同样的基准时间,因此对应的时间戳是不变的,

Grpc 实践

在 Grpc 的使用中,设计到时间戳对象 Timestamp 与时间的转换,Timestamp 对象支持通过 python 中的时间戳构建,即当前时间的对应的时间戳秒数,也支持通过 datetime 构建。对应的接口如下:

  • Timestamp.FromSeconds() 此方法是根据时间戳生成 Grpc 的时间戳对象,没有特殊的地方
  • Timestamp.FromDatetime() 谨慎使用 此方法根据 datetime 时间生成时间戳对象,隐含期望 datetime 是 UTC 时间,如果错误传入东八区时间,会导致得到一个 8 个小时后的绝对时间

我们在实践中有混用这两个方法,最终发现调用 FromDatetime() 时获得的时间戳是完全不符合预期的。一个简单例子如下:

from datetime import datetime
from google.protobuf.timestamp_pb2 import Timestamp

now = datetime.now()
now_timestamp = int(now.timestamp()) # 1610245593 t1 = Timestamp()
t1.FromSeconds(now_timestamp) # 1610245593
t2 = Timestamp()
t2.FromDatetime(now) # 1610274393

可以看到通过 FromDatetime() 得到订单时间戳与预期是不相符的,只有传入的 datetime 是 UTC 的时间时两者才是一致的

而转换为 datetime 对象的接口为:

  • Timestamp.ToSeconds() 此方法是根据时间戳对象得到对应的整数时间戳,没有问题
  • Timestamp.ToDatetime() 谨慎使用 此方法是根据 grpc 的时间戳对象生成 datetime,隐含输出的 datetime 是 UTC 时间 ,而生成的 datetime 是没有时区信息的,默认会按照本地时区进行处理,不做处理的情况下得到的就是 8 个小时前,对应的时间戳也是错误的

与上面的问题类似,通过 ToDatetime() 得到的时间是 UTC 时间,但是由于得到的 datetime 没有指定时区,只有在 UTC 的运行环境下得到的时间才是符合预期的。

Pymongo 实践

之前的在使用 Pymongo 进行数据存储时,直接使用的是 Pymongo 的默认设置,运行环境设置为东八区,在使用中直接将没有指定时区的 datetime 存入数据库中,之后再取出进行使用工作起来看起来一切正常。但是本次在梳理时区时查看数据库中存储的数据时,就发现了一个明显的问题,数据库中存储的看起来日期与时间是对的,但是是 UTC 的时间,也就是说实际存储的时间比预期晚 8 小时了,但是为什么又能正常工作呢?确认后结果如下:

  • Pymongo 在没有指定时区的情况下, 默认不认为此时间为本地时间,事实上认为此时间为 UTC 时间,最终会利用此时间计算得到对应的时间戳并进行存储,所以最终存储的时间戳会晚 8 小时;
  • 而在默认设置下,从 Pymongo 中返回的时间也没有时区,而时间依旧是 UTC 时间,因此会导致计算得到时间又早了 8 小时,因此时间看起来是正常的。

如何才能保证存入正确时间,返回的也是符合预期的呢?

  • 存入的时间可以设置上对应的时区,即避免存入 naive 类型的时间,应该存入 aware 类型的时间,避免输入是认为是 UTC 的时间
  • 在 Pymongo 中设置输出带时区的时间,避免默认输出时间的问题,Pymongo 可以通过 tz_aware 指定输出带时区的时间,通过 tzinfo 指定输出时间的时区,这个设置在构建 Pymongo 时传入即可。对应如下:
from datetime import timedelta, timezone

db = MongoClient(settings.MONGODB_DSN, tz_aware=True, tzifo=timezone(timedelta(hours=8))).get_default_database()

总结

根据上面的的实践,分别对三个部分进行使用如下:

  1. datetime 的使用中,如果运行环境设置为非 UTC 时区,建议禁用 utc 相关的方法,比如 utcnow ,utcfromtimestamp() ,同时尽量避免使用 naive 使用,保证时间与运行环境解耦;
  2. grpc 的使用中尽量避免调用 FromDatetime() 和 ToDatetime() 这种包含隐含信息的方法,尽量通过时间戳与 grpc 的 TimeStamp 对象进行交互;
  3. Pymongo 中尽量传入的带有时区的时间,输出也配置上时区输出,避免隐含的问题;

一条总原则就是:与第三方的服务交互或存储时,尽量只使用时间戳这种绝对机制,这样才能从根本上杜绝问题。

以上就是python中的时区问题的详细内容,更多关于python 时区的资料请关注我们其它相关文章!

(0)

相关推荐

  • 在python 不同时区之间的差值与转换方法

    之前有个程序,里面有个时间部分是按照国内时区,也就是东八区,来写的,程序中定义了北京时间2点到八点进行检查:后面程序在国外机器上,例如说韩国,欧美等,执行的时候发现会有时间上的问题,因为获取的是机器的本地时间 因为机器上不好装包,只能通过常用的模块进行改写了 原先的代码如下: #self.invalidStartTime = datetime.time(2,00) #self.invalidEndTime = datetime.time(8,59) 为了计算时区的差值并对以上两行代码的时间进行转

  • Python时区设置方法与pytz查询时区教程

    时区的概念与转换 首先要知道时区之间的转换关系,其实这很简单:把当地时间减去当地时区,剩下的就是格林威治时间了. 例如北京时间的18:00就是18:00+08:00,相减以后就是10:00+00:00,因此就是格林威治时间的10:00.而把格林威治时间加上当地时区,就能得到当地时间了. 例如格林威治时间的10:00是10:00+00:00,转换成太平洋标准时间就是加上-8小时,因此是02:00-08:00.而太平洋标准时间转换成北京时间转换也一样,时区相减即可. 例如太平洋标准时间的02:00-

  • Python datetime 如何处理时区信息

    在 Python 常用日期处理 -- 内置模块 datetime 探讨了 Python 如何使用 datetime, 如果是一个跨时区的应用(Web 应用都是),就不能只存储一个时间而不带时区,如此,全球用户将会看到一个相同的时间字符串,白天黑夜就错乱了.比说用户信息的更新时间存储为 2020-07-07 13:46:08, 上海的用户和芝加哥的用户看到的是同一个时间字符串,实质上却相差好多个小时. 我们可以这么做,在服务端只存储一个 Timestamp 长整型值或 UTC 时间,Timesta

  • Python用模块pytz来转换时区

    前言 最近遇到了一个问题:我的server和client不是在一个时区,server时区是EDT,即美国东部时区,client,就是我自己的电脑,时区是中国标准时区,东八区.处于测试需要,我需要向server发送一个时间,使得server在这个时间戳去执行一些动作.这个时间戳通常是当前时间加2分钟或者几分钟. 通常美东在夏令时时,和我们相差12小时,所以直接减掉这12小时,然后再加两分钟,可以实现发送基于server的时间戳,但是只有一半时间是夏令时,所以考虑还是基于时区来做.百度了一下,Pyt

  • python 带时区的日期格式化操作

    如下所示: Wed, 23 Oct 2019 21:12:01 +0800 Wed, 23 Oct 2019 06:08:37 +0000 (GMT) Fri, 11 Oct 2019 12:42:07 +0800 (CST) Wed, 23 Oct 2019 06:08:37 +0000 (UTC) 几种不同的日期格式化方式,不同的时区时间转换成北京时间,也就是东八区的时间,注意的是后面的时区表示方式, def getTimeStamp(self, date): result = re.sea

  • Python中用altzone()方法处理时区的教程

    altzone()方法是time模块的属性.当地的DST时区的这返回的偏移量,在UTC西部秒钟,如果一个定义.这是负值,如果当地的DST时区为UTC东边(如西欧,包括英国).只有用这个,如果白天不为零. 语法 以下是altzone()方法的语法: time.altzone 参数 NA 返回值 当地的DST时区的这个方法返回的偏移量,在UTC西部秒钟,如果一个定义. 例子 下面的例子显示了altzone()方法的使用. #!/usr/bin/python import time print "ti

  • python中的时区问题

    问题背景 使用 Python 进行了许久的开发,一直没有踩到时区的坑,最近新的业务中引入了比较多的服务,而且使用 grpc 进行数据通讯,不幸踩到了时区的坑,果然偷的懒最终还是会有报应的,于是梳理下对应的时区问题,同时发现系统中之前的数据库 Mongo 中的时区问题,一起整理如下. 基础概念 几个时间概念 首先是几个常见的时间概念 GMT 时间:格林威治时间,基准时间 UTC 时间:Coordinated Universal Time,全球协调时间,更精准的基准时间,与 GMT 基本等同 CST

  • python中日期和时间格式化输出的方法小结

    本文实例总结了python中日期和时间格式化输出的方法.分享给大家供大家参考.具体分析如下: python格式化日期时间的函数为datetime.datetime.strftime():由字符串转为日期型的函数为:datetime.datetime.strptime(),两个函数都涉及日期时间的格式化字符串,这里提供详细的代码详细演示了每一个参数的使用方法及范例. 下面是格式化日期和时间时可用的替换符号 %a 输出当前是星期几的英文简写 >>> import datetime >&

  • python中关于时间和日期函数的常用计算总结(time和datatime)

    1.获取当前时间的两种方法: 复制代码 代码如下: import datetime,timenow = time.strftime("%Y-%m-%d %H:%M:%S")print nownow = datetime.datetime.now()print now 2.获取上个月最后一天的日期(本月的第一天减去1天) 复制代码 代码如下: last = datetime.date(datetime.date.today().year,datetime.date.today().mon

  • Python中生成Epoch的方法

    在Python2中datetime对象没有timestamp方法,不能很方便的生成epoch,现有方法没有处理很容易导致错误.关于Epoch可以参见时区与Epoch 0 Python中生成Epoch from datetime import datetime # python3 datetime.now().timestamp() # python2 import time time.mktime(datetime.now().timetuple()) # 为了兼容python2和3,该用法使用

  • Python中time模块与datetime模块在使用中的不同之处

    Python 中提供了对时间日期的多种多样的处理方式,主要是在 time 和 datetime 这两个模块里.今天稍微梳理一下这两个模块在使用上的一些区别和联系. time 在 Python 文档里,time是归类在Generic Operating System Services中,换句话说, 它提供的功能是更加接近于操作系统层面的.通读文档可知,time 模块是围绕着 Unix Timestamp 进行的. 该模块主要包括一个类 struct_time,另外其他几个函数及相关常量. 需要注意

  • python中时间、日期、时间戳的转换的实现方法

    1.简介 在编写代码时,往往涉及时间.日期.时间戳的相互转换. 2.示例 # 引入模块 import time, datetime 2.1 str类型的日期转换为时间戳 # 字符类型的时间 tss1 = '2013-10-10 23:40:00' # 转为时间数组 timeArray = time.strptime(tss1, "%Y-%m-%d %H:%M:%S") print timeArray # timeArray可以调用tm_year等 print timeArray.tm_

  • 详解python中的time和datetime的常用方法

    一.time的常用方法: import time,datetime # 时间有三种展现方式:时间戳,时间元组,格式化的时间 print(time.time())#当前时间戳 print(int(time.time())) print(time.strftime('%Y-%m-%d %H:%M:%S'))#格式化的时间 print(time.strftime('%Y-%m-%d')) print(time.strftime('%H:%M:%S')) print(time.gmtime())#获取标

  • python中datetime模块中strftime/strptime函数的使用

    Python 的datetime模块 其实就是date和time 模块的结合,常见的属性方法都比较常用 比如: datetime.day,datetime.month,datetime.year 分别表示一个datetime对象的日,月,年:如下 from datetime import datetime dt=datetime.now() #创建一个datetime类对象 print dt.year,dt.month,dt.day ''' 输出为: 2015 3 8 ''' python中da

  • 详解Python中第三方库Faker

    项目开发初期,为了测试方便,我们总要造不少假数据到系统中,尽量模拟真实环境. 比如要创建一批用户名,创建一段文本,电话号码,街道地址.IP地址等等. 平时我们基本是键盘一顿乱敲,随便造个什么字符串出来,当然谁也不认识谁. 现在你不要这样做了,用Faker就能满足你的一切需求. 1. 安装 pip install Faker 2. 简单使用 >>> from faker import Faker >>> fake = Faker(locale='zh_CN') >&

随机推荐