python中的时间处理大总结
python中处理时间的模块有三个,datetime, time, calendar,融汇贯通三个模块,才能随心所欲地用python处理时间。本文就是为此而写。关注并回复“time”得notebook
概述
datetime模块主要是用来表示日期的,就是我们常说的年月日时分秒。
calendar模块主要是用来表示年月日,是星期几之类的信息。
time模块主要侧重点在时分秒。
粗略从功能来看,我们可以认为三者是一个互补的关系,各自专注一块。方便用户依据不同的使用目的选用趁手的模块。
从time模块说起
三个模块中,time模块是基础。为了学习time模块,我们需要先知道几个与时间相关的概念:
1,epoch
假设我们要将时间表示成毫秒数,比方说1000000毫秒,那有一个问题必须解决,这个1000000毫秒的起点是什么时间,也就是我们的时间基准点是什么时间?好比我说你身高1.8米,那这个身高是指相对于你站立的地面说的。这个时间基准点就是epoch,在Unix系统中,这个基准点就是1970年1月1日0点整那个时间点。
Unix时间戳(英文为Unix time, POSIX time 或 Unix timestamp)是从Epoch(1970年1月1日00:00:00 UTC)开始所经过的秒数,不考虑闰秒。
2,GMT, UTC
上面我们说epoch表示1970年的起始点,那这个1970年又是相对于哪个基准时间呢?一般来说,就是相对于格林尼治时间,也叫做GMT(Greenwich Mean Time) 时间,还叫做UTC(Coordinated Universal Time),为啥一个时间基准有两个名字?历史上,先有的GMT,后有的UTC。
UTC是我们现在用的时间标准,GMT是老的时间计量标准。UTC是根据原子钟来计算时间,而GMT是根据地球的自转和公转来计算时间。
所以,可以认为UTC是真正的基准时间,GMT相对UTC的偏差为0。
在实际中,我们的计算机中有一个硬件模块RCT,里面会实时记录UTC 时间,该模块有单独的电池供电,即使关机也不影响。
有了epoch这个时间基准,又有了UTC这个基准的基准,我们就可以精确地表示一个时间了。
时间戳,epoch,utc三者之间的关系如下图所示:

3,DST, tzone
尽管我们已经可以精确地表示一个时间,很多情况下,我们还是要根据地区实际情况对时间进行一个调整,最常见的就是时区,tzone,相信大家都比较熟悉。此时,当我们说5点5分这个时间时,还需加上是哪个时区的5点5分才能精确说明一个时间。
另外一个对时间做出调整的就是DST。
DST 全称是Daylight Saving Time,是说,为了充分利用日光,减少用电,人为地对时间做出一个调整,这取决于不同国家和地区的政策法规。比如说,假设你冬天7点天亮起床,但夏天6点天亮,那么在夏天到来时人为将时间加1个小时,这样就可以让你还是觉得7点起床,但实际上是提前一个小时了。
那么,好奇的我们,一定要问一问,python是如何知道tzone和DST这两个的值呢?答案是通过环境变量。
这里我们只以linux为例来说明一下。
在linux中有TZ环境变量,其值类似这样: CST+08EDT,M4.1.0,M10.5.0,这个字符串可以做如下解读,用空格分开他们,分成三部分
CST+08
EDT,
M4.1.0,M10.5.0
第一部分中的CST表示时区的名字,即China Standard Time,也就是我们说的北京时间,+8表示北京时间加上8小时就是UTC时间
第二部分EDT表示DST的名字,我们说DST是因各个国家地区的政策法规不同而不同的,EDT后面也可以像CST后面一样加一个时间调整值,但由于我们国内只在86年到92年实行过一段时间DST,现在已经废止,所以后面不用加调整时间。
第三部分表示的是实行DST的开始和结束时间,我们就不细解读了。
4,时间的表示,获取,转换
time模块中获取时间的基本方法是 now = time.time() 它返回的是从epoch到现在的秒数(用浮点数表示),用的是UTC时间。
如下图所示:

我们自然而然地想把这个秒数转为年月日时分秒的形式,而这种转换又分两种:
一种还是用UTC时间, 一种用我们所在时区进行调整后的时间。
time模块给我们提供了两个方法,
time.gmtime(t)
time.localtime(t)
二者都返回一个类struct_time的实例,如下图所示:

可以看到local time 确实比 gmt time 多了8个小时,说明确实是根据北京时区进行了调整。前面我们讲过 CST+08表示的就是北京时间比utc要加8个小时。
struct_time实例具有如下属性:

相比用秒数表示的时间,这样的表示更适合我们理解。
除了以上属性,struct_time还有一个tm_zone属性。如下图所示:

这两个函数如果调用时不传参数,它们内部会调用time.time(),并用返回的秒数做转换。
相反的,python同样提供了将这两种struct_time转为秒数的方法。
calendar.timegm()
方法用来把UTC的struct_time(gmtime的返回对象)转为从epoch开始的秒数
time.mktime()
用来把用时区调整过的struct_time(即localtime的返回对象)对象转为从epoch开始的秒数
如下图所示:

struct_time中已经有时区信息了,为啥还要专门分两个函数呢?
因为我们有可能从字符串中构建struct_time,而字符串中可能没有时区信息。
时间和表示时间的字符串之间进行转换
time模块中的strftime和strptime就是做这个用的。
看名字大家就应该知道它们的含义:
strftime 即 string format time,用来将时间格式化成字符串
strptime 即string parse time,用来将字符串解析成时间。
需要注意的是,这里的时间都是struct_time对象。
关于怎么格式化时间,就借用官网文档的内容了。

这里给一个简单的例子:

除了这两个函数,time模块中还提供了两个简便方法,来帮助将时间转为字符串
asctime
用来将一个struct_time对象转为标准24字符的字符串,如下所示:

ctime
它与asctime作用相同,只不过它接收的是秒数,在内部,会先把秒数通过localtime转为struct_time,再往后就与asctime一样了。
如下图所示:

以上就是time模块的核心内容,下面,我们要开始学习datetime模块。
datetime模块
time模块解决了时间的获取和表示,datetime模块则进一步解决了快速获取并操作时间中的年月日时分秒信息的能力。
1, datetime的时区问题
现在,让我们对时间中的时区进行一个小结。
首先,timestamp是基于utc的,它代表从epoch开始的秒数,而epoch是基于utc。在所有的时间表示中,它就是一个转换桥梁。
然后,我们学会了将timestamp转变成utc的struct_time和cst的struct_time。此时,struct_time中携带了时区信息。
紧接着,为了将struct_time变回timestamp,我们分别学习了 calendar.timegm和time.mktime。这两个方法会忽略struct_time中的时区信息,分别按照utc和local时区(也就是环境变量中的时区)进行转换。
当我们开始使用datetime.datetime时,时区问题变得复杂了。因为datetime有两种,一种是有时区信息的,一种是没有时区信息的。如下所示:

可以看到,如果没有使用pytz指定utc时区,默认的datetime是没有时区信息的。没有时区信息,意味着,这个datetime的具体时间是啥留给使用该对象的我们来解释了。
即使我们从timestamp中创建datetime,默认情况下,也是忽略了时区信息。

上述方法,得到的datetime是按照local时区转换过的,但是datetime并不携带时区信息。这里略有点绕,需要注意一下。
此时,必须明确指定时区,才能让datetime携带时区信息。

为了将struct_time转变为datetime,我们必须先转成timestamp,在这步转换中,必须合理地使用 calendar.gmtime和time.mktime。否则timestamp就会是错误的值。
2、时区的最佳实践
因为时区的存在,如何表示和存储时间就是一个麻烦的问题。最佳的方案就是存储时使用UTC,显示时使用local。
但是万事没绝对,你还是需要根据你实际的情况决定如何使用时区。
3、datetime和str的转换:
string转datetime
dt = datetime.datetime.strptime('2020-11-18 11:39:31 CST', "%Y-%m-%d %H:%M:%S %Z")
上面的转换中,尽管字符串中有时区信息,但是得到的dt对象仍然是没有时区信息的。这就是python中datetime容易令人困惑的地方,除非明确指定,否则datetime对象就是没有时区信息的。
要判断一个datetime对象有时区信息,需要同时满足两个条件:
dt.tzinfo不为空 dt.tzinfo.utcoffset(None) 返回不为空
稍加验证,即可知道。
datetime转string

可以看到,时区信息已经丢失了。
Note: datetime和time模块中都有strftime、striptime。作用还是不一样的。
datetime转时间戳
time_time = time.mktime(date_time.timetuple())
上述代码中,需要确保date_time是local的,否则表示就会错误。
时间戳转datetime
文章前面已有。
总结
表示时间是一个挺复杂的事情,因为时间本身就是复杂的。认真学习一下时间的表示和转换,无疑是有long term value的。因为时间是永恒的。
往期精彩回顾





