Why does datetime.timedelta only have the attributes 'days' and 'seconds'?

Jon Ribbens jon+usenet at unequivocal.eu
Sat Apr 16 16:35:22 EDT 2022


On 2022-04-16, Peter J. Holzer <hjp-python at hjp.at> wrote:
> On 2022-04-16 14:22:04 -0000, Jon Ribbens via Python-list wrote:
>> On 2022-04-16, Jon Ribbens <jon+usenet at unequivocal.eu> wrote:
>> > On 2022-04-16, Peter J. Holzer <hjp-python at hjp.at> wrote:
>> >> Python missed the switch to DST here, the timezone is wrong.
>> >
>> > Because you didn't let it use any timezone information. You need to
>> > either use the third-party 'pytz' module, or in Python 3.9 or above,
>> > the built-in 'zoneinfo' module.
>>
>> ... although now having looked into the new 'zoneinfo' module slightly,
>> it really should have a giant red flashing notice at the top of it
>> saying "BEWARE, TIMEZONES IN PYTHON ARE UTTERLY BROKEN, NEVER USE THEM".
>>
>> Suppose we do this:
>>
>>     >>> import datetime, zoneinfo
>>     >>> LOS_ANGELES = zoneinfo.ZoneInfo('America/Los_Angeles')
>>     >>> UTC = zoneinfo.ZoneInfo('UTC')
>>     >>> d = datetime.datetime(2020, 10, 31, 12, tzinfo=LOS_ANGELES)
>>     >>> print(d)
>>     2020-10-31 12:00:00-07:00
>>     >>> d1 = d + datetime.timedelta(days=1)
>>     >>> print(d1)
>>     2020-11-01 12:00:00-08:00
>>
>> d1 is *wrong*.
>
> No, this is correct. That's the result you want.

I can categorically guarantee you it is not. But let's put it a
different way, if you like, if I want to add 24 hours, i.e. 86,400
seconds (or indeed any other fixed time period), to a timezone-aware
datetime in Python, how do I do it?  It would appear that, without
converting to UTC before doing the calculation, you can't.

> So why didn't this work for me (I also used Python 3.9)? My guess is
> that astimezone() doesn't pick the correct time zone.

astimezone() doesn't pick a time zone at all. It works out the current
local offset from UTC. It doesn't know anything about when or if that
offset ever changes.

>> timedelta(days=1) is 24 hours (as you can check by
>> calling timedelta(days=1).total_seconds() ),
>
> It shouldn't be. 1 Day is not 24 hours in the real world.

Nevertheless, timedelta is a fixed time period so that is the only
definition possible.

>> then it can pretend timezones don't exist and do 'naive' arithmetic.
>
> On the contrary. When a datetime is timezone aware, it must use that
> timezone's rules. Adding one day to a datetime just before a DST switch
> must add 23 or 25 hours, not 24. This is NOT naive.

But it is. It's adding 24 hours and while it's doing so it's naively
assuming that the UTC offset doesn't change during that period. Then
once it's got that naive result it's labelling it with what it thinks
is the timezone data at that time.

Here's another example to prove the point:

    >>> LONDON = zoneinfo.ZoneInfo('Europe/London')
    >>> d0 = datetime.datetime(2022, 3, 27, 0, tzinfo=LONDON)
    >>> print(d0)
    2022-03-27 00:00:00+00:00
    >>> print(d0 + datetime.timedelta(seconds=3600+1800))
    2022-03-27 01:30:00+00:00

That is impossible - 2022-03-27 01:30 is a time that *doesn't exist*
in the Europe/London timezone. At 01:00 the clocks moved instantly
to 02:00 as daylight savings kicked in. So the following is wrong too:

    >>> print(d0 + datetime.timedelta(seconds=3600*2))
    2022-03-27 02:00:00+01:00

That's not 2 hours after midnight, that's 1 hour after midnight.

Doing the calculations in UTC works of course:

    >>> print((d0.astimezone(UTC) + datetime.timedelta(seconds=3600+1800)).astimezone(LONDON))
    2022-03-27 02:30:00+01:00
    >>> print((d0.astimezone(UTC) + datetime.timedelta(seconds=3600*2)).astimezone(LONDON))
    2022-03-27 03:00:00+01:00

> (There is an ambiguity, though: Should 2021-03-27T12:00 CEST -
> 2021-03-26T12:00 CET return 1 day or 25 hours? Both results are correct,
> and depending on context you might prefer one or the other).

But if you're returning that result as a timedelta then only "25 hours"
is correct (or indeed "1 day 3,600 seconds"), because for a timedelta a
day is 24 hours *by definition*.

>> There is a general guideline that you should always keep and use your
>> datetimes as UTC, only ever using timezones for the purposes of display.
>> Usually this is because it keeps things simpler for the programmer, and
>> hence they are less likely to introduce bugs into their programs.
>
> While I generally do this (and often preach it to my collegues) it must
> be stated that this is only a GENERAL guide line.

Yes, that's what I just said.

>> It appears that with Python it's not so much a guideline as an
>> absolute concrete rule, and not because programmers will introduce
>> bugs, but because you need to avoid bugs in the standard library!
>
> As a programmer you must always adapt to the problem. Saying "I must do
> it the wrong way because my library is buggy" is just lazy.

I didn't say any of that. I said you must do it the conservative way,
and it's not "my library" that's buggy, it's the language's built-in
*standard library* that's buggy.


More information about the Python-list mailing list