[1.11.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:
parent
d53fc7614d
commit
d03153e3b0
|
@ -262,7 +262,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(DatabaseOperations, self).subtract_temporals(internal_type, lhs, rhs)
|
return super(DatabaseOperations, self).subtract_temporals(internal_type, lhs, rhs)
|
||||||
|
|
||||||
def fulltext_search_sql(self, field_name):
|
def fulltext_search_sql(self, field_name):
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -955,6 +955,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.
|
||||||
|
@ -1018,6 +1019,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):
|
||||||
|
@ -1141,7 +1154,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'}
|
||||||
|
@ -1185,13 +1201,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
|
||||||
|
|
Loading…
Reference in New Issue