Django difference between aware datetimes across DST
I'm working on a Django application in which I need to calculate the difference between timestamps stored in the DB. This week I run into some problems related to DST.
In particular in the following code snippet:
tEndUtc = tEnd.astimezone(timezone.utc)
tStartUtc = tStart.astimezone(timezone.utc)
total_timeUTC = tEndUtc- tStartUtc
total_time = tEnd - tStart
total_time (which uses the timezone aware timestamp stored in the DB) is shorter of 1 hour than the one with the total_timeUTC. I use have USE_TZ = true in the settings file.
Here's what I get:
tStart = datetime.datetime(2025, 10, 24, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Rome'))
tEnd = datetime.datetime(2025, 10, 31, 23, 59, 59, 999999, tzinfo=zoneinfo.ZoneInfo(key='Europe/Rome'))
tStartUtc = datetime.datetime(2025, 10, 23, 22, 0, tzinfo=datetime.timezone.utc)
tEndUtc = datetime.datetime(2025, 10, 31, 22, 59, 59, 999999, tzinfo=datetime.timezone.utc)
total_timeUTC = datetime.timedelta(days=8, seconds=3599, microseconds=999999)
total_time = datetime.timedelta(days=7, seconds=86399, microseconds=999999)
What is the correct way to handle DST? And in particular how does someone correctly calculate time difference across DST?
The correct time delta is the one I get when using UTC. Having all the application built using timezone aware datetimes, I would like not change everything and convert to UTC timestamps.
Thanks in advance.
Interestingly this appears to be an "issue" with zoneinfo:
import pytz
from zoneinfo import ZoneInfo
z_rome = ZoneInfo('Europe/Rome')
p_rome = pytz.timezone('Europe/Rome')
datetime(2025, 10, 26, 4, tzinfo=z_rome) - datetime(2025, 10, 25, 4, tzinfo=z_rome)
# datetime.timedelta(days=1)
p_rome.localize(datetime(2025, 10, 26, 4)) - p_rome.localize(datetime(2025, 10, 25, 4))
# datetime.timedelta(days=1, seconds=3600)
According to pytz, 1 day and 1 hour elapsed between these two dates, which is the actual number of hours that have passed. But according to ZoneInfo, only 24 hours have elapsed, as it appears to do wall clock time arithmetic.
So, the difference is "correct", and converting to UTC does fix that behaviour and yield the expected duration:
datetime(2025, 10, 26, 4, tzinfo=z_rome).astimezone(timezone.utc) - datetime(2025, 10, 25, 4, tzinfo=z_rome).astimezone(timezone.utc)
# datetime.timedelta(days=1, seconds=3600)
Django follows suit behind Python core and leans on zoneinfo and PEP 495. So it mostly comes down to what you mean by "handle DST".
For inserting, updating or reading datetime objects to/from DB, with USE_TZ=True, the timestamp you end up with is guaranteed to be correct regardless of DST.
For computing timedeltas, you're "forced" to convert to UTC (unless your native time is unambiguous, in which case you should set USE_TZ=False instead and then make no changes to the rest of your code).
This is idiomatic for Python core per PEP 495:
The value of the
foldattribute will be ignored in all operations with naivedatetimeinstances. As a consequence, naivedatetime.datetimeordatetime.timeinstances that differ only by the value offoldwill compare as equal. Applications that need to differentiate between such instances should check the value offoldexplicitly or convert those instances to a timezone that does not have ambiguous times (such as UTC).
Which means the discrepancy you've found isn't Django's fault (nor something Django can help you with), it's how Python has chosen to handle timezones. Which leads to:
TL;DR: Either play by PEP 495 rules or look for a library that does this magic for you invisibly - either way, you won't get around having to do some code adjustments.