Created
May 6, 2021 13:51
-
-
Save pablogsal/38095b4f0aacb8a692f28ae8316c84dd to your computer and use it in GitHub Desktop.
timezones
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import pytz | |
from zoneinfo import ZoneInfo | |
from datetime import datetime, timedelta, timezone | |
from dateutil import tz | |
from zoneinfo import ZoneInfo | |
def time_travel(): | |
tzlondon = ZoneInfo("Europe/London") | |
start_date = datetime(2019, 3, 31, 0, 59, tzinfo=tzlondon) | |
# according to: | |
# - https://stackoverflow.com/questions/64440016/python-3-9-construct-dst-valid-timestamp-using-standard-library/64452687#64452687 | |
# - https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html | |
# a timedelta should be considered as moving the clock forward by X amount of time, in absolute terms | |
# which means it may return a local time that does not exist | |
# | |
# furthermore, the second link mentions timedelta addition should not modify the timezone, | |
# but it will do so in this example | |
print(f"Starting from {start_date.isoformat()}") | |
end_date = start_date + timedelta(minutes=1) | |
# https://www.python.org/dev/peps/pep-0495 | |
# as per the documentation, if (on a short day): | |
# - fold=0 (default), the timeline from *before* the transition is brought forward | |
# - fold=1, the timeline from *after* the transition is brought back | |
# | |
# as per docs | |
assert end_date.fold == 0 | |
# meaning that | |
assert end_date.utcoffset() == timedelta(seconds=0) | |
# prints 2019-03-31T01:00:00+00:00 | |
# this time does not exist in local time! As mentioned above, this is "by design" | |
# but we are still linear. We have increased by 60 absolute seconds | |
print(f"Add 1 minute, and we get {end_date.isoformat()}") | |
# however, if we add 61 minutes instead... | |
new_end_date = start_date + timedelta(minutes=61) | |
# prints 2019-03-31T02:00:00+01:00 | |
# this makes more sense in local time... | |
# but contradicts the statement above. We have added 3660 seconds (61 minutes), but we | |
# get a time back that is only 60 seconds past the start date in absolute terms | |
# (02:00:00+01:00 is the same as 01:00:00+00:00) | |
print(f"Add 61 minutes, and we get {new_end_date.isoformat()}") | |
# which has the right UTC offset now | |
assert new_end_date.utcoffset() == timedelta(seconds=3600) | |
# and since we're now fully on the new timeline, fold is still zero | |
assert new_end_date.fold == 0 | |
# At this point we have: | |
# end_date - built by adding 1 minute: | |
# =01:00:00+00:00, which does NOT exist as a local time | |
# | |
# new_end_date - built by adding 61 minutes: | |
# =02:00:00+01:00, which DOES exist as a local time | |
# ... but is the same as the 01:00:00+00:00 we got by adding just 1 minute | |
# | |
# If we compare their absolute time differences, by adding 61 minutes, we went up by.... | |
# 60 seconds | |
assert (new_end_date.timestamp() - start_date.timestamp()) == 60 | |
# and in fact, the timestamps we get by adding 1 minute or 61 minutes are identical | |
assert (new_end_date.timestamp() == end_date.timestamp()) | |
def no_time_travel_pytz(): | |
tzlondon = pytz.timezone("Europe/London") | |
start_date = tzlondon.localize(datetime(2019, 3, 31, 0, 59)) | |
print(f"Starting from {start_date.isoformat()}") | |
end_date = start_date + timedelta(minutes=1) | |
# prints 2019-03-31T01:00:00+00:00 | |
# which does not exist in local time, just like the other library | |
print(f"Add 1 minute and we get {end_date.isoformat()}") | |
new_end_date = start_date + timedelta(minutes=61) | |
# prints 2019-03-31T02:00:00+00:00 | |
# which is not the right time, but does exist | |
print(f"Add 61 minutes and we get {new_end_date.isoformat()}") | |
# we could use tzlondon.normalize, but that's not the point here | |
# | |
# if we compare their times, we always get something that makes sense: | |
assert (end_date.timestamp() - start_date.timestamp()) == 60 | |
assert (new_end_date.timestamp() - start_date.timestamp()) == 3660 | |
print("Standard lib time travels...") | |
time_travel() | |
print("pytz does not - I think?") | |
no_time_travel_pytz() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment