Skip to content

Commit 85f02ef

Browse files
committed
Fix Trunc database function with tzinfo parameter
1 parent 658f936 commit 85f02ef

File tree

6 files changed

+47
-11
lines changed

6 files changed

+47
-11
lines changed

django_mongodb_backend/features.py

-4
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
5757
# Pattern lookups that use regexMatch don't work on JSONField:
5858
# Unsupported conversion from array to string in $convert
5959
"model_fields.test_jsonfield.TestQuerying.test_icontains",
60-
# Truncating in another timezone doesn't work becauase MongoDB converts
61-
# the result back to UTC.
62-
"db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_func_with_timezone",
63-
"db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_trunc_timezone_applied_before_truncation",
6460
# Unexpected alias_refcount in alias_map.
6561
"queries.tests.Queries1Tests.test_order_by_tables",
6662
# The $sum aggregation returns 0 instead of None for null.

django_mongodb_backend/functions.py

+32
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
from datetime import datetime
2+
3+
from django.conf import settings
14
from django.db import NotSupportedError
5+
from django.db.models import DateField, DateTimeField, TimeField
26
from django.db.models.expressions import Func
37
from django.db.models.functions import JSONArray
48
from django.db.models.functions.comparison import Cast, Coalesce, Greatest, Least, NullIf
@@ -196,6 +200,33 @@ def trunc(self, compiler, connection):
196200
return {"$dateTrunc": lhs_mql}
197201

198202

203+
def trunc_convert_value(self, value, expression, connection):
204+
if connection.vendor == "mongodb":
205+
# A custom TruncBase.convert_value() for MongoDB.
206+
if value is None:
207+
return None
208+
convert_to_tz = settings.USE_TZ and self.get_tzname() != "UTC"
209+
if isinstance(self.output_field, DateTimeField):
210+
if convert_to_tz:
211+
# Unlike other databases, MongoDB returns the value in UTC,
212+
# so rather than setting the time zone equal to self.tzinfo,
213+
# the value must be converted to tzinfo.
214+
value = value.astimezone(self.tzinfo)
215+
elif isinstance(value, datetime):
216+
if isinstance(self.output_field, DateField):
217+
if convert_to_tz:
218+
value = value.astimezone(self.tzinfo)
219+
# Truncate for Trunc(..., output_field=DateField)
220+
value = value.date()
221+
elif isinstance(self.output_field, TimeField):
222+
if convert_to_tz:
223+
value = value.astimezone(self.tzinfo)
224+
# Truncate for Trunc(..., output_field=TimeField)
225+
value = value.time()
226+
return value
227+
return self.convert_value(value, expression, connection)
228+
229+
199230
def trunc_date(self, compiler, connection):
200231
# Cast to date rather than truncate to date.
201232
lhs_mql = process_lhs(self, compiler, connection)
@@ -256,6 +287,7 @@ def register_functions():
256287
Substr.as_mql = substr
257288
Trim.as_mql = trim("trim")
258289
TruncBase.as_mql = trunc
290+
TruncBase.convert_value = trunc_convert_value
259291
TruncDate.as_mql = trunc_date
260292
TruncTime.as_mql = trunc_time
261293
Upper.as_mql = preserve_null("toUpper")

django_mongodb_backend/operations.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from django.db.backends.base.operations import BaseDatabaseOperations
1111
from django.db.models import TextField
1212
from django.db.models.expressions import Combinable, Expression
13-
from django.db.models.functions import Cast
13+
from django.db.models.functions import Cast, Trunc
1414
from django.utils import timezone
1515
from django.utils.regex_helper import _lazy_re_compile
1616

@@ -97,7 +97,11 @@ def get_db_converters(self, expression):
9797
]
9898
)
9999
elif internal_type == "DateField":
100-
converters.append(self.convert_datefield_value)
100+
# Trunc(... output_field="DateField") values must remain datetime
101+
# until Trunc.convert_value() so they can be converted from UTC
102+
# before truncation.
103+
if not isinstance(expression, Trunc):
104+
converters.append(self.convert_datefield_value)
101105
elif internal_type == "DateTimeField":
102106
if settings.USE_TZ:
103107
converters.append(self.convert_datetimefield_value)
@@ -106,7 +110,11 @@ def get_db_converters(self, expression):
106110
elif internal_type == "JSONField":
107111
converters.append(self.convert_jsonfield_value)
108112
elif internal_type == "TimeField":
109-
converters.append(self.convert_timefield_value)
113+
# Trunc(... output_field="TimeField") values must remain datetime
114+
# until Trunc.convert_value() so they can be converted from UTC
115+
# before truncation.
116+
if not isinstance(expression, Trunc):
117+
converters.append(self.convert_timefield_value)
110118
elif internal_type == "UUIDField":
111119
converters.append(self.convert_uuidfield_value)
112120
return converters

docs/source/releases/5.1.x.rst

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Django MongoDB Backend 5.1.x
1010
- Added support for a field's custom lookups and transforms in
1111
``EmbeddedModelField``, e.g. ``ArrayField``’s ``contains``,
1212
``contained__by``, ``len``, etc.
13+
- Fixed the results of queries that use the ``tzinfo`` parameter of the
14+
``Trunc`` database functions.
1315

1416
.. _django-mongodb-backend-5.1.0-beta-2:
1517

docs/source/releases/5.2.x.rst

+2
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ Bug fixes
1919
- Added support for a field's custom lookups and transforms in
2020
``EmbeddedModelField``, e.g. ``ArrayField``’s ``contains``,
2121
``contained__by``, ``len``, etc.
22+
- Fixed the results of queries that use the ``tzinfo`` parameter of the
23+
``Trunc`` database functions.

docs/source/topics/known-issues.rst

-4
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,6 @@ Database functions
7575
:class:`~django.db.models.functions.SHA512`
7676
- :class:`~django.db.models.functions.Sign`
7777

78-
- The ``tzinfo`` parameter of the :class:`~django.db.models.functions.Trunc`
79-
database functions doesn't work properly because MongoDB converts the result
80-
back to UTC.
81-
8278
Transaction management
8379
======================
8480

0 commit comments

Comments
 (0)