summaryrefslogtreecommitdiff
path: root/tests/db_functions/datetime
diff options
context:
space:
mode:
authorNick Pope <nick.pope@flightdataservices.com>2018-08-16 01:14:37 +0100
committerTim Graham <timograham@gmail.com>2018-08-16 16:18:09 -0400
commitb523d42561c7832e6d5d7a4a2dcb5fb650b24fb5 (patch)
tree948dabaf7100748c01a10ddc28a07768f9f66ab3 /tests/db_functions/datetime
parent5c3db0ef6a9aeaa9ba81262aee67fa918b9545f1 (diff)
downloaddjango-b523d42561c7832e6d5d7a4a2dcb5fb650b24fb5.tar.gz
Reorganized datetime db function tests.
Diffstat (limited to 'tests/db_functions/datetime')
-rw-r--r--tests/db_functions/datetime/__init__.py0
-rw-r--r--tests/db_functions/datetime/test_extract_trunc.py1040
-rw-r--r--tests/db_functions/datetime/test_now.py46
3 files changed, 1086 insertions, 0 deletions
diff --git a/tests/db_functions/datetime/__init__.py b/tests/db_functions/datetime/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/db_functions/datetime/__init__.py
diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py
new file mode 100644
index 0000000000..077690630b
--- /dev/null
+++ b/tests/db_functions/datetime/test_extract_trunc.py
@@ -0,0 +1,1040 @@
+from datetime import datetime, timedelta
+
+import pytz
+
+from django.conf import settings
+from django.db.models import (
+ DateField, DateTimeField, IntegerField, Max, OuterRef, Subquery, TimeField,
+)
+from django.db.models.functions import (
+ Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
+ ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
+ Trunc, TruncDate, TruncDay, TruncHour, TruncMinute, TruncMonth,
+ TruncQuarter, TruncSecond, TruncTime, TruncWeek, TruncYear,
+)
+from django.test import (
+ TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
+)
+from django.utils import timezone
+
+from ..models import Author, DTModel, Fan
+
+
+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 == '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 isinstance(value, datetime):
+ return value.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
+ return value.replace(day=1)
+ if kind == 'quarter':
+ month_in_quarter = value.month - (value.month - 1) % 3
+ if isinstance(value, datetime):
+ return value.replace(month=month_in_quarter, day=1, hour=0, minute=0, second=0, microsecond=0)
+ return value.replace(month=month_in_quarter, 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 = 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)
+
+ with self.assertRaisesMessage(ValueError, 'lookup_name must be provided'):
+ Extract('start_datetime')
+
+ msg = 'Extract input expression must be DateField, DateTimeField, TimeField, or DurationField.'
+ 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', 'quarter')).order_by('start_datetime'),
+ [(start_datetime, 2), (end_datetime, 2)],
+ 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')).order_by('start_datetime'),
+ [(start_datetime, 25), (end_datetime, 24)],
+ 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)
+
+ @skipUnlessDBFeature('has_native_duration_field')
+ def test_extract_duration(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=Extract('duration', 'second')).order_by('start_datetime'),
+ [
+ (start_datetime, (end_datetime - start_datetime).seconds % 60),
+ (end_datetime, (start_datetime - end_datetime).seconds % 60)
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(
+ DTModel.objects.annotate(
+ duration_days=Extract('duration', 'day'),
+ ).filter(duration_days__gt=200).count(),
+ 1
+ )
+
+ @skipIfDBFeature('has_native_duration_field')
+ def test_extract_duration_without_native_duration_field(self):
+ msg = 'Extract requires native DurationField database support.'
+ with self.assertRaisesMessage(ValueError, msg):
+ list(DTModel.objects.annotate(extracted=Extract('duration', 'second')))
+
+ def test_extract_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=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 = 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=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 = 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=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_week_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=ExtractWeek('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, 25), (end_datetime, 24)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractWeek('start_date')).order_by('start_datetime'),
+ [(start_datetime, 25), (end_datetime, 24)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ # both dates are from the same week.
+ self.assertEqual(DTModel.objects.filter(start_datetime__week=ExtractWeek('start_datetime')).count(), 2)
+
+ def test_extract_quarter_func(self):
+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = datetime(2016, 8, 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=ExtractQuarter('start_datetime')).order_by('start_datetime'),
+ [(start_datetime, 2), (end_datetime, 3)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=ExtractQuarter('start_date')).order_by('start_datetime'),
+ [(start_datetime, 2), (end_datetime, 3)],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__quarter=ExtractQuarter('start_datetime')).count(), 2)
+
+ def test_extract_quarter_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)
+
+ last_quarter_2014 = datetime(2014, 12, 31, 13, 0)
+ first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
+ if settings.USE_TZ:
+ last_quarter_2014 = timezone.make_aware(last_quarter_2014, is_dst=False)
+ first_quarter_2015 = timezone.make_aware(first_quarter_2015, is_dst=False)
+ dates = [last_quarter_2014, first_quarter_2015]
+ self.create_model(last_quarter_2014, end_datetime)
+ self.create_model(first_quarter_2015, end_datetime)
+ qs = DTModel.objects.filter(start_datetime__in=dates).annotate(
+ extracted=ExtractQuarter('start_datetime'),
+ ).order_by('start_datetime')
+ self.assertQuerysetEqual(qs, [
+ (last_quarter_2014, 4),
+ (first_quarter_2015, 1),
+ ], lambda m: (m.start_datetime, m.extracted))
+
+ def test_extract_week_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=ExtractWeek('start_datetime'),
+ ).order_by('start_datetime')
+ self.assertQuerysetEqual(qs, [
+ (week_52_day_2014, 52),
+ (week_1_day_2014_2015, 1),
+ (week_53_day_2015, 53),
+ ], lambda m: (m.start_datetime, m.extracted))
+
+ def test_extract_weekday_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=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 = 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=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 = 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=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 = 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=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 = 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)
+
+ msg = 'output_field must be either DateField, TimeField, or DateTimeField'
+ with self.assertRaisesMessage(ValueError, msg):
+ list(DTModel.objects.annotate(truncated=Trunc('start_datetime', 'year', output_field=IntegerField())))
+
+ with self.assertRaisesMessage(AssertionError, "'name' isn't a DateField, TimeField, 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 TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=Trunc('start_time', 'month')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=Trunc('start_date', 'month', output_field=DateTimeField())))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=Trunc('start_time', 'second', 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)
+ )
+
+ def test_time_kind(kind):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc('start_time', kind, output_field=TimeField())
+ ).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.time(), kind)),
+ (end_datetime, truncate_to(end_datetime.time(), kind))
+ ],
+ lambda m: (m.start_datetime, m.truncated)
+ )
+
+ test_date_kind('year')
+ test_date_kind('quarter')
+ test_date_kind('month')
+ test_date_kind('week')
+ test_date_kind('day')
+ test_time_kind('hour')
+ test_time_kind('minute')
+ test_time_kind('second')
+ test_datetime_kind('year')
+ test_datetime_kind('quarter')
+ test_datetime_kind('month')
+ test_datetime_kind('week')
+ 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 = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(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)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncYear('start_time')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncYear('start_time', output_field=TimeField())))
+
+ def test_trunc_quarter_func(self):
+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(datetime(2016, 10, 15, 14, 10, 50, 123), 'quarter')
+ last_quarter_2015 = truncate_to(datetime(2015, 12, 31, 14, 10, 50, 123), 'quarter')
+ first_quarter_2016 = truncate_to(datetime(2016, 1, 1, 14, 10, 50, 123), 'quarter')
+ if settings.USE_TZ:
+ start_datetime = timezone.make_aware(start_datetime, is_dst=False)
+ end_datetime = timezone.make_aware(end_datetime, is_dst=False)
+ last_quarter_2015 = timezone.make_aware(last_quarter_2015, is_dst=False)
+ first_quarter_2016 = timezone.make_aware(first_quarter_2016, is_dst=False)
+ self.create_model(start_datetime=start_datetime, end_datetime=end_datetime)
+ self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
+ self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
+ self.create_model(start_datetime=first_quarter_2016, end_datetime=end_datetime)
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncQuarter('start_date')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.date(), 'quarter')),
+ (last_quarter_2015, truncate_to(last_quarter_2015.date(), 'quarter')),
+ (first_quarter_2016, truncate_to(first_quarter_2016.date(), 'quarter')),
+ (end_datetime, truncate_to(end_datetime.date(), 'quarter')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncQuarter('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime, 'quarter')),
+ (last_quarter_2015, truncate_to(last_quarter_2015, 'quarter')),
+ (first_quarter_2016, truncate_to(first_quarter_2016, 'quarter')),
+ (end_datetime, truncate_to(end_datetime, 'quarter')),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncQuarter('start_time')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncQuarter('start_time', output_field=TimeField())))
+
+ def test_trunc_month_func(self):
+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(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)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncMonth('start_time')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ 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):
+ 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=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)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateField"):
+ list(DTModel.objects.annotate(truncated=TruncDate('start_time')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateField"):
+ list(DTModel.objects.annotate(truncated=TruncDate('start_time', output_field=TimeField())))
+
+ def test_trunc_time_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=TruncTime('start_datetime')).order_by('start_datetime'),
+ [
+ (start_datetime, start_datetime.time()),
+ (end_datetime, end_datetime.time()),
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime__time=TruncTime('start_datetime')).count(), 2)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to TimeField"):
+ list(DTModel.objects.annotate(truncated=TruncTime('start_date')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to TimeField"):
+ list(DTModel.objects.annotate(truncated=TruncTime('start_date', output_field=DateField())))
+
+ def test_trunc_day_func(self):
+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(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)
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncDay('start_time')))
+
+ with self.assertRaisesMessage(ValueError, "Cannot truncate TimeField 'start_time' to DateTimeField"):
+ list(DTModel.objects.annotate(truncated=TruncDay('start_time', output_field=TimeField())))
+
+ def test_trunc_hour_func(self):
+ start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(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.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncHour('start_time')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.time(), 'hour')),
+ (end_datetime, truncate_to(end_datetime.time(), '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 = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(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.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncMinute('start_time')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.time(), 'minute')),
+ (end_datetime, truncate_to(end_datetime.time(), '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 = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = truncate_to(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)
+ )
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(extracted=TruncSecond('start_time')).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.time(), 'second')),
+ (end_datetime, truncate_to(end_datetime.time(), 'second'))
+ ],
+ lambda m: (m.start_datetime, m.extracted)
+ )
+ self.assertEqual(DTModel.objects.filter(start_datetime=TruncSecond('start_datetime')).count(), 1)
+
+ 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())))
+
+ def test_trunc_subquery_with_parameters(self):
+ author_1 = Author.objects.create(name='J. R. R. Tolkien')
+ author_2 = Author.objects.create(name='G. R. R. Martin')
+ fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
+ fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
+ fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
+ if settings.USE_TZ:
+ fan_since_1 = timezone.make_aware(fan_since_1, is_dst=False)
+ fan_since_2 = timezone.make_aware(fan_since_2, is_dst=False)
+ fan_since_3 = timezone.make_aware(fan_since_3, is_dst=False)
+ Fan.objects.create(author=author_1, name='Tom', fan_since=fan_since_1)
+ Fan.objects.create(author=author_1, name='Emma', fan_since=fan_since_2)
+ Fan.objects.create(author=author_2, name='Isabella', fan_since=fan_since_3)
+
+ inner = Fan.objects.filter(
+ author=OuterRef('pk'),
+ name__in=('Emma', 'Isabella', 'Tom')
+ ).values('author').annotate(newest_fan=Max('fan_since')).values('newest_fan')
+ outer = Author.objects.annotate(
+ newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
+ )
+ tz = pytz.UTC if settings.USE_TZ else None
+ self.assertSequenceEqual(
+ outer.order_by('name').values('name', 'newest_fan_year'),
+ [
+ {'name': 'G. R. R. Martin', 'newest_fan_year': datetime(2017, 1, 1, 0, 0, tzinfo=tz)},
+ {'name': 'J. R. R. Tolkien', 'newest_fan_year': datetime(2016, 1, 1, 0, 0, tzinfo=tz)},
+ ]
+ )
+
+
+@override_settings(USE_TZ=True, TIME_ZONE='UTC')
+class DateFunctionWithTimeZoneTests(DateFunctionTests):
+
+ def test_extract_func_with_timezone(self):
+ start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
+ end_datetime = 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),
+ week=Extract('start_datetime', 'week', tzinfo=melb),
+ weekday=ExtractWeekDay('start_datetime'),
+ weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
+ quarter=ExtractQuarter('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.week, 25)
+ self.assertEqual(utc_model.weekday, 2)
+ self.assertEqual(utc_model.weekday_melb, 3)
+ self.assertEqual(utc_model.quarter, 2)
+ 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.week, 25)
+ self.assertEqual(melb_model.weekday, 3)
+ self.assertEqual(melb_model.quarter, 2)
+ 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 = datetime(2015, 6, 15, 23, 30, 1, 321)
+ end_datetime = 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 = datetime(2016, 1, 1, 1, 30, 50, 321)
+ end_datetime = 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 = datetime(2015, 6, 15, 14, 30, 50, 321)
+ end_datetime = 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):
+ 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):
+ 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)
+ )
+
+ def test_time_kind(kind):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc('start_time', kind, output_field=TimeField(), tzinfo=melb)
+ ).order_by('start_datetime'),
+ [
+ (start_datetime, truncate_to(start_datetime.time(), kind)),
+ (end_datetime, truncate_to(end_datetime.time(), kind))
+ ],
+ lambda m: (m.start_datetime, m.truncated)
+ )
+
+ test_date_kind('year')
+ test_date_kind('quarter')
+ test_date_kind('month')
+ test_date_kind('week')
+ test_date_kind('day')
+ test_time_kind('hour')
+ test_time_kind('minute')
+ test_time_kind('second')
+ test_datetime_kind('year')
+ test_datetime_kind('quarter')
+ test_datetime_kind('month')
+ test_datetime_kind('week')
+ 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/db_functions/datetime/test_now.py b/tests/db_functions/datetime/test_now.py
new file mode 100644
index 0000000000..d7b43609b2
--- /dev/null
+++ b/tests/db_functions/datetime/test_now.py
@@ -0,0 +1,46 @@
+from datetime import datetime, timedelta
+
+from django.db.models.functions import Now
+from django.test import TestCase
+from django.utils import timezone
+
+from ..models import Article
+
+lorem_ipsum = """
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
+ tempor incididunt ut labore et dolore magna aliqua."""
+
+
+class NowTests(TestCase):
+
+ def test_basic(self):
+ a1 = Article.objects.create(
+ title='How to Django',
+ text=lorem_ipsum,
+ written=timezone.now(),
+ )
+ a2 = Article.objects.create(
+ title='How to Time Travel',
+ text=lorem_ipsum,
+ written=timezone.now(),
+ )
+ num_updated = Article.objects.filter(id=a1.id, published=None).update(published=Now())
+ self.assertEqual(num_updated, 1)
+ num_updated = Article.objects.filter(id=a1.id, published=None).update(published=Now())
+ self.assertEqual(num_updated, 0)
+ a1.refresh_from_db()
+ self.assertIsInstance(a1.published, datetime)
+ a2.published = Now() + timedelta(days=2)
+ a2.save()
+ a2.refresh_from_db()
+ self.assertIsInstance(a2.published, datetime)
+ self.assertQuerysetEqual(
+ Article.objects.filter(published__lte=Now()),
+ ['How to Django'],
+ lambda a: a.title
+ )
+ self.assertQuerysetEqual(
+ Article.objects.filter(published__gt=Now()),
+ ['How to Time Travel'],
+ lambda a: a.title
+ )