Skip to content

gh-116111: Clarify DST behaviour in datetime arithmetic and zoneinfo docs #137092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions Doc/library/datetime.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,18 @@ objects.
A :class:`timedelta` object represents a duration, the difference between two
:class:`.datetime` or :class:`date` instances.

.. warning::

When performing arithmetic with aware datetime objects that share the same
``tzinfo`` instance, time zone transitions (such as daylight saving time changes)
are not taken into account. This means the arithmetic may not reflect elapsed
time as measured in UTC. For example, adding one hour across a daylight saving
time boundary may result in a time that is not exactly one hour later in UTC.

For arithmetic that accounts for time zone transitions,
convert the datetime to UTC with :meth:`datetime.astimezone`,
perform the arithmetic, and convert back to the desired time zone.

.. class:: timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)

All arguments are optional and default to 0. Arguments may be integers
Expand Down Expand Up @@ -1289,6 +1301,29 @@ Supported operations:
object ``t`` such that ``datetime2 + t == datetime1``. No time zone adjustments
are done in this case.

.. note::

When two aware datetime objects share the same ``tzinfo`` instance,
arithmetic is performed in local (naive) time. As a result, differences
in UTC offset, such as daylight saving time transitions, are not applied.

For arithmetic that reflects elapsed time accurately across time zone transitions,
convert both values to UTC with :meth:`.astimezone`,
perform arithmetic, and convert back to the desired time zone if needed.

Example::

>>> from datetime import datetime, timedelta
>>> from zoneinfo import ZoneInfo
>>> t = datetime(2024, 10, 27, 1, 0, tzinfo=ZoneInfo("Europe/London"), fold=0)
>>> t.isoformat()
'2024-10-27T01:00:00+01:00'
>>> t + timedelta(hours=1)
datetime.datetime(2024, 10, 27, 2, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London'))

# This result reflects arithmetic in local time; DST transitions are not applied.
# The result is not the repeated 01:00 (fold=1), but instead the next distinct clock hour.

If both are aware and have different :attr:`~.datetime.tzinfo` attributes, ``a-b`` acts
as if ``a`` and ``b`` were first converted to naive UTC datetimes. The
result is ``(a.replace(tzinfo=None) - a.utcoffset()) - (b.replace(tzinfo=None)
Expand Down
17 changes: 15 additions & 2 deletions Doc/library/zoneinfo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,21 @@ method or :meth:`datetime.astimezone <datetime.datetime.astimezone>`::
>>> dt.tzname()
'PDT'

Datetimes constructed in this way are compatible with datetime arithmetic and
handle daylight saving time transitions with no further intervention::
Datetimes constructed in this way are compatible with datetime arithmetic in most cases,
but some operations may not account for daylight saving time transitions as expected.

.. note::

When adding or subtracting a :class:`datetime.timedelta` to a timezone-aware
:class:`datetime.datetime` object using the same :class:`ZoneInfo` instance,
daylight saving time transitions are not accounted for. These operations are
performed in local (naive) time.

To perform arithmetic that accounts for time zone transitions,
convert the datetime object to UTC with :meth:`~datetime.datetime.astimezone`,
perform the arithmetic, and convert the result back to the local time zone.

.. code-block:: pycon

>>> dt_add = dt + timedelta(days=1)

Expand Down
Loading