mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +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:
parent
5f0ed95e10
commit
033f11be06
@ -658,6 +658,41 @@ Usage example:
|
|||||||
2015-06-15 14:30:50.000321
|
2015-06-15 14:30:50.000321
|
||||||
2015-06-15 14:40:02.000123
|
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
|
``DateField`` truncation
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -1935,3 +1935,43 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||||||
DTModel.objects.annotate(
|
DTModel.objects.annotate(
|
||||||
hour_melb=Trunc("start_time", "hour", tzinfo=melb),
|
hour_melb=Trunc("start_time", "hour", tzinfo=melb),
|
||||||
).get()
|
).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)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user