time --- 时间相关函数

time 模块提供了用于获取当前时间和日期、测量时间间隔以及延时的函数。

时间纪元:基于 Alif 和 i.MX RT 的 OpenMV Cam 使用 POSIX 纪元 1970-01-01 00:00:00 UTC。基于 STM32 的 OpenMV Cam 使用纪元 2000-01-01 00:00:00 UTC。可在运行时通过 gmtime(0)[0] 确定纪元年份。

维护实际日历日期/时间:这需要一个实时时钟(RTC)。在 OpenMV Cam 上,系统时间由 machine.RTC 对象提供。当前日历时间可通过 machine.RTC().datetime(tuple) 设置,并由以下方式之一维护:

  • 后备电池(在某些 OpenMV Cam 上是可选组件)。

  • 诸如 ntptime 这样的网络时间协议(需要网络连接)。

  • 在每次上电时手动设置。RTC 通常在软复位后仍能保持,但在掉电时会丢失,除非安装了后备电池。

如果未维护日历时间,下面那些引用当前绝对时间的函数将不会按预期工作。

函数

time.gmtime(secs: int | None = None) Tuple[int, int, int, int, int, int, int, int]
time.localtime(secs: int | None = None) Tuple[int, int, int, int, int, int, int, int]

将以自纪元(见上文)以来的秒数表示的时间 secs 转换为一个 8 元组,其中包含:(year, month, mday, hour, minute, second, weekday, yearday)。如果未提供 secs 或其为 None,则使用 RTC 中的当前时间。

gmtime() 函数返回 UTC 的日期时间元组,localtime() 返回本地时间的日期时间元组。

8 元组中各条目的格式为:

  • year 包含世纪(例如 2014)。

  • month 为 1-12

  • mday 为 1-31

  • hour 为 0-23

  • minute 为 0-59

  • second 为 0-59

  • weekday 为 0-6,对应周一至周日

  • yearday 为 1-366

time.mktime(date_time_tuple: Tuple[int, int, int, int, int, int, int, int]) int

这是 localtime 的反函数。它的参数是一个完整的 8 元组,按 localtime 的方式表示时间。它返回一个整数,即自时间纪元以来的秒数。

time.sleep(seconds: float) None

休眠指定的秒数。seconds 可以是浮点数,以便休眠不足一秒的时间。若需更细粒度或仅限整数的延时,请使用 sleep_ms()sleep_us() 函数。

调用 sleep()(包括 sleep(0))保证会调用待处理的回调函数。

time.sleep_ms(ms: int) None

延时指定的毫秒数,应为正数或 0。

本函数会至少延时给定的毫秒数,但如果必须进行其他处理(例如中断处理程序或其他线程),所花时间可能更长。为 ms 传入 0 仍会允许这些其他处理发生。若需更精确的延时,请使用 sleep_us()

调用 sleep_ms()(包括 sleep_ms(0))保证会调用待处理的回调函数。

time.sleep_us(us: int) None

延时指定的微秒数,应为正数或 0。

本函数尝试提供至少 us 微秒的精确延时,但如果系统有其他更高优先级的处理要执行,所花时间可能更长。

time.ticks_ms() int

返回一个递增的毫秒计数器,其参考点是任意的,并会在某个值之后回绕。

回绕值不会被显式公开,但为了便于讨论,我们将其称为 TICKS_MAX。这些值的周期为 TICKS_PERIOD = TICKS_MAX + 1TICKS_PERIOD 保证是 2 的幂,但除此之外可能因移植而异。为简单起见,ticks_ms()ticks_us()ticks_cpu() 这几个函数都使用相同的周期值。因此,这些函数将返回 [0 .. TICKS_MAX] 范围内(含端点)的值,共 TICKS_PERIOD 个值。请注意,只使用非负值。在大多数情况下,你应将这些函数返回的值视为不透明的。对它们唯一可用的操作是下文所述的 ticks_diff()ticks_add() 函数。

注意:直接对这些值执行标准数学运算(+、-)或关系运算符(<、<=、>、>=)将导致无效结果。先执行数学运算再将其结果作为参数传给 ticks_diff()ticks_add() 同样会导致这些函数返回无效结果。

time.ticks_us() int

与上文的 ticks_ms() 类似,但以微秒为单位。

time.ticks_cpu() int

ticks_ms()ticks_us() 类似,但具有系统中可能的最高分辨率。这通常是 CPU 时钟,函数也因此而得名。但它不一定是 CPU 时钟,系统中可用的其他某种计时源(例如高分辨率定时器)也可以代替使用。本函数确切的计时单位(分辨率)在 time 模块层面未作规定,但特定移植的文档可能会提供更具体的信息。本函数适用于非常精细的基准测试或非常紧凑的实时循环。请避免在可移植代码中使用它。它在所有 OpenMV Cam 上都可用。

time.ticks_add(ticks: int, delta: int) int

将 ticks 值偏移给定的数值,该数值可正可负。给定一个 ticks 值,本函数可按 tick 值的模运算定义(见上文的 ticks_ms())计算其之前或之后 delta 个 tick 的 ticks 值。ticks 参数必须是调用 ticks_ms()ticks_us()ticks_cpu() 函数(或上一次调用 ticks_add())的直接结果。然而,delta 可以是任意整数或数值表达式。ticks_add() 适用于计算事件/任务的截止期限。(注意:你必须使用 ticks_diff() 函数来处理截止期限。)

示例:

# Find out what ticks value there was 100ms ago
print(ticks_add(time.ticks_ms(), -100))

# Calculate deadline for operation and test for it
deadline = ticks_add(time.ticks_ms(), 200)
while ticks_diff(deadline, time.ticks_ms()) > 0:
    do_a_little_of_something()

# Find out TICKS_MAX used by this port
print(ticks_add(0, -1))
time.ticks_diff(ticks1: int, ticks2: int) int

测量 ticks_ms()ticks_us()ticks_cpu() 函数返回值之间的 ticks 差值,结果为可能回绕的有符号值。

参数顺序与减法运算符相同,ticks_diff(ticks1, ticks2)ticks1 - ticks2 含义相同。然而,ticks_ms() 等函数返回的值可能回绕,因此直接对它们使用减法会产生不正确的结果。这正是需要 ticks_diff() 的原因,它实现了模运算(更确切地说是环运算),即使对回绕值也能产生正确结果(只要它们彼此相距不太远,见下文)。本函数返回 [-TICKS_PERIOD/2 .. TICKS_PERIOD/2-1] 范围内的 有符号 值(这是二进制补码有符号整数的典型范围定义)。如果结果为负,表示 ticks1 在时间上早于 ticks2。否则表示 ticks1 发生在 ticks2 之后。仅当 ticks1ticks2 相距不超过 TICKS_PERIOD/2-1 个 tick 时,这一结论才 成立。如果不成立,将返回不正确的结果。具体来说,如果两个 tick 值相距 TICKS_PERIOD/2-1 个 tick,函数将返回该值。但如果两者之间已经过去了 TICKS_PERIOD/2 个实时 tick,函数将转而返回 -TICKS_PERIOD/2,即结果值将回绕到可能取值的负值范围。

对上述约束的非正式说明:假设你被锁在一个房间里,除了一只标准的 12 刻度时钟外没有任何监测时间流逝的手段。那么如果你现在看一眼表盘,之后 13 个小时都不再看(例如你睡了很长一觉),那么当你最终再看时,可能会觉得只过了 1 个小时。要避免这种错误,只需定期看时钟即可。你的应用程序也应如此。“睡得太久”这个比喻同样直接映射到应用程序的行为:不要让你的应用程序在任何单个任务上运行太久。分步运行任务,并在各步之间进行时间记录。

ticks_diff() 旨在适应各种使用模式,其中包括:

  • 带超时的轮询。在这种情况下,事件的顺序是已知的,你只需处理 ticks_diff() 的正值结果:

    # Wait for GPIO pin to be asserted, but at most 500us
    start = time.ticks_us()
    while pin.value() == 0:
        if time.ticks_diff(time.ticks_us(), start) > 500:
            raise TimeoutError
    
  • 事件调度。在这种情况下,如果某个事件已逾期,ticks_diff() 的结果可能为负:

    # This code snippet is not optimized
    now = time.ticks_ms()
    scheduled_time = task.scheduled_time()
    if ticks_diff(scheduled_time, now) > 0:
        print("Too early, let's nap")
        sleep_ms(ticks_diff(scheduled_time, now))
        task.run()
    elif ticks_diff(scheduled_time, now) == 0:
        print("Right at time!")
        task.run()
    elif ticks_diff(scheduled_time, now) < 0:
        print("Oops, running late, tell task to run faster!")
        task.run(run_faster=true)
    

注意:不要将 time() 值传给 ticks_diff(),你应对它们使用普通的数学运算。但请注意,time() 也可能(而且终将)溢出。这被称为 https://en.wikipedia.org/wiki/Year_2038_problem

time.time() int

假设底层 RTC 已按上文所述设置并维护,返回自纪元以来的秒数,以整数形式表示。如果未设置 RTC,本函数将返回自某个移植特定的时间参考点以来的秒数(对于没有电池后备 RTC 的嵌入式开发板,通常是自上电或复位以来)。如果你想开发可移植的 MicroPython 应用程序,就不应依赖本函数提供高于秒级的精度。如果你需要更高精度的绝对时间戳,请使用 time_ns()。如果可以接受相对时间,则使用 ticks_ms()ticks_us() 函数。如果你需要日历时间,则不带参数的 gmtime()localtime() 是更好的选择。

与 CPython 的区别

在 CPython 中,本函数返回自 Unix 纪元(1970-01-01 00:00 UTC)以来的秒数,以浮点值表示,通常具有微秒精度。在 OpenMV Cam 上,它返回一个具有一秒精度的 整数——硬件无法在一个浮点数中同时表示长时间范围和亚秒精度——而且纪元因开发板而异(见上文的 时间纪元)。如果没有已设置的电池后备 RTC,它将转而统计自上电/复位以来的秒数。

time.time_ns() int

time() 类似,但返回自纪元以来的纳秒数,以整数形式表示(通常是大整数,因此会在堆上分配内存)。

构造函数

class time.clock

返回一个时钟对象。

方法

tick() None

开始跟踪流逝的时间。

fps() float

停止跟踪流逝的时间并返回当前的 FPS(每秒帧数)。

调用本函数之前请务必先调用 tick

avg() float

停止跟踪流逝的时间并返回当前的平均流逝时间(以毫秒为单位)。

调用本函数之前请务必先调用 tick

reset() None

重置时钟对象。