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

@ -24,12 +24,6 @@ DEFAULT_STORAGE_ALIAS = "default"
STATICFILES_STORAGE_ALIAS = "staticfiles"
# RemovedInDjango50Warning
USE_DEPRECATED_PYTZ_DEPRECATED_MSG = (
"The USE_DEPRECATED_PYTZ setting, and support for pytz timezones is "
"deprecated in favor of the stdlib zoneinfo module. Please update your "
"code to use zoneinfo and remove the USE_DEPRECATED_PYTZ setting."
)
CSRF_COOKIE_MASKED_DEPRECATED_MSG = (
"The CSRF_COOKIE_MASKED transitional setting is deprecated. Support for "
"it will be removed in Django 5.0."
@ -217,9 +211,6 @@ class Settings:
setattr(self, setting, setting_value)
self._explicit_settings.add(setting)
if self.is_overridden("USE_DEPRECATED_PYTZ"):
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
if self.is_overridden("CSRF_COOKIE_MASKED"):
warnings.warn(CSRF_COOKIE_MASKED_DEPRECATED_MSG, RemovedInDjango50Warning)
@ -294,8 +285,6 @@ class UserSettingsHolder:
}
warnings.warn(STATICFILES_STORAGE_DEPRECATED_MSG, RemovedInDjango51Warning)
super().__setattr__(name, value)
if name == "USE_DEPRECATED_PYTZ":
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
# RemovedInDjango51Warning.
if name == "STORAGES":
self.STORAGES.setdefault(

View File

@ -43,11 +43,6 @@ TIME_ZONE = "America/Chicago"
# If you set this to True, Django will use timezone-aware datetimes.
USE_TZ = True
# RemovedInDjango50Warning: It's a transitional setting helpful in migrating
# from pytz tzinfo to ZoneInfo(). Set True to continue using pytz tzinfo
# objects during the Django 4.x release cycle.
USE_DEPRECATED_PYTZ = False
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = "en-us"

View File

@ -1,6 +1,5 @@
import datetime
from django.conf import settings
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.admin.utils import (
display_for_field,
@ -357,10 +356,8 @@ def date_hierarchy(cl):
field = get_fields_from_path(cl.model, field_name)[-1]
if isinstance(field, models.DateTimeField):
dates_or_datetimes = "datetimes"
qs_kwargs = {"is_dst": True} if settings.USE_DEPRECATED_PYTZ else {}
else:
dates_or_datetimes = "dates"
qs_kwargs = {}
year_field = "%s__year" % field_name
month_field = "%s__month" % field_name
day_field = "%s__day" % field_name
@ -401,9 +398,7 @@ def date_hierarchy(cl):
],
}
elif year_lookup and month_lookup:
days = getattr(cl.queryset, dates_or_datetimes)(
field_name, "day", **qs_kwargs
)
days = getattr(cl.queryset, dates_or_datetimes)(field_name, "day")
return {
"show": True,
"back": {
@ -425,9 +420,7 @@ def date_hierarchy(cl):
],
}
elif year_lookup:
months = getattr(cl.queryset, dates_or_datetimes)(
field_name, "month", **qs_kwargs
)
months = getattr(cl.queryset, dates_or_datetimes)(field_name, "month")
return {
"show": True,
"back": {"link": link({}), "title": _("All dates")},
@ -444,9 +437,7 @@ def date_hierarchy(cl):
],
}
else:
years = getattr(cl.queryset, dates_or_datetimes)(
field_name, "year", **qs_kwargs
)
years = getattr(cl.queryset, dates_or_datetimes)(field_name, "year")
return {
"show": True,
"back": None,

View File

@ -32,15 +32,6 @@ RAN_DB_VERSION_CHECK = set()
logger = logging.getLogger("django.db.backends.base")
# RemovedInDjango50Warning
def timezone_constructor(tzname):
if settings.USE_DEPRECATED_PYTZ:
import pytz
return pytz.timezone(tzname)
return zoneinfo.ZoneInfo(tzname)
class BaseDatabaseWrapper:
"""Represent a database connection."""
@ -166,7 +157,7 @@ class BaseDatabaseWrapper:
elif self.settings_dict["TIME_ZONE"] is None:
return datetime.timezone.utc
else:
return timezone_constructor(self.settings_dict["TIME_ZONE"])
return zoneinfo.ZoneInfo(self.settings_dict["TIME_ZONE"])
@cached_property
def timezone_name(self):

View File

@ -26,7 +26,6 @@ from math import (
)
from re import search as re_search
from django.db.backends.base.base import timezone_constructor
from django.db.backends.utils import (
split_tzname_delta,
typecast_time,
@ -36,6 +35,11 @@ from django.utils import timezone
from django.utils.crypto import md5
from django.utils.duration import duration_microseconds
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
def register(connection):
create_deterministic_function = functools.partial(
@ -111,14 +115,14 @@ def _sqlite_datetime_parse(dt, tzname=None, conn_tzname=None):
except (TypeError, ValueError):
return None
if conn_tzname:
dt = dt.replace(tzinfo=timezone_constructor(conn_tzname))
dt = dt.replace(tzinfo=zoneinfo.ZoneInfo(conn_tzname))
if tzname is not None and tzname != conn_tzname:
tzname, sign, offset = split_tzname_delta(tzname)
if offset:
hours, minutes = offset.split(":")
offset_delta = timedelta(hours=int(hours), minutes=int(minutes))
dt += offset_delta if sign == "+" else -offset_delta
dt = timezone.localtime(dt, timezone_constructor(tzname))
dt = timezone.localtime(dt, zoneinfo.ZoneInfo(tzname))
return dt

View File

@ -215,9 +215,7 @@ def from_current_timezone(value):
if settings.USE_TZ and value is not None and timezone.is_naive(value):
current_timezone = timezone.get_current_timezone()
try:
if not timezone._is_pytz_zone(
current_timezone
) and timezone._datetime_ambiguous_or_imaginary(value, current_timezone):
if timezone._datetime_ambiguous_or_imaginary(value, current_timezone):
raise ValueError("Ambiguous or non-existent time.")
return timezone.make_aware(value, current_timezone)
except Exception as exc:

View File

@ -7,34 +7,12 @@ try:
except ImportError:
from backports import zoneinfo
from django.conf import settings
from django.template import Library, Node, TemplateSyntaxError
from django.utils import timezone
register = Library()
# RemovedInDjango50Warning: shim to allow catching the exception in the calling
# scope if pytz is not installed.
class UnknownTimezoneException(BaseException):
pass
# RemovedInDjango50Warning
def timezone_constructor(tzname):
if settings.USE_DEPRECATED_PYTZ:
import pytz
try:
return pytz.timezone(tzname)
except pytz.UnknownTimeZoneError:
raise UnknownTimezoneException
try:
return zoneinfo.ZoneInfo(tzname)
except zoneinfo.ZoneInfoNotFoundError:
raise UnknownTimezoneException
# HACK: datetime instances cannot be assigned new attributes. Define a subclass
# in order to define new attributes in do_timezone().
class datetimeobject(datetime):
@ -79,8 +57,7 @@ def do_timezone(value, arg):
if timezone.is_naive(value):
default_timezone = timezone.get_default_timezone()
value = timezone.make_aware(value, default_timezone)
# Filters must never raise exceptions, and pytz' exceptions inherit
# Exception directly, not a specific subclass. So catch everything.
# Filters must never raise exceptionsm, so catch everything.
except Exception:
return ""
@ -89,8 +66,8 @@ def do_timezone(value, arg):
tz = arg
elif isinstance(arg, str):
try:
tz = timezone_constructor(arg)
except UnknownTimezoneException:
tz = zoneinfo.ZoneInfo(arg)
except zoneinfo.ZoneInfoNotFoundError:
return ""
else:
return ""

View File

@ -3,7 +3,6 @@ Timezone-related classes and functions.
"""
import functools
import sys
import warnings
try:
@ -75,10 +74,6 @@ def get_default_timezone():
This is the time zone defined by settings.TIME_ZONE.
"""
if settings.USE_DEPRECATED_PYTZ:
import pytz
return pytz.timezone(settings.TIME_ZONE)
return zoneinfo.ZoneInfo(settings.TIME_ZONE)
@ -125,12 +120,7 @@ def activate(timezone):
if isinstance(timezone, tzinfo):
_active.value = timezone
elif isinstance(timezone, str):
if settings.USE_DEPRECATED_PYTZ:
import pytz
_active.value = pytz.timezone(timezone)
else:
_active.value = zoneinfo.ZoneInfo(timezone)
_active.value = zoneinfo.ZoneInfo(timezone)
else:
raise ValueError("Invalid timezone: %r" % timezone)
@ -282,15 +272,11 @@ def make_aware(value, timezone=None, is_dst=NOT_PASSED):
)
if timezone is None:
timezone = get_current_timezone()
if _is_pytz_zone(timezone):
# This method is available for pytz time zones.
return timezone.localize(value, is_dst=is_dst)
else:
# Check that we won't overwrite the timezone of an aware datetime.
if is_aware(value):
raise ValueError("make_aware expects a naive datetime, got %s" % value)
# This may be wrong around DST changes!
return value.replace(tzinfo=timezone)
# Check that we won't overwrite the timezone of an aware datetime.
if is_aware(value):
raise ValueError("make_aware expects a naive datetime, got %s" % value)
# This may be wrong around DST changes!
return value.replace(tzinfo=timezone)
def make_naive(value, timezone=None):
@ -303,53 +289,7 @@ def make_naive(value, timezone=None):
return value.astimezone(timezone).replace(tzinfo=None)
_PYTZ_IMPORTED = False
def _pytz_imported():
"""
Detects whether or not pytz has been imported without importing pytz.
Copied from pytz_deprecation_shim with thanks to Paul Ganssle.
"""
global _PYTZ_IMPORTED
if not _PYTZ_IMPORTED and "pytz" in sys.modules:
_PYTZ_IMPORTED = True
return _PYTZ_IMPORTED
def _is_pytz_zone(tz):
"""Checks if a zone is a pytz zone."""
# See if pytz was already imported rather than checking
# settings.USE_DEPRECATED_PYTZ to *allow* manually passing a pytz timezone,
# which some of the test cases (at least) rely on.
if not _pytz_imported():
return False
# If tz could be pytz, then pytz is needed here.
import pytz
_PYTZ_BASE_CLASSES = (pytz.tzinfo.BaseTzInfo, pytz._FixedOffset)
# In releases prior to 2018.4, pytz.UTC was not a subclass of BaseTzInfo
if not isinstance(pytz.UTC, pytz._FixedOffset):
_PYTZ_BASE_CLASSES += (type(pytz.UTC),)
return isinstance(tz, _PYTZ_BASE_CLASSES)
def _datetime_ambiguous_or_imaginary(dt, tz):
if _is_pytz_zone(tz):
import pytz
try:
tz.utcoffset(dt)
except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError):
return True
else:
return False
return tz.utcoffset(dt.replace(fold=not dt.fold)) != tz.utcoffset(dt)

View File

@ -2849,21 +2849,6 @@ the correct environment.
.. _list of time zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
.. setting:: USE_DEPRECATED_PYTZ
``USE_DEPRECATED_PYTZ``
-----------------------
Default: ``False``
A boolean that specifies whether to use ``pytz``, rather than :mod:`zoneinfo`,
as the default time zone implementation.
.. deprecated:: 4.0
This transitional setting is deprecated. Support for using ``pytz`` will be
removed in Django 5.0.
.. setting:: USE_I18N
``USE_I18N``

View File

@ -53,7 +53,7 @@ However, if you are working with non-UTC time zones, and using the ``pytz``
<DATABASE-TIME_ZONE>` setting, you will need to audit your code, since ``pytz``
and ``zoneinfo`` are not entirely equivalent.
To give time for such an audit, the transitional :setting:`USE_DEPRECATED_PYTZ`
To give time for such an audit, the transitional ``USE_DEPRECATED_PYTZ``
setting allows continued use of ``pytz`` during the 4.x release cycle. This
setting will be removed in Django 5.0.
@ -62,7 +62,7 @@ author, can be used to assist with the migration from ``pytz``. This package
provides shims to help you safely remove ``pytz``, and has a detailed
`migration guide`_ showing how to move to the new ``zoneinfo`` APIs.
Using `pytz_deprecation_shim`_ and the :setting:`USE_DEPRECATED_PYTZ`
Using `pytz_deprecation_shim`_ and the ``USE_DEPRECATED_PYTZ``
transitional setting is recommended if you need a gradual update path.
.. _pytz_deprecation_shim: https://pytz-deprecation-shim.readthedocs.io/en/latest/index.html

View File

@ -276,6 +276,10 @@ to remove usage of these features.
* The ``USE_L10N`` setting is removed.
* The ``USE_DEPRECATED_PYTZ`` transitional setting is removed.
* Support for ``pytz`` timezones is removed.
See :ref:`deprecated-features-4.1` for details on these changes, including how
to remove usage of these features.

View File

@ -677,5 +677,3 @@ Usage
:func:`zoneinfo.available_timezones` provides the set of all valid keys for
IANA time zones available to your system. See the docs for usage
considerations.
.. _pytz: http://pytz.sourceforge.net/

View File

@ -10,11 +10,6 @@ try:
except ImportError:
from backports import zoneinfo
try:
import pytz
except ImportError:
pytz = None
from django.contrib import admin
from django.contrib.admin import AdminSite, ModelAdmin
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
@ -158,9 +153,6 @@ def make_aware_datetimes(dt, iana_key):
"""Makes one aware datetime for each supported time zone provider."""
yield dt.replace(tzinfo=zoneinfo.ZoneInfo(iana_key))
if pytz is not None:
yield pytz.timezone(iana_key).localize(dt, is_dst=None)
class AdminFieldExtractionMixin:
"""

View File

@ -1,14 +1,7 @@
import datetime
import unittest
try:
import pytz
except ImportError:
pytz = None
from django.test import TestCase, ignore_warnings, override_settings
from django.test import TestCase, override_settings
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango50Warning
from .models import Article, Category, Comment
@ -102,46 +95,6 @@ class DateTimesTests(TestCase):
qs = Article.objects.datetimes("pub_date", "second")
self.assertEqual(qs[0], now)
@unittest.skipUnless(pytz is not None, "Test requires pytz")
@ignore_warnings(category=RemovedInDjango50Warning)
@override_settings(USE_TZ=True, TIME_ZONE="UTC", USE_DEPRECATED_PYTZ=True)
def test_datetimes_ambiguous_and_invalid_times(self):
sao = pytz.timezone("America/Sao_Paulo")
utc = pytz.UTC
article = Article.objects.create(
title="Article 1",
pub_date=utc.localize(datetime.datetime(2016, 2, 21, 1)),
)
Comment.objects.create(
article=article,
pub_date=utc.localize(datetime.datetime(2016, 10, 16, 13)),
)
with timezone.override(sao):
with self.assertRaisesMessage(
pytz.AmbiguousTimeError, "2016-02-20 23:00:00"
):
Article.objects.datetimes("pub_date", "hour").get()
with self.assertRaisesMessage(
pytz.NonExistentTimeError, "2016-10-16 00:00:00"
):
Comment.objects.datetimes("pub_date", "day").get()
self.assertEqual(
Article.objects.datetimes("pub_date", "hour", is_dst=False).get().dst(),
datetime.timedelta(0),
)
self.assertEqual(
Comment.objects.datetimes("pub_date", "day", is_dst=False).get().dst(),
datetime.timedelta(0),
)
self.assertEqual(
Article.objects.datetimes("pub_date", "hour", is_dst=True).get().dst(),
datetime.timedelta(0, 3600),
)
self.assertEqual(
Comment.objects.datetimes("pub_date", "hour", is_dst=True).get().dst(),
datetime.timedelta(0, 3600),
)
def test_datetimes_returns_available_dates_for_given_scope_and_given_field(self):
pub_dates = [
datetime.datetime(2005, 7, 28, 12, 15),

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()

View File

@ -15,11 +15,6 @@ try:
except ImportError:
from backports import zoneinfo
try:
import pytz
except ImportError:
pytz = None
import custom_migration_operations.more_operations
import custom_migration_operations.operations
@ -595,16 +590,6 @@ class WriterTests(SimpleTestCase):
{"import datetime"},
),
)
if pytz:
self.assertSerializedResultEqual(
pytz.timezone("Europe/Paris").localize(
datetime.datetime(2012, 1, 1, 2, 1)
),
(
"datetime.datetime(2012, 1, 1, 1, 1, tzinfo=datetime.timezone.utc)",
{"import datetime"},
),
)
def test_serialize_fields(self):
self.assertSerializedFieldEqual(models.CharField(max_length=255))

View File

@ -12,7 +12,6 @@ Pillow >= 6.2.1; sys.platform != 'win32' or python_version < '3.12'
# pylibmc/libmemcached can't be built on Windows.
pylibmc; sys.platform != 'win32'
pymemcache >= 3.4.0
pytz
pywatchman; sys.platform != 'win32'
PyYAML
redis >= 3.4.0

View File

@ -4,13 +4,7 @@ import unittest
from types import ModuleType, SimpleNamespace
from unittest import mock
from django.conf import (
ENVIRONMENT_VARIABLE,
USE_DEPRECATED_PYTZ_DEPRECATED_MSG,
LazySettings,
Settings,
settings,
)
from django.conf import ENVIRONMENT_VARIABLE, LazySettings, Settings, settings
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest
from django.test import (
@ -23,7 +17,6 @@ from django.test import (
)
from django.test.utils import requires_tz_support
from django.urls import clear_script_prefix, set_script_prefix
from django.utils.deprecation import RemovedInDjango50Warning
@modify_settings(ITEMS={"prepend": ["b"], "append": ["d"], "remove": ["a", "e"]})
@ -348,24 +341,6 @@ class SettingsTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, "Incorrect timezone setting: test"):
settings._setup()
def test_use_deprecated_pytz_deprecation(self):
settings_module = ModuleType("fake_settings_module")
settings_module.USE_DEPRECATED_PYTZ = True
sys.modules["fake_settings_module"] = settings_module
try:
with self.assertRaisesMessage(
RemovedInDjango50Warning, USE_DEPRECATED_PYTZ_DEPRECATED_MSG
):
Settings("fake_settings_module")
finally:
del sys.modules["fake_settings_module"]
holder = LazySettings()
with self.assertRaisesMessage(
RemovedInDjango50Warning, USE_DEPRECATED_PYTZ_DEPRECATED_MSG
):
holder.configure(USE_DEPRECATED_PYTZ=True)
class TestComplexSettingOverride(SimpleTestCase):
def setUp(self):

View File

@ -10,11 +10,6 @@ try:
except ImportError:
from backports import zoneinfo
try:
import pytz
except ImportError:
pytz = None
from django.contrib.auth.models import User
from django.core import serializers
from django.db import connection
@ -31,7 +26,6 @@ from django.test import (
SimpleTestCase,
TestCase,
TransactionTestCase,
ignore_warnings,
override_settings,
skipIfDBFeature,
skipUnlessDBFeature,
@ -79,14 +73,6 @@ UTC = datetime.timezone.utc
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
if pytz is not None:
ZONE_CONSTRUCTORS += (pytz.timezone,)
def get_timezones(key):
return [constructor(key) for constructor in ZONE_CONSTRUCTORS]
class UTCAliasTests(SimpleTestCase):
def test_alias_deprecation_warning(self):
@ -413,39 +399,17 @@ class NewDatabaseTests(TestCase):
self.assertEqual(Event.objects.filter(dt__gte=dt2).count(), 1)
self.assertEqual(Event.objects.filter(dt__gt=dt2).count(), 0)
def test_query_filter_with_pytz_timezones(self):
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=tz)
Event.objects.create(dt=dt)
next = dt + datetime.timedelta(seconds=3)
prev = dt - datetime.timedelta(seconds=3)
self.assertEqual(Event.objects.filter(dt__exact=dt).count(), 1)
self.assertEqual(Event.objects.filter(dt__exact=next).count(), 0)
self.assertEqual(Event.objects.filter(dt__in=(prev, next)).count(), 0)
self.assertEqual(
Event.objects.filter(dt__in=(prev, dt, next)).count(), 1
)
self.assertEqual(
Event.objects.filter(dt__range=(prev, next)).count(), 1
)
@ignore_warnings(category=RemovedInDjango50Warning)
def test_connection_timezone(self):
tests = [
(False, None, datetime.timezone),
(False, "Africa/Nairobi", zoneinfo.ZoneInfo),
]
if pytz is not None:
tests += [
(True, None, datetime.timezone),
(True, "Africa/Nairobi", pytz.BaseTzInfo),
]
for use_pytz, connection_tz, expected_type in tests:
with self.subTest(use_pytz=use_pytz, connection_tz=connection_tz):
with self.settings(USE_DEPRECATED_PYTZ=use_pytz):
with override_database_connection_timezone(connection_tz):
self.assertIsInstance(connection.timezone, expected_type)
def test_query_filter_with_timezones(self):
tz = zoneinfo.ZoneInfo("Europe/Paris")
dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=tz)
Event.objects.create(dt=dt)
next = dt + datetime.timedelta(seconds=3)
prev = dt - datetime.timedelta(seconds=3)
self.assertEqual(Event.objects.filter(dt__exact=dt).count(), 1)
self.assertEqual(Event.objects.filter(dt__exact=next).count(), 0)
self.assertEqual(Event.objects.filter(dt__in=(prev, next)).count(), 0)
self.assertEqual(Event.objects.filter(dt__in=(prev, dt, next)).count(), 1)
self.assertEqual(Event.objects.filter(dt__range=(prev, next)).count(), 1)
def test_query_convert_timezones(self):
# Connection timezone is equal to the current timezone, datetime
@ -1075,16 +1039,15 @@ class TemplateTests(SimpleTestCase):
)
# Use an IANA timezone as argument
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
ctx = Context(
{
"dt": datetime.datetime(2011, 9, 1, 13, 20, 30),
"tz": tz,
}
)
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
tz = zoneinfo.ZoneInfo("Europe/Paris")
tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
ctx = Context(
{
"dt": datetime.datetime(2011, 9, 1, 13, 20, 30),
"tz": tz,
}
)
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
def test_localtime_templatetag_invalid_argument(self):
with self.assertRaises(TemplateSyntaxError):
@ -1147,15 +1110,14 @@ class TemplateTests(SimpleTestCase):
tpl = Template("{% load tz %}{% timezone tz %}{{ dt }}{% endtimezone %}")
# Use a IANA timezone as argument
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
ctx = Context(
{
"dt": datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT),
"tz": tz,
}
)
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
tz = zoneinfo.ZoneInfo("Europe/Paris")
ctx = Context(
{
"dt": datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT),
"tz": tz,
}
)
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
# Use a IANA timezone name as argument
ctx = Context(
@ -1166,22 +1128,6 @@ class TemplateTests(SimpleTestCase):
)
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
@ignore_warnings(category=RemovedInDjango50Warning)
def test_timezone_templatetag_invalid_argument(self):
with self.assertRaises(TemplateSyntaxError):
Template("{% load tz %}{% timezone %}{% endtimezone %}").render()
with self.assertRaises(zoneinfo.ZoneInfoNotFoundError):
Template("{% load tz %}{% timezone tz %}{% endtimezone %}").render(
Context({"tz": "foobar"})
)
if pytz is not None:
with override_settings(USE_DEPRECATED_PYTZ=True), self.assertRaises(
pytz.UnknownTimeZoneError
):
Template("{% load tz %}{% timezone tz %}{% endtimezone %}").render(
Context({"tz": "foobar"})
)
@skipIf(sys.platform == "win32", "Windows uses non-standard time zone names")
def test_get_current_timezone_templatetag(self):
"""
@ -1205,16 +1151,12 @@ class TemplateTests(SimpleTestCase):
self.assertEqual(tpl.render(Context({"tz": ICT})), "+0700")
def test_get_current_timezone_templatetag_with_iana(self):
"""
Test the {% get_current_timezone %} templatetag with pytz.
"""
tpl = Template(
"{% load tz %}{% get_current_timezone as time_zone %}{{ time_zone }}"
)
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
with timezone.override(tz):
self.assertEqual(tpl.render(Context()), "Europe/Paris")
tz = zoneinfo.ZoneInfo("Europe/Paris")
with timezone.override(tz):
self.assertEqual(tpl.render(Context()), "Europe/Paris")
tpl = Template(
"{% load tz %}{% timezone 'Europe/Paris' %}"
@ -1282,27 +1224,25 @@ class LegacyFormsTests(TestCase):
def test_form_with_non_existent_time(self):
form = EventForm({"dt": "2011-03-27 02:30:00"})
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
with timezone.override(tz):
# This is a bug.
self.assertTrue(form.is_valid())
self.assertEqual(
form.cleaned_data["dt"],
datetime.datetime(2011, 3, 27, 2, 30, 0),
)
tz = zoneinfo.ZoneInfo("Europe/Paris")
with timezone.override(tz):
# This is a bug.
self.assertTrue(form.is_valid())
self.assertEqual(
form.cleaned_data["dt"],
datetime.datetime(2011, 3, 27, 2, 30, 0),
)
def test_form_with_ambiguous_time(self):
form = EventForm({"dt": "2011-10-30 02:30:00"})
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
with timezone.override(tz):
# This is a bug.
self.assertTrue(form.is_valid())
self.assertEqual(
form.cleaned_data["dt"],
datetime.datetime(2011, 10, 30, 2, 30, 0),
)
tz = zoneinfo.ZoneInfo("Europe/Paris")
with timezone.override(tz):
# This is a bug.
self.assertTrue(form.is_valid())
self.assertEqual(
form.cleaned_data["dt"],
datetime.datetime(2011, 10, 30, 2, 30, 0),
)
def test_split_form(self):
form = EventSplitForm({"dt_0": "2011-09-01", "dt_1": "13:20:30"})
@ -1338,32 +1278,30 @@ class NewFormsTests(TestCase):
)
def test_form_with_non_existent_time(self):
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
with timezone.override(tz):
form = EventForm({"dt": "2011-03-27 02:30:00"})
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors["dt"],
[
"2011-03-27 02:30:00 couldnt be interpreted in time zone "
"Europe/Paris; it may be ambiguous or it may not exist."
],
)
tz = zoneinfo.ZoneInfo("Europe/Paris")
with timezone.override(tz):
form = EventForm({"dt": "2011-03-27 02:30:00"})
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors["dt"],
[
"2011-03-27 02:30:00 couldnt be interpreted in time zone "
"Europe/Paris; it may be ambiguous or it may not exist."
],
)
def test_form_with_ambiguous_time(self):
for tz in get_timezones("Europe/Paris"):
with self.subTest(repr(tz)):
with timezone.override(tz):
form = EventForm({"dt": "2011-10-30 02:30:00"})
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors["dt"],
[
"2011-10-30 02:30:00 couldnt be interpreted in time zone "
"Europe/Paris; it may be ambiguous or it may not exist."
],
)
tz = zoneinfo.ZoneInfo("Europe/Paris")
with timezone.override(tz):
form = EventForm({"dt": "2011-10-30 02:30:00"})
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors["dt"],
[
"2011-10-30 02:30:00 couldnt be interpreted in time zone "
"Europe/Paris; it may be ambiguous or it may not exist."
],
)
@requires_tz_support
def test_split_form(self):

View File

@ -25,8 +25,7 @@ class DateFormatTests(SimpleTestCase):
self.assertEqual(datetime.fromtimestamp(int(format(dt, "U"))), dt)
def test_naive_ambiguous_datetime(self):
# dt is ambiguous in Europe/Copenhagen. pytz raises an exception for
# the ambiguity, which results in an empty string.
# dt is ambiguous in Europe/Copenhagen.
dt = datetime(2015, 10, 25, 2, 30, 0)
# Try all formatters that involve self.timezone.

View File

@ -1,18 +1,12 @@
import datetime
import unittest
from unittest import mock
try:
import pytz
except ImportError:
pytz = None
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.test import SimpleTestCase, ignore_warnings, override_settings
from django.test import SimpleTestCase, override_settings
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango50Warning
@ -21,38 +15,11 @@ EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
UTC = datetime.timezone.utc
HAS_PYTZ = pytz is not None
if not HAS_PYTZ:
CET = None
PARIS_IMPLS = (PARIS_ZI,)
needs_pytz = unittest.skip("Test requires pytz")
else:
CET = pytz.timezone("Europe/Paris")
PARIS_IMPLS = (PARIS_ZI, CET)
def needs_pytz(f):
return f
class TimezoneTests(SimpleTestCase):
def setUp(self):
# RemovedInDjango50Warning
timezone.get_default_timezone.cache_clear()
def tearDown(self):
# RemovedInDjango50Warning
timezone.get_default_timezone.cache_clear()
def test_default_timezone_is_zoneinfo(self):
self.assertIsInstance(timezone.get_default_timezone(), zoneinfo.ZoneInfo)
@needs_pytz
@ignore_warnings(category=RemovedInDjango50Warning)
@override_settings(USE_DEPRECATED_PYTZ=True)
def test_setting_allows_fallback_to_pytz(self):
self.assertIsInstance(timezone.get_default_timezone(), pytz.BaseTzInfo)
def test_now(self):
with override_settings(USE_TZ=True):
self.assertTrue(timezone.is_aware(timezone.now()))
@ -208,46 +175,15 @@ class TimezoneTests(SimpleTestCase):
def test_make_aware2(self):
CEST = datetime.timezone(datetime.timedelta(hours=2), "CEST")
for tz in PARIS_IMPLS:
with self.subTest(repr(tz)):
self.assertEqual(
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), tz),
datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=CEST),
)
if HAS_PYTZ:
with self.assertRaises(ValueError):
timezone.make_aware(
CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET
)
self.assertEqual(
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), PARIS_ZI),
datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=CEST),
)
with self.assertRaises(ValueError):
timezone.make_aware(
datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=PARIS_ZI), PARIS_ZI
)
@needs_pytz
def test_make_naive_pytz(self):
self.assertEqual(
timezone.make_naive(
CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET
),
datetime.datetime(2011, 9, 1, 12, 20, 30),
)
self.assertEqual(
timezone.make_naive(
pytz.timezone("Asia/Bangkok").localize(
datetime.datetime(2011, 9, 1, 17, 20, 30)
),
CET,
),
datetime.datetime(2011, 9, 1, 12, 20, 30),
)
with self.assertRaisesMessage(
ValueError, "make_naive() cannot be applied to a naive datetime"
):
timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30), CET)
def test_make_naive_zoneinfo(self):
self.assertEqual(
timezone.make_naive(
@ -264,21 +200,6 @@ class TimezoneTests(SimpleTestCase):
datetime.datetime(2011, 9, 1, 12, 20, 30, fold=1),
)
@needs_pytz
@ignore_warnings(category=RemovedInDjango50Warning)
def test_make_aware_pytz_ambiguous(self):
# 2:30 happens twice, once before DST ends and once after
ambiguous = datetime.datetime(2015, 10, 25, 2, 30)
with self.assertRaises(pytz.AmbiguousTimeError):
timezone.make_aware(ambiguous, timezone=CET)
std = timezone.make_aware(ambiguous, timezone=CET, is_dst=False)
dst = timezone.make_aware(ambiguous, timezone=CET, is_dst=True)
self.assertEqual(std - dst, datetime.timedelta(hours=1))
self.assertEqual(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
def test_make_aware_zoneinfo_ambiguous(self):
# 2:30 happens twice, once before DST ends and once after
ambiguous = datetime.datetime(2015, 10, 25, 2, 30)
@ -292,21 +213,6 @@ class TimezoneTests(SimpleTestCase):
self.assertEqual(std.utcoffset(), datetime.timedelta(hours=1))
self.assertEqual(dst.utcoffset(), datetime.timedelta(hours=2))
@needs_pytz
@ignore_warnings(category=RemovedInDjango50Warning)
def test_make_aware_pytz_non_existent(self):
# 2:30 never happened due to DST
non_existent = datetime.datetime(2015, 3, 29, 2, 30)
with self.assertRaises(pytz.NonExistentTimeError):
timezone.make_aware(non_existent, timezone=CET)
std = timezone.make_aware(non_existent, timezone=CET, is_dst=False)
dst = timezone.make_aware(non_existent, timezone=CET, is_dst=True)
self.assertEqual(std - dst, datetime.timedelta(hours=1))
self.assertEqual(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
def test_make_aware_zoneinfo_non_existent(self):
# 2:30 never happened due to DST
non_existent = datetime.datetime(2015, 3, 29, 2, 30)
@ -349,12 +255,6 @@ class TimezoneTests(SimpleTestCase):
(zoneinfo.ZoneInfo("Europe/Madrid"), "Europe/Madrid"),
(zoneinfo.ZoneInfo("Etc/GMT-10"), "+10"),
]
if HAS_PYTZ:
tests += [
# pytz, named and fixed offset.
(pytz.timezone("Europe/Madrid"), "Europe/Madrid"),
(pytz.timezone("Etc/GMT-10"), "+10"),
]
for tz, expected in tests:
with self.subTest(tz=tz, expected=expected):
self.assertEqual(timezone._get_timezone_name(tz), expected)