Fixed #28650 -- Added TruncWeek database function.
This commit is contained in:
parent
f229049d6c
commit
a455e732a0
|
@ -53,6 +53,10 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
return "MAKEDATE(YEAR(%s), 1) + INTERVAL QUARTER(%s) QUARTER - INTERVAL 1 QUARTER" % (
|
return "MAKEDATE(YEAR(%s), 1) + INTERVAL QUARTER(%s) QUARTER - INTERVAL 1 QUARTER" % (
|
||||||
field_name, field_name
|
field_name, field_name
|
||||||
)
|
)
|
||||||
|
elif lookup_type == 'week':
|
||||||
|
return "DATE_SUB(%s, INTERVAL WEEKDAY(%s) DAY)" % (
|
||||||
|
field_name, field_name
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return "DATE(%s)" % (field_name)
|
return "DATE(%s)" % (field_name)
|
||||||
|
|
||||||
|
@ -84,6 +88,12 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
"INTERVAL QUARTER({field_name}) QUARTER - " +
|
"INTERVAL QUARTER({field_name}) QUARTER - " +
|
||||||
"INTERVAL 1 QUARTER, '%%Y-%%m-01 00:00:00') AS DATETIME)"
|
"INTERVAL 1 QUARTER, '%%Y-%%m-01 00:00:00') AS DATETIME)"
|
||||||
).format(field_name=field_name)
|
).format(field_name=field_name)
|
||||||
|
if lookup_type == 'week':
|
||||||
|
return (
|
||||||
|
"CAST(DATE_FORMAT(DATE_SUB({field_name}, "
|
||||||
|
"INTERVAL WEEKDAY({field_name}) DAY), "
|
||||||
|
"'%%Y-%%m-%%d 00:00:00') AS DATETIME)"
|
||||||
|
).format(field_name=field_name)
|
||||||
try:
|
try:
|
||||||
i = fields.index(lookup_type) + 1
|
i = fields.index(lookup_type) + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
|
@ -78,6 +78,8 @@ END;
|
||||||
return "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
|
return "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
|
||||||
elif lookup_type == 'quarter':
|
elif lookup_type == 'quarter':
|
||||||
return "TRUNC(%s, 'Q')" % field_name
|
return "TRUNC(%s, 'Q')" % field_name
|
||||||
|
elif lookup_type == 'week':
|
||||||
|
return "TRUNC(%s, 'IW')" % field_name
|
||||||
else:
|
else:
|
||||||
return "TRUNC(%s)" % field_name
|
return "TRUNC(%s)" % field_name
|
||||||
|
|
||||||
|
@ -116,6 +118,8 @@ END;
|
||||||
sql = "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
|
sql = "TRUNC(%s, '%s')" % (field_name, lookup_type.upper())
|
||||||
elif lookup_type == 'quarter':
|
elif lookup_type == 'quarter':
|
||||||
sql = "TRUNC(%s, 'Q')" % field_name
|
sql = "TRUNC(%s, 'Q')" % field_name
|
||||||
|
elif lookup_type == 'week':
|
||||||
|
sql = "TRUNC(%s, 'IW')" % field_name
|
||||||
elif lookup_type == 'day':
|
elif lookup_type == 'day':
|
||||||
sql = "TRUNC(%s)" % field_name
|
sql = "TRUNC(%s)" % field_name
|
||||||
elif lookup_type == 'hour':
|
elif lookup_type == 'hour':
|
||||||
|
|
|
@ -335,6 +335,9 @@ def _sqlite_date_trunc(lookup_type, dt):
|
||||||
return '%i-%02i-01' % (dt.year, month_in_quarter)
|
return '%i-%02i-01' % (dt.year, month_in_quarter)
|
||||||
elif lookup_type == 'month':
|
elif lookup_type == 'month':
|
||||||
return "%i-%02i-01" % (dt.year, dt.month)
|
return "%i-%02i-01" % (dt.year, dt.month)
|
||||||
|
elif lookup_type == 'week':
|
||||||
|
dt = dt - datetime.timedelta(days=dt.weekday())
|
||||||
|
return "%i-%02i-%02i" % (dt.year, dt.month, dt.day)
|
||||||
elif lookup_type == 'day':
|
elif lookup_type == 'day':
|
||||||
return "%i-%02i-%02i" % (dt.year, dt.month, dt.day)
|
return "%i-%02i-%02i" % (dt.year, dt.month, dt.day)
|
||||||
|
|
||||||
|
@ -403,6 +406,9 @@ def _sqlite_datetime_trunc(lookup_type, dt, tzname):
|
||||||
return '%i-%02i-01 00:00:00' % (dt.year, month_in_quarter)
|
return '%i-%02i-01 00:00:00' % (dt.year, month_in_quarter)
|
||||||
elif lookup_type == 'month':
|
elif lookup_type == 'month':
|
||||||
return "%i-%02i-01 00:00:00" % (dt.year, dt.month)
|
return "%i-%02i-01 00:00:00" % (dt.year, dt.month)
|
||||||
|
elif lookup_type == 'week':
|
||||||
|
dt = dt - datetime.timedelta(days=dt.weekday())
|
||||||
|
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
||||||
elif lookup_type == 'day':
|
elif lookup_type == 'day':
|
||||||
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
return "%i-%02i-%02i 00:00:00" % (dt.year, dt.month, dt.day)
|
||||||
elif lookup_type == 'hour':
|
elif lookup_type == 'hour':
|
||||||
|
|
|
@ -3,7 +3,7 @@ from .datetime import (
|
||||||
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
||||||
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
||||||
Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
||||||
TruncQuarter, TruncSecond, TruncTime, TruncYear,
|
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
||||||
)
|
)
|
||||||
from .text import (
|
from .text import (
|
||||||
Concat, ConcatPair, Length, Lower, Replace, StrIndex, Substr, Upper,
|
Concat, ConcatPair, Length, Lower, Replace, StrIndex, Substr, Upper,
|
||||||
|
@ -21,7 +21,7 @@ __all__ = [
|
||||||
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
||||||
'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour',
|
'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour',
|
||||||
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
||||||
'TruncYear',
|
'TruncWeek', 'TruncYear',
|
||||||
# text
|
# text
|
||||||
'Concat', 'ConcatPair', 'Length', 'Lower', 'Replace', 'StrIndex', 'Substr',
|
'Concat', 'ConcatPair', 'Length', 'Lower', 'Replace', 'StrIndex', 'Substr',
|
||||||
'Upper',
|
'Upper',
|
||||||
|
|
|
@ -199,7 +199,8 @@ class TruncBase(TimezoneMixin, Transform):
|
||||||
field.name, output_field.__class__.__name__ if has_explicit_output_field else 'DateTimeField'
|
field.name, output_field.__class__.__name__ if has_explicit_output_field else 'DateTimeField'
|
||||||
))
|
))
|
||||||
elif isinstance(field, TimeField) and (
|
elif isinstance(field, TimeField) and (
|
||||||
isinstance(output_field, DateTimeField) or copy.kind in ('year', 'quarter', 'month', 'day', 'date')):
|
isinstance(output_field, DateTimeField) or
|
||||||
|
copy.kind in ('year', 'quarter', 'month', 'week', 'day', 'date')):
|
||||||
raise ValueError("Cannot truncate TimeField '%s' to %s. " % (
|
raise ValueError("Cannot truncate TimeField '%s' to %s. " % (
|
||||||
field.name, output_field.__class__.__name__ if has_explicit_output_field else 'DateTimeField'
|
field.name, output_field.__class__.__name__ if has_explicit_output_field else 'DateTimeField'
|
||||||
))
|
))
|
||||||
|
@ -242,6 +243,11 @@ class TruncMonth(TruncBase):
|
||||||
kind = 'month'
|
kind = 'month'
|
||||||
|
|
||||||
|
|
||||||
|
class TruncWeek(TruncBase):
|
||||||
|
"""Truncate to midnight on the Monday of the week."""
|
||||||
|
kind = 'week'
|
||||||
|
|
||||||
|
|
||||||
class TruncDay(TruncBase):
|
class TruncDay(TruncBase):
|
||||||
kind = 'day'
|
kind = 'day'
|
||||||
|
|
||||||
|
|
|
@ -777,8 +777,8 @@ class QuerySet:
|
||||||
Return a list of date objects representing all available dates for
|
Return a list of date objects representing all available dates for
|
||||||
the given field_name, scoped to 'kind'.
|
the given field_name, scoped to 'kind'.
|
||||||
"""
|
"""
|
||||||
assert kind in ("year", "month", "day"), \
|
assert kind in ('year', 'month', 'week', 'day'), \
|
||||||
"'kind' must be one of 'year', 'month' or 'day'."
|
"'kind' must be one of 'year', 'month', 'week', or 'day'."
|
||||||
assert order in ('ASC', 'DESC'), \
|
assert order in ('ASC', 'DESC'), \
|
||||||
"'order' must be either 'ASC' or 'DESC'."
|
"'order' must be either 'ASC' or 'DESC'."
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
|
@ -793,8 +793,8 @@ class QuerySet:
|
||||||
Return a list of datetime objects representing all available
|
Return a list of datetime objects representing all available
|
||||||
datetimes for the given field_name, scoped to 'kind'.
|
datetimes for the given field_name, scoped to 'kind'.
|
||||||
"""
|
"""
|
||||||
assert kind in ("year", "month", "day", "hour", "minute", "second"), \
|
assert kind in ('year', 'month', 'week', 'day', 'hour', 'minute', 'second'), \
|
||||||
"'kind' must be one of 'year', 'month', 'day', 'hour', 'minute' or 'second'."
|
"'kind' must be one of 'year', 'month', 'week', 'day', 'hour', 'minute', or 'second'."
|
||||||
assert order in ('ASC', 'DESC'), \
|
assert order in ('ASC', 'DESC'), \
|
||||||
"'order' must be either 'ASC' or 'DESC'."
|
"'order' must be either 'ASC' or 'DESC'."
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
|
|
|
@ -439,6 +439,7 @@ return:
|
||||||
* "year": 2015-01-01 00:00:00+00:00
|
* "year": 2015-01-01 00:00:00+00:00
|
||||||
* "quarter": 2015-04-01 00:00:00+00:00
|
* "quarter": 2015-04-01 00:00:00+00:00
|
||||||
* "month": 2015-06-01 00:00:00+00:00
|
* "month": 2015-06-01 00:00:00+00:00
|
||||||
|
* "week": 2015-06-15 00:00:00+00:00
|
||||||
* "day": 2015-06-15 00:00:00+00:00
|
* "day": 2015-06-15 00:00:00+00:00
|
||||||
* "hour": 2015-06-15 14:00:00+00:00
|
* "hour": 2015-06-15 14:00:00+00:00
|
||||||
* "minute": 2015-06-15 14:30:00+00:00
|
* "minute": 2015-06-15 14:30:00+00:00
|
||||||
|
@ -452,6 +453,7 @@ values returned when this timezone is active will be:
|
||||||
* "year": 2015-01-01 00:00:00+11:00
|
* "year": 2015-01-01 00:00:00+11:00
|
||||||
* "quarter": 2015-04-01 00:00:00+10:00
|
* "quarter": 2015-04-01 00:00:00+10:00
|
||||||
* "month": 2015-06-01 00:00:00+10:00
|
* "month": 2015-06-01 00:00:00+10:00
|
||||||
|
* "week": 2015-06-16 00:00:00+10:00
|
||||||
* "day": 2015-06-16 00:00:00+10:00
|
* "day": 2015-06-16 00:00:00+10:00
|
||||||
* "hour": 2015-06-16 00:00:00+10:00
|
* "hour": 2015-06-16 00:00:00+10:00
|
||||||
* "minute": 2015-06-16 00:30:00+10:00
|
* "minute": 2015-06-16 00:30:00+10:00
|
||||||
|
@ -504,6 +506,14 @@ Usage example::
|
||||||
|
|
||||||
.. attribute:: kind = 'month'
|
.. attribute:: kind = 'month'
|
||||||
|
|
||||||
|
.. class:: TruncWeek(expression, output_field=None, tzinfo=None, **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 2.1
|
||||||
|
|
||||||
|
Truncates to midnight on the Monday of the week.
|
||||||
|
|
||||||
|
.. attribute:: kind = 'week'
|
||||||
|
|
||||||
.. class:: TruncQuarter(expression, output_field=None, tzinfo=None, **extra)
|
.. class:: TruncQuarter(expression, output_field=None, tzinfo=None, **extra)
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
|
@ -694,13 +694,15 @@ objects representing all available dates of a particular kind within the
|
||||||
contents of the ``QuerySet``.
|
contents of the ``QuerySet``.
|
||||||
|
|
||||||
``field`` should be the name of a ``DateField`` of your model.
|
``field`` should be the name of a ``DateField`` of your model.
|
||||||
``kind`` should be either ``"year"``, ``"month"`` or ``"day"``. Each
|
``kind`` should be either ``"year"``, ``"month"``, ``"week"``, or ``"day"``.
|
||||||
``datetime.date`` object in the result list is "truncated" to the given
|
Each :class:`datetime.date` object in the result list is "truncated" to the
|
||||||
``type``.
|
given ``type``.
|
||||||
|
|
||||||
* ``"year"`` returns a list of all distinct year values for the field.
|
* ``"year"`` returns a list of all distinct year values for the field.
|
||||||
* ``"month"`` returns a list of all distinct year/month values for the
|
* ``"month"`` returns a list of all distinct year/month values for the
|
||||||
field.
|
field.
|
||||||
|
* ``"week"`` returns a list of all distinct year/week values for the field. All
|
||||||
|
dates will be a Monday.
|
||||||
* ``"day"`` returns a list of all distinct year/month/day values for the
|
* ``"day"`` returns a list of all distinct year/month/day values for the
|
||||||
field.
|
field.
|
||||||
|
|
||||||
|
@ -713,6 +715,8 @@ Examples::
|
||||||
[datetime.date(2005, 1, 1)]
|
[datetime.date(2005, 1, 1)]
|
||||||
>>> Entry.objects.dates('pub_date', 'month')
|
>>> Entry.objects.dates('pub_date', 'month')
|
||||||
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
|
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
|
||||||
|
>>> Entry.objects.dates('pub_date', 'week')
|
||||||
|
[datetime.date(2005, 2, 14), datetime.date(2005, 3, 14)]
|
||||||
>>> Entry.objects.dates('pub_date', 'day')
|
>>> Entry.objects.dates('pub_date', 'day')
|
||||||
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
|
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
|
||||||
>>> Entry.objects.dates('pub_date', 'day', order='DESC')
|
>>> Entry.objects.dates('pub_date', 'day', order='DESC')
|
||||||
|
@ -720,6 +724,10 @@ Examples::
|
||||||
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
|
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
|
||||||
[datetime.date(2005, 3, 20)]
|
[datetime.date(2005, 3, 20)]
|
||||||
|
|
||||||
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
|
"week" support was added.
|
||||||
|
|
||||||
``datetimes()``
|
``datetimes()``
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -731,9 +739,9 @@ contents of the ``QuerySet``.
|
||||||
|
|
||||||
``field_name`` should be the name of a ``DateTimeField`` of your model.
|
``field_name`` should be the name of a ``DateTimeField`` of your model.
|
||||||
|
|
||||||
``kind`` should be either ``"year"``, ``"month"``, ``"day"``, ``"hour"``,
|
``kind`` should be either ``"year"``, ``"month"``, ``"week"``, ``"day"``,
|
||||||
``"minute"`` or ``"second"``. Each ``datetime.datetime`` object in the result
|
``"hour"``, ``"minute"``, or ``"second"``. Each :class:`datetime.datetime`
|
||||||
list is "truncated" to the given ``type``.
|
object in the result list is "truncated" to the given ``type``.
|
||||||
|
|
||||||
``order``, which defaults to ``'ASC'``, should be either ``'ASC'`` or
|
``order``, which defaults to ``'ASC'``, should be either ``'ASC'`` or
|
||||||
``'DESC'``. This specifies how to order the results.
|
``'DESC'``. This specifies how to order the results.
|
||||||
|
@ -745,6 +753,10 @@ object. If it's ``None``, Django uses the :ref:`current time zone
|
||||||
<default-current-time-zone>`. It has no effect when :setting:`USE_TZ` is
|
<default-current-time-zone>`. It has no effect when :setting:`USE_TZ` is
|
||||||
``False``.
|
``False``.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.1
|
||||||
|
|
||||||
|
"week" support was added.
|
||||||
|
|
||||||
.. _database-time-zone-definitions:
|
.. _database-time-zone-definitions:
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
|
@ -173,6 +173,10 @@ Models
|
||||||
* The new :class:`~django.db.models.functions.Replace` database function
|
* The new :class:`~django.db.models.functions.Replace` database function
|
||||||
replaces strings in an expression.
|
replaces strings in an expression.
|
||||||
|
|
||||||
|
* The new :class:`~django.db.models.functions.TruncWeek` function truncates
|
||||||
|
:class:`~django.db.models.DateField` and
|
||||||
|
:class:`~django.db.models.DateTimeField` to the Monday of a week.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,12 @@ class DatesTests(TestCase):
|
||||||
datetime.date(2010, 7, 1),
|
datetime.date(2010, 7, 1),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
Comment.objects.dates("article__pub_date", "week"), [
|
||||||
|
datetime.date(2005, 7, 25),
|
||||||
|
datetime.date(2010, 7, 26),
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
Comment.objects.dates("article__pub_date", "day"), [
|
Comment.objects.dates("article__pub_date", "day"), [
|
||||||
datetime.date(2005, 7, 28),
|
datetime.date(2005, 7, 28),
|
||||||
|
@ -93,7 +99,8 @@ class DatesTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_dates_fails_when_given_invalid_kind_argument(self):
|
def test_dates_fails_when_given_invalid_kind_argument(self):
|
||||||
with self.assertRaisesMessage(AssertionError, "'kind' must be one of 'year', 'month' or 'day'."):
|
msg = "'kind' must be one of 'year', 'month', 'week', or 'day'."
|
||||||
|
with self.assertRaisesMessage(AssertionError, msg):
|
||||||
Article.objects.dates("pub_date", "bad_kind")
|
Article.objects.dates("pub_date", "bad_kind")
|
||||||
|
|
||||||
def test_dates_fails_when_given_invalid_order_argument(self):
|
def test_dates_fails_when_given_invalid_order_argument(self):
|
||||||
|
|
|
@ -53,6 +53,12 @@ class DateTimesTests(TestCase):
|
||||||
datetime.datetime(2010, 7, 1),
|
datetime.datetime(2010, 7, 1),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
self.assertSequenceEqual(
|
||||||
|
Comment.objects.datetimes("article__pub_date", "week"), [
|
||||||
|
datetime.datetime(2005, 7, 25),
|
||||||
|
datetime.datetime(2010, 7, 26),
|
||||||
|
],
|
||||||
|
)
|
||||||
self.assertSequenceEqual(
|
self.assertSequenceEqual(
|
||||||
Comment.objects.datetimes("article__pub_date", "day"), [
|
Comment.objects.datetimes("article__pub_date", "day"), [
|
||||||
datetime.datetime(2005, 7, 28),
|
datetime.datetime(2005, 7, 28),
|
||||||
|
@ -98,6 +104,9 @@ class DateTimesTests(TestCase):
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Article.objects.datetimes('pub_date', 'month'),
|
Article.objects.datetimes('pub_date', 'month'),
|
||||||
["datetime.datetime(2005, 7, 1, 0, 0)"])
|
["datetime.datetime(2005, 7, 1, 0, 0)"])
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
Article.objects.datetimes('pub_date', 'week'),
|
||||||
|
["datetime.datetime(2005, 7, 25, 0, 0)"])
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
Article.objects.datetimes('pub_date', 'day'),
|
Article.objects.datetimes('pub_date', 'day'),
|
||||||
["datetime.datetime(2005, 7, 28, 0, 0)",
|
["datetime.datetime(2005, 7, 28, 0, 0)",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ from django.db.models.functions import (
|
||||||
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
||||||
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
||||||
Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
||||||
TruncQuarter, TruncSecond, TruncTime, TruncYear,
|
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
||||||
)
|
)
|
||||||
from django.test import (
|
from django.test import (
|
||||||
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
||||||
|
@ -34,6 +34,10 @@ def truncate_to(value, kind, tzinfo=None):
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
return value.replace(hour=0, minute=0, second=0, microsecond=0)
|
return value.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
return value
|
return value
|
||||||
|
if kind == 'week':
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return (value - timedelta(days=value.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
return value - timedelta(days=value.weekday())
|
||||||
if kind == 'month':
|
if kind == 'month':
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
@ -536,6 +540,7 @@ class DateFunctionTests(TestCase):
|
||||||
test_date_kind('year')
|
test_date_kind('year')
|
||||||
test_date_kind('quarter')
|
test_date_kind('quarter')
|
||||||
test_date_kind('month')
|
test_date_kind('month')
|
||||||
|
test_date_kind('week')
|
||||||
test_date_kind('day')
|
test_date_kind('day')
|
||||||
test_time_kind('hour')
|
test_time_kind('hour')
|
||||||
test_time_kind('minute')
|
test_time_kind('minute')
|
||||||
|
@ -543,6 +548,7 @@ class DateFunctionTests(TestCase):
|
||||||
test_datetime_kind('year')
|
test_datetime_kind('year')
|
||||||
test_datetime_kind('quarter')
|
test_datetime_kind('quarter')
|
||||||
test_datetime_kind('month')
|
test_datetime_kind('month')
|
||||||
|
test_datetime_kind('week')
|
||||||
test_datetime_kind('day')
|
test_datetime_kind('day')
|
||||||
test_datetime_kind('hour')
|
test_datetime_kind('hour')
|
||||||
test_datetime_kind('minute')
|
test_datetime_kind('minute')
|
||||||
|
@ -656,6 +662,30 @@ class DateFunctionTests(TestCase):
|
||||||
with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
|
with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
|
||||||
list(DTModel.objects.annotate(truncated=TruncMonth('start_time', output_field=TimeField())))
|
list(DTModel.objects.annotate(truncated=TruncMonth('start_time', output_field=TimeField())))
|
||||||
|
|
||||||
|
def test_trunc_week_func(self):
|
||||||
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'week')
|
||||||
|
if settings.USE_TZ:
|
||||||
|
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
||||||
|
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
||||||
|
self.create_model(start_datetime, end_datetime)
|
||||||
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
DTModel.objects.annotate(extracted=TruncWeek('start_datetime')).order_by('start_datetime'),
|
||||||
|
[
|
||||||
|
(start_datetime, truncate_to(start_datetime, 'week')),
|
||||||
|
(end_datetime, truncate_to(end_datetime, 'week')),
|
||||||
|
],
|
||||||
|
lambda m: (m.start_datetime, m.extracted)
|
||||||
|
)
|
||||||
|
self.assertEqual(DTModel.objects.filter(start_datetime=TruncWeek('start_datetime')).count(), 1)
|
||||||
|
|
||||||
|
with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
|
||||||
|
list(DTModel.objects.annotate(truncated=TruncWeek('start_time')))
|
||||||
|
|
||||||
|
with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
|
||||||
|
list(DTModel.objects.annotate(truncated=TruncWeek('start_time', output_field=TimeField())))
|
||||||
|
|
||||||
def test_trunc_date_func(self):
|
def test_trunc_date_func(self):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
|
@ -960,6 +990,7 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
test_date_kind('year')
|
test_date_kind('year')
|
||||||
test_date_kind('quarter')
|
test_date_kind('quarter')
|
||||||
test_date_kind('month')
|
test_date_kind('month')
|
||||||
|
test_date_kind('week')
|
||||||
test_date_kind('day')
|
test_date_kind('day')
|
||||||
test_time_kind('hour')
|
test_time_kind('hour')
|
||||||
test_time_kind('minute')
|
test_time_kind('minute')
|
||||||
|
@ -967,6 +998,7 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
test_datetime_kind('year')
|
test_datetime_kind('year')
|
||||||
test_datetime_kind('quarter')
|
test_datetime_kind('quarter')
|
||||||
test_datetime_kind('month')
|
test_datetime_kind('month')
|
||||||
|
test_datetime_kind('week')
|
||||||
test_datetime_kind('day')
|
test_datetime_kind('day')
|
||||||
test_datetime_kind('hour')
|
test_datetime_kind('hour')
|
||||||
test_datetime_kind('minute')
|
test_datetime_kind('minute')
|
||||||
|
|
Loading…
Reference in New Issue