From b7f263551c64e3f80544892e314ed5b0b22cc7c8 Mon Sep 17 00:00:00 2001 From: nabil-rady Date: Mon, 21 Feb 2022 03:28:19 +0200 Subject: [PATCH] Refs #33517 -- Prevented __second lookup from returning fractional seconds on PostgreSQL. --- django/db/backends/oracle/features.py | 6 ++++++ django/db/backends/postgresql/operations.py | 9 +++++++++ .../db_functions/datetime/test_extract_trunc.py | 16 ++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py index 2580e5c6c1..c95cce90ac 100644 --- a/django/db/backends/oracle/features.py +++ b/django/db/backends/oracle/features.py @@ -86,6 +86,12 @@ class DatabaseFeatures(BaseDatabaseFeatures): "db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests." "test_trunc_week_before_1000", }, + "Oracle extracts seconds including fractional seconds (#33517).": { + "db_functions.datetime.test_extract_trunc.DateFunctionTests." + "test_extract_second_func_no_fractional", + "db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests." + "test_extract_second_func_no_fractional", + }, "Oracle doesn't support bitwise XOR.": { "expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor", "expressions.tests.ExpressionOperatorTests.test_lefthand_bitwise_xor_null", diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 49a61d148a..946baea212 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -77,6 +77,9 @@ class DatabaseOperations(BaseDatabaseOperations): def datetime_extract_sql(self, lookup_type, field_name, tzname): field_name = self._convert_field_to_tz(field_name, tzname) + if lookup_type == "second": + # Truncate fractional seconds. + return f"EXTRACT('second' FROM DATE_TRUNC('second', {field_name}))" return self.date_extract_sql(lookup_type, field_name) def datetime_trunc_sql(self, lookup_type, field_name, tzname): @@ -84,6 +87,12 @@ class DatabaseOperations(BaseDatabaseOperations): # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name) + def time_extract_sql(self, lookup_type, field_name): + if lookup_type == "second": + # Truncate fractional seconds. + return f"EXTRACT('second' FROM DATE_TRUNC('second', {field_name}))" + return self.date_extract_sql(lookup_type, field_name) + def time_trunc_sql(self, lookup_type, field_name, tzname=None): field_name = self._convert_field_to_tz(field_name, tzname) return "DATE_TRUNC('%s', %s)::time" % (lookup_type, field_name) diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py index 67a4b81766..a01778c719 100644 --- a/tests/db_functions/datetime/test_extract_trunc.py +++ b/tests/db_functions/datetime/test_extract_trunc.py @@ -899,6 +899,22 @@ class DateFunctionTests(TestCase): 2, ) + def test_extract_second_func_no_fractional(self): + start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321) + end_datetime = datetime(2016, 6, 15, 14, 30, 50, 783) + if settings.USE_TZ: + start_datetime = timezone.make_aware(start_datetime) + end_datetime = timezone.make_aware(end_datetime) + obj = self.create_model(start_datetime, end_datetime) + self.assertSequenceEqual( + DTModel.objects.filter(start_datetime__second=F("end_datetime__second")), + [obj], + ) + self.assertSequenceEqual( + DTModel.objects.filter(start_time__second=F("end_time__second")), + [obj], + ) + def test_trunc_func(self): start_datetime = datetime(999, 6, 15, 14, 30, 50, 321) end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)