跳转到主要内容

在测试中穿越时空。

项目描述

https://img.shields.io/github/actions/workflow/status/adamchainz/time-machine/main.yml.svg?branch=main&style=for-the-badge https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge https://img.shields.io/pypi/v/time-machine.svg?style=for-the-badge https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge pre-commit

在测试中穿越时空。

快速示例

import datetime as dt
from zoneinfo import ZoneInfo
import time_machine

hill_valley_tz = ZoneInfo("America/Los_Angeles")


@time_machine.travel(dt.datetime(1985, 10, 26, 1, 24, tzinfo=hill_valley_tz))
def test_delorean():
    assert dt.date.today().isoformat() == "1985-10-26"

关于一些背景信息,请参阅介绍博客文章基准测试博客文章


测试Django项目? 请查看我的书籍加快您的Django测试速度,它涵盖了编写更快、更准确测试的多种方法。我在写这本书的时候创建了time-machine。


安装

使用 pip

python -m pip install time-machine

支持Python 3.8到3.13。目前只支持CPython,因为time-machine直接连接到C级API。

使用方法

如果您来自freezegun或libfaketime,请参阅下面的迁移部分。

travel(destination, *, tick=True)

travel() 是一个允许时间旅行的类,可以移动到由 destination 指定的日期时间。它是通过模拟 Python 标准库中所有返回当前日期或日期时间的函数来实现的。它可以独立使用,作为函数装饰器,或作为上下文管理器。

destination 指定了要移动到的日期时间。它可以是指:

  • 一个 datetime.datetime。如果是无知的,它将被假定为 UTC 时区。如果设置了 tzinfo 为一个 zoneinfo.ZoneInfo 实例,当前时区也将被模拟。

  • 一个 datetime.date。这将转换为 UTC 日期时间,时间为 00:00:00。

  • 一个 datetime.timedelta。这将被解释为相对于当前时间。如果在 travel() 块内,则 shift() 方法更易于使用(下面将进行说明)。

  • 一个指定 Unix 时间戳floatint

  • 一个字符串,它将使用 dateutil.parse 进行解析,并转换为时间戳。如果结果是无知的,它将被假定为本地时间。

此外,您还可以提供一些更复杂的数据类型:

  • 一个生成器,在这种情况下,将对它调用 next(),并将结果按上述方式处理。

  • 一个可调用对象,在这种情况下,它将不带参数被调用,并将结果按上述方式处理。

tick 定义了在旅行后时间是否继续“滴答”或被冻结。如果为 True,默认值,则对模拟函数的后续调用返回的值将增加自第一次调用以来经过的实时时间。因此,在开始旅行到 0.0(UNIX 纪元)后,对任何日期时间函数的第一次调用将返回其 1970-01-01 00:00:00.000000 的表示。后续调用“滴答”,所以如果调用正好晚半秒钟,它将返回 1970-01-01 00:00:00.500000

模拟的函数

标准库中的所有日期时间函数都被模拟,以移动到目标当前日期时间

  • datetime.datetime.now()

  • datetime.datetime.utcnow()

  • time.clock_gettime()(仅适用于 CLOCK_REALTIME

  • time.clock_gettime_ns()(仅适用于 CLOCK_REALTIME

  • time.gmtime()

  • time.localtime()

  • time.monotonic()(不是真正的单调时钟,返回 time.time()

  • time.monotonic_ns()(不是真正的单调时钟,返回 time.time_ns()

  • time.strftime()

  • time.time()

  • time.time_ns()

模拟是在 C 层面上完成的,替换了这些内置函数的函数指针。因此,它自动影响所有这些函数被导入的地方,与使用 unittest.mock.patch() 不同。

start() / stop() 一起使用

要独立使用,创建一个实例,使用 start() 移动到目标时间,并使用 stop() 返回。例如

import datetime as dt
import time_machine

traveller = time_machine.travel(dt.datetime(1985, 10, 26))
traveller.start()
# It's the past!
assert dt.date.today() == dt.date(1985, 10, 26)
traveller.stop()
# We've gone back to the future!
assert dt.date.today() > dt.date(2020, 4, 29)

travel() 实例可以嵌套,但在手动管理时,需要小心调用它们的 stop() 方法,尤其是在出现异常时。建议使用装饰器或上下文管理器形式,以利用 Python 特性来完成此操作。

函数装饰器

当用作函数装饰器时,在包装函数的执行期间会模拟时间

import time
import time_machine


@time_machine.travel("1970-01-01 00:00 +0000")
def test_in_the_deep_past():
    assert 0.0 < time.time() < 1.0

您还可以装饰异步函数(协程)

import time
import time_machine


@time_machine.travel("1970-01-01 00:00 +0000")
async def test_in_the_deep_past():
    assert 0.0 < time.time() < 1.0

注意:时间是 全局 状态 - 见下文

上下文管理器

当用作上下文管理器时,在 with 块期间会模拟时间

import time
import time_machine


def test_in_the_deep_past():
    with time_machine.travel(0.0):
        assert 0.0 < time.time() < 1.0

类装饰器

仅支持 unittest.TestCase 子类。当将这些类用作类装饰器时,时间从 setUpClass() 的开始模拟到 tearDownClass() 的结束

import time
import time_machine
import unittest


@time_machine.travel(0.0)
class DeepPastTests(TestCase):
    def test_in_the_deep_past(self):
        assert 0.0 < time.time() < 1.0

注意,这与 unittest.mock.patch() 的行为不同,它仅在测试方法期间模拟。有关 pytest 风格的测试类,请参阅下文文档中的模式 documented below

时区模拟

如果传递给 time_machine.travel()Coordinates.move_to()destination 将其 tzinfo 设置为 zoneinfo.ZoneInfo 实例,则当前时区将被模拟。这将通过调用 time.tzset() 来完成,因此它仅在 Unix 上可用。zoneinfo 模块是 Python 3.8 中新增的 - 在较旧的 Python 版本中使用由原始 zoneinfo 作者提供的 backports.zoneinfo

time.tzset() 改变了 time 模块的时间区常量及其依赖特性,如 time.localtime()。它不会影响其他“当前时区”的概念,例如 Django 的(可以通过其 timezone.override() 来更改)。

以下是一个更改当前时区的示例

import datetime as dt
import time
from zoneinfo import ZoneInfo
import time_machine

hill_valley_tz = ZoneInfo("America/Los_Angeles")


@time_machine.travel(dt.datetime(2015, 10, 21, 16, 29, tzinfo=hill_valley_tz))
def test_hoverboard_era():
    assert time.tzname == ("PST", "PDT")
    now = dt.datetime.now()
    assert (now.hour, now.minute) == (16, 29)

坐标

start() 方法以及上下文管理器的入口都返回一个与给定“旅行”时间对应的 Coordinates 对象。它有几个可以用来在其它时间旅行的方法。

move_to(destination, tick=None)

move_to() 将当前时间移动到新的目的地。目的地可以是 travel 支持的任何类型。

tick 可以设置为布尔值,以更改 traveltick 标志。

例如

import datetime as dt
import time
import time_machine

with time_machine.travel(0, tick=False) as traveller:
    assert time.time() == 0

    traveller.move_to(234)
    assert time.time() == 234

shift(delta)

shift() 接受一个参数 delta,它将当前时间移动给定的偏移量。 delta 可以是 timedelta 或秒数,它将被添加到目的地。它可以负数,在这种情况下,时间将移动到较早的点。

例如

import datetime as dt
import time
import time_machine

with time_machine.travel(0, tick=False) as traveller:
    assert time.time() == 0

    traveller.shift(dt.timedelta(seconds=100))
    assert time.time() == 100

    traveller.shift(-dt.timedelta(seconds=10))
    assert time.time() == 90

pytest 插件

time-machine 还可以作为 pytest 插件使用。它提供了一个函数作用域的固定装置,名为 time_machine,具有 move_to()shift() 方法,它们的签名与 Coordinates 中的等效方法相同。这可以用来在不同的时间点模拟测试,并在测试拆解时自动取消模拟。

例如

import datetime as dt


def test_delorean(time_machine):
    time_machine.move_to(dt.datetime(1985, 10, 26))

    assert dt.date.today().isoformat() == "1985-10-26"

    time_machine.move_to(dt.datetime(2015, 10, 21))

    assert dt.date.today().isoformat() == "2015-10-21"

    time_machine.shift(dt.timedelta(days=1))

    assert dt.date.today().isoformat() == "2015-10-22"

如果您正在使用 pytest 测试类,您可以通过添加自动使用固定装置将固定装置应用于类中的所有测试方法

import time

import pytest


class TestSomething:
    @pytest.fixture(autouse=True)
    def set_time(self, time_machine):
        time_machine.move_to(1000.0)

    def test_one(self):
        assert int(time.time()) == 1000.0

    def test_two(self, time_machine):
        assert int(time.time()) == 1000.0
        time_machine.move_to(2000.0)
        assert int(time.time()) == 2000.0

escape_hatch

“escape_hatch”对象提供了绕过时间机的函数。这些函数允许您调用真实的datetime函数,而不进行任何模拟。它还提供了一种方法来检查时间机是否正在当前进行时间旅行。

在罕见情况下,这些功能很有用。例如,如果您需要在时间旅行期间与外部服务进行身份验证,您可能需要datetime.now()的真实值。

这些函数是

  • escape_hatch.is_travelling() -> bool - 如果time_machine.travel()处于活动状态,则返回True,否则返回False

  • escape_hatch.datetime.datetime.now() - 包装真实的datetime.datetime.now()

  • escape_hatch.datetime.datetime.utcnow() - 包装真实的datetime.datetime.utcnow()

  • escape_hatch.time.clock_gettime() - 包装真实的time.clock_gettime()

  • escape_hatch.time.clock_gettime_ns() - 包装真实的time.clock_gettime_ns()

  • escape_hatch.time.gmtime() - 包装真实的time.gmtime()

  • escape_hatch.time.localtime() - 包装真实的time.localtime()

  • escape_hatch.time.strftime() - 包装真实的time.strftime()

  • escape_hatch.time.time() - 包装真实的time.time()

  • escape_hatch.time.time_ns() - 包装真实的time.time_ns()

例如

import time_machine


with time_machine.travel(...):
    if time_machine.escape_hatch.is_travelling():
        print("We need to go back to the future!")

    real_now = time_machine.escape_hatch.datetime.datetime.now()
    external_authenticate(now=real_now)

注意事项

时间是全局状态。任何并发线程或异步函数也会受到影响。有些可能还没有准备好这么快地或向后移动时间,可能会崩溃或产生意外的结果。

请注意,其他进程不会受到影响。例如,如果您在数据库服务器上使用SQL日期时间函数,它们将返回真实的时间。

比较

有一些以前的库试图实现相同的功能。它们有各自的优势和劣势。以下是快速比较。

unittest.mock

标准库的unittest.mock可用于针对datetimetime的导入来更改返回的当前时间值。不幸的是,这是脆弱的,因为它仅影响模拟的目标导入位置。因此,如果您有多个模块在调用树中请求日期/时间,则需要多个模拟。这是unittest.mock的通用问题 - 请参阅为什么你的模拟不起作用

无法模拟某些引用,例如函数默认参数

def update_books(_now=time.time):  # set as default argument so faster lookup
    for book in books:
        ...

尽管这种情况很少见,但它们有时被用于优化高度重复的循环。

freezegun

Steve Pulec的freezegun库是一种流行的解决方案。它提供了一个清晰的API,这是时间机的大部分灵感来源。

其主要缺点是其实施速度较慢。它基本上是对所有已导入datetimetime模块的位置进行了查找和替换的模拟。这绕过了使用unittest.mock的问题,但意味着模拟所需的时间与加载的模块数量成正比。在大型项目中,这可能需要几秒钟,对于单个测试来说是不切实际的额外开销。

它也不是一个完美的搜索,因为它只搜索模块级别的导入。这种导入无疑是项目使用日期和时间函数最常见的方式,但并非唯一的方式。freezegun无法找到被“隐藏”在任意对象中的函数,例如类级别的属性。

它也无法影响调用标准库函数的C扩展,包括(我相信)Cython化的Python代码。

python-libfaketime

Simon Weber的python-libfaketime封装了libfaketime库。libfaketime用自身的封装替换了所有针对当前时间的C级系统调用。因此,它是对当前进程的“完美”模拟,影响当前时间可能被获取的每个点,并且比freezegun执行速度快得多。

不幸的是,python-libfaketime带有LD_PRELOAD的限制。这是一个在程序加载时替换系统库的机制(解释)。这在使用python-libfaketime时导致两个特别的问题。

首先,LD_PRELOAD仅适用于Unix平台,这阻止你在Windows上使用它。

其次,你必须帮助管理LD_PRELOAD。你可以使用python-libfaketime的reexec_if_needed()函数,该函数在加载时重启(重新执行)你的测试进程,或者手动管理LD_PRELOAD环境变量。这两种方法都不理想。重新执行会破坏可能封装你的测试进程的任何内容,如分析器、调试器和IDE测试运行器。手动管理环境变量会带来一些开销,并且必须在运行测试的每个环境中执行,包括每个开发者的机器。

time-machine

time-machine旨在结合freezegun和libfaketime的优点。它不使用LD_PRELOAD,但仍然在所有可能引用标准库函数的地方进行模拟。它的弱点是它不会模拟使用日期和时间系统调用的其他库。幸运的是,这种情况很少见。也有可能将这些python库添加到time-machine模拟的集合中。

一个缺点是它仅适用于CPython,因此不能与其他Python解释器(如PyPy)一起使用。然而,它可能通过不同的模拟机制扩展以支持其他解释器。

从libfaketime或freezegun迁移

freezegun有一个有用的API,python-libfaketime复制了一些,但函数名不同。time-machine也复制了一些freezegun的API,在travel()的destination和tick参数中,以及shift()方法中。有一些区别

  • time-machine的tick参数默认为True,因为代码往往会合理地假设时间在运行时是进展的,并且通常应该按此方式测试。使用时间冻结进行测试可以很容易地编写完整的断言,但这相当人为。编写针对时间范围的断言,而不是针对精确值。

  • freezegun将日期和naive datetime解释为本地时区(包括从带有dateutil的字符串解析的日期)。这意味着在某个时区运行时测试可以通过,而在另一个时区运行时失败。time-machine相反,将日期和naive datetime解释为UTC,因此它们是固定的时间点。在需要时提供时区。

  • freezegun的tick()方法已实现为shift(),以避免与tick参数混淆。它还要求一个显式的delta,而不是默认为1秒。

  • freezegun的tz_offset参数不受支持,因为它只部分模拟当前时区。时区比从UTC的单一偏移更复杂,而freezegun只使用time.localtime()中的偏移。相反,如果你提供一个带有ZoneInfo时区的datetime,time-machine将模拟当前时区。

某些功能不受支持,例如auto_tick_seconds参数。这些可能在未来的版本中添加。

如果你只进行了相对简单的函数调用,你应该可以通过将freezegun.freeze_time()libfaketime.fake_time()的调用替换为time_machine.travel()来迁移。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源代码分发

time_machine-2.15.0.tar.gz (25.1 kB 查看哈希值)

上传时间 源代码

构建分发

time_machine-2.15.0-cp313-cp313-win_arm64.whl (18.3 kB 查看哈希值)

上传时间 CPython 3.13 Windows ARM64

time_machine-2.15.0-cp313-cp313-win_amd64.whl (20.2 kB 查看哈希值)

上传时间 CPython 3.13 Windows x86-64

time_machine-2.15.0-cp313-cp313-win32.whl (19.4 kB 查看哈希值)

上传时间 CPython 3.13 Windows x86

time_machine-2.15.0-cp313-cp313-musllinux_1_2_x86_64.whl (33.3 kB 查看哈希值)

上传时间 CPython 3.13 musllinux: musl 1.2+ x86-64

time_machine-2.15.0-cp313-cp313-musllinux_1_2_i686.whl (31.8 kB 查看哈希值)

上传时间: CPython 3.13 musllinux: musl 1.2+ i686

time_machine-2.15.0-cp313-cp313-musllinux_1_2_aarch64.whl (33.7 kB 查看哈希值)

上传时间: CPython 3.13 musllinux: musl 1.2+ ARM64

time_machine-2.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (34.1 kB 查看哈希值)

上传时间: CPython 3.13 manylinux: glibc 2.17+ ARM64

time_machine-2.15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (33.7 kB 查看哈希值)

上传时间: CPython 3.13 manylinux: glibc 2.17+ x86-64 manylinux: glibc 2.5+ x86-64

time_machine-2.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (32.0 kB 查看哈希值)

上传时间: CPython 3.13 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

time_machine-2.15.0-cp313-cp313-macosx_10_13_x86_64.whl (17.0 kB 查看哈希值)

上传时间: CPython 3.13 macOS 10.13+ x86-64

time_machine-2.15.0-cp313-cp313-macosx_10_13_universal2.whl (20.5 kB 查看哈希值)

上传时间: CPython 3.13 macOS 10.13+ universal2 (ARM64, x86-64)

time_machine-2.15.0-cp312-cp312-win_arm64.whl (18.3 kB 查看哈希值)

上传时间: CPython 3.12 Windows ARM64

time_machine-2.15.0-cp312-cp312-win_amd64.whl (20.2 kB 查看哈希值)

上传时间: CPython 3.12 Windows x86-64

time_machine-2.15.0-cp312-cp312-win32.whl (19.4 kB 查看哈希值)

上传时间: CPython 3.12 Windows x86

time_machine-2.15.0-cp312-cp312-musllinux_1_2_x86_64.whl (33.3 kB 查看哈希)

上传于 CPython 3.12 musllinux: musl 1.2+ x86-64

time_machine-2.15.0-cp312-cp312-musllinux_1_2_i686.whl (31.7 kB 查看哈希)

上传于 CPython 3.12 musllinux: musl 1.2+ i686

time_machine-2.15.0-cp312-cp312-musllinux_1_2_aarch64.whl (33.6 kB 查看哈希)

上传于 CPython 3.12 musllinux: musl 1.2+ ARM64

time_machine-2.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (34.0 kB 查看哈希)

上传于 CPython 3.12 manylinux: glibc 2.17+ ARM64

time_machine-2.15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (33.7 kB 查看哈希)

上传于 CPython 3.12 manylinux: glibc 2.17+ x86-64 manylinux: glibc 2.5+ x86-64

time_machine-2.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (31.9 kB 查看哈希)

上传于 CPython 3.12 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

time_machine-2.15.0-cp312-cp312-macosx_11_0_arm64.whl (17.2 kB 查看哈希)

上传于 CPython 3.12 macOS 11.0+ ARM64

time_machine-2.15.0-cp312-cp312-macosx_10_9_x86_64.whl (16.9 kB 查看哈希)

上传于 CPython 3.12 macOS 10.9+ x86-64

time_machine-2.15.0-cp312-cp312-macosx_10_9_universal2.whl (20.4 kB 查看哈希)

上传于 CPython 3.12 macOS 10.9+ universal2 (ARM64, x86-64)

time_machine-2.15.0-cp311-cp311-win_arm64.whl (18.3 kB 查看哈希)

上传于 CPython 3.11 Windows ARM64

time_machine-2.15.0-cp311-cp311-win_amd64.whl (20.2 kB 查看哈希值)

上传时间 CPython 3.11 Windows x86-64

time_machine-2.15.0-cp311-cp311-win32.whl (19.3 kB 查看哈希值)

上传时间 CPython 3.11 Windows x86

time_machine-2.15.0-cp311-cp311-musllinux_1_2_x86_64.whl (32.2 kB 查看哈希值)

上传时间 CPython 3.11 musllinux: musl 1.2+ x86-64

time_machine-2.15.0-cp311-cp311-musllinux_1_2_i686.whl (30.9 kB 查看哈希值)

上传时间 CPython 3.11 musllinux: musl 1.2+ i686

time_machine-2.15.0-cp311-cp311-musllinux_1_2_aarch64.whl (32.5 kB 查看哈希值)

上传时间 CPython 3.11 musllinux: musl 1.2+ ARM64

time_machine-2.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (32.8 kB 查看哈希值)

上传时间 CPython 3.11 manylinux: glibc 2.17+ ARM64

time_machine-2.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (32.6 kB 查看哈希值)

上传时间 CPython 3.11 manylinux: glibc 2.17+ x86-64 manylinux: glibc 2.5+ x86-64

time_machine-2.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (30.9 kB 查看哈希值)

上传时间 CPython 3.11 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

time_machine-2.15.0-cp311-cp311-macosx_10_9_x86_64.whl (16.9 kB 查看哈希值)

上传时间 CPython 3.11 macOS 10.9+ x86-64

time_machine-2.15.0-cp311-cp311-macosx_10_9_universal2.whl (20.4 kB 查看哈希值)

上传时间 CPython 3.11 macOS 10.9+ universal2 (ARM64, x86-64)

time_machine-2.15.0-cp310-cp310-win_arm64.whl (18.4 kB 查看哈希值)

上传于 CPython 3.10 Windows ARM64

time_machine-2.15.0-cp310-cp310-win_amd64.whl (20.3 kB 查看哈希值)

上传于 CPython 3.10 Windows x86-64

time_machine-2.15.0-cp310-cp310-win32.whl (19.4 kB 查看哈希值)

上传于 CPython 3.10 Windows x86

time_machine-2.15.0-cp310-cp310-musllinux_1_2_x86_64.whl (34.0 kB 查看哈希值)

上传于 CPython 3.10 musllinux: musl 1.2+ x86-64

time_machine-2.15.0-cp310-cp310-musllinux_1_2_i686.whl (32.6 kB 查看哈希值)

上传于 CPython 3.10 musllinux: musl 1.2+ i686

time_machine-2.15.0-cp310-cp310-musllinux_1_2_aarch64.whl (34.3 kB 查看哈希值)

上传于 CPython 3.10 musllinux: musl 1.2+ ARM64

time_machine-2.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (34.8 kB 查看哈希值)

上传于 CPython 3.10 manylinux: glibc 2.17+ ARM64

time_machine-2.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (34.6 kB 查看哈希值)

上传于 CPython 3.10 manylinux: glibc 2.17+ x86-64 manylinux: glibc 2.5+ x86-64

time_machine-2.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (32.8 kB 查看哈希值)

上传于 CPython 3.10 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

time_machine-2.15.0-cp310-cp310-macosx_10_9_x86_64.whl (17.0 kB 查看哈希值)

上传于 CPython 3.10 macOS 10.9+ x86-64

time_machine-2.15.0-cp310-cp310-macosx_10_9_universal2.whl (20.8 kB 查看哈希值)

上传于 CPython 3.10 macOS 10.9+ universal2 (ARM64, x86-64)

time_machine-2.15.0-cp39-cp39-win_arm64.whl (18.4 kB 查看哈希值)

上传于 CPython 3.9 Windows ARM64

time_machine-2.15.0-cp39-cp39-win_amd64.whl (20.3 kB 查看哈希值)

上传于 CPython 3.9 Windows x86-64

time_machine-2.15.0-cp39-cp39-win32.whl (19.4 kB 查看哈希值)

上传于 CPython 3.9 Windows x86

time_machine-2.15.0-cp39-cp39-musllinux_1_2_x86_64.whl (33.7 kB 查看哈希值)

上传于 CPython 3.9 musllinux: musl 1.2+ x86-64

time_machine-2.15.0-cp39-cp39-musllinux_1_2_i686.whl (32.4 kB 查看哈希值)

上传于 CPython 3.9 musllinux: musl 1.2+ i686

time_machine-2.15.0-cp39-cp39-musllinux_1_2_aarch64.whl (34.1 kB 查看哈希值)

上传于 CPython 3.9 musllinux: musl 1.2+ ARM64

time_machine-2.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (34.5 kB 查看哈希值)

上传于 CPython 3.9 manylinux: glibc 2.17+ ARM64

time_machine-2.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (34.3 kB 查看哈希值)

上传于 CPython 3.9 manylinux: glibc 2.17+ x86-64 manylinux: glibc 2.5+ x86-64

time_machine-2.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (32.5 kB 查看哈希值)

上传于 CPython 3.9 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

time_machine-2.15.0-cp39-cp39-macosx_10_9_x86_64.whl (17.0 kB 查看哈希值)

上传于 CPython 3.9 macOS 10.9+ x86-64

time_machine-2.15.0-cp39-cp39-macosx_10_9_universal2.whl (20.8 kB 查看哈希值)

上传时间: CPython 3.9 macOS 10.9+ universal2 (ARM64, x86-64)

time_machine-2.15.0-cp38-cp38-win_amd64.whl (20.3 kB 查看哈希值)

上传时间: CPython 3.8 Windows x86-64

time_machine-2.15.0-cp38-cp38-win32.whl (19.4 kB 查看哈希值)

上传时间: CPython 3.8 Windows x86

time_machine-2.15.0-cp38-cp38-musllinux_1_2_x86_64.whl (34.1 kB 查看哈希值)

上传时间: CPython 3.8 musllinux: musl 1.2+ x86-64

time_machine-2.15.0-cp38-cp38-musllinux_1_2_i686.whl (32.7 kB 查看哈希值)

上传时间: CPython 3.8 musllinux: musl 1.2+ i686

time_machine-2.15.0-cp38-cp38-musllinux_1_2_aarch64.whl (34.4 kB 查看哈希值)

上传时间: CPython 3.8 musllinux: musl 1.2+ ARM64

time_machine-2.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (35.5 kB 查看哈希值)

上传时间: CPython 3.8 manylinux: glibc 2.17+ ARM64

time_machine-2.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (35.4 kB 查看哈希值)

上传时间: CPython 3.8 manylinux: glibc 2.17+ x86-64 manylinux: glibc 2.5+ x86-64

time_machine-2.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (33.4 kB 查看哈希值)

上传时间: CPython 3.8 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

time_machine-2.15.0-cp38-cp38-macosx_10_9_x86_64.whl (17.0 kB 查看哈希值)

上传时间: CPython 3.8 macOS 10.9+ x86-64

time_machine-2.15.0-cp38-cp38-macosx_10_9_universal2.whl (20.7 kB 查看哈希值)

上传于 CPython 3.8 macOS 10.9+ universal2 (ARM64, x86-64)

由以下支持