diff options
| author | Nick Pope <nick.pope@flightdataservices.com> | 2018-08-16 01:14:37 +0100 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2018-08-16 16:18:09 -0400 |
| commit | b523d42561c7832e6d5d7a4a2dcb5fb650b24fb5 (patch) | |
| tree | 948dabaf7100748c01a10ddc28a07768f9f66ab3 /tests/db_functions/datetime | |
| parent | 5c3db0ef6a9aeaa9ba81262aee67fa918b9545f1 (diff) | |
| download | django-b523d42561c7832e6d5d7a4a2dcb5fb650b24fb5.tar.gz | |
Reorganized datetime db function tests.
Diffstat (limited to 'tests/db_functions/datetime')
| -rw-r--r-- | tests/db_functions/datetime/__init__.py | 0 | ||||
| -rw-r--r-- | tests/db_functions/datetime/test_extract_trunc.py | 1040 | ||||
| -rw-r--r-- | tests/db_functions/datetime/test_now.py | 46 |
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 + ) |
