summaryrefslogtreecommitdiff
path: root/tests/db_functions/datetime/test_extract_trunc.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/db_functions/datetime/test_extract_trunc.py')
-rw-r--r--tests/db_functions/datetime/test_extract_trunc.py373
1 files changed, 199 insertions, 174 deletions
diff --git a/tests/db_functions/datetime/test_extract_trunc.py b/tests/db_functions/datetime/test_extract_trunc.py
index 035900da93..258600127f 100644
--- a/tests/db_functions/datetime/test_extract_trunc.py
+++ b/tests/db_functions/datetime/test_extract_trunc.py
@@ -2,6 +2,14 @@ from datetime import datetime, timedelta, timezone as datetime_timezone
import pytz
+try:
+ import zoneinfo
+except ImportError:
+ try:
+ from backports import zoneinfo
+ except ImportError:
+ zoneinfo = None
+
from django.conf import settings
from django.db.models import (
DateField, DateTimeField, F, IntegerField, Max, OuterRef, Subquery,
@@ -21,6 +29,10 @@ from django.utils import timezone
from ..models import Author, DTModel, Fan
+ZONE_CONSTRUCTORS = (pytz.timezone,)
+if zoneinfo is not None:
+ ZONE_CONSTRUCTORS += (zoneinfo.ZoneInfo,)
+
def truncate_to(value, kind, tzinfo=None):
# Convert to target timezone before truncation
@@ -1039,7 +1051,7 @@ class DateFunctionTests(TestCase):
outer = Author.objects.annotate(
newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
)
- tz = pytz.UTC if settings.USE_TZ else None
+ tz = timezone.utc if settings.USE_TZ else None
self.assertSequenceEqual(
outer.order_by('name').values('name', 'newest_fan_year'),
[
@@ -1052,63 +1064,68 @@ class DateFunctionTests(TestCase):
@override_settings(USE_TZ=True, TIME_ZONE='UTC')
class DateFunctionWithTimeZoneTests(DateFunctionTests):
+ def get_timezones(self, key):
+ for constructor in ZONE_CONSTRUCTORS:
+ yield constructor(key)
+
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')
delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
- qs = DTModel.objects.annotate(
- day=Extract('start_datetime', 'day'),
- day_melb=Extract('start_datetime', 'day', tzinfo=melb),
- week=Extract('start_datetime', 'week', tzinfo=melb),
- isoyear=ExtractIsoYear('start_datetime', tzinfo=melb),
- weekday=ExtractWeekDay('start_datetime'),
- weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
- isoweekday=ExtractIsoWeekDay('start_datetime'),
- isoweekday_melb=ExtractIsoWeekDay('start_datetime', tzinfo=melb),
- quarter=ExtractQuarter('start_datetime', tzinfo=melb),
- hour=ExtractHour('start_datetime'),
- hour_melb=ExtractHour('start_datetime', tzinfo=melb),
- hour_with_delta_pos=ExtractHour('start_datetime', tzinfo=delta_tzinfo_pos),
- hour_with_delta_neg=ExtractHour('start_datetime', tzinfo=delta_tzinfo_neg),
- minute_with_delta_neg=ExtractMinute('start_datetime', tzinfo=delta_tzinfo_neg),
- ).order_by('start_datetime')
+ for melb in self.get_timezones('Australia/Melbourne'):
+ with self.subTest(repr(melb)):
+ qs = DTModel.objects.annotate(
+ day=Extract('start_datetime', 'day'),
+ day_melb=Extract('start_datetime', 'day', tzinfo=melb),
+ week=Extract('start_datetime', 'week', tzinfo=melb),
+ isoyear=ExtractIsoYear('start_datetime', tzinfo=melb),
+ weekday=ExtractWeekDay('start_datetime'),
+ weekday_melb=ExtractWeekDay('start_datetime', tzinfo=melb),
+ isoweekday=ExtractIsoWeekDay('start_datetime'),
+ isoweekday_melb=ExtractIsoWeekDay('start_datetime', tzinfo=melb),
+ quarter=ExtractQuarter('start_datetime', tzinfo=melb),
+ hour=ExtractHour('start_datetime'),
+ hour_melb=ExtractHour('start_datetime', tzinfo=melb),
+ hour_with_delta_pos=ExtractHour('start_datetime', tzinfo=delta_tzinfo_pos),
+ hour_with_delta_neg=ExtractHour('start_datetime', tzinfo=delta_tzinfo_neg),
+ minute_with_delta_neg=ExtractMinute('start_datetime', tzinfo=delta_tzinfo_neg),
+ ).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.isoyear, 2015)
- self.assertEqual(utc_model.weekday, 2)
- self.assertEqual(utc_model.weekday_melb, 3)
- self.assertEqual(utc_model.isoweekday, 1)
- self.assertEqual(utc_model.isoweekday_melb, 2)
- self.assertEqual(utc_model.quarter, 2)
- self.assertEqual(utc_model.hour, 23)
- self.assertEqual(utc_model.hour_melb, 9)
- self.assertEqual(utc_model.hour_with_delta_pos, 4)
- self.assertEqual(utc_model.hour_with_delta_neg, 18)
- self.assertEqual(utc_model.minute_with_delta_neg, 47)
+ 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.isoyear, 2015)
+ self.assertEqual(utc_model.weekday, 2)
+ self.assertEqual(utc_model.weekday_melb, 3)
+ self.assertEqual(utc_model.isoweekday, 1)
+ self.assertEqual(utc_model.isoweekday_melb, 2)
+ self.assertEqual(utc_model.quarter, 2)
+ self.assertEqual(utc_model.hour, 23)
+ self.assertEqual(utc_model.hour_melb, 9)
+ self.assertEqual(utc_model.hour_with_delta_pos, 4)
+ self.assertEqual(utc_model.hour_with_delta_neg, 18)
+ self.assertEqual(utc_model.minute_with_delta_neg, 47)
- with timezone.override(melb):
- melb_model = qs.get()
+ 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.isoyear, 2015)
- self.assertEqual(melb_model.weekday, 3)
- self.assertEqual(melb_model.isoweekday, 2)
- self.assertEqual(melb_model.quarter, 2)
- self.assertEqual(melb_model.weekday_melb, 3)
- self.assertEqual(melb_model.isoweekday_melb, 2)
- self.assertEqual(melb_model.hour, 9)
- self.assertEqual(melb_model.hour_melb, 9)
+ self.assertEqual(melb_model.day, 16)
+ self.assertEqual(melb_model.day_melb, 16)
+ self.assertEqual(melb_model.week, 25)
+ self.assertEqual(melb_model.isoyear, 2015)
+ self.assertEqual(melb_model.weekday, 3)
+ self.assertEqual(melb_model.isoweekday, 2)
+ self.assertEqual(melb_model.quarter, 2)
+ self.assertEqual(melb_model.weekday_melb, 3)
+ self.assertEqual(melb_model.isoweekday_melb, 2)
+ 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)
@@ -1116,27 +1133,29 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
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)
+ for melb in self.get_timezones('Australia/Melbourne'):
+ with self.subTest(repr(melb)):
+ 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_extract_invalid_field_with_timezone(self):
- melb = pytz.timezone('Australia/Melbourne')
- msg = 'tzinfo can only be used with DateTimeField.'
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- day_melb=Extract('start_date', 'day', tzinfo=melb),
- ).get()
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- hour_melb=Extract('start_time', 'hour', tzinfo=melb),
- ).get()
+ for melb in self.get_timezones('Australia/Melbourne'):
+ with self.subTest(repr(melb)):
+ msg = 'tzinfo can only be used with DateTimeField.'
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ day_melb=Extract('start_date', 'day', tzinfo=melb),
+ ).get()
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ hour_melb=Extract('start_time', 'hour', tzinfo=melb),
+ ).get()
def test_trunc_timezone_applied_before_truncation(self):
start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
@@ -1145,36 +1164,37 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
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),
- melb_date=TruncDate('start_datetime', tzinfo=melb),
- pacific_date=TruncDate('start_datetime', tzinfo=pacific),
- melb_time=TruncTime('start_datetime', tzinfo=melb),
- pacific_time=TruncTime('start_datetime', tzinfo=pacific),
- ).order_by('start_datetime').get()
+ for melb, pacific in zip(
+ self.get_timezones('Australia/Melbourne'), self.get_timezones('America/Los_Angeles')
+ ):
+ with self.subTest((repr(melb), repr(pacific))):
+ model = DTModel.objects.annotate(
+ melb_year=TruncYear('start_datetime', tzinfo=melb),
+ pacific_year=TruncYear('start_datetime', tzinfo=pacific),
+ melb_date=TruncDate('start_datetime', tzinfo=melb),
+ pacific_date=TruncDate('start_datetime', tzinfo=pacific),
+ melb_time=TruncTime('start_datetime', tzinfo=melb),
+ pacific_time=TruncTime('start_datetime', tzinfo=pacific),
+ ).order_by('start_datetime').get()
- melb_start_datetime = start_datetime.astimezone(melb)
- pacific_start_datetime = start_datetime.astimezone(pacific)
- 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)
- self.assertEqual(model.melb_date, melb_start_datetime.date())
- self.assertEqual(model.pacific_date, pacific_start_datetime.date())
- self.assertEqual(model.melb_time, melb_start_datetime.time())
- self.assertEqual(model.pacific_time, pacific_start_datetime.time())
+ melb_start_datetime = start_datetime.astimezone(melb)
+ pacific_start_datetime = start_datetime.astimezone(pacific)
+ 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)
+ self.assertEqual(model.melb_date, melb_start_datetime.date())
+ self.assertEqual(model.pacific_date, pacific_start_datetime.date())
+ self.assertEqual(model.melb_time, melb_start_datetime.time())
+ self.assertEqual(model.pacific_time, pacific_start_datetime.time())
def test_trunc_ambiguous_and_invalid_times(self):
sao = pytz.timezone('America/Sao_Paulo')
- utc = pytz.timezone('UTC')
- start_datetime = utc.localize(datetime(2016, 10, 16, 13))
- end_datetime = utc.localize(datetime(2016, 2, 21, 1))
+ utc = timezone.utc
+ start_datetime = datetime(2016, 10, 16, 13, tzinfo=utc)
+ end_datetime = datetime(2016, 2, 21, 1, tzinfo=utc)
self.create_model(start_datetime, end_datetime)
with timezone.override(sao):
with self.assertRaisesMessage(pytz.NonExistentTimeError, '2016-10-16 00:00:00'):
@@ -1206,94 +1226,99 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
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)
- )
+ for melb in self.get_timezones('Australia/Melbourne'):
+ with self.subTest(repr(melb)):
+ 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_datetime_to_date_kind(kind):
- self.assertQuerysetEqual(
- DTModel.objects.annotate(
- truncated=Trunc(
- 'start_datetime',
- kind,
- output_field=DateField(),
- tzinfo=melb,
- ),
- ).order_by('start_datetime'),
- [
- (
- start_datetime,
- truncate_to(start_datetime.astimezone(melb).date(), kind),
- ),
- (
- end_datetime,
- truncate_to(end_datetime.astimezone(melb).date(), kind),
- ),
- ],
- lambda m: (m.start_datetime, m.truncated),
- )
+ def test_datetime_to_date_kind(kind):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc(
+ 'start_datetime',
+ kind,
+ output_field=DateField(),
+ tzinfo=melb,
+ ),
+ ).order_by('start_datetime'),
+ [
+ (
+ start_datetime,
+ truncate_to(start_datetime.astimezone(melb).date(), kind),
+ ),
+ (
+ end_datetime,
+ truncate_to(end_datetime.astimezone(melb).date(), kind),
+ ),
+ ],
+ lambda m: (m.start_datetime, m.truncated),
+ )
- def test_datetime_to_time_kind(kind):
- self.assertQuerysetEqual(
- DTModel.objects.annotate(
- truncated=Trunc(
- 'start_datetime',
- kind,
- output_field=TimeField(),
- tzinfo=melb,
+ def test_datetime_to_time_kind(kind):
+ self.assertQuerysetEqual(
+ DTModel.objects.annotate(
+ truncated=Trunc(
+ 'start_datetime',
+ kind,
+ output_field=TimeField(),
+ tzinfo=melb,
+ )
+ ).order_by('start_datetime'),
+ [
+ (
+ start_datetime,
+ truncate_to(start_datetime.astimezone(melb).time(), kind),
+ ),
+ (
+ end_datetime,
+ truncate_to(end_datetime.astimezone(melb).time(), kind),
+ ),
+ ],
+ lambda m: (m.start_datetime, m.truncated),
)
- ).order_by('start_datetime'),
- [
- (
- start_datetime,
- truncate_to(start_datetime.astimezone(melb).time(), kind),
- ),
- (
- end_datetime,
- truncate_to(end_datetime.astimezone(melb).time(), kind),
- ),
- ],
- lambda m: (m.start_datetime, m.truncated),
- )
- test_datetime_to_date_kind('year')
- test_datetime_to_date_kind('quarter')
- test_datetime_to_date_kind('month')
- test_datetime_to_date_kind('week')
- test_datetime_to_date_kind('day')
- test_datetime_to_time_kind('hour')
- test_datetime_to_time_kind('minute')
- test_datetime_to_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')
+ test_datetime_to_date_kind('year')
+ test_datetime_to_date_kind('quarter')
+ test_datetime_to_date_kind('month')
+ test_datetime_to_date_kind('week')
+ test_datetime_to_date_kind('day')
+ test_datetime_to_time_kind('hour')
+ test_datetime_to_time_kind('minute')
+ test_datetime_to_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)
+ qs = DTModel.objects.filter(
+ start_datetime__date=Trunc('start_datetime', 'day', output_field=DateField())
+ )
+ self.assertEqual(qs.count(), 2)
def test_trunc_invalid_field_with_timezone(self):
- melb = pytz.timezone('Australia/Melbourne')
- msg = 'tzinfo can only be used with DateTimeField.'
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- day_melb=Trunc('start_date', 'day', tzinfo=melb),
- ).get()
- with self.assertRaisesMessage(ValueError, msg):
- DTModel.objects.annotate(
- hour_melb=Trunc('start_time', 'hour', tzinfo=melb),
- ).get()
+ for melb in self.get_timezones('Australia/Melbourne'):
+ with self.subTest(repr(melb)):
+ msg = 'tzinfo can only be used with DateTimeField.'
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ day_melb=Trunc('start_date', 'day', tzinfo=melb),
+ ).get()
+ with self.assertRaisesMessage(ValueError, msg):
+ DTModel.objects.annotate(
+ hour_melb=Trunc('start_time', 'hour', tzinfo=melb),
+ ).get()