Skip to main content

Travel through time in your tests.

Project description

https://github.com/adamchainz/time-machine/workflows/CI/badge.svg?branch=master https://coveralls.io/repos/adamchainz/time-machine/badge.svg https://img.shields.io/pypi/v/time-machine.svg https://img.shields.io/badge/code%20style-black-000000.svg

Travel through time in your tests.

A quick example:

import datetime as dt
import time_machine

@time_machine.travel("1955-11-05 01:22")
def test_delorean():
    assert dt.date.today().isoformat() == "1955-11-05"

For a bit of background, see the introductory blog post.

Installation

Use pip:

python -m pip install time-machine

Python 3.6 to 3.8 supported (CPython only).

Usage

travel(destination, *, tick=True, tz_offset=None)

travel() is a class that allows time travel, to the datetime specified by destination. It does so by mocking all functions from Python’s standard library that return the current date or datetime. It can be used independently, as a function decorator, or as a context manager.

destination specifies the datetime to move to. It may be:

  • A datetime.datetime. If it is naive, it will be assumed to have the UTC timezone.

  • A datetime.date. This will be converted to a UTC datetime with the time 00:00:00.

  • A float or int specifying a Unix timestamp

  • A string, which will be parsed with dateutil.parse and converted to a timestamp.

Additionally, you can provide some more complex types:

  • A generator, in which case next() will be called on it, with the result treated as above.

  • A callable, in which case it will be called with no parameters, with the result treated as above.

tick defines whether time continues to “tick” after travelling, or is frozen. If True, the default, successive calls to mocked functions return values increasing by the elapsed real time since the first call. So after starting travel to 0.0 (the UNIX epoch), the first call to any datetime function will return its representation of 1970-01-01 00:00:00.000000 exactly. The following calls “tick,” so if a call was made exactly half a second later, it would return 1970-01-01 00:00:00.500000.

tz_offset allows you to offset the given destination. It may be a timedelta or a number of seconds, which will be added to destination. It may be negative.

Mocked Functions

All datetime functions in the standard library are mocked to move to the destination current datetime:

  • datetime.datetime.now()

  • datetime.datetime.utcnow()

  • time.gmtime()

  • time.localtime()

  • time.clock_gettime() (only for CLOCK_REALTIME)

  • time.clock_gettime_ns() (only for CLOCK_REALTIME)

  • time.strftime()

  • time.time()

  • time.time_ns()

The mocking is done at the C layer, replacing the function pointers for these built-ins. Therefore, it automatically affects everywhere those functions have been imported, unlike use of unittest.mock.patch().

Usage with start() / stop()

To use independently, create and instance, use start() to move to the destination time, and stop() to move back. For example:

import datetime as dt
import time_machine

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

travel() instances are nestable, but you’ll need to be careful when manually managing to call their stop() methods in the correct order, even when exceptions occur. It’s recommended to use the decorator or context manager forms instead, to take advantage of Python features to do this.

Usage with shift() method

The start() method and entry of the context manager both return a Coordinates object that corresponds to the time travel. This has a shift() method that takes one argument, delta, which moves the current time. delta may be a timedelta or a number of seconds, which will be added to destination.

For example:

import datetime as dt
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

Function Decorator

When used as a function decorator, time is mocked during the wrapped function’s duration:

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

You can also decorate asynchronous functions (coroutines):

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

Beware: time is a global state - see below.

Context Manager

When used as a context manager, time is mocked during the with block:

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

Class Decorator

Only unittest.TestCase subclasses are supported. When applied as a class decorator to such classes, time is mocked from the start of setUpClass() to the end of 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

Note this is different to unittest.mock.patch()'s behaviour, which is to mock only during the test methods.

Caveats

Time is a global state. Any concurrent threads or asynchronous functions are also be affected. Some aren’t ready for time to move so rapidly or backwards, and may crash or produce unexpected results.

Also beware that other processes are not affected. For example, if you use SQL datetime functions on a database server, they will return the real time.

Comparison

There are some prior libraries that try to achieve the same thing. They have their own strengths and weaknesses. Here’s a quick comparison.

unittest.mock

The standard library’s unittest.mock can be used to target imports of datetime and time to change the returned value for current time. Unfortunately, this is fragile as it only affects the import location the mock targets. Therefore, if you have several modules in a call tree requesting the date/time, you need several mocks. This is a general problem with unittest.mock - see Why Your Mock Doesn’t Work.

It’s also impossible to mock certain references, such as function default arguments:

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

Although this is rare, it’s often used to optimize repeat loops.

freezegun

Steve Pulec’s freezegun library is a popular solution. It provides a clear API which was much of the inspiration for time-machine.

The main drawback is its slow implementation. It essentially does a find-and-replace mock of all the places that the datetime and time modules have been imported. This gets around the problems with using unittest.mock, but it means the time it takes to do the mocking is proportional to the number of loaded modules. In large projects, this can take several seconds, an impractical overhead for an individual test.

It’s also not a perfect search, since it searches only module-level imports. Such imports are definitely the most common way projects use date and time functions, but they’re not the only way. freezegun won’t find functions that have been “hidden” inside arbitrary objects, such as class-level attributes.

It also can’t affect C extensions that call the standard library functions, including (I believe) Cython-ized Python code.

python-libfaketime

Simon Weber’s python-libfaketime wraps the libfaketime library. libfaketime replaces all the C-level system calls for the current time with its own wrappers. It’s therefore a “perfect” mock for the current process, affecting every single point the current time might be fetched, and performs much faster than freezegun.

Unfortunately python-libfaketime comes with the limitations of LD_PRELOAD. This is a mechanism to replace system libraries for a program as it loads (explanation). This causes two issues in particular when you use python-libfaketime.

First, LD_PRELOAD is only available on Unix platforms, which prevents you from using it on Windows. This can be a complete blocker for many teams.

Second, you have to help manage LD_PRELOAD. You either use python-libfaketime’s reexec_if_needed() function, which restarts (re-execs) your test process while loading, or manually manage the LD_PRELOAD environment variable. Neither is ideal. Re-execing breaks anything that might wrap your test process, such as profilers, debuggers, and IDE test runners. Manually managing the environment variable is a bit of overhead, and must be done for each environment you run your tests in, including each developer’s machine.

time-machine

time-machine is intended to combine the advantages of freezegun and libfaketime. It works without LD_PRELOAD but still mocks the standard library functions everywhere they may be referenced. Its weak point is that other libraries using date/time system calls won’t be mocked. Thankfully this is rare. It’s also possible such python libraries can be added to the set mocked by time-machine.

One drawback is that it only works with CPython, so can’t be used with other Python interpreters like PyPy. However it may possible to extend it to support other interpreters through different mocking mechanisms.

Migrating from libfaketime or freezegun

freezegun has a useful API, and python-libfaketime copies some of it, with a different function name. time-machine also copies some of freezegun’s API, in travel()'s destination, tick, and tz_offset arguments, and the shift() method. There are a few differences:

  • time-machine’s tick argument defaults to True, because code tends to make the (reasonable) assumption that time progresses between function calls, and should normally be tested as such. Testing with time frozen can make it easy to write complete assertions, but it’s quite artificial.

  • freezegun’s tick() method has been implemented as shift(), to avoid confusion with the tick argument. It also requires an explicit delta rather than defaulting to 1 second.

Some features aren’t supported like the auto_tick_seconds argument, or the move_to() method. These may be added in a future release.

If you are only fairly simple function calls, you should be able to migrate by replacing calls to freezegun.freeze_time() and libfaketime.fake_time() with time_machine.travel().

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

time-machine-1.1.1.tar.gz (43.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

time_machine-1.1.1-cp38-cp38-manylinux2010_x86_64.whl (32.4 kB view details)

Uploaded CPython 3.8manylinux: glibc 2.12+ x86-64

time_machine-1.1.1-cp38-cp38-manylinux1_x86_64.whl (25.9 kB view details)

Uploaded CPython 3.8

time_machine-1.1.1-cp38-cp38-manylinux1_i686.whl (24.8 kB view details)

Uploaded CPython 3.8

time_machine-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl (12.0 kB view details)

Uploaded CPython 3.8macOS 10.14+ x86-64

time_machine-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl (30.5 kB view details)

Uploaded CPython 3.7mmanylinux: glibc 2.12+ x86-64

time_machine-1.1.1-cp37-cp37m-manylinux1_x86_64.whl (24.3 kB view details)

Uploaded CPython 3.7m

time_machine-1.1.1-cp37-cp37m-manylinux1_i686.whl (23.2 kB view details)

Uploaded CPython 3.7m

time_machine-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl (12.0 kB view details)

Uploaded CPython 3.7mmacOS 10.14+ x86-64

time_machine-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl (28.3 kB view details)

Uploaded CPython 3.6mmanylinux: glibc 2.12+ x86-64

time_machine-1.1.1-cp36-cp36m-manylinux1_x86_64.whl (23.0 kB view details)

Uploaded CPython 3.6m

time_machine-1.1.1-cp36-cp36m-manylinux1_i686.whl (22.0 kB view details)

Uploaded CPython 3.6m

time_machine-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl (11.7 kB view details)

Uploaded CPython 3.6mmacOS 10.14+ x86-64

File details

Details for the file time-machine-1.1.1.tar.gz.

File metadata

  • Download URL: time-machine-1.1.1.tar.gz
  • Upload date:
  • Size: 43.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time-machine-1.1.1.tar.gz
Algorithm Hash digest
SHA256 d60f6e9dbf5d9fcb4a2c9d57a72983dbd521b6b8007b25dddd9922d419c22c6b
MD5 718e2b6043c2f62f6d7a834b7bdec586
BLAKE2b-256 d4db28bda750609c245cb567272160513c7a6228da0e2febedc77cb89fbe486e

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp38-cp38-manylinux2010_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp38-cp38-manylinux2010_x86_64.whl
  • Upload date:
  • Size: 32.4 kB
  • Tags: CPython 3.8, manylinux: glibc 2.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp38-cp38-manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 6ef6369d6b2028dcb870c210a9cabdd098c7697af16b482285142c2c4b5f092f
MD5 f8ea51d72f17d1e6dd34a4f7c21efb05
BLAKE2b-256 13cd4d05fd28643128396c054a844aae230eeb1d9aa83b4a4f155869186eb31f

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp38-cp38-manylinux1_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp38-cp38-manylinux1_x86_64.whl
  • Upload date:
  • Size: 25.9 kB
  • Tags: CPython 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp38-cp38-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 4f6f62de27e3a27bcb6815c7b61f7036591242352ad6dac15d135d31a5fd1f4f
MD5 eb66e0c831e4d6e62d246cb78ee5fee0
BLAKE2b-256 eeb1e3260e3eca3a82ea990fb32ec8ed090c707e10d637ea20326f293f977361

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp38-cp38-manylinux1_i686.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp38-cp38-manylinux1_i686.whl
  • Upload date:
  • Size: 24.8 kB
  • Tags: CPython 3.8
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp38-cp38-manylinux1_i686.whl
Algorithm Hash digest
SHA256 6f3fefc21ae8e2f333f8afb067bbc6aefb9beca3c606cde7610b6554a5709a42
MD5 94d5667be7e21bc067769115cfa50603
BLAKE2b-256 63370adcaeb031f91a2f42b435dd51b00309392b2472d80a241ce73e6fa8f506

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl
  • Upload date:
  • Size: 12.0 kB
  • Tags: CPython 3.8, macOS 10.14+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl
Algorithm Hash digest
SHA256 3437cb10b1157dc516024acce547c45beb7180b3d441ae04889b40ef23fc2f49
MD5 b0facfbc95a8f74dd536011188a92854
BLAKE2b-256 82ebf075ebbe934c0b30c618718e90d16bf3ec9d29e4f137fca9cf8513268a00

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl
  • Upload date:
  • Size: 30.5 kB
  • Tags: CPython 3.7m, manylinux: glibc 2.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 2ee483699ffe8c22259005ded7d63bcbacf3cfaba98bc76ac8142ed5d592df6c
MD5 465c579ff91962e43bd36357ee371429
BLAKE2b-256 0dedb94853d22132efb4ef1b251f86059c0d74ef6be92b00bad1f485fbbdaa4f

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp37-cp37m-manylinux1_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
  • Upload date:
  • Size: 24.3 kB
  • Tags: CPython 3.7m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp37-cp37m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 a92a21326a941eb79585659a2c7930277e0a357d6931b59925830667ea445f16
MD5 059f067916f0cb1d1703bd862d1a2a8f
BLAKE2b-256 08e24210722acbf6beef33891ffd6364bcd7d01ba777e7784e09d1858e08452f

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp37-cp37m-manylinux1_i686.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp37-cp37m-manylinux1_i686.whl
  • Upload date:
  • Size: 23.2 kB
  • Tags: CPython 3.7m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp37-cp37m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 6f2532c750562520ec09212510757b9398b4915c0e48dd6a240bf9fe6c1e8859
MD5 4a1450f27e12771d5ccd2d6bdac7dd75
BLAKE2b-256 3427d8b0baaab6edee65ac15173bffdd99416b8fa154e58c6e4789e08d71a816

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl
  • Upload date:
  • Size: 12.0 kB
  • Tags: CPython 3.7m, macOS 10.14+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl
Algorithm Hash digest
SHA256 d63304e59601980d3300cadee6493fd7f35a97cfc38e5f717c9645b34dfa3f0c
MD5 1419c5133049a4487e4ccc2b348324ae
BLAKE2b-256 222fe712c1e600c8ecc500fa714d014323c69ef367a4489b5668a60fd2424b5b

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl
  • Upload date:
  • Size: 28.3 kB
  • Tags: CPython 3.6m, manylinux: glibc 2.12+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl
Algorithm Hash digest
SHA256 29d1f77f4a68be1f887f2135d4608aaf6e433102df32d52b33a677df028469cb
MD5 b7cd1a1c9857d6744db94a65d676bb93
BLAKE2b-256 6a81e0f8e6bd2e6ac2ecab156c49d8dc3e372f733067f29b9ab077b499564391

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp36-cp36m-manylinux1_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp36-cp36m-manylinux1_x86_64.whl
  • Upload date:
  • Size: 23.0 kB
  • Tags: CPython 3.6m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp36-cp36m-manylinux1_x86_64.whl
Algorithm Hash digest
SHA256 51a603248b94f447098090cd385992191df2a22c019c3d22b63ea178bdf25501
MD5 b5d1eecfd13b7e59fc465b9646363489
BLAKE2b-256 a67c87dfaa8b1bfc6adcb827e7836fb07447cc390a61bbe6401fcf0f076b9d67

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp36-cp36m-manylinux1_i686.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp36-cp36m-manylinux1_i686.whl
  • Upload date:
  • Size: 22.0 kB
  • Tags: CPython 3.6m
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp36-cp36m-manylinux1_i686.whl
Algorithm Hash digest
SHA256 9e9b1028d9d639d264d92d7034d6a06cc9999081066dafa9c843ef6a5a9f297a
MD5 5c25427fb9b9e303831fd67f99934627
BLAKE2b-256 4779ecd2ccd63db6ac9c5d428ecbd7fa0388ca775168dc41efcf46ebc544fd97

See more details on using hashes here.

File details

Details for the file time_machine-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl.

File metadata

  • Download URL: time_machine-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl
  • Upload date:
  • Size: 11.7 kB
  • Tags: CPython 3.6m, macOS 10.14+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.24.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.46.1 CPython/3.8.3

File hashes

Hashes for time_machine-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl
Algorithm Hash digest
SHA256 c067faf1b9460a729bbc8f683720ebd3a5d449d38a620fd7a8c30422c663c716
MD5 669510a001eb144d6183925d24f5b04a
BLAKE2b-256 46a9c329f0d2c8655894ee82c60a890d4fec8e29f941f9e2cd16e3a19cdb970a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page