From 5c5f1b1c0ee9a0af6c2f98a8307d6ecb9ebfdd40 Mon Sep 17 00:00:00 2001 From: Matthew Kenigsberg Date: Fri, 23 Jul 2021 17:35:07 -0400 Subject: [PATCH 1/7] Fix now(tz) now(tz) should return the same time utcnow returns adjusted by whatever offset is contained by tz. Currently, the offset to freeze_time is also added, which is removed by this change The current unit test is wrong because the UTC time is 2:00:00, so GMT5 should be 7:00:00, not 3:00:00 Closes https://github.com/spulec/freezegun/issues/405 --- freezegun/api.py | 2 +- tests/test_operations.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freezegun/api.py b/freezegun/api.py index 81d4da17..bfa44378 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -385,7 +385,7 @@ def timestamp(self): def now(cls, tz=None): now = cls._time_to_freeze() or real_datetime.now() if tz: - result = tz.fromutc(now.replace(tzinfo=tz)) + cls._tz_offset() + result = tz.fromutc(now.replace(tzinfo=tz)) else: result = now + cls._tz_offset() return datetime_to_fakedatetime(result) diff --git a/tests/test_operations.py b/tests/test_operations.py index 303a1dc8..6f7b9a77 100644 --- a/tests/test_operations.py +++ b/tests/test_operations.py @@ -66,7 +66,7 @@ def test_datetime_timezone_real(): @freeze_time("2012-01-14 2:00:00", tz_offset=-4) def test_datetime_timezone_real_with_offset(): now = datetime.datetime.now(tz=GMT5()) - assert now == datetime.datetime(2012, 1, 14, 3, tzinfo=GMT5()) + assert now == datetime.datetime(2012, 1, 14, 7, tzinfo=GMT5()) assert now.utcoffset() == timedelta(0, 60 * 60 * 5) From ca2116f820aee4ecbbfd22d82ad3d09219aa3452 Mon Sep 17 00:00:00 2001 From: Alexandr Priezzhev Date: Mon, 18 Dec 2023 20:33:17 +0100 Subject: [PATCH 2/7] Update api.py --- freezegun/api.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/freezegun/api.py b/freezegun/api.py index f8ab8c35..24f65af9 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -17,7 +17,6 @@ import inspect from dateutil import parser -from dateutil.tz import tzlocal try: from maya import MayaDT @@ -87,6 +86,8 @@ _GLOBAL_MODULES_CACHE = {} +_tzlocal = real_datetime.now(datetime.UTC).astimezone().tzinfo + def _get_module_attributes(module): result = [] try: @@ -366,8 +367,9 @@ def __sub__(self, other): def astimezone(self, tz=None): if tz is None: - tz = tzlocal() - return datetime_to_fakedatetime(real_datetime.astimezone(self, tz)) + tz = _tzlocal + + return self.replace(tzinfo=tz) if self.tzinfo is None else datetime_to_fakedatetime(real_datetime.astimezone(self, tz)) @classmethod def fromtimestamp(cls, t, tz=None): @@ -385,10 +387,11 @@ def timestamp(self): @classmethod def now(cls, tz=None): now = cls._time_to_freeze() or real_datetime.now() - if tz: + if tz is not None: result = tz.fromutc(now.replace(tzinfo=tz)) else: result = now + cls._tz_offset() + return datetime_to_fakedatetime(result) def date(self): @@ -647,7 +650,7 @@ def start(self): is_already_started = len(freeze_factories) > 0 freeze_factories.append(freeze_factory) - tz_offsets.append(self.tz_offset) + tz_offsets.append(self.tz_offset or _tzlocal) ignore_lists.append(self.ignore) tick_flags.append(self.tick) From 7b408f015a65a467121b03169462ca5a10cad65c Mon Sep 17 00:00:00 2001 From: Alexandr Priezzhev Date: Mon, 18 Dec 2023 20:57:43 +0100 Subject: [PATCH 3/7] Fix exception when no tz_offset is provided --- freezegun/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freezegun/api.py b/freezegun/api.py index 24f65af9..9ce39547 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -650,7 +650,7 @@ def start(self): is_already_started = len(freeze_factories) > 0 freeze_factories.append(freeze_factory) - tz_offsets.append(self.tz_offset or _tzlocal) + tz_offsets.append(self.tz_offset or _tzlocal.utcoffset(real_datetime.now(datetime.UTC))) ignore_lists.append(self.ignore) tick_flags.append(self.tick) From 9a0a891a2a247400314e2dba2dccdd0af7986910 Mon Sep 17 00:00:00 2001 From: Alexandr Priezzhev Date: Mon, 18 Dec 2023 23:20:25 +0100 Subject: [PATCH 4/7] Fix time.localtime --- freezegun/api.py | 52 +++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/freezegun/api.py b/freezegun/api.py index 9ce39547..4a015052 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -87,6 +87,7 @@ _tzlocal = real_datetime.now(datetime.UTC).astimezone().tzinfo +_tzlocal_offset = _tzlocal.utcoffset(real_datetime.now(datetime.UTC)) def _get_module_attributes(module): result = [] @@ -190,8 +191,8 @@ def fake_localtime(t=None): return real_localtime(t) if _should_use_real_time(): return real_localtime() - shifted_time = get_current_time() - datetime.timedelta(seconds=time.timezone) - return shifted_time.timetuple() + + return get_current_time().timetuple() def fake_gmtime(t=None): @@ -199,7 +200,9 @@ def fake_gmtime(t=None): return real_gmtime(t) if _should_use_real_time(): return real_gmtime() - return get_current_time().timetuple() + shifted_time = get_current_time() - datetime.timedelta(seconds=tz_offsets[-1].total_seconds()) + + return shifted_time.timetuple() def _get_fake_monotonic(): @@ -324,9 +327,12 @@ def __sub__(self, other): @classmethod def today(cls): - result = cls._date_to_freeze() + cls._tz_offset() + result = cls._date_to_freeze() + return date_to_fakedate(result) + + @staticmethod def _date_to_freeze(): return get_current_time() @@ -367,16 +373,17 @@ def __sub__(self, other): def astimezone(self, tz=None): if tz is None: - tz = _tzlocal + tz = self._tz() - return self.replace(tzinfo=tz) if self.tzinfo is None else datetime_to_fakedatetime(real_datetime.astimezone(self, tz)) + if self.tzinfo is None: + return self.replace(tzinfo=tz) + else: + return datetime_to_fakedatetime(real_datetime.astimezone(self, tz)) @classmethod def fromtimestamp(cls, t, tz=None): if tz is None: - return real_datetime.fromtimestamp( - t, tz=dateutil.tz.tzoffset("freezegun", cls._tz_offset()) - ).replace(tzinfo=None) + return real_datetime.fromtimestamp(t, tz=cls._tz()).replace(tzinfo=None) return datetime_to_fakedatetime(real_datetime.fromtimestamp(t, tz)) def timestamp(self): @@ -388,9 +395,9 @@ def timestamp(self): def now(cls, tz=None): now = cls._time_to_freeze() or real_datetime.now() if tz is not None: - result = tz.fromutc(now.replace(tzinfo=tz)) + result = tz.fromutc(now.replace(tzinfo=tz)) - cls._tz_offset() else: - result = now + cls._tz_offset() + result = now return datetime_to_fakedatetime(result) @@ -423,6 +430,10 @@ def _time_to_freeze(): def _tz_offset(cls): return tz_offsets[-1] + @classmethod + def _tz(cls): + return dateutil.tz.tzoffset("freezegun", tz_offsets[-1]) + FakeDatetime.min = datetime_to_fakedatetime(real_datetime.min) FakeDatetime.max = datetime_to_fakedatetime(real_datetime.max) @@ -461,7 +472,7 @@ def pickle_fake_datetime(datetime_): ) -def _parse_time_to_freeze(time_to_freeze_str): +def _parse_time_to_freeze(time_to_freeze_str, tz_offset): """Parses all the possible inputs for freeze_time :returns: a naive ``datetime.datetime`` object """ @@ -477,14 +488,16 @@ def _parse_time_to_freeze(time_to_freeze_str): else: time_to_freeze = parser.parse(time_to_freeze_str) - return convert_to_timezone_naive(time_to_freeze) + return convert_to_timezone_naive(time_to_freeze) + tz_offset def _parse_tz_offset(tz_offset): if isinstance(tz_offset, datetime.timedelta): return tz_offset - else: + elif isinstance(tz_offset, numbers.Real): return datetime.timedelta(hours=tz_offset) + else: + return _tzlocal_offset class TickingDateTimeFactory: @@ -514,7 +527,7 @@ def tick(self, delta=datetime.timedelta(seconds=1)): def move_to(self, target_datetime): """Moves frozen date to the given ``target_datetime``""" - target_datetime = _parse_time_to_freeze(target_datetime) + target_datetime = _parse_time_to_freeze(target_datetime, self.tz_offset) delta = target_datetime - self.time_to_freeze self.tick(delta=delta) @@ -540,7 +553,7 @@ def update_step_width(self, step_width): def move_to(self, target_datetime): """Moves frozen date to the given ``target_datetime``""" - target_datetime = _parse_time_to_freeze(target_datetime) + target_datetime = _parse_time_to_freeze(target_datetime, self.tz_offset) delta = target_datetime - self.time_to_freeze self.tick(delta=delta) @@ -548,8 +561,8 @@ def move_to(self, target_datetime): class _freeze_time: def __init__(self, time_to_freeze_str, tz_offset, ignore, tick, as_arg, as_kwarg, auto_tick_seconds): - self.time_to_freeze = _parse_time_to_freeze(time_to_freeze_str) self.tz_offset = _parse_tz_offset(tz_offset) + self.time_to_freeze = _parse_time_to_freeze(time_to_freeze_str, self.tz_offset) self.ignore = tuple(ignore) self.tick = tick self.auto_tick_seconds = auto_tick_seconds @@ -640,7 +653,6 @@ def __exit__(self, *args): self.stop() def start(self): - if self.auto_tick_seconds: freeze_factory = StepTickTimeFactory(self.time_to_freeze, self.auto_tick_seconds) elif self.tick: @@ -650,7 +662,7 @@ def start(self): is_already_started = len(freeze_factories) > 0 freeze_factories.append(freeze_factory) - tz_offsets.append(self.tz_offset or _tzlocal.utcoffset(real_datetime.now(datetime.UTC))) + tz_offsets.append(self.tz_offset) ignore_lists.append(self.ignore) tick_flags.append(self.tick) @@ -832,7 +844,7 @@ def wrapper(*args, **kwargs): return wrapper -def freeze_time(time_to_freeze=None, tz_offset=0, ignore=None, tick=False, as_arg=False, as_kwarg='', +def freeze_time(time_to_freeze=None, tz_offset=None, ignore=None, tick=False, as_arg=False, as_kwarg='', auto_tick_seconds=0): acceptable_times = (type(None), str, datetime.date, datetime.timedelta, types.FunctionType, types.GeneratorType) From 6f93b51ebe394bc73bcdaf7b6253381ea7bc51b0 Mon Sep 17 00:00:00 2001 From: Alexandr Priezzhev Date: Tue, 19 Dec 2023 01:45:57 +0100 Subject: [PATCH 5/7] Fix time.strftime; fix using time_to_freeze with no timezone --- freezegun/api.py | 69 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/freezegun/api.py b/freezegun/api.py index 4a015052..a5dd1bf6 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -3,6 +3,7 @@ import asyncio import copyreg import dateutil +import dateutil.tz import datetime import functools import sys @@ -10,6 +11,7 @@ import uuid import calendar import unittest +import os import platform import warnings import types @@ -38,6 +40,8 @@ real_date = datetime.date real_datetime = datetime.datetime real_date_objects = [real_time, real_localtime, real_gmtime, real_monotonic, real_perf_counter, real_strftime, real_date, real_datetime] +real_tzlocal = dateutil.tz.tzlocal +real_tz_env = os.environ["TZ"] if _TIME_NS_PRESENT: real_time_ns = time.time_ns @@ -176,8 +180,16 @@ def get_current_time(): def fake_time(): if _should_use_real_time(): return real_time() - current_time = get_current_time() - return calendar.timegm(current_time.timetuple()) + current_time.microsecond / 1000000.0 + + current_time_utc = get_current_time() - datetime.timedelta(seconds=tz_offsets[-1].total_seconds()) + + return calendar.timegm(current_time_utc.timetuple()) + current_time_utc.microsecond / 1000000.0 + +def fake_tzlocal(): + if _should_use_real_time(): + return real_tzlocal() + + return tz_offsets[-1] if _TIME_NS_PRESENT: def fake_time_ns(): @@ -200,9 +212,10 @@ def fake_gmtime(t=None): return real_gmtime(t) if _should_use_real_time(): return real_gmtime() - shifted_time = get_current_time() - datetime.timedelta(seconds=tz_offsets[-1].total_seconds()) - return shifted_time.timetuple() + current_time_utc = get_current_time() - datetime.timedelta(seconds=tz_offsets[-1].total_seconds()) + + return current_time_utc.timetuple() def _get_fake_monotonic(): @@ -260,6 +273,8 @@ def fake_strftime(format, time_to_format=None): if time_to_format is None: return real_strftime(format) else: + # if time_to_format.tzinfo is None: + # time_to_format = time.time(time_to_format).replace(tzinfo=fake_tzlocal()) return real_strftime(format, time_to_format) if real_clock is not None: @@ -418,8 +433,7 @@ def today(cls): @classmethod def utcnow(cls): - result = cls._time_to_freeze() or real_datetime.now(datetime.timezone.utc) - return datetime_to_fakedatetime(result) + return cls.now(datetime.UTC).replace(tzinfo=None) @staticmethod def _time_to_freeze(): @@ -432,7 +446,7 @@ def _tz_offset(cls): @classmethod def _tz(cls): - return dateutil.tz.tzoffset("freezegun", tz_offsets[-1]) + return dateutil.tz.tzoffset("", tz_offsets[-1]) FakeDatetime.min = datetime_to_fakedatetime(real_datetime.min) @@ -443,7 +457,7 @@ def convert_to_timezone_naive(time_to_freeze): """ Converts a potentially timezone-aware datetime to be a naive UTC datetime """ - if time_to_freeze.tzinfo: + if time_to_freeze.tzinfo is not None: time_to_freeze -= time_to_freeze.utcoffset() time_to_freeze = time_to_freeze.replace(tzinfo=None) return time_to_freeze @@ -472,23 +486,26 @@ def pickle_fake_datetime(datetime_): ) -def _parse_time_to_freeze(time_to_freeze_str, tz_offset): +def _parse_time_to_freeze(time_to_freeze, tz_offset): """Parses all the possible inputs for freeze_time :returns: a naive ``datetime.datetime`` object """ - if time_to_freeze_str is None: - time_to_freeze_str = datetime.datetime.now(datetime.timezone.utc) - - if isinstance(time_to_freeze_str, datetime.datetime): - time_to_freeze = time_to_freeze_str - elif isinstance(time_to_freeze_str, datetime.date): - time_to_freeze = datetime.datetime.combine(time_to_freeze_str, datetime.time()) - elif isinstance(time_to_freeze_str, datetime.timedelta): - time_to_freeze = datetime.datetime.now(datetime.timezone.utc) + time_to_freeze_str + if time_to_freeze is None: + time_to_freeze = datetime.datetime.now(datetime.timezone.utc) + + if isinstance(time_to_freeze, datetime.datetime): + result = time_to_freeze + elif isinstance(time_to_freeze, datetime.date): + result = datetime.datetime.combine(time_to_freeze, datetime.time()) + elif isinstance(time_to_freeze, datetime.timedelta): + result = datetime.datetime.now(datetime.timezone.utc) + time_to_freeze else: - time_to_freeze = parser.parse(time_to_freeze_str) + result = parser.parse(time_to_freeze) - return convert_to_timezone_naive(time_to_freeze) + tz_offset + if result.tzinfo is None: + return result + else: + return convert_to_timezone_naive(result) + tz_offset def _parse_tz_offset(tz_offset): @@ -560,9 +577,9 @@ def move_to(self, target_datetime): class _freeze_time: - def __init__(self, time_to_freeze_str, tz_offset, ignore, tick, as_arg, as_kwarg, auto_tick_seconds): + def __init__(self, time_to_freeze, tz_offset, ignore, tick, as_arg, as_kwarg, auto_tick_seconds): self.tz_offset = _parse_tz_offset(tz_offset) - self.time_to_freeze = _parse_time_to_freeze(time_to_freeze_str, self.tz_offset) + self.time_to_freeze = _parse_time_to_freeze(time_to_freeze, self.tz_offset) self.ignore = tuple(ignore) self.tick = tick self.auto_tick_seconds = auto_tick_seconds @@ -672,6 +689,7 @@ def start(self): # Change the modules datetime.datetime = FakeDatetime datetime.date = FakeDate + dateutil.tz.tzlocal = fake_tzlocal time.time = fake_time time.monotonic = fake_monotonic @@ -687,6 +705,8 @@ def start(self): copyreg.dispatch_table[real_datetime] = pickle_fake_datetime copyreg.dispatch_table[real_date] = pickle_fake_date + os.environ["TZ"] = "FRZ" + ("-" if self.tz_offset.seconds > 0 else "") + ':'.join(str(self.tz_offset).split(':')[:2]) + # Change any place where the module had already been imported to_patch = [ ('real_date', real_date, FakeDate), @@ -697,6 +717,7 @@ def start(self): ('real_perf_counter', real_perf_counter, fake_perf_counter), ('real_strftime', real_strftime, fake_strftime), ('real_time', real_time, fake_time), + ('real_tzlocal', real_tzlocal, fake_tzlocal), ] if _TIME_NS_PRESENT: @@ -801,6 +822,7 @@ def stop(self): if real: setattr(module, module_attribute, real) + dateutil.tz.tzlocal = real_tzlocal time.time = real_time time.monotonic = real_monotonic time.perf_counter = real_perf_counter @@ -808,6 +830,7 @@ def stop(self): time.localtime = real_localtime time.strftime = real_strftime time.clock = real_clock + os.environ["TZ"] = real_tz_env if _TIME_NS_PRESENT: time.time_ns = real_time_ns @@ -876,7 +899,7 @@ def freeze_time(time_to_freeze=None, tz_offset=None, ignore=None, tick=False, as ignore.extend(config.settings.default_ignore_list) return _freeze_time( - time_to_freeze_str=time_to_freeze, + time_to_freeze=time_to_freeze, tz_offset=tz_offset, ignore=ignore, tick=tick, From cdde1d54e4b96756f18c9bb2305e8a32216e1106 Mon Sep 17 00:00:00 2001 From: Alexandr Priezzhev Date: Tue, 19 Dec 2023 02:04:22 +0100 Subject: [PATCH 6/7] Fix when TZ environment var is not set --- freezegun/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freezegun/api.py b/freezegun/api.py index a5dd1bf6..b333b84a 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -41,7 +41,7 @@ real_datetime = datetime.datetime real_date_objects = [real_time, real_localtime, real_gmtime, real_monotonic, real_perf_counter, real_strftime, real_date, real_datetime] real_tzlocal = dateutil.tz.tzlocal -real_tz_env = os.environ["TZ"] +real_tz_env = os.environ["TZ"] if "TZ" in os.environ else '' if _TIME_NS_PRESENT: real_time_ns = time.time_ns From b48ad3c3e26b5f7ac24ab47ff4c10c1744014201 Mon Sep 17 00:00:00 2001 From: Alexandr Priezzhev Date: Tue, 19 Dec 2023 02:30:32 +0100 Subject: [PATCH 7/7] Add move_to to TickingDateTimeFactory; fix factories init. --- freezegun/api.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/freezegun/api.py b/freezegun/api.py index b333b84a..d46e9cad 100644 --- a/freezegun/api.py +++ b/freezegun/api.py @@ -519,18 +519,25 @@ def _parse_tz_offset(tz_offset): class TickingDateTimeFactory: - def __init__(self, time_to_freeze, start): + def __init__(self, time_to_freeze, tz_offset): self.time_to_freeze = time_to_freeze - self.start = start + self.start = real_datetime.now() + self.tz_offset = tz_offset def __call__(self): return self.time_to_freeze + (real_datetime.now() - self.start) + def move_to(self, target_datetime): + """Moves frozen date to the given ``target_datetime``""" + self.time_to_freeze = _parse_time_to_freeze(target_datetime, self.tz_offset) + self.start = real_datetime.now() + class FrozenDateTimeFactory: - def __init__(self, time_to_freeze): + def __init__(self, time_to_freeze, tz_offset): self.time_to_freeze = time_to_freeze + self.tz_offset = tz_offset def __call__(self): return self.time_to_freeze @@ -551,9 +558,10 @@ def move_to(self, target_datetime): class StepTickTimeFactory: - def __init__(self, time_to_freeze, step_width): + def __init__(self, time_to_freeze, step_width, tz_offset): self.time_to_freeze = time_to_freeze self.step_width = step_width + self.tz_offset = tz_offset def __call__(self): return_time = self.time_to_freeze @@ -671,11 +679,11 @@ def __exit__(self, *args): def start(self): if self.auto_tick_seconds: - freeze_factory = StepTickTimeFactory(self.time_to_freeze, self.auto_tick_seconds) + freeze_factory = StepTickTimeFactory(self.time_to_freeze, self.auto_tick_seconds, self.tz_offset) elif self.tick: - freeze_factory = TickingDateTimeFactory(self.time_to_freeze, real_datetime.now()) + freeze_factory = TickingDateTimeFactory(self.time_to_freeze, self.tz_offset) else: - freeze_factory = FrozenDateTimeFactory(self.time_to_freeze) + freeze_factory = FrozenDateTimeFactory(self.time_to_freeze, self.tz_offset) is_already_started = len(freeze_factories) > 0 freeze_factories.append(freeze_factory)