[1.10.x] Fixed #27856 -- Improved accuracy of date subtraction on PostgreSQL.

Accuracy was incorrect when dates differ by a month or more.

Backport of 4045fd56cb from master
This commit is contained in:
Vytis Banaitis 2017-02-17 20:34:22 +02:00 committed by Tim Graham
parent 75327b88a8
commit 92ce31fd8c
3 changed files with 23 additions and 4 deletions

View File

@ -252,7 +252,7 @@ class DatabaseOperations(BaseDatabaseOperations):
if internal_type == 'DateField':
lhs_sql, lhs_params = lhs
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(DatabaseOperations, self).subtract_temporals(internal_type, lhs, rhs)
def fulltext_search_sql(self, field_name):

View File

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

View File

@ -676,6 +676,7 @@ class FTimeDeltaTests(TestCase):
delta2 = datetime.timedelta(seconds=44)
delta3 = datetime.timedelta(hours=21, minutes=8)
delta4 = datetime.timedelta(days=10)
delta5 = datetime.timedelta(days=90)
# Test data is set so that deltas and delays will be
# strictly increasing.
@ -739,6 +740,18 @@ class FTimeDeltaTests(TestCase):
cls.deltas.append(delta4)
cls.delays.append(e4.start - datetime.datetime.combine(e4.assigned, midnight))
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()]
def test_multiple_query_compilation(self):
@ -862,7 +875,10 @@ class FTimeDeltaTests(TestCase):
)
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))}
expected = {'e0', 'e2'}
@ -906,7 +922,7 @@ class FTimeDeltaTests(TestCase):
over_estimate = Experiment.objects.exclude(name='e1').filter(
completed__gt=self.stime + F('estimated_time'),
).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):
value = (
@ -915,7 +931,7 @@ class FTimeDeltaTests(TestCase):
else Value(datetime.timedelta(days=4), output_field=models.DurationField())
)
more_than_4_days = Experiment.objects.filter(assigned__lt=F('completed') - value)
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)
class ValueTests(TestCase):