Fixed #28649 -- Added ExtractIsoYear database function and iso_year lookup.
This commit is contained in:
parent
c832885a3e
commit
3e09b37f80
@ -42,6 +42,10 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
# other database backends.
|
# other database backends.
|
||||||
# Mode 3: Monday, 1-53, with 4 or more days this year.
|
# Mode 3: Monday, 1-53, with 4 or more days this year.
|
||||||
return "WEEK(%s, 3)" % field_name
|
return "WEEK(%s, 3)" % field_name
|
||||||
|
elif lookup_type == 'iso_year':
|
||||||
|
# Get the year part from the YEARWEEK function, which returns a
|
||||||
|
# number as year * 100 + week.
|
||||||
|
return "TRUNCATE(YEARWEEK(%s, 3), -2) / 100" % field_name
|
||||||
else:
|
else:
|
||||||
# EXTRACT returns 1-53 based on ISO-8601 for the week number.
|
# EXTRACT returns 1-53 based on ISO-8601 for the week number.
|
||||||
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||||
|
@ -69,6 +69,8 @@ END;
|
|||||||
return "TO_CHAR(%s, 'IW')" % field_name
|
return "TO_CHAR(%s, 'IW')" % field_name
|
||||||
elif lookup_type == 'quarter':
|
elif lookup_type == 'quarter':
|
||||||
return "TO_CHAR(%s, 'Q')" % field_name
|
return "TO_CHAR(%s, 'Q')" % field_name
|
||||||
|
elif lookup_type == 'iso_year':
|
||||||
|
return "TO_CHAR(%s, 'IYYY')" % field_name
|
||||||
else:
|
else:
|
||||||
# https://docs.oracle.com/database/121/SQLRF/functions067.htm#SQLRF00639
|
# https://docs.oracle.com/database/121/SQLRF/functions067.htm#SQLRF00639
|
||||||
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name)
|
||||||
|
@ -31,6 +31,8 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||||||
if lookup_type == 'week_day':
|
if lookup_type == 'week_day':
|
||||||
# For consistency across backends, we return Sunday=1, Saturday=7.
|
# For consistency across backends, we return Sunday=1, Saturday=7.
|
||||||
return "EXTRACT('dow' FROM %s) + 1" % field_name
|
return "EXTRACT('dow' FROM %s) + 1" % field_name
|
||||||
|
elif lookup_type == 'iso_year':
|
||||||
|
return "EXTRACT('isoyear' FROM %s)" % field_name
|
||||||
else:
|
else:
|
||||||
return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)
|
return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name)
|
||||||
|
|
||||||
|
@ -338,6 +338,8 @@ def _sqlite_date_extract(lookup_type, dt):
|
|||||||
return dt.isocalendar()[1]
|
return dt.isocalendar()[1]
|
||||||
elif lookup_type == 'quarter':
|
elif lookup_type == 'quarter':
|
||||||
return math.ceil(dt.month / 3)
|
return math.ceil(dt.month / 3)
|
||||||
|
elif lookup_type == 'iso_year':
|
||||||
|
return dt.isocalendar()[0]
|
||||||
else:
|
else:
|
||||||
return getattr(dt, lookup_type)
|
return getattr(dt, lookup_type)
|
||||||
|
|
||||||
@ -410,6 +412,8 @@ def _sqlite_datetime_extract(lookup_type, dt, tzname):
|
|||||||
return dt.isocalendar()[1]
|
return dt.isocalendar()[1]
|
||||||
elif lookup_type == 'quarter':
|
elif lookup_type == 'quarter':
|
||||||
return math.ceil(dt.month / 3)
|
return math.ceil(dt.month / 3)
|
||||||
|
elif lookup_type == 'iso_year':
|
||||||
|
return dt.isocalendar()[0]
|
||||||
else:
|
else:
|
||||||
return getattr(dt, lookup_type)
|
return getattr(dt, lookup_type)
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from .comparison import Cast, Coalesce, Greatest, Least
|
from .comparison import Cast, Coalesce, Greatest, Least
|
||||||
from .datetime import (
|
from .datetime import (
|
||||||
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
|
||||||
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
|
||||||
Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
ExtractYear, Now, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute,
|
||||||
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
TruncMonth, TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
||||||
)
|
)
|
||||||
from .math import (
|
from .math import (
|
||||||
Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
|
Abs, ACos, ASin, ATan, ATan2, Ceil, Cos, Cot, Degrees, Exp, Floor, Ln, Log,
|
||||||
@ -24,7 +24,8 @@ __all__ = [
|
|||||||
# datetime
|
# datetime
|
||||||
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
'Extract', 'ExtractDay', 'ExtractHour', 'ExtractMinute', 'ExtractMonth',
|
||||||
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
'ExtractQuarter', 'ExtractSecond', 'ExtractWeek', 'ExtractWeekDay',
|
||||||
'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay', 'TruncHour',
|
'ExtractIsoYear', 'ExtractYear', 'Now', 'Trunc', 'TruncDate', 'TruncDay',
|
||||||
|
'TruncHour', 'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond',
|
||||||
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
'TruncMinute', 'TruncMonth', 'TruncQuarter', 'TruncSecond', 'TruncTime',
|
||||||
'TruncWeek', 'TruncYear',
|
'TruncWeek', 'TruncYear',
|
||||||
# math
|
# math
|
||||||
|
@ -80,6 +80,11 @@ class ExtractYear(Extract):
|
|||||||
lookup_name = 'year'
|
lookup_name = 'year'
|
||||||
|
|
||||||
|
|
||||||
|
class ExtractIsoYear(Extract):
|
||||||
|
"""Return the ISO-8601 week-numbering year."""
|
||||||
|
lookup_name = 'iso_year'
|
||||||
|
|
||||||
|
|
||||||
class ExtractMonth(Extract):
|
class ExtractMonth(Extract):
|
||||||
lookup_name = 'month'
|
lookup_name = 'month'
|
||||||
|
|
||||||
@ -126,6 +131,7 @@ DateField.register_lookup(ExtractMonth)
|
|||||||
DateField.register_lookup(ExtractDay)
|
DateField.register_lookup(ExtractDay)
|
||||||
DateField.register_lookup(ExtractWeekDay)
|
DateField.register_lookup(ExtractWeekDay)
|
||||||
DateField.register_lookup(ExtractWeek)
|
DateField.register_lookup(ExtractWeek)
|
||||||
|
DateField.register_lookup(ExtractIsoYear)
|
||||||
DateField.register_lookup(ExtractQuarter)
|
DateField.register_lookup(ExtractQuarter)
|
||||||
|
|
||||||
TimeField.register_lookup(ExtractHour)
|
TimeField.register_lookup(ExtractHour)
|
||||||
@ -142,6 +148,12 @@ ExtractYear.register_lookup(YearGte)
|
|||||||
ExtractYear.register_lookup(YearLt)
|
ExtractYear.register_lookup(YearLt)
|
||||||
ExtractYear.register_lookup(YearLte)
|
ExtractYear.register_lookup(YearLte)
|
||||||
|
|
||||||
|
ExtractIsoYear.register_lookup(YearExact)
|
||||||
|
ExtractIsoYear.register_lookup(YearGt)
|
||||||
|
ExtractIsoYear.register_lookup(YearGte)
|
||||||
|
ExtractIsoYear.register_lookup(YearLt)
|
||||||
|
ExtractIsoYear.register_lookup(YearLte)
|
||||||
|
|
||||||
|
|
||||||
class Now(Func):
|
class Now(Func):
|
||||||
template = 'CURRENT_TIMESTAMP'
|
template = 'CURRENT_TIMESTAMP'
|
||||||
|
@ -182,6 +182,7 @@ Given the datetime ``2015-06-15 23:30:01.000321+00:00``, the built-in
|
|||||||
``lookup_name``\s return:
|
``lookup_name``\s return:
|
||||||
|
|
||||||
* "year": 2015
|
* "year": 2015
|
||||||
|
* "iso_year": 2015
|
||||||
* "quarter": 2
|
* "quarter": 2
|
||||||
* "month": 6
|
* "month": 6
|
||||||
* "day": 15
|
* "day": 15
|
||||||
@ -252,6 +253,14 @@ Usage example::
|
|||||||
|
|
||||||
.. attribute:: lookup_name = 'year'
|
.. attribute:: lookup_name = 'year'
|
||||||
|
|
||||||
|
.. class:: ExtractIsoYear(expression, tzinfo=None, **extra)
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
Returns the ISO-8601 week-numbering year.
|
||||||
|
|
||||||
|
.. attribute:: lookup_name = 'iso_year'
|
||||||
|
|
||||||
.. class:: ExtractMonth(expression, tzinfo=None, **extra)
|
.. class:: ExtractMonth(expression, tzinfo=None, **extra)
|
||||||
|
|
||||||
.. attribute:: lookup_name = 'month'
|
.. attribute:: lookup_name = 'month'
|
||||||
@ -283,7 +292,7 @@ that deal with date-parts can be used with ``DateField``::
|
|||||||
>>> from django.utils import timezone
|
>>> from django.utils import timezone
|
||||||
>>> from django.db.models.functions import (
|
>>> from django.db.models.functions import (
|
||||||
... ExtractDay, ExtractMonth, ExtractQuarter, ExtractWeek,
|
... ExtractDay, ExtractMonth, ExtractQuarter, ExtractWeek,
|
||||||
... ExtractWeekDay, ExtractYear,
|
... ExtractWeekDay, ExtractIsoYear, ExtractYear,
|
||||||
... )
|
... )
|
||||||
>>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc)
|
>>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc)
|
||||||
>>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc)
|
>>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc)
|
||||||
@ -292,15 +301,17 @@ that deal with date-parts can be used with ``DateField``::
|
|||||||
... end_datetime=end_2015, end_date=end_2015.date())
|
... end_datetime=end_2015, end_date=end_2015.date())
|
||||||
>>> Experiment.objects.annotate(
|
>>> Experiment.objects.annotate(
|
||||||
... year=ExtractYear('start_date'),
|
... year=ExtractYear('start_date'),
|
||||||
|
... isoyear=ExtractIsoYear('start_date'),
|
||||||
... quarter=ExtractQuarter('start_date'),
|
... quarter=ExtractQuarter('start_date'),
|
||||||
... month=ExtractMonth('start_date'),
|
... month=ExtractMonth('start_date'),
|
||||||
... week=ExtractWeek('start_date'),
|
... week=ExtractWeek('start_date'),
|
||||||
... day=ExtractDay('start_date'),
|
... day=ExtractDay('start_date'),
|
||||||
... weekday=ExtractWeekDay('start_date'),
|
... weekday=ExtractWeekDay('start_date'),
|
||||||
... ).values('year', 'quarter', 'month', 'week', 'day', 'weekday').get(
|
... ).values('year', 'isoyear', 'quarter', 'month', 'week', 'day', 'weekday').get(
|
||||||
... end_date__year=ExtractYear('start_date'),
|
... end_date__year=ExtractYear('start_date'),
|
||||||
... )
|
... )
|
||||||
{'year': 2015, 'quarter': 2, 'month': 6, 'week': 25, 'day': 15, 'weekday': 2}
|
{'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25,
|
||||||
|
'day': 15, 'weekday': 2}
|
||||||
|
|
||||||
``DateTimeField`` extracts
|
``DateTimeField`` extracts
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -340,6 +351,7 @@ Each class is also a ``Transform`` registered on ``DateTimeField`` as
|
|||||||
... end_datetime=end_2015, end_date=end_2015.date())
|
... end_datetime=end_2015, end_date=end_2015.date())
|
||||||
>>> Experiment.objects.annotate(
|
>>> Experiment.objects.annotate(
|
||||||
... year=ExtractYear('start_datetime'),
|
... year=ExtractYear('start_datetime'),
|
||||||
|
... isoyear=ExtractIsoYear('start_datetime'),
|
||||||
... quarter=ExtractQuarter('start_datetime'),
|
... quarter=ExtractQuarter('start_datetime'),
|
||||||
... month=ExtractMonth('start_datetime'),
|
... month=ExtractMonth('start_datetime'),
|
||||||
... week=ExtractWeek('start_datetime'),
|
... week=ExtractWeek('start_datetime'),
|
||||||
@ -349,10 +361,11 @@ Each class is also a ``Transform`` registered on ``DateTimeField`` as
|
|||||||
... minute=ExtractMinute('start_datetime'),
|
... minute=ExtractMinute('start_datetime'),
|
||||||
... second=ExtractSecond('start_datetime'),
|
... second=ExtractSecond('start_datetime'),
|
||||||
... ).values(
|
... ).values(
|
||||||
... 'year', 'month', 'week', 'day', 'weekday', 'hour', 'minute', 'second',
|
... 'year', 'isoyear', 'month', 'week', 'day',
|
||||||
|
... 'weekday', 'hour', 'minute', 'second',
|
||||||
... ).get(end_datetime__year=ExtractYear('start_datetime'))
|
... ).get(end_datetime__year=ExtractYear('start_datetime'))
|
||||||
{'year': 2015, 'quarter': 2, 'month': 6, 'week': 25, 'day': 15, 'weekday': 2,
|
{'year': 2015, 'isoyear': 2015, 'quarter': 2, 'month': 6, 'week': 25,
|
||||||
'hour': 23, 'minute': 30, 'second': 1}
|
'day': 15, 'weekday': 2, 'hour': 23, 'minute': 30, 'second': 1}
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``True`` then datetimes are stored in the database
|
When :setting:`USE_TZ` is ``True`` then datetimes are stored in the database
|
||||||
in UTC. If a different timezone is active in Django, the datetime is converted
|
in UTC. If a different timezone is active in Django, the datetime is converted
|
||||||
|
@ -2927,6 +2927,26 @@ SQL equivalent::
|
|||||||
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
||||||
current time zone before filtering.
|
current time zone before filtering.
|
||||||
|
|
||||||
|
.. fieldlookup:: iso_year
|
||||||
|
|
||||||
|
``iso_year``
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
For date and datetime fields, an exact ISO 8601 week-numbering year match.
|
||||||
|
Allows chaining additional field lookups. Takes an integer year.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
Entry.objects.filter(pub_date__iso_year=2005)
|
||||||
|
Entry.objects.filter(pub_date__iso_year__gte=2005)
|
||||||
|
|
||||||
|
(The exact SQL syntax varies for each database engine.)
|
||||||
|
|
||||||
|
When :setting:`USE_TZ` is ``True``, datetime fields are converted to the
|
||||||
|
current time zone before filtering.
|
||||||
|
|
||||||
.. fieldlookup:: month
|
.. fieldlookup:: month
|
||||||
|
|
||||||
``month``
|
``month``
|
||||||
|
@ -189,6 +189,11 @@ Models
|
|||||||
:meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore
|
:meth:`.QuerySet.bulk_create` to ``True`` tells the database to ignore
|
||||||
failure to insert rows that fail uniqueness constraints or other checks.
|
failure to insert rows that fail uniqueness constraints or other checks.
|
||||||
|
|
||||||
|
* The new :class:`~django.db.models.functions.ExtractIsoYear` function extracts
|
||||||
|
ISO-8601 week-numbering years from :class:`~django.db.models.DateField` and
|
||||||
|
:class:`~django.db.models.DateTimeField`, and the new :lookup:`iso_year`
|
||||||
|
lookup allows querying by an ISO-8601 week-numbering year.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -7,10 +7,10 @@ from django.db.models import (
|
|||||||
DateField, DateTimeField, IntegerField, Max, OuterRef, Subquery, TimeField,
|
DateField, DateTimeField, IntegerField, Max, OuterRef, Subquery, TimeField,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import (
|
from django.db.models.functions import (
|
||||||
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
|
Extract, ExtractDay, ExtractHour, ExtractIsoYear, ExtractMinute,
|
||||||
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
|
ExtractMonth, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay,
|
||||||
Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
|
ExtractYear, Trunc, TruncDate, TruncDay, TruncHour, TruncMinute,
|
||||||
TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
TruncMonth, TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
|
||||||
)
|
)
|
||||||
from django.test import (
|
from django.test import (
|
||||||
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
||||||
@ -86,25 +86,25 @@ class DateFunctionTests(TestCase):
|
|||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
|
||||||
qs = DTModel.objects.filter(start_datetime__year__exact=2015)
|
for lookup in ('year', 'iso_year'):
|
||||||
self.assertEqual(qs.count(), 1)
|
with self.subTest(lookup):
|
||||||
query_string = str(qs.query).lower()
|
qs = DTModel.objects.filter(**{'start_datetime__%s__exact' % lookup: 2015})
|
||||||
self.assertEqual(query_string.count(' between '), 1)
|
self.assertEqual(qs.count(), 1)
|
||||||
self.assertEqual(query_string.count('extract'), 0)
|
query_string = str(qs.query).lower()
|
||||||
|
self.assertEqual(query_string.count(' between '), 1)
|
||||||
# exact is implied and should be the same
|
self.assertEqual(query_string.count('extract'), 0)
|
||||||
qs = DTModel.objects.filter(start_datetime__year=2015)
|
# exact is implied and should be the same
|
||||||
self.assertEqual(qs.count(), 1)
|
qs = DTModel.objects.filter(**{'start_datetime__%s' % lookup: 2015})
|
||||||
query_string = str(qs.query).lower()
|
self.assertEqual(qs.count(), 1)
|
||||||
self.assertEqual(query_string.count(' between '), 1)
|
query_string = str(qs.query).lower()
|
||||||
self.assertEqual(query_string.count('extract'), 0)
|
self.assertEqual(query_string.count(' between '), 1)
|
||||||
|
self.assertEqual(query_string.count('extract'), 0)
|
||||||
# date and datetime fields should behave the same
|
# date and datetime fields should behave the same
|
||||||
qs = DTModel.objects.filter(start_date__year=2015)
|
qs = DTModel.objects.filter(**{'start_date__%s' % lookup: 2015})
|
||||||
self.assertEqual(qs.count(), 1)
|
self.assertEqual(qs.count(), 1)
|
||||||
query_string = str(qs.query).lower()
|
query_string = str(qs.query).lower()
|
||||||
self.assertEqual(query_string.count(' between '), 1)
|
self.assertEqual(query_string.count(' between '), 1)
|
||||||
self.assertEqual(query_string.count('extract'), 0)
|
self.assertEqual(query_string.count('extract'), 0)
|
||||||
|
|
||||||
def test_extract_year_greaterthan_lookup(self):
|
def test_extract_year_greaterthan_lookup(self):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 10)
|
start_datetime = datetime(2015, 6, 15, 14, 10)
|
||||||
@ -115,12 +115,14 @@ class DateFunctionTests(TestCase):
|
|||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
|
||||||
qs = DTModel.objects.filter(start_datetime__year__gt=2015)
|
for lookup in ('year', 'iso_year'):
|
||||||
self.assertEqual(qs.count(), 1)
|
with self.subTest(lookup):
|
||||||
self.assertEqual(str(qs.query).lower().count('extract'), 0)
|
qs = DTModel.objects.filter(**{'start_datetime__%s__gt' % lookup: 2015})
|
||||||
qs = DTModel.objects.filter(start_datetime__year__gte=2015)
|
self.assertEqual(qs.count(), 1)
|
||||||
self.assertEqual(qs.count(), 2)
|
self.assertEqual(str(qs.query).lower().count('extract'), 0)
|
||||||
self.assertEqual(str(qs.query).lower().count('extract'), 0)
|
qs = DTModel.objects.filter(**{'start_datetime__%s__gte' % lookup: 2015})
|
||||||
|
self.assertEqual(qs.count(), 2)
|
||||||
|
self.assertEqual(str(qs.query).lower().count('extract'), 0)
|
||||||
|
|
||||||
def test_extract_year_lessthan_lookup(self):
|
def test_extract_year_lessthan_lookup(self):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 10)
|
start_datetime = datetime(2015, 6, 15, 14, 10)
|
||||||
@ -131,12 +133,14 @@ class DateFunctionTests(TestCase):
|
|||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
|
||||||
qs = DTModel.objects.filter(start_datetime__year__lt=2016)
|
for lookup in ('year', 'iso_year'):
|
||||||
self.assertEqual(qs.count(), 1)
|
with self.subTest(lookup):
|
||||||
self.assertEqual(str(qs.query).count('extract'), 0)
|
qs = DTModel.objects.filter(**{'start_datetime__%s__lt' % lookup: 2016})
|
||||||
qs = DTModel.objects.filter(start_datetime__year__lte=2016)
|
self.assertEqual(qs.count(), 1)
|
||||||
self.assertEqual(qs.count(), 2)
|
self.assertEqual(str(qs.query).count('extract'), 0)
|
||||||
self.assertEqual(str(qs.query).count('extract'), 0)
|
qs = DTModel.objects.filter(**{'start_datetime__%s__lte' % lookup: 2016})
|
||||||
|
self.assertEqual(qs.count(), 2)
|
||||||
|
self.assertEqual(str(qs.query).count('extract'), 0)
|
||||||
|
|
||||||
def test_extract_func(self):
|
def test_extract_func(self):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
@ -261,6 +265,51 @@ class DateFunctionTests(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(DTModel.objects.filter(start_datetime__year=ExtractYear('start_datetime')).count(), 2)
|
self.assertEqual(DTModel.objects.filter(start_datetime__year=ExtractYear('start_datetime')).count(), 2)
|
||||||
|
|
||||||
|
def test_extract_iso_year_func(self):
|
||||||
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
|
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=ExtractIsoYear('start_datetime')).order_by('start_datetime'),
|
||||||
|
[(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
|
||||||
|
lambda m: (m.start_datetime, m.extracted)
|
||||||
|
)
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
DTModel.objects.annotate(extracted=ExtractIsoYear('start_date')).order_by('start_datetime'),
|
||||||
|
[(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
|
||||||
|
lambda m: (m.start_datetime, m.extracted)
|
||||||
|
)
|
||||||
|
# Both dates are from the same week year.
|
||||||
|
self.assertEqual(DTModel.objects.filter(start_datetime__iso_year=ExtractIsoYear('start_datetime')).count(), 2)
|
||||||
|
|
||||||
|
def test_extract_iso_year_func_boundaries(self):
|
||||||
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
|
if settings.USE_TZ:
|
||||||
|
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
||||||
|
week_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
|
||||||
|
week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
|
||||||
|
week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
|
||||||
|
if settings.USE_TZ:
|
||||||
|
week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015, is_dst=False)
|
||||||
|
week_52_day_2014 = timezone.make_aware(week_52_day_2014, is_dst=False)
|
||||||
|
week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
|
||||||
|
days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
|
||||||
|
self.create_model(week_53_day_2015, end_datetime)
|
||||||
|
self.create_model(week_52_day_2014, end_datetime)
|
||||||
|
self.create_model(week_1_day_2014_2015, end_datetime)
|
||||||
|
qs = DTModel.objects.filter(start_datetime__in=days).annotate(
|
||||||
|
extracted=ExtractIsoYear('start_datetime'),
|
||||||
|
).order_by('start_datetime')
|
||||||
|
self.assertQuerysetEqual(qs, [
|
||||||
|
(week_52_day_2014, 2014),
|
||||||
|
(week_1_day_2014_2015, 2015),
|
||||||
|
(week_53_day_2015, 2015),
|
||||||
|
], lambda m: (m.start_datetime, m.extracted))
|
||||||
|
|
||||||
def test_extract_month_func(self):
|
def test_extract_month_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)
|
||||||
@ -902,6 +951,7 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||||||
day=Extract('start_datetime', 'day'),
|
day=Extract('start_datetime', 'day'),
|
||||||
day_melb=Extract('start_datetime', 'day', tzinfo=melb),
|
day_melb=Extract('start_datetime', 'day', tzinfo=melb),
|
||||||
week=Extract('start_datetime', 'week', tzinfo=melb),
|
week=Extract('start_datetime', 'week', tzinfo=melb),
|
||||||
|
isoyear=ExtractIsoYear('start_datetime', tzinfo=melb),
|
||||||
weekday=ExtractWeekDay('start_datetime'),
|
weekday=ExtractWeekDay('start_datetime'),
|
||||||
weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
|
weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
|
||||||
quarter=ExtractQuarter('start_datetime', tzinfo=melb),
|
quarter=ExtractQuarter('start_datetime', tzinfo=melb),
|
||||||
@ -913,6 +963,7 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||||||
self.assertEqual(utc_model.day, 15)
|
self.assertEqual(utc_model.day, 15)
|
||||||
self.assertEqual(utc_model.day_melb, 16)
|
self.assertEqual(utc_model.day_melb, 16)
|
||||||
self.assertEqual(utc_model.week, 25)
|
self.assertEqual(utc_model.week, 25)
|
||||||
|
self.assertEqual(utc_model.isoyear, 2015)
|
||||||
self.assertEqual(utc_model.weekday, 2)
|
self.assertEqual(utc_model.weekday, 2)
|
||||||
self.assertEqual(utc_model.weekday_melb, 3)
|
self.assertEqual(utc_model.weekday_melb, 3)
|
||||||
self.assertEqual(utc_model.quarter, 2)
|
self.assertEqual(utc_model.quarter, 2)
|
||||||
@ -925,6 +976,7 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||||||
self.assertEqual(melb_model.day, 16)
|
self.assertEqual(melb_model.day, 16)
|
||||||
self.assertEqual(melb_model.day_melb, 16)
|
self.assertEqual(melb_model.day_melb, 16)
|
||||||
self.assertEqual(melb_model.week, 25)
|
self.assertEqual(melb_model.week, 25)
|
||||||
|
self.assertEqual(melb_model.isoyear, 2015)
|
||||||
self.assertEqual(melb_model.weekday, 3)
|
self.assertEqual(melb_model.weekday, 3)
|
||||||
self.assertEqual(melb_model.quarter, 2)
|
self.assertEqual(melb_model.quarter, 2)
|
||||||
self.assertEqual(melb_model.weekday_melb, 3)
|
self.assertEqual(melb_model.weekday_melb, 3)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user