Fixed #27856 -- Improved accuracy of date subtraction on PostgreSQL.

Accuracy was incorrect when dates differ by a month or more.
This commit is contained in:
Vytis Banaitis 2017-02-17 20:34:22 +02:00 committed by Tim Graham
parent 5a6f70b428
commit 4045fd56cb
3 changed files with 23 additions and 4 deletions

View File

@ -253,7 +253,7 @@ class DatabaseOperations(BaseDatabaseOperations):
if internal_type == 'DateField': if internal_type == 'DateField':
lhs_sql, lhs_params = lhs lhs_sql, lhs_params = lhs
rhs_sql, rhs_params = rhs rhs_sql, rhs_params = rhs
return "age(%s, %s)" % (lhs_sql, rhs_sql), lhs_params + rhs_params return "(interval '1 day' * (%s - %s))" % (lhs_sql, rhs_sql), lhs_params + rhs_params
return super().subtract_temporals(internal_type, lhs, rhs) return super().subtract_temporals(internal_type, lhs, rhs)
def fulltext_search_sql(self, field_name): def fulltext_search_sql(self, field_name):

View File

@ -17,3 +17,6 @@ Bugfixes
* Fixed a crash on Oracle and PostgreSQL when subtracting ``DurationField`` * Fixed a crash on Oracle and PostgreSQL when subtracting ``DurationField``
or ``IntegerField`` from ``DateField`` (:ticket:`27828`). or ``IntegerField`` from ``DateField`` (:ticket:`27828`).
* Fixed query expression date subtraction accuracy on PostgreSQL for
differences large an a month (:ticket:`27856`).

View File

@ -952,6 +952,7 @@ class FTimeDeltaTests(TestCase):
delta2 = datetime.timedelta(seconds=44) delta2 = datetime.timedelta(seconds=44)
delta3 = datetime.timedelta(hours=21, minutes=8) delta3 = datetime.timedelta(hours=21, minutes=8)
delta4 = datetime.timedelta(days=10) delta4 = datetime.timedelta(days=10)
delta5 = datetime.timedelta(days=90)
# Test data is set so that deltas and delays will be # Test data is set so that deltas and delays will be
# strictly increasing. # strictly increasing.
@ -1015,6 +1016,18 @@ class FTimeDeltaTests(TestCase):
cls.deltas.append(delta4) cls.deltas.append(delta4)
cls.delays.append(e4.start - datetime.datetime.combine(e4.assigned, midnight)) cls.delays.append(e4.start - datetime.datetime.combine(e4.assigned, midnight))
cls.days_long.append(e4.completed - e4.assigned) cls.days_long.append(e4.completed - e4.assigned)
# e5: started a month after assignment, very long duration
delay = datetime.timedelta(30)
end = stime + delay + delta5
e5 = Experiment.objects.create(
name='e5', assigned=sday, start=stime + delay, end=end,
completed=end.date(), estimated_time=delta5,
)
cls.deltas.append(delta5)
cls.delays.append(e5.start - datetime.datetime.combine(e5.assigned, midnight))
cls.days_long.append(e5.completed - e5.assigned)
cls.expnames = [e.name for e in Experiment.objects.all()] cls.expnames = [e.name for e in Experiment.objects.all()]
def test_multiple_query_compilation(self): def test_multiple_query_compilation(self):
@ -1138,7 +1151,10 @@ class FTimeDeltaTests(TestCase):
) )
at_least_5_days = {e.name for e in queryset.filter(completion_duration__gte=datetime.timedelta(days=5))} at_least_5_days = {e.name for e in queryset.filter(completion_duration__gte=datetime.timedelta(days=5))}
self.assertEqual(at_least_5_days, {'e3', 'e4'}) self.assertEqual(at_least_5_days, {'e3', 'e4', 'e5'})
at_least_120_days = {e.name for e in queryset.filter(completion_duration__gte=datetime.timedelta(days=120))}
self.assertEqual(at_least_120_days, {'e5'})
less_than_5_days = {e.name for e in queryset.filter(completion_duration__lt=datetime.timedelta(days=5))} less_than_5_days = {e.name for e in queryset.filter(completion_duration__lt=datetime.timedelta(days=5))}
expected = {'e0', 'e2'} expected = {'e0', 'e2'}
@ -1182,13 +1198,13 @@ class FTimeDeltaTests(TestCase):
over_estimate = Experiment.objects.exclude(name='e1').filter( over_estimate = Experiment.objects.exclude(name='e1').filter(
completed__gt=self.stime + F('estimated_time'), completed__gt=self.stime + F('estimated_time'),
).order_by('name') ).order_by('name')
self.assertQuerysetEqual(over_estimate, ['e3', 'e4'], lambda e: e.name) self.assertQuerysetEqual(over_estimate, ['e3', 'e4', 'e5'], lambda e: e.name)
def test_date_minus_duration(self): def test_date_minus_duration(self):
more_than_4_days = Experiment.objects.filter( more_than_4_days = Experiment.objects.filter(
assigned__lt=F('completed') - Value(datetime.timedelta(days=4), output_field=models.DurationField()) assigned__lt=F('completed') - Value(datetime.timedelta(days=4), output_field=models.DurationField())
) )
self.assertQuerysetEqual(more_than_4_days, ['e3', 'e4'], lambda e: e.name) self.assertQuerysetEqual(more_than_4_days, ['e3', 'e4', 'e5'], lambda e: e.name)
def test_negative_timedelta_update(self): def test_negative_timedelta_update(self):
# subtract 30 seconds, 30 minutes, 2 hours and 2 days # subtract 30 seconds, 30 minutes, 2 hours and 2 days