I would like to contruct DST-valid timestamps only using the standard library in Python 3.9 and was hoping this was possible with this version.
In my timezone "Europe/Berlin", the DST crossings for 2020 are:
2020-03-29 at 02:00 the clock switches to 03:00 (there is no hour 2!)
2020-10-25 at 03:00 the clock switches back to 02:00 (the hour 2 exists two times!)
My script yields the following output:
MARCH
2020-03-29 01:59:00+01:00 CET plus 1 h: 2020-03-29 02:59:00+01:00 CET
(should be 03:59:00 CEST since there is no hour 2!)
OCTOBER
2020-10-25 02:00:00+02:00 CEST plus 1 h: 2020-10-25 03:00:00+01:00 CET
(seems OK- EDIT: should be 02:00 CET!!!)
Example code is provided below. Windows user may need to "pip install tzdata" to make it work.
Any advise would be greatly appreciated!
'''
Should work out of the box with Python 3.9
Got a fallback import statement.
BACKPORT (3.6+)
pip install backports.zoneinfo
WINDOWS (TM) needs:
pip install tzdata
'''
from datetime import datetime, timedelta
from time import tzname
try:
from zoneinfo import ZoneInfo
except ImportError:
from backports import zoneinfo
ZoneInfo = zoneinfo.ZoneInfo
tz = ZoneInfo("Europe/Berlin")
hour = timedelta(hours=1)
print("MARCH")
dt_01 = datetime(2020, 3, 29, 1, 59, tzinfo=tz)
dt_02 = dt_01 + hour
print(f"{dt_01} {dt_01.tzname()} plus 1 h: {dt_02} {dt_02.tzname()}")
print("\nOCTOBER")
dt_01 = datetime(2020, 10, 25, 2, 0, tzinfo=tz)
dt_02 = dt_01 + hour
print(f"{dt_01} {dt_01.tzname()} plus 1 h: {dt_02} {dt_02.tzname()}")
Although it is counter-intuitive, this is as expected. See this blog post for more details on how datetime arithmetic works. The reason for this is that adding a
timedelta
to adatetime
should be thought of as "advance the calendar/clock by X amount" rather than "what will the calendar/clock say after this amount of time has elapsed". Note that the first question might result in a time that doesn't even occur in the local time zone!If you want, "What
datetime
represents what time it will be after the amount of time represented by thistimedelta
has elapsed?" (which it seems you do), you should do something equivalent to converting to UTC and back, like so:I believe you can create a
timedelta
subclass that overrides__add__
to do this for you (I'd kind of like to introduce something like this to the standard library if I can).Note that if
dt.tzinfo
isNone
, this will use your system local time zone to determine how to do absolute addition, and it will return an aware time zone. Running this inAmerica/New_York
:If you want this to do civil addition for naïve datetimes and absolute addition for aware datetimes, you can check whether or not it's naïve in the function:
Also, to be clear, this is not something to do with
zoneinfo
. This has always been the semantics of datetimes in Python, and it wasn't something we could change in a backwards-compatible way.pytz
does work a little differently, because addingpytz
-aware datetimes does the wrong thing, and requires anormalize
step after the arithmetic has occurred, and thepytz
author decided thatnormalize
should use absolute-time semantics.absolute_add
also works withpytz
anddateutil
, since it uses operations that work well for all time zone libraries.