(edited to add)
PROLOG: Today I became aware of the concept of "wall time". I will always and forever consider it harmful.
I have two datetimes, one representing a certain time-of-day just before a time change, and the other representing the same time-of-day one calendar day after the the first datetime. I would expect the time difference between these two objects to not be exactly one day, but that's what I see (with python3.8, which is all I have to work with).
Taking the difference of the timestamps associated with the datetimes returns exactly what I would expect to see. Taking the difference of datetime objects when they span a time change looks flat-out wrong to me.
Is this expected behavior?
from datetime import datetime, timedelta
from dateutil.tz import gettz # pip install dateutil
def iso(dt):
return dt.strftime('%FT%T%z')
# Daylight saving time begins at 2 a.m. local time on Sunday, March 10, 2024
us_central = gettz('US/Central')
before = datetime(2024, 3, 9, 15, 22, 1, tzinfo=us_central)
after = datetime(2024, 3, 10, 15, 22, 1, tzinfo=us_central)
print()
print(f'before time change: {iso(before)}')
print(f' after time change: {iso(after)}')
naive = after - before
by_timestamps = timedelta(seconds = after.timestamp() - before.timestamp())
difference_difference = naive.total_seconds() - by_timestamps.total_seconds()
print()
print('Differences:')
print(f' naive: {repr(naive)}')
print(f'by timestamps: {repr(by_timestamps)}')
print(f' error: {difference_difference}s')
Output (python3.8)
before time change: 2024-03-09T15:22:01-0600
after time change: 2024-03-10T15:22:01-0500
Differences:
naive: datetime.timedelta(days=1)
by timestamps: datetime.timedelta(seconds=82800)
error: 3600.0s
(Edited to add)
This is so counterintuitive to me. "Wall time" is very strange.
from datetime import datetime, timedelta
from dateutil.tz import gettz # pip install dateutil
# Daylight saving time begins at 2 a.m. local time on Sunday, March 10, 2024
us_central = gettz('US/Central')
before = datetime(2024, 3, 9, 15, 22, 1, tzinfo=us_central)
after = datetime(2024, 3, 10, 15, 22, 1, tzinfo=us_central)
print(repr((before + timedelta(days=1)) - after))
print(repr((before + timedelta(seconds=86400)) - after))
# what I think the above should do...
print(repr(datetime.fromtimestamp(before.timestamp() + 86400, tz=before.tzinfo) - after))
Output (python3.8)
datetime.timedelta(0)
datetime.timedelta(0)
datetime.timedelta(seconds=3600)
From cpython's source code (the pure Python fallback module, though I expect the results to be equivalent to the C code):
Note that there's no code to account for daylight saving time changes.
Therefore I'd suggest localizing your datetime objects to UTC before performing any timedelta computation.
And if your code needs to be accurate to the second, note that Python also ignores leap seconds, and even Unix time (generated by
.timestamp()) makes bad choices. Subtracting two Unix timestamps around a leap second will be off by one, because even though the timestamps look independent of calendar, they were patched for backwards compatibility with code that assumed a fixed amount of seconds per day.