diff --git a/HISTORY.rst b/HISTORY.rst index e1e2c8f..dc83381 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,6 +11,7 @@ History * Remove ``tz_offset`` argument. This was incorrectly copied from ``freezegun``. Use the new timezone mocking with ``ZoneInfo`` instead. * Add pytest plugin and fixture ``time_machine``. +* Work with Windows’ different epoch. 1.3.0 (2020-12-12) ------------------ diff --git a/src/time_machine.py b/src/time_machine.py index 9a36961..ed2cfb2 100644 --- a/src/time_machine.py +++ b/src/time_machine.py @@ -4,6 +4,7 @@ import os import sys import uuid +from time import gmtime as orig_gmtime from time import tzset from types import GeneratorType from typing import Optional @@ -33,6 +34,22 @@ NANOSECONDS_PER_SECOND = 1_000_000_000 +# Windows' time epoch is not unix epoch but in 1601. This constant helps us +# translate to it. +_system_epoch = orig_gmtime(0) +SYSTEM_EPOCH_TIMESTAMP_NS = int( + dt.datetime( + _system_epoch.tm_year, + _system_epoch.tm_mon, + _system_epoch.tm_mday, + _system_epoch.tm_hour, + _system_epoch.tm_min, + _system_epoch.tm_sec, + tzinfo=dt.timezone.utc, + ).timestamp() + * NANOSECONDS_PER_SECOND +) + def extract_timestamp_tzname(destination): if callable(destination): @@ -82,14 +99,15 @@ def time_ns(self): if not self._tick: return self._destination_timestamp_ns + base = SYSTEM_EPOCH_TIMESTAMP_NS + self._destination_timestamp_ns now_ns = self._time_ns() if not self._requested: self._requested = True self._real_start_timestamp_ns = now_ns - return self._destination_timestamp_ns + return base - return self._destination_timestamp_ns + (now_ns - self._real_start_timestamp_ns) + return base + (now_ns - self._real_start_timestamp_ns) if sys.version_info >= (3, 7): diff --git a/tests/test_time_machine.py b/tests/test_time_machine.py index 3721d45..8f6aa5f 100644 --- a/tests/test_time_machine.py +++ b/tests/test_time_machine.py @@ -4,7 +4,7 @@ import time import uuid from importlib.util import module_from_spec, spec_from_file_location -from unittest import SkipTest, TestCase +from unittest import SkipTest, TestCase, mock import pytest from dateutil import tz @@ -249,6 +249,25 @@ def test_time_time(): assert now >= LIBRARY_EPOCH +windows_epoch_in_posix = -11_644_445_222 + + +@mock.patch.object( + time_machine, + "SYSTEM_EPOCH_TIMESTAMP_NS", + (windows_epoch_in_posix * NANOSECONDS_PER_SECOND), +) +def test_time_time_windows(): + with time_machine.travel(EPOCH): + first = time.time() + assert isinstance(first, float) + assert first == windows_epoch_in_posix + + second = time.time() + assert isinstance(second, float) + assert windows_epoch_in_posix < second < windows_epoch_in_posix + 1.0 + + def test_time_time_no_tick(): with time_machine.travel(EPOCH, tick=False): assert time.time() == EPOCH