diff --git a/README.md b/README.md index d41820995..79731cd10 100644 --- a/README.md +++ b/README.md @@ -203,8 +203,6 @@ Congratulations, your project is ready to go! - `Right` - `SHA1`, `SHA224`, `SHA256`, `SHA384`, `SHA512` - `Sign` - - `TruncDate` - - `TruncTime` - The `tzinfo` parameter of the `Trunc` database functions doesn't work properly because MongoDB converts the result back to UTC. diff --git a/django_mongodb/features.py b/django_mongodb/features.py index 433c9f009..4e585242c 100644 --- a/django_mongodb/features.py +++ b/django_mongodb/features.py @@ -469,21 +469,10 @@ def django_test_expected_failures(self): "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_quarter_func", "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_quarter_func_boundaries", }, - "TruncDate database function not supported.": { - "aggregation.tests.AggregateTestCase.test_aggregation_default_using_date_from_database", - "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_date_func", - "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_date_none", - "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_lookup_name_sql_injection", - "expressions.tests.FieldTransformTests.test_multiple_transforms_in_values", + "TruncDate database function's tzinfo not supported.": { "model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_with_use_tz", - "model_fields.test_datetimefield.DateTimeFieldTests.test_lookup_date_without_use_tz", "timezones.tests.NewDatabaseTests.test_query_convert_timezones", }, - "TruncTime database function not supported.": { - "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_time_comparison", - "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_time_func", - "db_functions.datetime.test_extract_trunc.DateFunctionTests.test_trunc_time_none", - }, "MongoDB can't annotate ($project) a function like PI().": { "aggregation.tests.AggregateTestCase.test_aggregation_default_using_decimal_from_database", "db_functions.math.test_pi.PiTests.test", diff --git a/django_mongodb/functions.py b/django_mongodb/functions.py index fc4057daa..750a548f8 100644 --- a/django_mongodb/functions.py +++ b/django_mongodb/functions.py @@ -15,6 +15,8 @@ ExtractYear, Now, TruncBase, + TruncDate, + TruncTime, ) from django.db.models.functions.math import Ceil, Cot, Degrees, Log, Power, Radians, Random, Round from django.db.models.functions.text import ( @@ -191,6 +193,44 @@ def trunc(self, compiler, connection): return {"$dateTrunc": lhs_mql} +def trunc_date(self, compiler, connection): + # Cast to date rather than truncate to date. + lhs_mql = process_lhs(self, compiler, connection) + tzname = self.get_tzname() + if tzname and tzname != "UTC": + raise NotSupportedError(f"TruncDate with tzinfo ({tzname}) isn't supported on MongoDB.") + return { + "$dateFromString": { + "dateString": { + "$concat": [ + {"$dateToString": {"format": "%Y-%m-%d", "date": lhs_mql}}, + # Dates are stored with time(0, 0), so by replacing any + # existing time component with that, the result of + # TruncDate can be compared to DateField. + "T00:00:00.000", + ] + }, + } + } + + +def trunc_time(self, compiler, connection): + lhs_mql = process_lhs(self, compiler, connection) + return { + "$dateFromString": { + "dateString": { + "$concat": [ + # Times are stored with date(1, 1, 1)), so by + # replacing any existing date component with that, the + # result of TruncTime can be compared to TimeField. + "0001-01-01T", + {"$dateToString": {"format": "%H:%M:%S.%L", "date": lhs_mql}}, + ] + } + } + } + + def register_functions(): Cast.as_mql = cast Concat.as_mql = concat @@ -212,4 +252,6 @@ def register_functions(): Substr.as_mql = substr Trim.as_mql = trim("trim") TruncBase.as_mql = trunc + TruncDate.as_mql = trunc_date + TruncTime.as_mql = trunc_time Upper.as_mql = preserve_null("toUpper")