summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJosh Smeaton <josh.smeaton@gmail.com>2016-03-05 23:05:47 +1100
committerJosh Smeaton <josh.smeaton@gmail.com>2016-05-18 20:14:58 +1000
commit2a4af0ea43512370764303d35bc5309f8abce666 (patch)
treeea0c9ba8051ae30df2f09e9a57e564e88d156489 /tests
parent77b73e79a4750dcbfabc528bf00cad81ff5bb4d9 (diff)
Fixed #25774 -- Refactor datetime expressions into public API
Diffstat (limited to 'tests')
-rw-r--r--tests/datetimes/models.py1
-rw-r--r--tests/datetimes/tests.py6
-rw-r--r--tests/db_functions/models.py15
-rw-r--r--tests/db_functions/test_datetime.py694
-rw-r--r--tests/expressions/tests.py7
-rw-r--r--tests/queries/tests.py2
6 files changed, 719 insertions, 6 deletions
diff --git a/tests/datetimes/models.py b/tests/datetimes/models.py
index 47ce417e4c..2fcb72be09 100644
--- a/tests/datetimes/models.py
+++ b/tests/datetimes/models.py
@@ -8,6 +8,7 @@ from django.utils.encoding import python_2_unicode_compatible
class Article(models.Model):
title = models.CharField(max_length=100)
pub_date = models.DateTimeField()
+ published_on = models.DateField(null=True)
categories = models.ManyToManyField("Category", related_name="articles")
diff --git a/tests/datetimes/tests.py b/tests/datetimes/tests.py
index 447cbef99a..5bd3bc6d5f 100644
--- a/tests/datetimes/tests.py
+++ b/tests/datetimes/tests.py
@@ -153,3 +153,9 @@ class DateTimesTests(TestCase):
datetime.datetime(2005, 7, 30, 0, 0),
datetime.datetime(2005, 7, 29, 0, 0),
datetime.datetime(2005, 7, 28, 0, 0)])
+
+ def test_datetimes_disallows_date_fields(self):
+ dt = datetime.datetime(2005, 7, 28, 12, 15)
+ Article.objects.create(pub_date=dt, published_on=dt.date(), title="Don't put dates into datetime functions!")
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'published_on' to DateTimeField"):
+ list(Article.objects.datetimes('published_on', 'second'))
diff --git a/tests/db_functions/models.py b/tests/db_functions/models.py
index 788fe01b9f..7494680c23 100644
--- a/tests/db_functions/models.py
+++ b/tests/db_functions/models.py
@@ -41,3 +41,18 @@ class Fan(models.Model):
def __str__(self):
return self.name
+
+
+@python_2_unicode_compatible
+class DTModel(models.Model):
+ name = models.CharField(max_length=32)
+ start_datetime = models.DateTimeField(null=True, blank=True)
+ end_datetime = models.DateTimeField(null=True, blank=True)
+ start_date = models.DateField(null=True, blank=True)
+ end_date = models.DateField(null=True, blank=True)
+ start_time = models.TimeField(null=True, blank=True)
+ end_time = models.TimeField(null=True, blank=True)
+ duration = models.DurationField(null=True, blank=True)
+
+ def __str__(self):
+ return 'DTModel({0})'.format(self.name)
diff --git a/tests/db_functions/test_datetime.py b/tests/db_functions/test_datetime.py
new file mode 100644
index 0000000000..011db8bb88
--- /dev/null
+++ b/tests/db_functions/test_datetime.py
@@ -0,0 +1,694 @@
+from __future__ import unicode_literals
+
+from datetime import datetime
+from unittest import skipIf
+
+from django.conf import settings
+from django.db import connection
+from django.db.models import DateField, DateTimeField, IntegerField
+from django.db.models.functions import (
+ Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
+ ExtractSecond, ExtractWeekDay, ExtractYear, Trunc, TruncDate, TruncDay,
+ TruncHour, TruncMinute, TruncMonth, TruncSecond, TruncYear,
+)
+from django.test import TestCase, override_settings
+from django.utils import timezone
+
+from .models import DTModel
+
+try:
+ import pytz
+except ImportError:
+ pytz = None
+
+
+def microsecond_support(value):
+ return value if connection.features.supports_microsecond_precision else value.replace(microsecond=0)
+
+
+def truncate_to(value, kind, tzinfo=None):
+ # Convert to target timezone before truncation
+ if tzinfo is not None:
+ value = value.astimezone(tzinfo)
+
+ def truncate(value, kind):
+ if kind == 'second':
+ return value.replace(microsecond=0)
+ if kind == 'minute':
+ return value.replace(second=0, microsecond=0)
+ if kind == 'hour':
+ return value.replace(minute=0, second=0, microsecond=0)
+ if kind == 'day':
+ if isinstance(value, datetime):
+ return value.replace(hour=0, minute=0, second=0, microsecond=0)
+ return value
+ if kind == 'month':
+ if isinstance(value, datetime):
+ return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
+ return value.replace(day=1)
+ # otherwise, truncate to year
+ if isinstance(value, datetime):
+ return value.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
+ return value.replace(month=1, day=1)
+
+ value = truncate(value, kind)
+ if tzinfo is not None:
+ # If there was a daylight saving transition, then reset the timezone.
+ value = timezone.make_aware(value.replace(tzinfo=None), tzinfo)
+ return value
+
+
+@override_settings(USE_TZ=False)
+class DateFunctionTests(TestCase):
+
+ def create_model(self, start_datetime, end_datetime):
+ return DTModel.objects.create(
+ name=start_datetime.isoformat(),
+ start_datetime=start_datetime, end_datetime=end_datetime,
+ start_date=start_datetime.date(), end_date=end_datetime.date(),
+ start_time=start_datetime.time(), end_time=end_datetime.time(),
+ duration=(end_datetime - start_datetime),
+ )
+
+ def test_extract_year_exact_lookup(self):
+ """
+ Extract year uses a BETWEEN filter to compare the year to allow indexes
+ to be used.
+ """
+ start_datetime = datetime(2015, 6, 15, 14, 10)
+ end_datetime = datetime(2016, 6, 15, 14, 10)
+ 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)
+
+ qs = DTModel.objects.filter(start_datetime__year__exact=2015)
+ self.assertEqual(qs.count(), 1)
+ query_string = str(qs.query).lower()
+ self.assertEqual(query_string.count(' between '), 1)
+ self.assertEqual(query_string.count('extract'), 0)
+
+ # exact is implied and should be the same
+ qs = DTModel.objects.filter(start_datetime__year=2015)
+ self.assertEqual(qs.count(), 1)
+ query_string = str(qs.query).lower()
+ self.assertEqual(query_string.count(' between '), 1)
+ self.assertEqual(query_string.count('extract'), 0)
+
+ # date and datetime fields should behave the same
+ qs = DTModel.objects.filter(start_date__year=2015)
+ self.assertEqual(qs.count(), 1)
+ query_string = str(qs.query).lower()
+ self.assertEqual(query_string.count(' between '), 1)
+ self.assertEqual(query_string.count('extract'), 0)
+
+ def test_extract_year_greaterthan_lookup(self):
+ start_datetime = datetime(2015, 6, 15, 14, 10)
+ end_datetime = datetime(2016, 6, 15, 14, 10)
+ 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)
+
+ qs = DTModel.objects.filter(start_datetime__year__gt=2015)
+ self.assertEqual(qs.count(), 1)
+ self.assertEqual(str(qs.query).lower().count('extract'), 0)
+ qs = DTModel.objects.filter(start_datetime__year__gte=2015)
+ self.assertEqual(qs.count(), 2)
+ self.assertEqual(str(qs.query).lower().count('extract'), 0)
+
+ def test_extract_year_lessthan_lookup(self):
+ start_datetime = datetime(2015, 6, 15, 14, 10)
+ end_datetime = datetime(2016, 6, 15, 14, 10)
+ 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)
+
+ qs = DTModel.objects.filter(start_datetime__year__lt=2016)
+ self.assertEqual(qs.count(), 1)
+ self.assertEqual(str(qs.query).count('extract'), 0)
+ qs = DTModel.objects.filter(start_datetime__year__lte=2016)
+ self.assertEqual(qs.count(), 2)
+ self.assertEqual(str(qs.query).count('extract'), 0)
+
+ def test_extract_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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)
+
+ with self.assertRaisesMessage(ValueError, 'lookup_name must be provided'):
+ Extract('start_datetime')
+
+ msg = 'Extract input expression must be DateField, DateTimeField, or TimeField.'
+ with self.assertRaisesMessage(ValueError, msg):
+ list(DTModel.objects.annotate(extracted=Extract('name', 'hour')))
+
+ with self.assertRaisesMessage(
+ ValueError, "Cannot extract time component 'second' from DateField 'start_date'."):
+ list(DTModel.objects.annotate(extracted=Extract('start_date', 'second')))
+
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=Extract('start_datetime', 'year')).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=Extract('start_datetime', 'month')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=Extract('start_datetime', 'day')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=Extract('start_datetime', 'week_day')).order_by('start_datetime'),
+ [
+ (start_datetime, (start_datetime.isoweekday() % 7) + 1),
+ (end_datetime, (end_datetime.isoweekday() % 7) + 1)
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=Extract('start_datetime', 'hour')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=Extract('start_datetime', 'minute')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=Extract('start_datetime', 'second')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__year=Extract('start_datetime', 'year')).count(), 2)
+ self.assertEqual(DTModel.objects.filter(start_datetime__hour=Extract('start_datetime', 'hour')).count(), 2)
+ self.assertEqual(DTModel.objects.filter(start_date__month=Extract('start_date', 'month')).count(), 2)
+ self.assertEqual(DTModel.objects.filter(start_time__hour=Extract('start_time', 'hour')).count(), 2)
+
+ def test_extract_year_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractYear('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=ExtractYear('start_date')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.year), (end_datetime, end_datetime.year)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__year=ExtractYear('start_datetime')).count(), 2)
+
+ def test_extract_month_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractMonth('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractMonth('start_date')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.month), (end_datetime, end_datetime.month)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__month=ExtractMonth('start_datetime')).count(), 2)
+
+ def test_extract_day_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractDay('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractDay('start_date')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.day), (end_datetime, end_datetime.day)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__day=ExtractDay('start_datetime')).count(), 2)
+
+ def test_extract_weekday_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractWeekDay('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, (start_datetime.isoweekday() % 7) + 1),
+ (end_datetime, (end_datetime.isoweekday() % 7) + 1),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractWeekDay('start_date')).order_by('start_datetime'),
+ [
+ (start_datetime, (start_datetime.isoweekday() % 7) + 1),
+ (end_datetime, (end_datetime.isoweekday() % 7) + 1),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__week_day=ExtractWeekDay('start_datetime')).count(), 2)
+
+ def test_extract_hour_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractHour('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractHour('start_time')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.hour), (end_datetime, end_datetime.hour)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__hour=ExtractHour('start_datetime')).count(), 2)
+
+ def test_extract_minute_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractMinute('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractMinute('start_time')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.minute), (end_datetime, end_datetime.minute)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__minute=ExtractMinute('start_datetime')).count(), 2)
+
+ def test_extract_second_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=ExtractSecond('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractSecond('start_time')).order_by('start_datetime'),
+ [(start_datetime, start_datetime.second), (end_datetime, end_datetime.second)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__second=ExtractSecond('start_datetime')).count(), 2)
+
+ def test_trunc_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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)
+
+ with self.assertRaisesMessage(ValueError, 'output_field must be either DateField or DateTimeField'):
+ list(DTModel.objects.annotate(truncated=Trunc('start_datetime', 'year', output_field=IntegerField())))
+
+ with self.assertRaisesMessage(AssertionError, "'name' isn't a DateField or DateTimeField."):
+ list(DTModel.objects.annotate(truncated=Trunc('name', 'year', output_field=DateTimeField())))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=Trunc('start_date', 'second')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=Trunc('start_date', 'month', output_field=DateTimeField())))
+
+ def test_datetime_kind(kind):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc('start_datetime', kind, output_field=DateTimeField())
+ ).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, kind)),
+ (end_datetime, truncate_to(end_datetime, kind))
+ ],
+ lambda m: (m.start_datetime, m.truncated)
+ )
+
+ def test_date_kind(kind):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc('start_date', kind, output_field=DateField())
+ ).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.date(), kind)),
+ (end_datetime, truncate_to(end_datetime.date(), kind))
+ ],
+ lambda m: (m.start_datetime, m.truncated)
+ )
+
+ test_date_kind('year')
+ test_date_kind('month')
+ test_date_kind('day')
+ test_datetime_kind('year')
+ test_datetime_kind('month')
+ test_datetime_kind('day')
+ test_datetime_kind('hour')
+ test_datetime_kind('minute')
+ test_datetime_kind('second')
+
+ qs = DTModel.objects.filter(start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField()))
+ self.assertEqual(qs.count(), 2)
+
+ def test_trunc_year_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = truncate_to(microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)), 'year')
+ 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=TruncYear('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'year')),
+ (end_datetime, truncate_to(end_datetime, 'year')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncYear('start_date')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.date(), 'year')),
+ (end_datetime, truncate_to(end_datetime.date(), 'year')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncYear('start_datetime')).count(), 1)
+
+ def test_trunc_month_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = truncate_to(microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)), 'month')
+ 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=TruncMonth('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'month')),
+ (end_datetime, truncate_to(end_datetime, 'month')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncMonth('start_date')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.date(), 'month')),
+ (end_datetime, truncate_to(end_datetime.date(), 'month')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncMonth('start_datetime')).count(), 1)
+
+ def test_trunc_date_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(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=TruncDate('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, start_datetime.date()),
+ (end_datetime, end_datetime.date()),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__date=TruncDate('start_datetime')).count(), 2)
+
+ def test_trunc_day_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = truncate_to(microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)), 'day')
+ 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=TruncDay('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'day')),
+ (end_datetime, truncate_to(end_datetime, 'day')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncDay('start_datetime')).count(), 1)
+
+ def test_trunc_hour_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = truncate_to(microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)), 'hour')
+ 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=TruncHour('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'hour')),
+ (end_datetime, truncate_to(end_datetime, 'hour')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncHour('start_datetime')).count(), 1)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncHour('start_date')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncHour('start_date', output_field=DateField())))
+
+ def test_trunc_minute_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = truncate_to(microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)), 'minute')
+ 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=TruncMinute('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'minute')),
+ (end_datetime, truncate_to(end_datetime, 'minute')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncMinute('start_datetime')).count(), 1)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncMinute('start_date')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncMinute('start_date', output_field=DateField())))
+
+ def test_trunc_second_func(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = truncate_to(microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123)), 'second')
+ 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=TruncSecond('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'second')),
+ (end_datetime, truncate_to(end_datetime, 'second'))
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+
+ result = 1 if connection.features.supports_microsecond_precision else 2
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncSecond('start_datetime')).count(), result)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncSecond('start_date')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField())))
+
+
+@skipIf(pytz is None, "this test requires pytz")
+@override_settings(USE_TZ=True, TIME_ZONE='UTC')
+class DateFunctionWithTimeZoneTests(DateFunctionTests):
+
+ def test_extract_func_with_timezone(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 23, 30, 1, 321))
+ end_datetime = microsecond_support(datetime(2015, 6, 16, 13, 11, 27, 123))
+ 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)
+ melb = pytz.timezone('Australia/Melbourne')
+
+ qs = DTModel.objects.annotate(
+ day=Extract('start_datetime', 'day'),
+ day_melb=Extract('start_datetime', 'day', tzinfo=melb),
+ weekday=ExtractWeekDay('start_datetime'),
+ weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
+ hour=ExtractHour('start_datetime'),
+ hour_melb=ExtractHour('start_datetime', tzinfo=melb),
+ ).order_by('start_datetime')
+
+ utc_model = qs.get()
+ self.assertEqual(utc_model.day, 15)
+ self.assertEqual(utc_model.day_melb, 16)
+ self.assertEqual(utc_model.weekday, 2)
+ self.assertEqual(utc_model.weekday_melb, 3)
+ self.assertEqual(utc_model.hour, 23)
+ self.assertEqual(utc_model.hour_melb, 9)
+
+ with timezone.override(melb):
+ melb_model = qs.get()
+
+ self.assertEqual(melb_model.day, 16)
+ self.assertEqual(melb_model.day_melb, 16)
+ self.assertEqual(melb_model.weekday, 3)
+ self.assertEqual(melb_model.weekday_melb, 3)
+ self.assertEqual(melb_model.hour, 9)
+ self.assertEqual(melb_model.hour_melb, 9)
+
+ def test_extract_func_explicit_timezone_priority(self):
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 23, 30, 1, 321))
+ end_datetime = microsecond_support(datetime(2015, 6, 16, 13, 11, 27, 123))
+ 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)
+ melb = pytz.timezone('Australia/Melbourne')
+
+ with timezone.override(melb):
+ model = DTModel.objects.annotate(
+ day_melb=Extract('start_datetime', 'day'),
+ day_utc=Extract('start_datetime', 'day', tzinfo=timezone.utc),
+ ).order_by('start_datetime').get()
+ self.assertEqual(model.day_melb, 16)
+ self.assertEqual(model.day_utc, 15)
+
+ def test_trunc_timezone_applied_before_truncation(self):
+ start_datetime = microsecond_support(datetime(2016, 1, 1, 1, 30, 50, 321))
+ end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123))
+ 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)
+
+ melb = pytz.timezone('Australia/Melbourne')
+ pacific = pytz.timezone('US/Pacific')
+
+ model = DTModel.objects.annotate(
+ melb_year=TruncYear('start_datetime', tzinfo=melb),
+ pacific_year=TruncYear('start_datetime', tzinfo=pacific),
+ ).order_by('start_datetime').get()
+
+ self.assertEqual(model.start_datetime, start_datetime)
+ self.assertEqual(model.melb_year, truncate_to(start_datetime, 'year', melb))
+ self.assertEqual(model.pacific_year, truncate_to(start_datetime, 'year', pacific))
+ self.assertEqual(model.start_datetime.year, 2016)
+ self.assertEqual(model.melb_year.year, 2016)
+ self.assertEqual(model.pacific_year.year, 2015)
+
+ def test_trunc_func_with_timezone(self):
+ """
+ If the truncated datetime transitions to a different offset (daylight
+ saving) then the returned value will have that new timezone/offset.
+ """
+ start_datetime = microsecond_support(datetime(2015, 6, 15, 14, 30, 50, 321))
+ end_datetime = microsecond_support(datetime(2016, 6, 15, 14, 10, 50, 123))
+ 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)
+
+ melb = pytz.timezone('Australia/Melbourne')
+
+ def test_datetime_kind(kind, tzinfo=melb):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc('start_datetime', kind, output_field=DateTimeField(), tzinfo=melb)
+ ).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.astimezone(melb), kind, melb)),
+ (end_datetime, truncate_to(end_datetime.astimezone(melb), kind, melb))
+ ],
+ lambda m: (m.start_datetime, m.truncated)
+ )
+
+ def test_date_kind(kind, tzinfo=melb):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc('start_date', kind, output_field=DateField(), tzinfo=melb)
+ ).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.date(), kind)),
+ (end_datetime, truncate_to(end_datetime.date(), kind))
+ ],
+ lambda m: (m.start_datetime, m.truncated)
+ )
+
+ test_date_kind('year')
+ test_date_kind('month')
+ test_date_kind('day')
+ test_datetime_kind('year')
+ test_datetime_kind('month')
+ test_datetime_kind('day')
+ test_datetime_kind('hour')
+ test_datetime_kind('minute')
+ test_datetime_kind('second')
+
+ qs = DTModel.objects.filter(start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField()))
+ self.assertEqual(qs.count(), 2)
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index 478754b6fa..27929c9146 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -11,8 +11,8 @@ from django.db.models.aggregates import (
Avg, Count, Max, Min, StdDev, Sum, Variance,
)
from django.db.models.expressions import (
- Case, Col, Date, DateTime, ExpressionWrapper, F, Func, OrderBy, Random,
- RawSQL, Ref, Value, When,
+ Case, Col, ExpressionWrapper, F, Func, OrderBy, Random, RawSQL, Ref, Value,
+ When,
)
from django.db.models.functions import (
Coalesce, Concat, Length, Lower, Substr, Upper,
@@ -20,7 +20,6 @@ from django.db.models.functions import (
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.test.utils import Approximate
from django.utils import six
-from django.utils.timezone import utc
from .models import UUID, Company, Employee, Experiment, Number, Time
@@ -930,8 +929,6 @@ class ReprTests(TestCase):
"<Case: CASE WHEN <Q: (AND: ('a', 1))> THEN Value(None), ELSE Value(None)>"
)
self.assertEqual(repr(Col('alias', 'field')), "Col(alias, field)")
- self.assertEqual(repr(Date('published', 'exact')), "Date(published, exact)")
- self.assertEqual(repr(DateTime('published', 'exact', utc)), "DateTime(published, exact, %s)" % utc)
self.assertEqual(repr(F('published')), "F(published)")
self.assertEqual(repr(F('cost') + F('tax')), "<CombinedExpression: F(cost) + F(tax)>")
self.assertEqual(
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 6af66ba7f9..19735295ce 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -1312,7 +1312,7 @@ class Queries3Tests(BaseQuerysetTest):
def test_ticket8683(self):
# An error should be raised when QuerySet.datetimes() is passed the
# wrong type of field.
- with self.assertRaisesMessage(AssertionError, "'name' isn't a DateTimeField."):
+ with self.assertRaisesMessage(AssertionError, "'name' isn't a DateField or DateTimeField."):
Item.objects.datetimes('name', 'month')
def test_ticket22023(self):