共计 4795 个字符,预计需要花费 12 分钟才能阅读完成。
导读 | python 中处理时间的模块有三个,datetime, time,calendar,融汇贯通三个模块,才能随心所欲地用 python 处理时间。本文就是为此而写,文章着重点在于梳理出三个模块的设计脉络,便于大家记忆里面的 api。在需要的时候能够去查找相应的方法。但由于 calendar 模块使用不多,限于篇幅,本文没有涉及。 |
datetime 模块主要是用来表示日期的,就是我们常说的年月日时分秒,calendar 模块主要是用来表示年月日,是星期几之类的信息,time 模块主要侧重点在时分秒,粗略从功能来看,我们可以认为三者是一个互补的关系,各自专注一块。方便用户依据不同的使用目的选用趁手的模块。
为了学习 time 模块,我们需要先知道几个与时间相关的概念:
假设我们要将时间表示成毫秒数,比方说 1000000 毫秒,那有一个问题必须解决,这个 1000000 毫秒的起点是什么时间,也就是我们的时间基准点是什么时间?好比我说你身高 1.8 米,那这个身高是指相对于你站立的地面说的。这个时间基准点就是 epoch,在 Unix 系统中,这个基准点就是 1970 年 1 月 1 日 0 点整那个时间点。
上面我们说 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 这个基准的基准,我们就可以精确地表示一个时间了。
尽管我们已经可以精确地表示一个时间,很多情况下,我们还是要根据地区实际情况对时间进行一个调整,最常见的就是时区,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 的开始和结束时间,我们就不细解读了。
time 模块中获取时间的基本方法是
t = time.time()
它返回的是从 epoch 到现在的秒数(用浮点数表示),用的是 UTC 时间。
我们自然而然地想把这个秒数转为年月日时分秒的形式,而这种转换又分两种,一种还是用 UTC 时间,一种用我们所在时区进行调整后的时间。
time 模块给我们提供了两个方法,
time. gmtime(t)
time.localtime(t)
二者都返回一个类 struct_time 的实例,该实例具有如下属性:
相比用秒数表示的时间,这样的表示更适合我们理解。
这两个函数如果调用时不传参数,它们内部会调用 time.time(),并用返回的秒数做转换。
相反的,python 同样提供了将这两种 struct_time 转为秒数的方法。
calendar.timegm()方法用来把 UTC 的 struct_time(gmtime 的返回对象)转为从 epoch 开始的秒数
time.mktime()用来把用时区调整过的 struct_time(即 localtime 的返回对象)对象转为从 epoch 开始的秒数
也就是说 mktime 方法会先找到系统中的时区和 DST 信息,并利用这个信息对 struct_time 进行调整后再换算成秒数。
另一种常见的需求是在时间和表示时间的字符串之间进行转换。
time 模块中的 strftime 和 strptime 就是做这个用的。
看名字大家就应该知道它们的含义,
strftime 即 string format time,用来将时间格式化成字符串
strptime 即 string parse time,用来将字符串解析成时间。
需要注意的是,这里的时间都是 struct_time 对象。
关于怎么格式化时间,是很简单的知识,这里就借用官网文档的内容了。
除了这两个函数,time 模块中还提供了两个简便方法,来帮助将时间转为字符串
asctime 用来将一个 struct_time 对象转为标准 24 字符的字符串,如下所示:
Sun Jun 20 23:21:05 1993
ctime 方法与 asctime 作用相同,只不过它接收的是秒数,在内部,会先把秒数通过 localtime 转为 struct_time,再往后就与 asctime 一样了。
以上就是 time 模块的核心内容,我尝试用一个口诀帮助记忆这些 API
time 点 time 得秒数
传入 gm, local time 得 struct_time
要想变回原秒数
你得传回 calendar.timegm 和 time. mktime
string f 和 string p
格式化时间靠哥俩
你要还是嫌费事
asctime ,ctime 来助力
专门帮你转字符串
前者接收 struct_time
后者专门处理秒数
分工合作不费力
学好 time 模块基本功
做个时间的明白人!
下面,我们要开始学习 datetime 模块。
time 模块解决了时间的获取和表示,datetime 模块则进一步解决了快速获取并操作时间中的年月日时分秒信息的能力。
简单说,该模块核心的类就三个,date 类表示年月日,time 类表示时分秒毫秒,这里不要和 time 模块搞混淆了。一句顺口溜可以帮助记清这个情况:
time 里面没 time
藏在 datetime 里
编的是不是不咋地?嗯,我也这么觉得。
datetime 类就是 date 和 time 的组合。
有一点需要提前说明一下,time 类和 datetime 类都有一个属性,它的值是一个 tzinfo 对象,里面包含了该 time 或者 datetime 的时区信息,一般称这个 time 或者 datetime 对象是 aware 的,它能够准确换算成自 epoch 开始的秒数。
如果该属性设置为 None,那么,这时的 time 对象或者 datetime 对象就没有时区信息,具体它表示的是 local time 还是 utc time,需要我们自己在程序中去决定。
这里我们所说的 local time 是指我们所在时区的时间,utc time 指的就是国际标准时间,也就是格林尼治时间。下文同。
请记住一点,date 中是没有时区信息的。
创建 datetime 对象,我最常用的办法如下
dt=datetime.datetime.fromtimestamp(time.time())
以上,time.time()获得自 epoch 开始的秒数,fromtimestamp 方法会将这个秒数转变成一个 datetime 对象。
这里有一个问题,这个 datetime 对象究竟是 utc 的还是 local 的?
答案是 local 的,这是该方法的默认行为。如果你在 fromtimestamp 方法中传入一个表示时区的参数,即 tzinfo 对象,就会按传入的时区信息进行转换。
获得表示当前 local 时间的 datetime 对象,还有两个简便方法
datetime. datetime. now()
datetime. datetime. today()
以上我们得到的都是 local 的 datetime 对象,如何获得 utc 的 datetime 对象呢?有两个办法
datetime. datetime. utcfromtimestamp()
datetime. datetime. utcnow()
我们还可以从字符串中创建 datetime 对象,
其内部还是先调用的 time 模块中的 striptime 方法,获取 struct_time 对象,再利用 struct_time 对象中的年月日时分秒信息构建 datetime 对象。
同样的,datetime 类也提供了 strftime(),asctime(),ctime()方法,相信不说你也知道是做什么的了。
datetime 类还提供了一个 combine 方法,用来将一个 date 对象和一个 time 对象组合成一个 datetime 对象。
需要注意的是,datetime 模块中出现 timestamp 时,一般可将其理解成 time.time()返回的秒数
date 对象的创建和 datetime 非常相似,
datetime. date. today()
datetime.date.fromtimestamp()都可以创建一个 date 对象。
当然,你也可以通过构造方法传入年月日来创建 date 对象。
相比之下,time 对象的创建就很有限,只能通过
datetime.time([hour[, minute[, second[, microsecond[, tzinfo]]]]])
这个方法创建。
在实际使用中,我们有一大块需求就是对日期进行比较和加减运算。得益于 python 的操作符重载能力,python 中可以方便地对
date 对象之间,或者 datetime 对象之间进行小于 (<) 比较和减法 (-) 操作。
注意,这里仅限于同类对象之间,而且,不包括 time 对象之间。
两个 date 对象作减,或者两个 datetime 对象之间作减,差值用一个 timedelta 对象表示。
同理,一个 date 对象或者 datetime 对象也可以加或者减一个 timedelta 对象。
一个 timedelta 对象含有三个属性:days,seconds, microseconds,days 属性可以取负值,另外两个属性都只能是正值。
你可以用 total_seconds()方法获得一个 timedelta 对象的秒数表示。
两个 timedelta 对象之间可加,可减,但不能做大小比较,因为这样没什么意义。
一个 timedelta 对象还可以与整数相乘,或通过 // 操作与一个整数相除。
还可以取反,或者用 abs 函数获得绝对值
本文的目的不在于详细说明 python 处理时间日期的 api 如何使用,而是想通过一个概览的形式,让大家抓住 time 和 datetime 模块的设计结构,从而能够清楚这些模块提供了哪些能力,在需要的时候能够想起来去用,至于查详细的 api,应该是可以轻松解决的。