Refs #32365 -- Removed support for pytz timezones per deprecation timeline.

This commit is contained in:
Mariusz Felisiak 2023-01-09 09:03:38 +01:00
parent 8d98f99a4a
commit e6f82438d4
21 changed files with 314 additions and 781 deletions

View file

@ -1,4 +1,3 @@
import unittest
from datetime import datetime, timedelta
from datetime import timezone as datetime_timezone
@ -7,11 +6,6 @@ try:
except ImportError:
from backports import zoneinfo
try:
import pytz
except ImportError:
pytz = None
from django.conf import settings
from django.db import DataError, OperationalError
from django.db.models import (
@ -51,29 +45,14 @@ from django.db.models.functions import (
)
from django.test import (
TestCase,
ignore_warnings,
override_settings,
skipIfDBFeature,
skipUnlessDBFeature,
)
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango50Warning
from ..models import Author, DTModel, Fan
HAS_PYTZ = pytz is not None
if not HAS_PYTZ:
needs_pytz = unittest.skip("Test requires pytz")
else:
def needs_pytz(f):
return f
ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
if HAS_PYTZ:
ZONE_CONSTRUCTORS += (pytz.timezone,)
def truncate_to(value, kind, tzinfo=None):
# Convert to target timezone before truncation
@ -1690,10 +1669,6 @@ 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)
@ -1702,62 +1677,57 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
self.create_model(start_datetime, end_datetime)
delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
melb = zoneinfo.ZoneInfo("Australia/Melbourne")
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")
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_with_timezone_minus_no_offset(self):
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
@ -1765,22 +1735,22 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
start_datetime = timezone.make_aware(start_datetime)
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
for ust_nera in self.get_timezones("Asia/Ust-Nera"):
with self.subTest(repr(ust_nera)):
qs = DTModel.objects.annotate(
hour=ExtractHour("start_datetime"),
hour_tz=ExtractHour("start_datetime", tzinfo=ust_nera),
).order_by("start_datetime")
ust_nera = zoneinfo.ZoneInfo("Asia/Ust-Nera")
utc_model = qs.get()
self.assertEqual(utc_model.hour, 23)
self.assertEqual(utc_model.hour_tz, 9)
qs = DTModel.objects.annotate(
hour=ExtractHour("start_datetime"),
hour_tz=ExtractHour("start_datetime", tzinfo=ust_nera),
).order_by("start_datetime")
with timezone.override(ust_nera):
ust_nera_model = qs.get()
utc_model = qs.get()
self.assertEqual(utc_model.hour, 23)
self.assertEqual(utc_model.hour_tz, 9)
self.assertEqual(ust_nera_model.hour, 9)
self.assertEqual(ust_nera_model.hour_tz, 9)
with timezone.override(ust_nera):
ust_nera_model = qs.get()
self.assertEqual(ust_nera_model.hour, 9)
self.assertEqual(ust_nera_model.hour_tz, 9)
def test_extract_func_explicit_timezone_priority(self):
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
@ -1788,35 +1758,32 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
start_datetime = timezone.make_aware(start_datetime)
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
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=datetime_timezone.utc
),
)
.order_by("start_datetime")
.get()
)
self.assertEqual(model.day_melb, 16)
self.assertEqual(model.day_utc, 15)
melb = zoneinfo.ZoneInfo("Australia/Melbourne")
with timezone.override(melb):
model = (
DTModel.objects.annotate(
day_melb=Extract("start_datetime", "day"),
day_utc=Extract(
"start_datetime", "day", tzinfo=datetime_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):
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()
melb = zoneinfo.ZoneInfo("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()
def test_trunc_timezone_applied_before_truncation(self):
start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
@ -1824,74 +1791,36 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
start_datetime = timezone.make_aware(start_datetime)
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
melb = zoneinfo.ZoneInfo("Australia/Melbourne")
pacific = zoneinfo.ZoneInfo("America/Los_Angeles")
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()
)
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())
@needs_pytz
@ignore_warnings(category=RemovedInDjango50Warning)
def test_trunc_ambiguous_and_invalid_times(self):
sao = pytz.timezone("America/Sao_Paulo")
start_datetime = datetime(2016, 10, 16, 13, tzinfo=datetime_timezone.utc)
end_datetime = datetime(2016, 2, 21, 1, tzinfo=datetime_timezone.utc)
self.create_model(start_datetime, end_datetime)
with timezone.override(sao):
with self.assertRaisesMessage(
pytz.NonExistentTimeError, "2016-10-16 00:00:00"
):
model = DTModel.objects.annotate(
truncated_start=TruncDay("start_datetime")
).get()
with self.assertRaisesMessage(
pytz.AmbiguousTimeError, "2016-02-20 23:00:00"
):
model = DTModel.objects.annotate(
truncated_end=TruncHour("end_datetime")
).get()
model = DTModel.objects.annotate(
truncated_start=TruncDay("start_datetime", is_dst=False),
truncated_end=TruncHour("end_datetime", is_dst=False),
).get()
self.assertEqual(model.truncated_start.dst(), timedelta(0))
self.assertEqual(model.truncated_end.dst(), timedelta(0))
model = DTModel.objects.annotate(
truncated_start=TruncDay("start_datetime", is_dst=True),
truncated_end=TruncHour("end_datetime", is_dst=True),
).get()
self.assertEqual(model.truncated_start.dst(), timedelta(0, 3600))
self.assertEqual(model.truncated_end.dst(), timedelta(0, 3600))
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_func_with_timezone(self):
"""
@ -1904,118 +1833,109 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
end_datetime = timezone.make_aware(end_datetime)
self.create_model(start_datetime, end_datetime)
self.create_model(end_datetime, start_datetime)
melb = zoneinfo.ZoneInfo("Australia/Melbourne")
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_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,
)
).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),
)
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),
)
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):
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()
melb = zoneinfo.ZoneInfo("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()