Python标准库time:处理时间

0

time,顾名思义,是用来处理时间的一个标准库。该库主要由C语言的标准库 time.h 封装而成,因此部分函数的命名可读性一般,整体设计思路也不够面向对象。

此外,Python与时间、日期相关的标准库有若干个,它们的功能可能有一定交集,但是侧重点不同:

  • time库:主要用于处理时间相关的事务,但是功能比较基础
  • datetime库:是 time 库的封装版本,具有更现代化的处理方式
  • calendar库:主要用于得到日历
  • zoneinfo库:主要用于处理时区

时间并不只是日历,为了明白该库的一些用法,不仅需要对地理概念上的时间有所了解,还需要对计算机概念中的时间有所认识。

以下简单介绍一些关于时间的概念:

  • 格林尼治平均时间(Greenwich Mean Time, GMT)

由于全地球每个地方的时间都并不相同,某地处于“早上8时”时,大洋彼岸却并不是“早上8时”,这可能会在交流时引起一定混乱。因此全地球需要一个标准,当人们需要表达明确的时间时,将当地的时间翻译成标准时间以供参照。

这个标准就是格林尼治平均时间 GMT 。它以位于英国伦敦的格林尼治天文台所处的以太阳参照的时间为标准,所谓“平均”指的是消除由于公转引起的太阳升降的差别,而造成每天长度都不完全一致的影响。

  • 协调世界时(Coordinated Universal Time, UTC)

协调世界时由格林尼治平均时间发展而来,是最主要的世界时间标准。它与格林尼治平均时间的区别在于使用原子钟确定精确的秒数,这样报时更准确,因此被许多标准用作标准时间。

其缩写UTC并不完全对应原英文,原因是法文缩写和英文缩写都包含U、T、C三个字母,最后标准采用中庸的方式,决定都不按照这两种语言的缩写排列。

  • Unix计时元年(Unix Epoch)

Unix计时元年是目前大多数计算机和编程语言确定时间原点的一种方式。由于Unix操作系统诞生自1970年,因此该操作系统将时间的原点确定在 1970-01-01 00:00:00(UTC)。目前计算机要处理的大部分时间都发生在这之后,因此可以将该时间点作为坐标原点,以时间的流逝方向作为正向,确定一个计算时间的坐标轴。

  • Unix时间(Unix Time),或Unix时间戳(Unix Timestamp)

这是计算机表示当前时间的一种方式,它表示从Unix计时元年到现在所经过的秒数。例如,在编写这篇文章时,当前的Unix时间戳是 1646380540 。时间戳可以非常方便地标识某一个具体的时间,并对比两个时间点的先后顺序。

gmtime() 是一个转换计算机时间戳到标准UTC时间的函数。例如,可以通过 gmtime(0) 来检查当前计算机的时间原点:

>>> import time
>>> time.gmtime(0)
time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)

结果表示,当前计算机使用Unix时间来表示时间。

结构化时间对象

以上函数得到的结果是一个表示时间的结构化对象,它的用法和 namedtuple 类似,该类包含以下属性:

索引属性名描述索引属性名描述
0tm_year对应的四位数年5tm_sec对应的秒钟
1tm_mon对应的月6tm_wday对应的星期,星期一记为0
2tm_mday对应的日7tm_yday这是一年中的第几天
3tm_hour对应的小时,从0到238tm_isdst是否使用夏令时,0是1否-1不确定
4tm_min对应的分钟

关于夏令时

所谓夏令时,也称日光节约时间(daylight saving time),是一种在夏季将时间调快一小时,以符合人们作息规律的一种时间。目前仅有部分地区采用这种报时方式。

可以通过访问以上结构的某些成员,来获取其中的信息:

>>> epoch = time.gmtime(0)
>>> print(f'Unix timestamp since {epoch.tm_year}-{epoch.tm_mon}-{epoch.tm_mday} {epoch.tm_hour}:{epoch.tm_min}:{epoch.tm_sec} UTC')
Unix timestamp since 1970-1-1 0:0:0 UTC

这样就可以创建需要的日期格式。


注意,由于 struct_time 是一种类似元组的类型,因此它的每个元素都是只读的。在作为参数时,也可以使用包含 9 个元素的元组代替 struct_time 类型。

如果省略参数,那么 gmtime() 会返回当前的 UTC 时间。也可以使用 localtime() ,该函数的用法和 gmtime() 类似,不过得到的是表示本地时区时间的结构化时间对象:

>>> time.gmtime()
time.struct_time(tm_year=2022, tm_mon=3, tm_mday=22, tm_hour=5, tm_min=39, tm_sec=32, tm_wday=1, tm_yday=81, tm_isdst=0)
>>> time.localtime()
time.struct_time(tm_year=2022, tm_mon=3, tm_mday=22, tm_hour=13, tm_min=39, tm_sec=34, tm_wday=1, tm_yday=81, tm_isdst=0)

localtime() 函数,为获取本地时间提供了一个合适的方式。

如果认为 localtime() 得到的结构化时间对象不够易读,可以使用 asctime() 函数将其转换为一个简单的表示时间的字符串描述:

>>> time.asctime(time.localtime())
'Tue Mar 22 13:53:59 2022'

实际上,asctime() 函数在省略参数的情况下默认得到的就是对当前的本地时间描述:

>>> time.asctime(time.localtime()) == time.asctime()
True

时间戳的用法

time() 是一个基本的函数,它用来获取当前(或者说语句执行时)的时间戳:

>>> time.time()
1646384946.4768329

这种方式得到的时间戳可以比秒更精确。

结构化时间对象可以使用 asctime() 来获取字符串描述,而时间戳也有类似功能。ctime() 函数用于将时间戳转化为字符串描述:

>>> time.ctime(time.time())
'Tue Mar 22 14:07:57 2022'

ctime() 函数也可以省略参数,同样默认得到对当前的本地时间描述:

>>> time.ctime() == time.ctime(time.time())
True
>>> time.ctime() == time.asctime()
True

既然有了结构化时间对象,为何要使用时间戳?首先,时间戳的优点在于,只需要对时间戳使用数值上的运算,就可以间接得到日期上的运算。

例如,可以通过加减运算来获取一定间隔前后的日期信息:

>>> day = 3600 * 24
>>> time.ctime(time.time() + 30 * day)  # 30 days later
'Wed Apr 20 23:33:12 2022'

给当前时间戳加上 30 天对应的秒数,就表示 30 天后的时刻,然后便可以调用 ctime() 获取这一时刻的信息。

其次,时间戳可以方便地标识具体的时刻。基于此,只需要通过比较数值大小就可以得出两个时间的先后。

一种常见的需求是计算两个时间的差值,这种情况下只需要对两个时间戳相减即可。例如,以下实现了这样的一个装饰器,可以计算某个函数运行的时间:

import time

def timeit(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        return time.time() - start
    return wrapper

通过以下函数测试它的效果:

@timeit
def test(start, length=10000):
    product = 1
    for i in range(start, start + length):
        product = product * start
    return product

print(test(start=30000))

结果为:

0.07980942726135254

可以看到计时结果还是比较精确的。


时间戳和结构化时间对象是两种基本的时间表示方法。之前说过,可以使用 gmtime() 或 localtime() 来将时间戳转换为结构化时间对象。mktime() 是 localtime() 的逆过程,它可以用一个表示本地时间的结构化时间对象生成时间戳:

>>> time.mktime((2022, 3, 22, 14, 57, 40, 1, 81, 0))
1647932260.0
>>> time.mktime(time.localtime())
1647932333.0

以上使用元组的形式来表示结构化时间对象。

处理时间格式

asctime() 和 ctime() 得到的时间格式可能无法符合需求。此时,一个改善思路就是重新借用 localtime() 返回的结构化时间对象,重新编写一个函数利用它提供的信息得到格式化的字符串。

time 库就提供了这样的一个函数 strftime(format, t=localtime()) 。它利用 format 参数提供的格式字符串,将一个结构化时间对象提供的信息填入其中。

format 参数使用类似 Python2 的百分号形式的格式化字符串。即,以一个百分号紧跟一个字符作为格式化的转换说明。这也是C语言标准的字符串格式方法。

以下给出了一个示例,将当前的时间格式化为另一种阅读习惯的方式:

>>> time.ctime()
'Tue Mar 22 15:15:26 2022'
>>> time.strftime("%Y %b %d %a %H:%M:%d")
'2022 Mar 22 Tue 15:16:22'

下表列出了完整的转换说明符:

符号含义符号含义
%a星期几,英文缩写%p现在是上午(AM)还是下午(PM),使用本地化表述
%A星期几,英文全称%S秒钟,取值为 [00, 61]
%b月份名,英文缩写%U一年的第几周,取值为 [00, 53]
%B月份名,英文全称%w星期几,序号表示,取值为 [0, 6] 。0 表示星期天
%cctime() 等使用的本地化日期时间完整表示格式%W类似 %U ,不过使用星期天而不是星期一来界定第0周的结束
%d日期,取值为 [01, 31]%x本地化日期表示格式
%H24进制小时,取值为 [00, 23] %X本地化时间表示格式
%I12进制小时,取值为 [01, 12]%y两位数年份,取值为 [00, 99]
%j一年的第几天,取值为 [001, 366]%Y四位数年份
%m月份,取值为 [01, 12]%z本地时区与UTC的时间差,取值为 [-23:59, +23:59]
%M分钟,取值为 [00, 59]%%表示一个百分号 %

利用转换说明,就可以自定义时间表示的格式。以下再次给出一个示例:

>>> time.strftime("Tomorrow is %m-%d", time.localtime(time.time() + day))
'Tomorrow is 03-23'

strptime(string, format="%a %b %d %H:%M:%S %Y") 与 strftime() 函数的作用相反,它用于将一个已经格式化后的字符串,通过提供的格式反推得到结构化时间对象。例如:

>>> time.strptime("2020-03-22", "%Y-%m-%d")
time.struct_time(tm_year=2020, tm_mon=3, tm_mday=22, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=82, tm_isdst=-1)

如果提供的信息不足,那么结构化时间对象里的元素将使用默认值。如果提供的字符串与格式字符串不匹配,那么会引发 ValueError 错误。

其余内容

除了日期和时间相关的格式化外,time 库还提供了几个常用的与时间相关的功能。

sleep(secs: float) 是一个常用的函数,它的功能是暂停当前的程序一段时间。它的效果就相当于进入一个空循环,直到达到了设定的时间为止。

@timeit
def delay():
    time.sleep(3.14)

print(delay())

结果为:

3.1410584449768066

当然暂停的时间不会那么精确。暂停过程可以被 KeyboardInterrupt 中断。


perf_counter() 是一个用于精确计时的函数。它并没有确定的计时零点,但是它确定的时间差值却非常精确。因此,上文编写的 timeit() 函数可以使用 perf_counter() 函数用于计时以获取更精确的结果。

下面给出了这样一个示例,使用 time() 函数无法捕捉到如此小的时间差:

start = time.time()
sum(range(1000))
print(time.time() - start)

start = time.perf_counter()
sum(range(1000))
print(time.perf_counter() - start)

结果为:

0.0
4.190000000001137e-05

除此之外,time 库还包括一些其余的函数,包括获取纳秒级别的时间信息、操作Unix的系统时钟等。不过这些都不够常用,具体可以参阅Python官方文档。

最后,用一张图总结 time 库的内容:

参考资料

https://en.wikipedia.org/wiki/Greenwich_Mean_Time

https://en.wikipedia.org/wiki/Coordinated_Universal_Time

https://en.wikipedia.org/wiki/Leap_second

https://en.wikipedia.org/wiki/Unix_time

https://en.wikipedia.org/wiki/Daylight_saving_time

维基百科对一些概念的解释

https://docs.python.org/3/library/time.html

Python3文档——标准库 time

京ICP备2021034974号
contact me by hello@frozencandles.fun