Fixed #29754 -- Added is_dst parameter to Trunc database functions.
This commit is contained in:
parent
38c3f7ea59
commit
d527639804
|
@ -170,8 +170,9 @@ class TruncBase(TimezoneMixin, Transform):
|
||||||
kind = None
|
kind = None
|
||||||
tzinfo = None
|
tzinfo = None
|
||||||
|
|
||||||
def __init__(self, expression, output_field=None, tzinfo=None, **extra):
|
def __init__(self, expression, output_field=None, tzinfo=None, is_dst=None, **extra):
|
||||||
self.tzinfo = tzinfo
|
self.tzinfo = tzinfo
|
||||||
|
self.is_dst = is_dst
|
||||||
super().__init__(expression, output_field=output_field, **extra)
|
super().__init__(expression, output_field=output_field, **extra)
|
||||||
|
|
||||||
def as_sql(self, compiler, connection):
|
def as_sql(self, compiler, connection):
|
||||||
|
@ -222,7 +223,7 @@ class TruncBase(TimezoneMixin, Transform):
|
||||||
pass
|
pass
|
||||||
elif value is not None:
|
elif value is not None:
|
||||||
value = value.replace(tzinfo=None)
|
value = value.replace(tzinfo=None)
|
||||||
value = timezone.make_aware(value, self.tzinfo)
|
value = timezone.make_aware(value, self.tzinfo, is_dst=self.is_dst)
|
||||||
elif not connection.features.has_zoneinfo_database:
|
elif not connection.features.has_zoneinfo_database:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Database returned an invalid datetime value. Are time '
|
'Database returned an invalid datetime value. Are time '
|
||||||
|
@ -240,9 +241,12 @@ class TruncBase(TimezoneMixin, Transform):
|
||||||
|
|
||||||
class Trunc(TruncBase):
|
class Trunc(TruncBase):
|
||||||
|
|
||||||
def __init__(self, expression, kind, output_field=None, tzinfo=None, **extra):
|
def __init__(self, expression, kind, output_field=None, tzinfo=None, is_dst=None, **extra):
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
super().__init__(expression, output_field=output_field, tzinfo=tzinfo, **extra)
|
super().__init__(
|
||||||
|
expression, output_field=output_field, tzinfo=tzinfo,
|
||||||
|
is_dst=is_dst, **extra
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TruncYear(TruncBase):
|
class TruncYear(TruncBase):
|
||||||
|
|
|
@ -442,7 +442,7 @@ Usage example::
|
||||||
``Trunc``
|
``Trunc``
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. class:: Trunc(expression, kind, output_field=None, tzinfo=None, **extra)
|
.. class:: Trunc(expression, kind, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
Truncates a date up to a significant component.
|
Truncates a date up to a significant component.
|
||||||
|
|
||||||
|
@ -460,6 +460,14 @@ value. If ``output_field`` is omitted, it will default to the ``output_field``
|
||||||
of ``expression``. A ``tzinfo`` subclass, usually provided by ``pytz``, can be
|
of ``expression``. A ``tzinfo`` subclass, usually provided by ``pytz``, can be
|
||||||
passed to truncate a value in a specific timezone.
|
passed to truncate a value in a specific timezone.
|
||||||
|
|
||||||
|
The ``is_dst`` parameter indicates whether or not ``pytz`` should interpret
|
||||||
|
nonexistent and ambiguous datetimes in daylight saving time. By default (when
|
||||||
|
``is_dst=None``), ``pytz`` raises an exception for such datetimes.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
The ``is_dst`` parameter was added.
|
||||||
|
|
||||||
Given the datetime ``2015-06-15 14:30:50.000321+00:00``, the built-in ``kind``\s
|
Given the datetime ``2015-06-15 14:30:50.000321+00:00``, the built-in ``kind``\s
|
||||||
return:
|
return:
|
||||||
|
|
||||||
|
@ -525,21 +533,21 @@ Usage example::
|
||||||
``DateField`` truncation
|
``DateField`` truncation
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. class:: TruncYear(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncYear(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'year'
|
.. attribute:: kind = 'year'
|
||||||
|
|
||||||
.. class:: TruncMonth(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncMonth(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'month'
|
.. attribute:: kind = 'month'
|
||||||
|
|
||||||
.. class:: TruncWeek(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncWeek(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
Truncates to midnight on the Monday of the week.
|
Truncates to midnight on the Monday of the week.
|
||||||
|
|
||||||
.. attribute:: kind = 'week'
|
.. attribute:: kind = 'week'
|
||||||
|
|
||||||
.. class:: TruncQuarter(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncQuarter(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'quarter'
|
.. attribute:: kind = 'quarter'
|
||||||
|
|
||||||
|
@ -603,19 +611,19 @@ truncate function. It's also registered as a transform on ``DateTimeField`` as
|
||||||
truncate function. It's also registered as a transform on ``DateTimeField`` as
|
truncate function. It's also registered as a transform on ``DateTimeField`` as
|
||||||
``__time``.
|
``__time``.
|
||||||
|
|
||||||
.. class:: TruncDay(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncDay(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'day'
|
.. attribute:: kind = 'day'
|
||||||
|
|
||||||
.. class:: TruncHour(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncHour(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'hour'
|
.. attribute:: kind = 'hour'
|
||||||
|
|
||||||
.. class:: TruncMinute(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncMinute(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'minute'
|
.. attribute:: kind = 'minute'
|
||||||
|
|
||||||
.. class:: TruncSecond(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncSecond(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'second'
|
.. attribute:: kind = 'second'
|
||||||
|
|
||||||
|
@ -653,15 +661,15 @@ Usage example::
|
||||||
``TimeField`` truncation
|
``TimeField`` truncation
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. class:: TruncHour(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncHour(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'hour'
|
.. attribute:: kind = 'hour'
|
||||||
|
|
||||||
.. class:: TruncMinute(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncMinute(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'minute'
|
.. attribute:: kind = 'minute'
|
||||||
|
|
||||||
.. class:: TruncSecond(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncSecond(expression, output_field=None, tzinfo=None, is_dst=None, **extra)
|
||||||
|
|
||||||
.. attribute:: kind = 'second'
|
.. attribute:: kind = 'second'
|
||||||
|
|
||||||
|
|
|
@ -164,6 +164,10 @@ Models
|
||||||
|
|
||||||
* Added the :class:`~django.db.models.functions.MD5` database function.
|
* Added the :class:`~django.db.models.functions.MD5` database function.
|
||||||
|
|
||||||
|
* The new ``is_dst`` parameter of the
|
||||||
|
:class:`~django.db.models.functions.Trunc` database functions determines the
|
||||||
|
treatment of nonexistent and ambiguous datetimes.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -1044,6 +1044,30 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
self.assertEqual(model.melb_year.year, 2016)
|
self.assertEqual(model.melb_year.year, 2016)
|
||||||
self.assertEqual(model.pacific_year.year, 2015)
|
self.assertEqual(model.pacific_year.year, 2015)
|
||||||
|
|
||||||
|
def test_trunc_ambiguous_and_invalid_times(self):
|
||||||
|
sao = pytz.timezone('America/Sao_Paulo')
|
||||||
|
utc = pytz.timezone('UTC')
|
||||||
|
start_datetime = utc.localize(datetime(2016, 10, 16, 13))
|
||||||
|
end_datetime = utc.localize(datetime(2016, 2, 21, 1))
|
||||||
|
self.create_model(start_datetime, end_datetime)
|
||||||
|
with timezone.override(sao):
|
||||||
|
with self.assertRaisesMessage(pytz.NonExistentTimeError, '2016-10-16 00:00:00'):
|
||||||
|
model = DTModel.objects.annotate(truncated_start=TruncDay('start_datetime')).get()
|
||||||
|
with self.assertRaisesMessage(pytz.AmbiguousTimeError, '2016-02-20 23:00:00'):
|
||||||
|
model = DTModel.objects.annotate(truncated_end=TruncHour('end_datetime')).get()
|
||||||
|
model = DTModel.objects.annotate(
|
||||||
|
truncated_start=TruncDay('start_datetime', is_dst=False),
|
||||||
|
truncated_end=TruncHour('end_datetime', is_dst=False),
|
||||||
|
).get()
|
||||||
|
self.assertEqual(model.truncated_start.dst(), timedelta(0))
|
||||||
|
self.assertEqual(model.truncated_end.dst(), timedelta(0))
|
||||||
|
model = DTModel.objects.annotate(
|
||||||
|
truncated_start=TruncDay('start_datetime', is_dst=True),
|
||||||
|
truncated_end=TruncHour('end_datetime', is_dst=True),
|
||||||
|
).get()
|
||||||
|
self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
|
||||||
|
self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
|
||||||
|
|
||||||
def test_trunc_func_with_timezone(self):
|
def test_trunc_func_with_timezone(self):
|
||||||
"""
|
"""
|
||||||
If the truncated datetime transitions to a different offset (daylight
|
If the truncated datetime transitions to a different offset (daylight
|
||||||
|
|
Loading…
Reference in New Issue