host timezone can't be faked
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]>)
- tz from
pytz- can't resolve tz for same reason, looks like bug inpytzitself - tz from
arrow- tz resolved,naive timecorrect, tzinfo incorrect(not affected at all) - tz from
tz_offsetarg -naive timeincorrect, tzinfo incorrect(not affected at all)
possible workaround - set env vaiable TZ=Asia/Kuala_Lumpur https://docs.python.org/3/library/time.html#time.tzset
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
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?
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.
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?