Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

host timezone can't be faked #299

Open
IaroslavR opened this issue May 28, 2019 · 5 comments
Open

host timezone can't be faked #299

IaroslavR opened this issue May 28, 2019 · 5 comments

Comments

@IaroslavR
Copy link

IaroslavR commented May 28, 2019

fake_tz.py:

from datetime import datetime
import sys

import arrow
import freezegun
from freezegun import freeze_time
import pytz
from tzlocal import get_localzone


def print_dt(tmpl="freeze_time({})"):
    print("Results for: " + tmpl.format(d))
    print("Naive dt", datetime.now())
    print("Local dt: ", get_localzone().localize(datetime.now()))
    print("As arrow obj: ", arrow.now())


print(sys.version)
print(freezegun.__version__, arrow.__version__, pytz.__version__)
print("Real host tz: {}".format(get_localzone()))
d = 'datetime(2015, 8, 18, 10, 00, 00, tzinfo=pytz.timezone("Asia/Kuala_Lumpur"))'
with freeze_time(eval(d)):
    print_dt()
d = 'datetime(2015, 8, 18, 10, 00, 00, tzinfo=arrow.now("Asia/Kuala_Lumpur").tzinfo)'
with freeze_time(eval(d)):
    print_dt()
d = "datetime(2015, 8, 18, 10, 00, 00)"
with freeze_time(eval(d), tz_offset=8):
    print_dt("freeze_time({}, tz_offset=8)")

Output:

$ fake_tz.py
2.7.12 (default, Dec  4 2017, 14:50:18) 
[GCC 5.4.0 20160609]
('0.3.11', '0.13.2', '2019.1')
Real host tz: Europe/Kiev
Results for: freeze_time(datetime(2015, 8, 18, 10, 00, 00, tzinfo=pytz.timezone("Asia/Kuala_Lumpur")))
('Naive dt', FakeDatetime(2015, 8, 18, 3, 13))
('Local dt: ', FakeDatetime(2015, 8, 18, 3, 13, tzinfo=<DstTzInfo 'Europe/Kiev' EEST+3:00:00 DST>))
('As arrow obj: ', <Arrow [2015-08-18T06:13:00+03:00]>)
Results for: freeze_time(datetime(2015, 8, 18, 10, 00, 00, tzinfo=arrow.now("Asia/Kuala_Lumpur").tzinfo))
('Naive dt', FakeDatetime(2015, 8, 18, 2, 0))
('Local dt: ', FakeDatetime(2015, 8, 18, 2, 0, tzinfo=<DstTzInfo 'Europe/Kiev' EEST+3:00:00 DST>))
('As arrow obj: ', <Arrow [2015-08-18T05:00:00+03:00]>)
Results for: freeze_time(datetime(2015, 8, 18, 10, 00, 00), tz_offset=8)
('Naive dt', FakeDatetime(2015, 8, 18, 18, 0))
('Local dt: ', FakeDatetime(2015, 8, 18, 18, 0, tzinfo=<DstTzInfo 'Europe/Kiev' EEST+3:00:00 DST>))
('As arrow obj: ', <Arrow [2015-08-18T21:00:00+03:00]>)
  1. tz from pytz - can't resolve tz for same reason, looks like bug in pytz itself
  2. tz from arrow - tz resolved, naive time correct, tzinfo incorrect(not affected at all)
  3. tz from tz_offset arg - naive time incorrect, tzinfo incorrect(not affected at all)
@IaroslavR
Copy link
Author

possible workaround - set env vaiable TZ=Asia/Kuala_Lumpur https://docs.python.org/3/library/time.html#time.tzset

@RemiCardona
Copy link

based on @IaroslavR 's comment, I wrote the following context manager which does the job in my testsuite:

@contextlib.contextmanager
def mock_tz(new_tz):
    old_tz = os.environ.get('TZ')
    os.environ['TZ'] = new_tz
    time.tzset()
    try:
        yield
    finally:
        if old_tz is not None:
            os.environ['TZ'] = old_tz
        else:
            del os.environ['TZ']
        time.tzset()

I went with a standalone utility so that I could set the time in any TZ and have my fake system time in a specific TZ. Not sure if this is a common use case. Any how, maybe someone will have a brilliant idea on how to provide a nice API inside freezegun.

Cheers

@bplevin36
Copy link

I also think this is a problem. It appears the freezegun devs did this intentionally. There's code in _freeze_time that specifically makes sure to coerce tz-aware datetime objects into a naive form. But for the life of me I can't figure out why they did this. Faking the timezone is part of faking times. Why should a user of this library be prevented from doing this?

@boxed
Copy link
Contributor

boxed commented Jul 1, 2020

I also believe this is a mistake. I would argue that it's well worth breaking peoples existing tests if they rely on the current behavior in these situations.

@micahjsmith
Copy link

Relatedly, I believe the freezegun implementation violates the spec of datetime.astimezone, which says:

If called without arguments (or with tz=None) the system local timezone is assumed for the target timezone. The .tzinfo attribute of the converted datetime instance will be set to an instance of timezone with the zone name and offset obtained from the OS.

However, freezegun sets tzinfo to dateutil.tz.tzlocal() which is not an instance of datetime.timezone (and doesn't even have the same methods).

>>> with freeze_time(tz_offset=3):
...     datetime.datetime.now().astimezone()
... 
FakeDatetime(2022, 4, 5, 23, 5, 9, 750369, tzinfo=tzlocal())

Unless I'm missing something?

benthorner added a commit to benthorner/snsary that referenced this issue Apr 27, 2022
"utcnow" returns a naive timestamp, which effectively subtracts an
hour from the local time, which is what we're trying to represent.

    datetime.fromtimestamp(datetime.utcnow().timestamp())
    datetime.datetime(2022, 4, 24, 8, 8, 29, 576299)

    datetime.fromtimestamp(datetime.now().timestamp())
    datetime.datetime(2022, 4, 24, 9, 9, 5, 365402)

Using "now" is correct. Because reasoning about naive times can be
so confusing, this also makes all of them timezone-aware e.g.

    datetime.utcnow().isoformat()
    '2022-04-26T07:13:24.259928'

    datetime.now().astimezone().isoformat()
    '2022-04-26T08:13:58.523714+01:00'

Using "astimezone" converts the datetime object to the specified
timezone by interpreting it in the implicit local timezone.

Tests now set explicit timestamps or zone-aware datetimes to make it
clear what actual time is being tested. Unfortunately freeze_gun is
not a reliable tool for this [^1][^2] so this commit replaces it.

In the case of the Octopus sensor, we are effectively converting a
date to a time so we also need to lock the system timezone to ensure
test stability. time-machine needs Python 3.9+ to do this [^3].

[^1]: spulec/freezegun#348
[^2]: spulec/freezegun#299
[^3]: https://github.com/adamchainz/time-machine#timezone-mocking
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants