Right, but the comment could have just been left as is, because there wasn’t really anything new to add. Instead by splitting it off it’s made it look like a new question, so we’re repeating discussions and points already made.
So, to add something new to the discussion, I’ve had time to mull with the python datetime pitfalls blog since I last posted in that thread, and I’ve come to the conclusion that IMO:
- Python datetime should emit a warning when any timezoned non-UTC datetime arithmetic is done
- Users should be warned off using any timezoned datetime arithmetic until Python sufficiently warns people during runtime
Here are some examples why:
>>> # It's 10pm on the 25th March 2023 in Paris, in 8 hours it will be 7am because of DST
>>> now = datetime.datetime(2023, 3, 25, 22, tzinfo=zoneinfo.ZoneInfo("Europe/Paris"))
>>> now + datetime.timedelta(hours=8)
datetime.datetime(2023, 3, 26, 6, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
>>> now = pendulum.datetime(2023, 3, 25, 22, tz="Europe/Paris")
>>> now + datetime.timedelta(hours=8)
DateTime(2023, 3, 26, 7, 0, 0, tzinfo=Timezone('Europe/Paris'))
>>> now = whenever.ZonedDateTime(2023, 3, 25, 22, tz="Europe/Paris")
>>> now + datetime.timedelta(hours=8)
>>> now.add(hours=8)
ZonedDateTime(2023-03-26 07:00:00+02:00[Europe/Paris])
You run into the same problem reversing the situation, bear in mind that 8 hours is the correct answer, which is 28,800 seconds:
>>> now = datetime.datetime(2023, 3, 25, 22, tzinfo=paris)
>>> then = datetime.datetime(2023, 3, 26, 7, tzinfo=paris)
>>> then - now
datetime.timedelta(seconds=32400)
>>> (then - now).seconds
32400
>>> now = pendulum.datetime(2023, 3, 25, 22, tz="Europe/Paris")
>>> then = pendulum.datetime(2023, 3, 26, 7, tz="Europe/Paris")
>>> then - now
<Interval [2023-03-25 22:00:00+01:00 -> 2023-03-26 07:00:00+02:00]>
>>> (then - now).seconds
28800
>>> now = whenever.ZonedDateTime(2023, 3, 25, 22, tz="Europe/Paris")
>>> then = whenever.ZonedDateTime(2023, 3, 26, 7, tz="Europe/Paris")
>>> then - now
TimeDelta(08:00:00)
>>> (then - now).in_seconds()
28800.0
I therefore continue to advise what I advised in the previous thread, and am now having to repeat, if you currently use datetime.datetime.utcnow()
you are best served by replacing it with the expression which produces the identical result datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
. Trying to change logic and replace APIs at the same time is fraught with risks.
However, now I would advocate further, unless you can convince yourself that your code is definitely not susceptible to the design choices that lead to the wrong results above, then avoid using all non-utc timezoned arithmetic with from the datetime standard library, and if you can use a different library for timezoned arithmetic, especially if you are not heavily testing and validating your logic.
Standard library datetimes without timezones remain intuitive, and good to model many real world systems and data stores.