1
0
mirror of https://github.com/django/django.git synced 2025-04-22 00:04:43 +00:00

ticket #34699. Added a warning about using Trunc functions in a filter when the timezone is not UTC to database-functions. Added tests to confirm that the documentation is correct.

This commit is contained in:
wesley 2024-10-08 20:07:04 -04:00
parent 5f0ed95e10
commit 033f11be06
2 changed files with 75 additions and 0 deletions

View File

@ -658,6 +658,41 @@ Usage example:
2015-06-15 14:30:50.000321
2015-06-15 14:40:02.000123
.. warning::
Trunc functions, at the database level, return a timezone naive value which
is converted to a timezone aware value by the ORM. When you use a Trunc
function in a filter you will need to remember that it is a timezone naive
value. This can lead to unexpected results if you are using timezones other
than UTC. Django will store date/time values in the database in the UTC
timezone. The following example demonstrates what happens when using the
timezone "Europe/Berlin" and how to adjust for this:
Filter example using the "Europe/Berlin" timezone.:
.. code-block:: pycon
>>> from django.utils import timezone
>>> from datetime import datetime
>>> from django.db.models.functions import TruncSecond
>>> import zoneinfo
>>> start = datetime(2015, 6, 15, 14, 30, 50, 321)
>>> start = timezone.make_aware(start)
>>> exp = Experiment.objects.create(start_datetime=start)
>>> find_this_exp = Experiment.objects.annotate(
... trunc_start=TruncSecond("start_datetime")
... ).filter(trunc_start__lte=start)
>>> find_this_exp.count() # We expect to find one result but 0 are found
...
0
>>> start_adjusted = timezone.localtime(start).replace(tzinfo=zoneinfo.ZoneInfo(key='UTC'))
>>> find_this_exp_adjusted = Experiment.objects.annotate(
... trunc_start=TruncSecond("start_datetime")
... ).filter(trunc_start__lte=start_adjusted)
>>> find_this_exp.count()
...
1
``DateField`` truncation
~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1935,3 +1935,43 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
DTModel.objects.annotate(
hour_melb=Trunc("start_time", "hour", tzinfo=melb),
).get()
def test_trunc_in_filter(self):
"""
ticket #34699. When TruncSecond is used in a filter it can behave unexpectedly
because the function at the database level returns a timezone naive value. The
documentation at docs/ref/models/database-functions.txt describes the problem
and provides a work-around in specific cases. These tests confirm the issue
exists and confirm that the work-around performs as described. If these tests
fail it could be because functionality has changed in which case the
documentation should be updated and the release notes should include information
about a potentially breaking change.
"""
# UTC: No adjustment required to filtering for TruncSecond
now = timezone.now()
later = now + timedelta(hours=2)
non_utc_model = self.create_model(now, later)
models_qs = DTModel.objects.annotate(
start_trunc=TruncSecond('start_datetime')).filter(id=non_utc_model.id,
start_trunc__lte=now)
self.assertEqual(models_qs.count(), 1)
test_timezones = [
zoneinfo.ZoneInfo("Europe/Berlin"),
zoneinfo.ZoneInfo("Australia/Melbourne")
]
for test_tz in test_timezones:
with timezone.override(test_tz):
now = timezone.now()
later = now + timedelta(hours=2)
non_utc_model = self.create_model(now, later)
models_qs = DTModel.objects.annotate(
start_trunc=TruncSecond('start_datetime')).filter(id=non_utc_model.id,
start_trunc__lte=now)
self.assertNotEqual(models_qs.count(), 1)
adjusted_now = timezone.localtime(now).replace(tzinfo=zoneinfo.ZoneInfo(key='UTC'))
models_qs = DTModel.objects.annotate(
start_trunc=TruncSecond('start_datetime')).filter(id=non_utc_model.id,
start_trunc__lte=adjusted_now)
self.assertEqual(models_qs.count(), 1)