Implementing Virtual Time in Python
For testing in our product (which does operations management including features like providing shift handovers on industrial plants), we quite often need to simulate a different time, or to move through a period of time fairly rapidly to see what happens when different schedules run etc. It’s possible to do that by altering the system clock, but that has several issues:
- It can have side-effects on the rest of the operating system (so you generally need to do it in a virtual machine)
- It’s hard to make it move through time quickly without confusion resulting
So we created a library called virtualtime (pypi, github) which wraps all the methods in the Python time and datetime libraries to return a simulated time. It works by keeping an offset to the current time, and allows:
- Enabling and disabling virtual time
- Setting the time offset directly
- Setting the current time (by adjusting the offset) – using a time.time()-style constant, a local datetime, or a UTC datetime
- Fast-forwarding through time in small increments, allowing schedulers to indicate when processes that were launched at that time are completed before proceeding
- There’s an API for being notified of changes and for requiring a callback for the fast-forward mentioned above
In order to accomplish this, it needs to patch the time and datetime modules; patching datetime is slightly awkward because you can’t override methods on CPython objects. The library therefore replaces this datetime class with a patchable one, and then has separate methods to enable or disable its adjusted methods. It should therefore be imported as soon as possible when an application starts up (so that other libraries don’t use the unpatched datetime).
We’ve found this useful for manual testing as well as for automated tests that can be kicked off at a virtual time.
We also use a library called python-datetime-tz (github) that enhances Python’s timezone support for datetime objects. The virtualtime library patches this library as well, and we have a fork of the library that supports this alongside some other changes we’ve made