Skip to content

Commit b61982e

Browse files
committed
Fix Trunc database function with tzinfo parameter
1 parent 782feb7 commit b61982e

File tree

4 files changed

+37
-11
lines changed

4 files changed

+37
-11
lines changed

django_mongodb_backend/features.py

Lines changed: 0 additions & 4 deletions
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

Lines changed: 32 additions & 0 deletions
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+
tzname = self.get_tzname()
207+
if isinstance(self.output_field, DateTimeField):
208+
if not settings.USE_TZ:
209+
pass
210+
elif value is not None and tzname != "UTC":
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 value is None:
217+
pass
218+
elif isinstance(self.output_field, DateField):
219+
if settings.USE_TZ and tzname != "UTC":
220+
value = value.astimezone(self.tzinfo)
221+
value = value.date()
222+
elif isinstance(self.output_field, TimeField):
223+
if settings.USE_TZ and tzname != "UTC":
224+
value = value.astimezone(self.tzinfo)
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

Lines changed: 5 additions & 3 deletions
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,8 @@ def get_db_converters(self, expression):
9797
]
9898
)
9999
elif internal_type == "DateField":
100-
converters.append(self.convert_datefield_value)
100+
if not isinstance(expression, Trunc):
101+
converters.append(self.convert_datefield_value)
101102
elif internal_type == "DateTimeField":
102103
if settings.USE_TZ:
103104
converters.append(self.convert_datetimefield_value)
@@ -106,7 +107,8 @@ def get_db_converters(self, expression):
106107
elif internal_type == "JSONField":
107108
converters.append(self.convert_jsonfield_value)
108109
elif internal_type == "TimeField":
109-
converters.append(self.convert_timefield_value)
110+
if not isinstance(expression, Trunc):
111+
converters.append(self.convert_timefield_value)
110112
elif internal_type == "UUIDField":
111113
converters.append(self.convert_uuidfield_value)
112114
return converters

docs/source/topics/known-issues.rst

Lines changed: 0 additions & 4 deletions
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)