Fixed #32365 -- Made zoneinfo the default timezone implementation.
Thanks to Adam Johnson, Aymeric Augustin, David Smith, Mariusz Felisiak, Nick Pope, and Paul Ganssle for reviews.
This commit is contained in:
parent
7132d17de1
commit
306607d5b9
|
@ -21,6 +21,13 @@ from django.utils.functional import LazyObject, empty
|
|||
|
||||
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
|
||||
|
||||
# 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.'
|
||||
)
|
||||
|
||||
USE_L10N_DEPRECATED_MSG = (
|
||||
'The USE_L10N setting is deprecated. Starting with Django 5.0, localized '
|
||||
'formatting of data will always be enabled. For example Django will '
|
||||
|
@ -196,6 +203,9 @@ class Settings:
|
|||
category=RemovedInDjango50Warning,
|
||||
)
|
||||
|
||||
if self.is_overridden('USE_DEPRECATED_PYTZ'):
|
||||
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||
|
||||
if hasattr(time, 'tzset') and self.TIME_ZONE:
|
||||
# When we can, attempt to validate the timezone. If we can't find
|
||||
# this file, no check happens and it's harmless.
|
||||
|
@ -245,6 +255,8 @@ class UserSettingsHolder:
|
|||
if name == 'USE_L10N':
|
||||
warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||
super().__setattr__(name, value)
|
||||
if name == 'USE_DEPRECATED_PYTZ':
|
||||
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||
|
||||
def __delattr__(self, name):
|
||||
self._deleted.add(name)
|
||||
|
|
|
@ -43,6 +43,11 @@ TIME_ZONE = 'America/Chicago'
|
|||
# If you set this to True, Django will use timezone-aware datetimes.
|
||||
USE_TZ = False
|
||||
|
||||
# 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'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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, display_for_value, get_fields_from_path,
|
||||
|
@ -328,7 +329,7 @@ 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}
|
||||
qs_kwargs = {'is_dst': True} if settings.USE_DEPRECATED_PYTZ else {}
|
||||
else:
|
||||
dates_or_datetimes = 'dates'
|
||||
qs_kwargs = {}
|
||||
|
|
|
@ -6,7 +6,10 @@ import warnings
|
|||
from collections import deque
|
||||
from contextlib import contextmanager
|
||||
|
||||
import pytz
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
@ -23,6 +26,14 @@ from django.utils.functional import cached_property
|
|||
NO_DB_ALIAS = '__no_db__'
|
||||
|
||||
|
||||
# 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."""
|
||||
# Mapping of Field objects to their column types.
|
||||
|
@ -135,7 +146,7 @@ class BaseDatabaseWrapper:
|
|||
elif self.settings_dict['TIME_ZONE'] is None:
|
||||
return timezone.utc
|
||||
else:
|
||||
return pytz.timezone(self.settings_dict['TIME_ZONE'])
|
||||
return timezone_constructor(self.settings_dict['TIME_ZONE'])
|
||||
|
||||
@cached_property
|
||||
def timezone_name(self):
|
||||
|
|
|
@ -14,12 +14,12 @@ import warnings
|
|||
from itertools import chain
|
||||
from sqlite3 import dbapi2 as Database
|
||||
|
||||
import pytz
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import IntegrityError
|
||||
from django.db.backends import utils as backend_utils
|
||||
from django.db.backends.base.base import BaseDatabaseWrapper
|
||||
from django.db.backends.base.base import (
|
||||
BaseDatabaseWrapper, timezone_constructor,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.asyncio import async_unsafe
|
||||
from django.utils.dateparse import parse_datetime, parse_time
|
||||
|
@ -431,7 +431,7 @@ def _sqlite_datetime_parse(dt, tzname=None, conn_tzname=None):
|
|||
except (TypeError, ValueError):
|
||||
return None
|
||||
if conn_tzname:
|
||||
dt = dt.replace(tzinfo=pytz.timezone(conn_tzname))
|
||||
dt = dt.replace(tzinfo=timezone_constructor(conn_tzname))
|
||||
if tzname is not None and tzname != conn_tzname:
|
||||
sign_index = tzname.find('+') + tzname.find('-') + 1
|
||||
if sign_index > -1:
|
||||
|
@ -441,7 +441,7 @@ def _sqlite_datetime_parse(dt, tzname=None, conn_tzname=None):
|
|||
hours, minutes = offset.split(':')
|
||||
offset_delta = datetime.timedelta(hours=int(hours), minutes=int(minutes))
|
||||
dt += offset_delta if sign == '+' else -offset_delta
|
||||
dt = timezone.localtime(dt, pytz.timezone(tzname))
|
||||
dt = timezone.localtime(dt, timezone_constructor(tzname))
|
||||
return dt
|
||||
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class DatetimeDatetimeSerializer(BaseSerializer):
|
|||
imports = ["import datetime"]
|
||||
if self.value.tzinfo is not None:
|
||||
imports.append("from django.utils.timezone import utc")
|
||||
return repr(self.value).replace('<UTC>', 'utc'), set(imports)
|
||||
return repr(self.value).replace('datetime.timezone.utc', 'utc'), set(imports)
|
||||
|
||||
|
||||
class DecimalSerializer(BaseSerializer):
|
||||
|
|
|
@ -188,7 +188,9 @@ class TruncBase(TimezoneMixin, Transform):
|
|||
kind = None
|
||||
tzinfo = None
|
||||
|
||||
def __init__(self, expression, output_field=None, tzinfo=None, is_dst=None, **extra):
|
||||
# RemovedInDjango50Warning: when the deprecation ends, remove is_dst
|
||||
# argument.
|
||||
def __init__(self, expression, output_field=None, tzinfo=None, is_dst=timezone.NOT_PASSED, **extra):
|
||||
self.tzinfo = tzinfo
|
||||
self.is_dst = is_dst
|
||||
super().__init__(expression, output_field=output_field, **extra)
|
||||
|
@ -264,7 +266,9 @@ class TruncBase(TimezoneMixin, Transform):
|
|||
|
||||
class Trunc(TruncBase):
|
||||
|
||||
def __init__(self, expression, kind, output_field=None, tzinfo=None, is_dst=None, **extra):
|
||||
# RemovedInDjango50Warning: when the deprecation ends, remove is_dst
|
||||
# argument.
|
||||
def __init__(self, expression, kind, output_field=None, tzinfo=None, is_dst=timezone.NOT_PASSED, **extra):
|
||||
self.kind = kind
|
||||
super().__init__(
|
||||
expression, output_field=output_field, tzinfo=tzinfo,
|
||||
|
|
|
@ -916,7 +916,9 @@ class QuerySet:
|
|||
'datefield', flat=True
|
||||
).distinct().filter(plain_field__isnull=False).order_by(('-' if order == 'DESC' else '') + 'datefield')
|
||||
|
||||
def datetimes(self, field_name, kind, order='ASC', tzinfo=None, is_dst=None):
|
||||
# RemovedInDjango50Warning: when the deprecation ends, remove is_dst
|
||||
# argument.
|
||||
def datetimes(self, field_name, kind, order='ASC', tzinfo=None, is_dst=timezone.NOT_PASSED):
|
||||
"""
|
||||
Return a list of datetime objects representing all available
|
||||
datetimes for the given field_name, scoped to 'kind'.
|
||||
|
|
|
@ -1,13 +1,37 @@
|
|||
from datetime import datetime, tzinfo
|
||||
|
||||
import pytz
|
||||
try:
|
||||
import zoneinfo
|
||||
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):
|
||||
|
@ -61,8 +85,8 @@ def do_timezone(value, arg):
|
|||
tz = arg
|
||||
elif isinstance(arg, str):
|
||||
try:
|
||||
tz = pytz.timezone(arg)
|
||||
except pytz.UnknownTimeZoneError:
|
||||
tz = timezone_constructor(arg)
|
||||
except UnknownTimezoneException:
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
|
|
|
@ -3,13 +3,21 @@ Timezone-related classes and functions.
|
|||
"""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo
|
||||
|
||||
from contextlib import ContextDecorator
|
||||
from datetime import datetime, timedelta, timezone, tzinfo
|
||||
|
||||
import pytz
|
||||
from asgiref.local import Local
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.deprecation import RemovedInDjango50Warning
|
||||
|
||||
__all__ = [
|
||||
'utc', 'get_fixed_timezone',
|
||||
|
@ -20,14 +28,11 @@ __all__ = [
|
|||
'is_aware', 'is_naive', 'make_aware', 'make_naive',
|
||||
]
|
||||
|
||||
# RemovedInDjango50Warning: sentinel for deprecation of is_dst parameters.
|
||||
NOT_PASSED = object()
|
||||
|
||||
# UTC time zone as a tzinfo instance.
|
||||
utc = pytz.utc
|
||||
|
||||
_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 = _PYTZ_BASE_CLASSES + (type(pytz.UTC),)
|
||||
utc = timezone.utc
|
||||
|
||||
|
||||
def get_fixed_timezone(offset):
|
||||
|
@ -49,7 +54,10 @@ def get_default_timezone():
|
|||
|
||||
This is the time zone defined by settings.TIME_ZONE.
|
||||
"""
|
||||
return pytz.timezone(settings.TIME_ZONE)
|
||||
if settings.USE_DEPRECATED_PYTZ:
|
||||
import pytz
|
||||
return pytz.timezone(settings.TIME_ZONE)
|
||||
return zoneinfo.ZoneInfo(settings.TIME_ZONE)
|
||||
|
||||
|
||||
# This function exists for consistency with get_current_timezone_name
|
||||
|
@ -94,7 +102,11 @@ def activate(timezone):
|
|||
if isinstance(timezone, tzinfo):
|
||||
_active.value = timezone
|
||||
elif isinstance(timezone, str):
|
||||
_active.value = pytz.timezone(timezone)
|
||||
if settings.USE_DEPRECATED_PYTZ:
|
||||
import pytz
|
||||
_active.value = pytz.timezone(timezone)
|
||||
else:
|
||||
_active.value = zoneinfo.ZoneInfo(timezone)
|
||||
else:
|
||||
raise ValueError("Invalid timezone: %r" % timezone)
|
||||
|
||||
|
@ -229,8 +241,17 @@ def is_naive(value):
|
|||
return value.utcoffset() is None
|
||||
|
||||
|
||||
def make_aware(value, timezone=None, is_dst=None):
|
||||
def make_aware(value, timezone=None, is_dst=NOT_PASSED):
|
||||
"""Make a naive datetime.datetime in a given time zone aware."""
|
||||
if is_dst is NOT_PASSED:
|
||||
is_dst = None
|
||||
else:
|
||||
warnings.warn(
|
||||
'The is_dst argument to make_aware(), used by the Trunc() '
|
||||
'database functions and QuerySet.datetimes(), is deprecated as it '
|
||||
'has no effect with zoneinfo time zones.',
|
||||
RemovedInDjango50Warning,
|
||||
)
|
||||
if timezone is None:
|
||||
timezone = get_current_timezone()
|
||||
if _is_pytz_zone(timezone):
|
||||
|
@ -255,13 +276,45 @@ 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 = _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):
|
||||
|
|
|
@ -36,6 +36,24 @@ details on these changes.
|
|||
|
||||
* The ``USE_L10N`` setting will be removed.
|
||||
|
||||
* The ``USE_DEPRECATED_PYTZ`` transitional setting will be removed.
|
||||
|
||||
* Support for ``pytz`` timezones will be removed.
|
||||
|
||||
* The ``is_dst`` argument will be removed from:
|
||||
|
||||
* ``QuerySet.datetimes()``
|
||||
* ``django.utils.timezone.make_aware()``
|
||||
* ``django.db.models.functions.Trunc()``
|
||||
* ``django.db.models.functions.TruncSecond()``
|
||||
* ``django.db.models.functions.TruncMinute()``
|
||||
* ``django.db.models.functions.TruncHour()``
|
||||
* ``django.db.models.functions.TruncDay()``
|
||||
* ``django.db.models.functions.TruncWeek()``
|
||||
* ``django.db.models.functions.TruncMonth()``
|
||||
* ``django.db.models.functions.TruncQuarter()``
|
||||
* ``django.db.models.functions.TruncYear()``
|
||||
|
||||
.. _deprecation-removed-in-4.1:
|
||||
|
||||
4.1
|
||||
|
|
|
@ -242,7 +242,8 @@ Takes an ``expression`` representing a ``DateField``, ``DateTimeField``,
|
|||
of the date referenced by ``lookup_name`` as an ``IntegerField``.
|
||||
Django usually uses the databases' extract function, so you may use any
|
||||
``lookup_name`` that your database supports. A ``tzinfo`` subclass, usually
|
||||
provided by ``pytz``, can be passed to extract a value in a specific timezone.
|
||||
provided by :mod:`zoneinfo`, can be passed to extract a value in a specific
|
||||
timezone.
|
||||
|
||||
Given the datetime ``2015-06-15 23:30:01.000321+00:00``, the built-in
|
||||
``lookup_name``\s return:
|
||||
|
@ -450,8 +451,8 @@ to that timezone before the value is extracted. The example below converts to
|
|||
the Melbourne timezone (UTC +10:00), which changes the day, weekday, and hour
|
||||
values that are returned::
|
||||
|
||||
>>> import pytz
|
||||
>>> melb = pytz.timezone('Australia/Melbourne') # UTC+10:00
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') # UTC+10:00
|
||||
>>> with timezone.override(melb):
|
||||
... Experiment.objects.annotate(
|
||||
... day=ExtractDay('start_datetime'),
|
||||
|
@ -466,8 +467,8 @@ values that are returned::
|
|||
Explicitly passing the timezone to the ``Extract`` function behaves in the same
|
||||
way, and takes priority over an active timezone::
|
||||
|
||||
>>> import pytz
|
||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> Experiment.objects.annotate(
|
||||
... day=ExtractDay('start_datetime', tzinfo=melb),
|
||||
... weekday=ExtractWeekDay('start_datetime', tzinfo=melb),
|
||||
|
@ -517,12 +518,16 @@ part, and an ``output_field`` that's either ``DateTimeField()``,
|
|||
``TimeField()``, or ``DateField()``. It returns a datetime, date, or time
|
||||
depending on ``output_field``, with fields up to ``kind`` set to their minimum
|
||||
value. If ``output_field`` is omitted, it will default to the ``output_field``
|
||||
of ``expression``. A ``tzinfo`` subclass, usually provided by ``pytz``, can be
|
||||
passed to truncate a value in a specific timezone.
|
||||
of ``expression``. A ``tzinfo`` subclass, usually provided by :mod:`zoneinfo`,
|
||||
can be passed to truncate a value in a specific timezone.
|
||||
|
||||
The ``is_dst`` parameter indicates whether or not ``pytz`` should interpret
|
||||
nonexistent and ambiguous datetimes in daylight saving time. By default (when
|
||||
``is_dst=None``), ``pytz`` raises an exception for such datetimes.
|
||||
.. deprecated:: 4.0
|
||||
|
||||
The ``is_dst`` parameter indicates whether or not ``pytz`` should interpret
|
||||
nonexistent and ambiguous datetimes in daylight saving time. By default
|
||||
(when ``is_dst=None``), ``pytz`` raises an exception for such datetimes.
|
||||
|
||||
The ``is_dst`` parameter is deprecated and will be removed in Django 5.0.
|
||||
|
||||
Given the datetime ``2015-06-15 14:30:50.000321+00:00``, the built-in ``kind``\s
|
||||
return:
|
||||
|
@ -607,6 +612,10 @@ Usage example::
|
|||
|
||||
.. attribute:: kind = 'quarter'
|
||||
|
||||
.. deprecated:: 4.0
|
||||
|
||||
The ``is_dst`` parameter is deprecated and will be removed in Django 5.0.
|
||||
|
||||
These are logically equivalent to ``Trunc('date_field', kind)``. They truncate
|
||||
all parts of the date up to ``kind`` which allows grouping or filtering dates
|
||||
with less precision. ``expression`` can have an ``output_field`` of either
|
||||
|
@ -634,8 +643,8 @@ that deal with date-parts can be used with ``DateField``::
|
|||
2014-01-01 1
|
||||
2015-01-01 2
|
||||
|
||||
>>> import pytz
|
||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> experiments_per_month = Experiment.objects.annotate(
|
||||
... month=TruncMonth('start_datetime', tzinfo=melb)).values('month').annotate(
|
||||
... experiments=Count('id'))
|
||||
|
@ -691,6 +700,10 @@ truncate function. It's also registered as a transform on ``DateTimeField`` as
|
|||
|
||||
.. attribute:: kind = 'second'
|
||||
|
||||
.. deprecated:: 4.0
|
||||
|
||||
The ``is_dst`` parameter is deprecated and will be removed in Django 5.0.
|
||||
|
||||
These are logically equivalent to ``Trunc('datetime_field', kind)``. They
|
||||
truncate all parts of the date up to ``kind`` and allow grouping or filtering
|
||||
datetimes with less precision. ``expression`` must have an ``output_field`` of
|
||||
|
@ -704,10 +717,10 @@ Usage example::
|
|||
... TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond,
|
||||
... )
|
||||
>>> from django.utils import timezone
|
||||
>>> import pytz
|
||||
>>> import zoneinfo
|
||||
>>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc)
|
||||
>>> Experiment.objects.create(start_datetime=start1, start_date=start1.date())
|
||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> Experiment.objects.annotate(
|
||||
... date=TruncDate('start_datetime'),
|
||||
... day=TruncDay('start_datetime', tzinfo=melb),
|
||||
|
@ -716,10 +729,10 @@ Usage example::
|
|||
... second=TruncSecond('start_datetime'),
|
||||
... ).values('date', 'day', 'hour', 'minute', 'second').get()
|
||||
{'date': datetime.date(2014, 6, 15),
|
||||
'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=<DstTzInfo 'Australia/Melbourne' AEST+10:00:00 STD>),
|
||||
'hour': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=<DstTzInfo 'Australia/Melbourne' AEST+10:00:00 STD>),
|
||||
'minute': 'minute': datetime.datetime(2014, 6, 15, 14, 30, tzinfo=<UTC>),
|
||||
'second': datetime.datetime(2014, 6, 15, 14, 30, 50, tzinfo=<UTC>)
|
||||
'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')),
|
||||
'hour': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')),
|
||||
'minute': 'minute': datetime.datetime(2014, 6, 15, 14, 30, tzinfo=zoneinfo.ZoneInfo('UTC')),
|
||||
'second': datetime.datetime(2014, 6, 15, 14, 30, 50, tzinfo=zoneinfo.ZoneInfo('UTC'))
|
||||
}
|
||||
|
||||
``TimeField`` truncation
|
||||
|
@ -740,6 +753,10 @@ Usage example::
|
|||
|
||||
.. attribute:: kind = 'second'
|
||||
|
||||
.. deprecated:: 4.0
|
||||
|
||||
The ``is_dst`` parameter is deprecated and will be removed in Django 5.0.
|
||||
|
||||
These are logically equivalent to ``Trunc('time_field', kind)``. They truncate
|
||||
all parts of the time up to ``kind`` which allows grouping or filtering times
|
||||
with less precision. ``expression`` can have an ``output_field`` of either
|
||||
|
@ -767,8 +784,8 @@ that deal with time-parts can be used with ``TimeField``::
|
|||
14:00:00 2
|
||||
17:00:00 1
|
||||
|
||||
>>> import pytz
|
||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
||||
>>> import zoneinfo
|
||||
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||
>>> experiments_per_hour = Experiment.objects.annotate(
|
||||
... hour=TruncHour('start_datetime', tzinfo=melb),
|
||||
... ).values('hour').annotate(experiments=Count('id'))
|
||||
|
|
|
@ -834,6 +834,10 @@ object. If it's ``None``, Django uses the :ref:`current time zone
|
|||
ambiguous datetimes in daylight saving time. By default (when ``is_dst=None``),
|
||||
``pytz`` raises an exception for such datetimes.
|
||||
|
||||
.. deprecated:: 4.0
|
||||
|
||||
The ``is_dst`` parameter is deprecated and will be removed in Django 5.0.
|
||||
|
||||
.. _database-time-zone-definitions:
|
||||
|
||||
.. note::
|
||||
|
@ -842,13 +846,11 @@ ambiguous datetimes in daylight saving time. By default (when ``is_dst=None``),
|
|||
As a consequence, your database must be able to interpret the value of
|
||||
``tzinfo.tzname(None)``. This translates into the following requirements:
|
||||
|
||||
- SQLite: no requirements. Conversions are performed in Python with pytz_
|
||||
(installed when you install Django).
|
||||
- SQLite: no requirements. Conversions are performed in Python.
|
||||
- PostgreSQL: no requirements (see `Time Zones`_).
|
||||
- Oracle: no requirements (see `Choosing a Time Zone File`_).
|
||||
- MySQL: load the time zone tables with `mysql_tzinfo_to_sql`_.
|
||||
|
||||
.. _pytz: http://pytz.sourceforge.net/
|
||||
.. _Time Zones: https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-TIMEZONES
|
||||
.. _Choosing a Time Zone File: https://docs.oracle.com/en/database/oracle/
|
||||
oracle-database/18/nlspg/datetime-data-types-and-time-zone-support.html
|
||||
|
|
|
@ -677,8 +677,8 @@ When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
|||
otherwise, you should leave this option unset. It's best to store datetimes
|
||||
in UTC because it avoids ambiguous or nonexistent datetimes during daylight
|
||||
saving time changes. Also, receiving datetimes in UTC keeps datetime
|
||||
arithmetic simple — there's no need for the ``normalize()`` method provided
|
||||
by pytz.
|
||||
arithmetic simple — there's no need to consider potential offset changes
|
||||
over a DST transition.
|
||||
|
||||
* If you're connecting to a third-party database that stores datetimes in a
|
||||
local time rather than UTC, then you must set this option to the
|
||||
|
@ -695,8 +695,8 @@ When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
|||
as ``date_trunc``, because their results depend on the time zone.
|
||||
|
||||
However, this has a downside: receiving all datetimes in local time makes
|
||||
datetime arithmetic more tricky — you must call the ``normalize()`` method
|
||||
provided by pytz after each operation.
|
||||
datetime arithmetic more tricky — you must account for possible offset
|
||||
changes over DST transitions.
|
||||
|
||||
Consider converting to local time explicitly with ``AT TIME ZONE`` in raw SQL
|
||||
queries instead of setting the ``TIME_ZONE`` option.
|
||||
|
@ -2758,6 +2758,23 @@ 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``
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
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``
|
||||
|
|
|
@ -941,24 +941,30 @@ appropriate entities.
|
|||
:class:`~datetime.datetime`. If ``timezone`` is set to ``None``, it
|
||||
defaults to the :ref:`current time zone <default-current-time-zone>`.
|
||||
|
||||
When using ``pytz``, the ``pytz.AmbiguousTimeError`` exception is raised if
|
||||
you try to make ``value`` aware during a DST transition where the same time
|
||||
occurs twice (when reverting from DST). Setting ``is_dst`` to ``True`` or
|
||||
``False`` will avoid the exception by choosing if the time is
|
||||
pre-transition or post-transition respectively.
|
||||
.. deprecated:: 4.0
|
||||
|
||||
When using ``pytz``, the ``pytz.NonExistentTimeError`` exception is raised
|
||||
if you try to make ``value`` aware during a DST transition such that the
|
||||
time never occurred. For example, if the 2:00 hour is skipped during a DST
|
||||
transition, trying to make 2:30 aware in that time zone will raise an
|
||||
exception. To avoid that you can use ``is_dst`` to specify how
|
||||
``make_aware()`` should interpret such a nonexistent time. If
|
||||
``is_dst=True`` then the above time would be interpreted as 2:30 DST time
|
||||
(equivalent to 1:30 local time). Conversely, if ``is_dst=False`` the time
|
||||
would be interpreted as 2:30 standard time (equivalent to 3:30 local time).
|
||||
When using ``pytz``, the ``pytz.AmbiguousTimeError`` exception is
|
||||
raised if you try to make ``value`` aware during a DST transition where
|
||||
the same time occurs twice (when reverting from DST). Setting
|
||||
``is_dst`` to ``True`` or ``False`` will avoid the exception by
|
||||
choosing if the time is pre-transition or post-transition respectively.
|
||||
|
||||
The ``is_dst`` parameter has no effect when using non-``pytz`` timezone
|
||||
implementations.
|
||||
When using ``pytz``, the ``pytz.NonExistentTimeError`` exception is
|
||||
raised if you try to make ``value`` aware during a DST transition such
|
||||
that the time never occurred. For example, if the 2:00 hour is skipped
|
||||
during a DST transition, trying to make 2:30 aware in that time zone
|
||||
will raise an exception. To avoid that you can use ``is_dst`` to
|
||||
specify how ``make_aware()`` should interpret such a nonexistent time.
|
||||
If ``is_dst=True`` then the above time would be interpreted as 2:30 DST
|
||||
time (equivalent to 1:30 local time). Conversely, if ``is_dst=False``
|
||||
the time would be interpreted as 2:30 standard time (equivalent to 3:30
|
||||
local time).
|
||||
|
||||
The ``is_dst`` parameter has no effect when using non-``pytz`` timezone
|
||||
implementations.
|
||||
|
||||
The ``is_dst`` parameter is deprecated and will be removed in Django
|
||||
5.0.
|
||||
|
||||
.. function:: make_naive(value, timezone=None)
|
||||
|
||||
|
|
|
@ -28,6 +28,46 @@ The Django 3.2.x series is the last to support Python 3.6 and 3.7.
|
|||
What's new in Django 4.0
|
||||
========================
|
||||
|
||||
``zoneinfo`` default timezone implementation
|
||||
--------------------------------------------
|
||||
|
||||
The Python standard library's :mod:`zoneinfo` is now the default timezone
|
||||
implementation in Django.
|
||||
|
||||
This is the next step in the migration from using ``pytz`` to using
|
||||
:mod:`zoneinfo`. Django 3.2 allowed the use of non-``pytz`` time zones. Django
|
||||
4.0 makes ``zoneinfo`` the default implementation. Support for ``pytz`` is now
|
||||
deprecated and will be removed in Django 5.0.
|
||||
|
||||
:mod:`zoneinfo` is part of the Python standard library from Python 3.9. The
|
||||
``backports.zoneinfo`` package is automatically installed alongside Django if
|
||||
you are using Python 3.8.
|
||||
|
||||
The move to ``zoneinfo`` should be largely transparent. Selection of the
|
||||
current timezone, conversion of datetime instances to the current timezone in
|
||||
forms and templates, as well as operations on aware datetimes in UTC are
|
||||
unaffected.
|
||||
|
||||
However, if you are you are working with non-UTC time zones, and using the
|
||||
``pytz`` ``normalize()`` and ``localize()`` APIs, possibly with the
|
||||
:setting:`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`
|
||||
setting allows continued use of ``pytz`` during the 4.x release cycle. This
|
||||
setting will be removed in Django 5.0.
|
||||
|
||||
In addition, a `pytz_deprecation_shim`_ package, created by the ``zoneinfo``
|
||||
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`
|
||||
transitional setting is recommended if you need a gradual update path.
|
||||
|
||||
.. _pytz_deprecation_shim: https://pytz-deprecation-shim.readthedocs.io/en/latest/index.html
|
||||
.. _migration guide: https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html
|
||||
|
||||
Functional unique constraints
|
||||
-----------------------------
|
||||
|
||||
|
@ -595,11 +635,37 @@ Miscellaneous
|
|||
* The default value of the ``USE_L10N`` setting is changed to ``True``. See the
|
||||
:ref:`Localization section <use_l10n_deprecation>` above for more details.
|
||||
|
||||
* As part of the :ref:`move to zoneinfo <whats-new-4.0>`,
|
||||
:attr:`django.utils.timezone.utc` is changed to alias
|
||||
:attr:`datetime.timezone.utc`.
|
||||
|
||||
.. _deprecated-features-4.0:
|
||||
|
||||
Features deprecated in 4.0
|
||||
==========================
|
||||
|
||||
Use of ``pytz`` time zones
|
||||
--------------------------
|
||||
|
||||
As part of the :ref:`move to zoneinfo <whats-new-4.0>`, use of ``pytz`` time
|
||||
zones is deprecated.
|
||||
|
||||
Accordingly, the ``is_dst`` arguments to the following are also deprecated:
|
||||
|
||||
* :meth:`django.db.models.query.QuerySet.datetimes()`
|
||||
* :func:`django.db.models.functions.Trunc()`
|
||||
* :func:`django.db.models.functions.TruncSecond()`
|
||||
* :func:`django.db.models.functions.TruncMinute()`
|
||||
* :func:`django.db.models.functions.TruncHour()`
|
||||
* :func:`django.db.models.functions.TruncDay()`
|
||||
* :func:`django.db.models.functions.TruncWeek()`
|
||||
* :func:`django.db.models.functions.TruncMonth()`
|
||||
* :func:`django.db.models.functions.TruncQuarter()`
|
||||
* :func:`django.db.models.functions.TruncYear()`
|
||||
* :func:`django.utils.timezone.make_aware()`
|
||||
|
||||
Support for use of ``pytz`` will be removed in Django 5.0.
|
||||
|
||||
Time zone support
|
||||
-----------------
|
||||
|
||||
|
|
|
@ -19,10 +19,9 @@ practice to store data in UTC in your database. The main reason is daylight
|
|||
saving time (DST). Many countries have a system of DST, where clocks are moved
|
||||
forward in spring and backward in autumn. If you're working in local time,
|
||||
you're likely to encounter errors twice a year, when the transitions happen.
|
||||
(The pytz_ documentation discusses `these issues`_ in greater detail.) This
|
||||
probably doesn't matter for your blog, but it's a problem if you over-bill or
|
||||
under-bill your customers by one hour, twice a year, every year. The solution
|
||||
to this problem is to use UTC in the code and use local time only when
|
||||
This probably doesn't matter for your blog, but it's a problem if you over bill
|
||||
or under bill your customers by one hour, twice a year, every year. The
|
||||
solution to this problem is to use UTC in the code and use local time only when
|
||||
interacting with end users.
|
||||
|
||||
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
|
||||
|
@ -32,15 +31,20 @@ True <USE_TZ>` in your settings file.
|
|||
|
||||
In Django 5.0, time zone support will be enabled by default.
|
||||
|
||||
By default, time zone support uses pytz_, which is installed when you install
|
||||
Django; Django also supports the use of other time zone implementations like
|
||||
:mod:`zoneinfo` by passing :class:`~datetime.tzinfo` objects directly to
|
||||
functions in :mod:`django.utils.timezone`.
|
||||
Time zone support uses :mod:`zoneinfo`, which is part of the Python standard
|
||||
library from Python 3.9. The ``backports.zoneinfo`` package is automatically
|
||||
installed alongside Django if you are using Python 3.8.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
|
||||
Support for non-``pytz`` timezone implementations was added.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
:mod:`zoneinfo` was made the default timezone implementation. You may
|
||||
continue to use `pytz`_ during the 4.x release cycle via the
|
||||
:setting:`USE_DEPRECATED_PYTZ` setting.
|
||||
|
||||
.. note::
|
||||
|
||||
The default :file:`settings.py` file created by :djadmin:`django-admin
|
||||
|
@ -88,8 +92,8 @@ should be aware too. In this mode, the example above becomes::
|
|||
Dealing with aware datetime objects isn't always intuitive. For instance,
|
||||
the ``tzinfo`` argument of the standard datetime constructor doesn't work
|
||||
reliably for time zones with DST. Using UTC is generally safe; if you're
|
||||
using other time zones, you should review the `pytz`_ documentation
|
||||
carefully.
|
||||
using other time zones, you should review the :mod:`zoneinfo`
|
||||
documentation carefully.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -113,8 +117,10 @@ receives one, it attempts to make it aware by interpreting it in the
|
|||
:ref:`default time zone <default-current-time-zone>` and raises a warning.
|
||||
|
||||
Unfortunately, during DST transitions, some datetimes don't exist or are
|
||||
ambiguous. In such situations, pytz_ raises an exception. That's why you should
|
||||
always create aware datetime objects when time zone support is enabled.
|
||||
ambiguous. That's why you should always create aware datetime objects when time
|
||||
zone support is enabled. (See the :mod:`Using ZoneInfo section of the zoneinfo
|
||||
docs <zoneinfo>` for examples using the ``fold`` attribute to specify the
|
||||
offset that should apply to a datetime during a DST transition.)
|
||||
|
||||
In practice, this is rarely an issue. Django gives you aware datetime objects
|
||||
in the models and forms, and most often, new datetime objects are created from
|
||||
|
@ -163,16 +169,16 @@ selection logic that makes sense for you.
|
|||
|
||||
Most websites that care about time zones ask users in which time zone they live
|
||||
and store this information in the user's profile. For anonymous users, they use
|
||||
the time zone of their primary audience or UTC. pytz_ provides helpers_, like a
|
||||
list of time zones per country, that you can use to pre-select the most likely
|
||||
choices.
|
||||
the time zone of their primary audience or UTC.
|
||||
:func:`zoneinfo.available_timezones` provides a set of available timezones that
|
||||
you can use to build a map from likely locations to time zones.
|
||||
|
||||
Here's an example that stores the current timezone in the session. (It skips
|
||||
error handling entirely for the sake of simplicity.)
|
||||
|
||||
Add the following middleware to :setting:`MIDDLEWARE`::
|
||||
|
||||
import pytz
|
||||
import zoneinfo
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -183,7 +189,7 @@ Add the following middleware to :setting:`MIDDLEWARE`::
|
|||
def __call__(self, request):
|
||||
tzname = request.session.get('django_timezone')
|
||||
if tzname:
|
||||
timezone.activate(pytz.timezone(tzname))
|
||||
timezone.activate(zoneinfo.ZoneInfo(tzname))
|
||||
else:
|
||||
timezone.deactivate()
|
||||
return self.get_response(request)
|
||||
|
@ -192,12 +198,19 @@ Create a view that can set the current timezone::
|
|||
|
||||
from django.shortcuts import redirect, render
|
||||
|
||||
# Prepare a map of common locations to timezone choices you wish to offer.
|
||||
common_timezones = {
|
||||
'London': 'Europe/London',
|
||||
'Paris': 'Europe/Paris',
|
||||
'New York': 'America/New_York',
|
||||
}
|
||||
|
||||
def set_timezone(request):
|
||||
if request.method == 'POST':
|
||||
request.session['django_timezone'] = request.POST['timezone']
|
||||
return redirect('/')
|
||||
else:
|
||||
return render(request, 'template.html', {'timezones': pytz.common_timezones})
|
||||
return render(request, 'template.html', {'timezones': common_timezones})
|
||||
|
||||
Include a form in ``template.html`` that will ``POST`` to this view:
|
||||
|
||||
|
@ -209,8 +222,8 @@ Include a form in ``template.html`` that will ``POST`` to this view:
|
|||
{% csrf_token %}
|
||||
<label for="timezone">Time zone:</label>
|
||||
<select name="timezone">
|
||||
{% for tz in timezones %}
|
||||
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ tz }}</option>
|
||||
{% for city, tz in timezones %}
|
||||
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Set">
|
||||
|
@ -225,9 +238,8 @@ When you enable time zone support, Django interprets datetimes entered in
|
|||
forms in the :ref:`current time zone <default-current-time-zone>` and returns
|
||||
aware datetime objects in ``cleaned_data``.
|
||||
|
||||
If the current time zone raises an exception for datetimes that don't exist or
|
||||
are ambiguous because they fall in a DST transition (the timezones provided by
|
||||
pytz_ do this), such datetimes will be reported as invalid values.
|
||||
Converted datetimes that don't exist or are ambiguous because they fall in a
|
||||
DST transition will be reported as invalid values.
|
||||
|
||||
.. _time-zones-in-templates:
|
||||
|
||||
|
@ -583,20 +595,20 @@ Troubleshooting
|
|||
None of this is true in a time zone aware environment::
|
||||
|
||||
>>> import datetime
|
||||
>>> import pytz
|
||||
>>> paris_tz = pytz.timezone("Europe/Paris")
|
||||
>>> new_york_tz = pytz.timezone("America/New_York")
|
||||
>>> paris = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30))
|
||||
# This is the correct way to convert between time zones with pytz.
|
||||
>>> new_york = new_york_tz.normalize(paris.astimezone(new_york_tz))
|
||||
>>> import zoneinfo
|
||||
>>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris")
|
||||
>>> new_york_tz = zoneinfo.ZoneInfo("America/New_York")
|
||||
>>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz)
|
||||
# This is the correct way to convert between time zones.
|
||||
>>> new_york = paris.astimezone(new_york_tz)
|
||||
>>> paris == new_york, paris.date() == new_york.date()
|
||||
(True, False)
|
||||
>>> paris - new_york, paris.date() - new_york.date()
|
||||
(datetime.timedelta(0), datetime.timedelta(1))
|
||||
>>> paris
|
||||
datetime.datetime(2012, 3, 3, 1, 30, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
|
||||
datetime.datetime(2012, 3, 3, 1, 30, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
|
||||
>>> new_york
|
||||
datetime.datetime(2012, 3, 2, 19, 30, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
|
||||
datetime.datetime(2012, 3, 2, 19, 30, tzinfo=zoneinfo.ZoneInfo(key='America/New_York'))
|
||||
|
||||
As this example shows, the same datetime has a different date, depending on
|
||||
the time zone in which it is represented. But the real problem is more
|
||||
|
@ -621,14 +633,13 @@ Troubleshooting
|
|||
will be the current timezone::
|
||||
|
||||
>>> from django.utils import timezone
|
||||
>>> timezone.activate(pytz.timezone("Asia/Singapore"))
|
||||
>>> timezone.activate(zoneinfo.ZoneInfo("Asia/Singapore"))
|
||||
# For this example, we set the time zone to Singapore, but here's how
|
||||
# you would obtain the current time zone in the general case.
|
||||
>>> current_tz = timezone.get_current_timezone()
|
||||
# Again, this is the correct way to convert between time zones with pytz.
|
||||
>>> local = current_tz.normalize(paris.astimezone(current_tz))
|
||||
>>> local = paris.astimezone(current_tz)
|
||||
>>> local
|
||||
datetime.datetime(2012, 3, 3, 8, 30, tzinfo=<DstTzInfo 'Asia/Singapore' SGT+8:00:00 STD>)
|
||||
datetime.datetime(2012, 3, 3, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='Asia/Singapore'))
|
||||
>>> local.date()
|
||||
datetime.date(2012, 3, 3)
|
||||
|
||||
|
@ -645,18 +656,14 @@ Usage
|
|||
``"Europe/Helsinki"`` **time zone. How do I turn that into an aware
|
||||
datetime?**
|
||||
|
||||
This is exactly what pytz_ is for.
|
||||
Here you need to create the required ``ZoneInfo`` instance and attach it to
|
||||
the naïve datetime::
|
||||
|
||||
>>> import zoneinfo
|
||||
>>> from django.utils.dateparse import parse_datetime
|
||||
>>> naive = parse_datetime("2012-02-21 10:28:45")
|
||||
>>> import pytz
|
||||
>>> pytz.timezone("Europe/Helsinki").localize(naive, is_dst=None)
|
||||
datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=<DstTzInfo 'Europe/Helsinki' EET+2:00:00 STD>)
|
||||
|
||||
Note that ``localize`` is a pytz extension to the :class:`~datetime.tzinfo`
|
||||
API. Also, you may want to catch ``pytz.InvalidTimeError``. The
|
||||
documentation of pytz contains `more examples`_. You should review it
|
||||
before attempting to manipulate aware datetimes.
|
||||
>>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki"))
|
||||
datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))
|
||||
|
||||
#. **How can I obtain the local time in the current time zone?**
|
||||
|
||||
|
@ -677,19 +684,14 @@ Usage
|
|||
|
||||
>>> from django.utils import timezone
|
||||
>>> timezone.localtime(timezone.now())
|
||||
datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
|
||||
datetime.datetime(2012, 3, 3, 20, 10, 53, 873365, tzinfo=zoneinfo.ZoneInfo(key='Europe/Paris'))
|
||||
|
||||
In this example, the current time zone is ``"Europe/Paris"``.
|
||||
|
||||
#. **How can I see all available time zones?**
|
||||
|
||||
pytz_ provides helpers_, including a list of current time zones and a list
|
||||
of all available time zones -- some of which are only of historical
|
||||
interest. :mod:`zoneinfo` also provides similar functionality via
|
||||
:func:`zoneinfo.available_timezones`.
|
||||
: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/
|
||||
.. _more examples: http://pytz.sourceforge.net/#example-usage
|
||||
.. _these issues: http://pytz.sourceforge.net/#problems-with-localtime
|
||||
.. _helpers: http://pytz.sourceforge.net/#helpers
|
||||
.. _tz database: https://en.wikipedia.org/wiki/Tz_database
|
||||
|
|
|
@ -38,8 +38,9 @@ include_package_data = true
|
|||
zip_safe = false
|
||||
install_requires =
|
||||
asgiref >= 3.3.2
|
||||
pytz
|
||||
backports.zoneinfo; python_version<"3.9"
|
||||
sqlparse >= 0.2.2
|
||||
tzdata; sys_platform == 'win32'
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
|
|
|
@ -5,15 +5,15 @@ import unittest
|
|||
from unittest import mock
|
||||
from urllib.parse import parse_qsl, urljoin, urlparse
|
||||
|
||||
import pytz
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
try:
|
||||
from backports import zoneinfo
|
||||
except ImportError:
|
||||
zoneinfo = None
|
||||
from backports import zoneinfo
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import AdminSite, ModelAdmin
|
||||
|
@ -73,10 +73,10 @@ MULTIPART_ENCTYPE = 'enctype="multipart/form-data"'
|
|||
|
||||
def make_aware_datetimes(dt, iana_key):
|
||||
"""Makes one aware datetime for each supported time zone provider."""
|
||||
yield pytz.timezone(iana_key).localize(dt, is_dst=None)
|
||||
yield dt.replace(tzinfo=zoneinfo.ZoneInfo(iana_key))
|
||||
|
||||
if zoneinfo is not None:
|
||||
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:
|
||||
|
|
|
@ -4,7 +4,10 @@ import re
|
|||
from datetime import datetime, timedelta
|
||||
from importlib import import_module
|
||||
|
||||
import pytz
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
@ -967,8 +970,8 @@ class DateTimePickerShortcutsSeleniumTests(AdminWidgetSeleniumTestCase):
|
|||
error_margin = timedelta(seconds=10)
|
||||
|
||||
# If we are neighbouring a DST, we add an hour of error margin.
|
||||
tz = pytz.timezone('America/Chicago')
|
||||
utc_now = datetime.now(pytz.utc)
|
||||
tz = zoneinfo.ZoneInfo('America/Chicago')
|
||||
utc_now = datetime.now(zoneinfo.ZoneInfo('UTC'))
|
||||
tz_yesterday = (utc_now - timedelta(days=1)).astimezone(tz).tzname()
|
||||
tz_tomorrow = (utc_now + timedelta(days=1)).astimezone(tz).tzname()
|
||||
if tz_yesterday != tz_tomorrow:
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import datetime
|
||||
import unittest
|
||||
|
||||
import pytz
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test import TestCase, ignore_warnings, override_settings
|
||||
from django.utils import timezone
|
||||
from django.utils.deprecation import RemovedInDjango50Warning
|
||||
|
||||
from .models import Article, Category, Comment
|
||||
|
||||
|
@ -91,7 +96,9 @@ class DateTimesTests(TestCase):
|
|||
qs = Article.objects.datetimes('pub_date', 'second')
|
||||
self.assertEqual(qs[0], now)
|
||||
|
||||
@override_settings(USE_TZ=True, TIME_ZONE='UTC')
|
||||
@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
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import unittest
|
||||
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 backports import zoneinfo
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import (
|
||||
|
@ -23,15 +24,24 @@ from django.db.models.functions import (
|
|||
TruncYear,
|
||||
)
|
||||
from django.test import (
|
||||
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
||||
TestCase, ignore_warnings, override_settings, skipIfDBFeature,
|
||||
skipUnlessDBFeature,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.deprecation import RemovedInDjango50Warning
|
||||
|
||||
from ..models import Author, DTModel, Fan
|
||||
|
||||
ZONE_CONSTRUCTORS = (pytz.timezone,)
|
||||
if zoneinfo is not None:
|
||||
ZONE_CONSTRUCTORS += (zoneinfo.ZoneInfo,)
|
||||
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):
|
||||
|
@ -98,8 +108,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
|
||||
|
@ -135,8 +145,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
|
||||
|
@ -158,8 +168,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
|
||||
|
@ -181,8 +191,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
|
||||
|
@ -280,8 +290,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -319,8 +329,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -339,8 +349,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -359,14 +369,14 @@ class DateFunctionTests(TestCase):
|
|||
def test_extract_iso_year_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)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
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)
|
||||
week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015)
|
||||
week_52_day_2014 = timezone.make_aware(week_52_day_2014)
|
||||
week_53_day_2015 = timezone.make_aware(week_53_day_2015)
|
||||
days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
|
||||
obj_1_iso_2014 = self.create_model(week_52_day_2014, end_datetime)
|
||||
obj_1_iso_2015 = self.create_model(week_1_day_2014_2015, end_datetime)
|
||||
|
@ -397,8 +407,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -417,8 +427,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -437,8 +447,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -458,8 +468,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -477,13 +487,13 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
|
||||
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)
|
||||
last_quarter_2014 = timezone.make_aware(last_quarter_2014)
|
||||
first_quarter_2015 = timezone.make_aware(first_quarter_2015)
|
||||
dates = [last_quarter_2014, first_quarter_2015]
|
||||
self.create_model(last_quarter_2014, end_datetime)
|
||||
self.create_model(first_quarter_2015, end_datetime)
|
||||
|
@ -498,15 +508,15 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
|
||||
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)
|
||||
week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_2015)
|
||||
week_52_day_2014 = timezone.make_aware(week_52_day_2014)
|
||||
week_53_day_2015 = timezone.make_aware(week_53_day_2015)
|
||||
|
||||
days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
|
||||
self.create_model(week_53_day_2015, end_datetime)
|
||||
|
@ -525,8 +535,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -551,8 +561,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -586,8 +596,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -606,8 +616,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -626,8 +636,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -646,8 +656,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
|
||||
|
@ -752,8 +762,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -786,10 +796,10 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
last_quarter_2015 = timezone.make_aware(last_quarter_2015)
|
||||
first_quarter_2016 = timezone.make_aware(first_quarter_2016)
|
||||
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)
|
||||
|
@ -825,8 +835,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -857,8 +867,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -881,8 +891,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -909,8 +919,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -937,8 +947,8 @@ class DateFunctionTests(TestCase):
|
|||
start_datetime = datetime(2015, 6, 15, 14, 30, 26) # 0 microseconds.
|
||||
end_datetime = datetime(2015, 6, 15, 14, 30, 26, 321)
|
||||
if settings.USE_TZ:
|
||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.assertIs(
|
||||
DTModel.objects.filter(
|
||||
|
@ -962,8 +972,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -986,8 +996,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -1018,8 +1028,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -1050,8 +1060,8 @@ class DateFunctionTests(TestCase):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
self.assertQuerysetEqual(
|
||||
|
@ -1085,9 +1095,9 @@ class DateFunctionTests(TestCase):
|
|||
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_since_1 = timezone.make_aware(fan_since_1)
|
||||
fan_since_2 = timezone.make_aware(fan_since_2)
|
||||
fan_since_3 = timezone.make_aware(fan_since_3)
|
||||
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)
|
||||
|
@ -1113,9 +1123,9 @@ class DateFunctionTests(TestCase):
|
|||
datetime_2 = datetime(2001, 3, 5)
|
||||
datetime_3 = datetime(2002, 1, 3)
|
||||
if settings.USE_TZ:
|
||||
datetime_1 = timezone.make_aware(datetime_1, is_dst=False)
|
||||
datetime_2 = timezone.make_aware(datetime_2, is_dst=False)
|
||||
datetime_3 = timezone.make_aware(datetime_3, is_dst=False)
|
||||
datetime_1 = timezone.make_aware(datetime_1)
|
||||
datetime_2 = timezone.make_aware(datetime_2)
|
||||
datetime_3 = timezone.make_aware(datetime_3)
|
||||
obj_1 = self.create_model(datetime_1, datetime_3)
|
||||
obj_2 = self.create_model(datetime_2, datetime_1)
|
||||
obj_3 = self.create_model(datetime_3, datetime_2)
|
||||
|
@ -1144,8 +1154,8 @@ 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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
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))
|
||||
|
@ -1203,8 +1213,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||
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)
|
||||
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'):
|
||||
|
@ -1233,8 +1243,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
|
||||
for melb, pacific in zip(
|
||||
|
@ -1263,6 +1273,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||
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')
|
||||
utc = timezone.utc
|
||||
|
@ -1294,8 +1306,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
|||
"""
|
||||
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)
|
||||
start_datetime = timezone.make_aware(start_datetime)
|
||||
end_datetime = timezone.make_aware(end_datetime)
|
||||
self.create_model(start_datetime, end_datetime)
|
||||
self.create_model(end_datetime, start_datetime)
|
||||
|
||||
|
|
|
@ -10,6 +10,16 @@ import sys
|
|||
import uuid
|
||||
from unittest import mock
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
import custom_migration_operations.more_operations
|
||||
import custom_migration_operations.operations
|
||||
|
||||
|
@ -503,6 +513,22 @@ class WriterTests(SimpleTestCase):
|
|||
)
|
||||
)
|
||||
|
||||
self.assertSerializedResultEqual(
|
||||
datetime.datetime(2012, 1, 1, 2, 1, tzinfo=zoneinfo.ZoneInfo('Europe/Paris')),
|
||||
(
|
||||
"datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)",
|
||||
{'import datetime', 'from django.utils.timezone import utc'},
|
||||
)
|
||||
)
|
||||
if pytz:
|
||||
self.assertSerializedResultEqual(
|
||||
pytz.timezone('Europe/Paris').localize(datetime.datetime(2012, 1, 1, 2, 1)),
|
||||
(
|
||||
"datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)",
|
||||
{'import datetime', 'from django.utils.timezone import utc'},
|
||||
)
|
||||
)
|
||||
|
||||
def test_serialize_fields(self):
|
||||
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
||||
self.assertSerializedResultEqual(
|
||||
|
|
|
@ -4,7 +4,10 @@ import unittest
|
|||
from types import ModuleType, SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import ENVIRONMENT_VARIABLE, LazySettings, Settings, settings
|
||||
from django.conf import (
|
||||
ENVIRONMENT_VARIABLE, USE_DEPRECATED_PYTZ_DEPRECATED_MSG, LazySettings,
|
||||
Settings, settings,
|
||||
)
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpRequest
|
||||
from django.test import (
|
||||
|
@ -348,6 +351,21 @@ class SettingsTests(SimpleTestCase):
|
|||
finally:
|
||||
del sys.modules['fake_settings_module']
|
||||
|
||||
def test_use_deprecated_pytz_deprecation(self):
|
||||
settings_module = ModuleType('fake_settings_module')
|
||||
settings_module.USE_DEPRECATED_PYTZ = True
|
||||
settings_module.USE_TZ = 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):
|
||||
|
|
|
@ -5,15 +5,15 @@ from contextlib import contextmanager
|
|||
from unittest import SkipTest, skipIf
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
import pytz
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
try:
|
||||
from backports import zoneinfo
|
||||
except ImportError:
|
||||
zoneinfo = None
|
||||
from backports import zoneinfo
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import serializers
|
||||
|
@ -61,9 +61,9 @@ UTC = timezone.utc
|
|||
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
|
||||
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
|
||||
|
||||
ZONE_CONSTRUCTORS = (pytz.timezone,)
|
||||
if zoneinfo is not None:
|
||||
ZONE_CONSTRUCTORS += (zoneinfo.ZoneInfo,)
|
||||
ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
|
||||
if pytz is not None:
|
||||
ZONE_CONSTRUCTORS += (pytz.timezone,)
|
||||
|
||||
|
||||
def get_timezones(key):
|
||||
|
@ -363,6 +363,23 @@ class NewDatabaseTests(TestCase):
|
|||
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_convert_timezones(self):
|
||||
# Connection timezone is equal to the current timezone, datetime
|
||||
# shouldn't be converted.
|
||||
|
@ -921,7 +938,7 @@ class TemplateTests(SimpleTestCase):
|
|||
tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
|
||||
ctx = Context({
|
||||
'dt': datetime.datetime(2011, 9, 1, 13, 20, 30),
|
||||
'tz': pytz.timezone('Europe/Paris'),
|
||||
'tz': tz,
|
||||
})
|
||||
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||
|
||||
|
@ -994,11 +1011,15 @@ 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(pytz.UnknownTimeZoneError):
|
||||
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):
|
||||
|
|
|
@ -14,7 +14,10 @@ from pathlib import Path
|
|||
from subprocess import CompletedProcess
|
||||
from unittest import mock, skip, skipIf
|
||||
|
||||
import pytz
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
from backports import zoneinfo
|
||||
|
||||
import django.__main__
|
||||
from django.apps.registry import Apps
|
||||
|
@ -247,7 +250,7 @@ class TestChildArguments(SimpleTestCase):
|
|||
class TestUtilities(SimpleTestCase):
|
||||
def test_is_django_module(self):
|
||||
for module, expected in (
|
||||
(pytz, False),
|
||||
(zoneinfo, False),
|
||||
(sys, False),
|
||||
(autoreload, True)
|
||||
):
|
||||
|
@ -256,7 +259,7 @@ class TestUtilities(SimpleTestCase):
|
|||
|
||||
def test_is_django_path(self):
|
||||
for module, expected in (
|
||||
(pytz.__file__, False),
|
||||
(zoneinfo.__file__, False),
|
||||
(contextlib.__file__, False),
|
||||
(autoreload.__file__, True)
|
||||
):
|
||||
|
|
|
@ -2,41 +2,58 @@ import datetime
|
|||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytz
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
pytz = None
|
||||
|
||||
try:
|
||||
import zoneinfo
|
||||
except ImportError:
|
||||
try:
|
||||
from backports import zoneinfo
|
||||
except ImportError:
|
||||
zoneinfo = None
|
||||
from backports import zoneinfo
|
||||
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
from django.test import SimpleTestCase, ignore_warnings, override_settings
|
||||
from django.utils import timezone
|
||||
from django.utils.deprecation import RemovedInDjango50Warning
|
||||
|
||||
CET = pytz.timezone("Europe/Paris")
|
||||
PARIS_ZI = zoneinfo.ZoneInfo('Europe/Paris')
|
||||
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
|
||||
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
|
||||
UTC = datetime.timezone.utc
|
||||
|
||||
HAS_ZONEINFO = zoneinfo is not None
|
||||
HAS_PYTZ = pytz is not None
|
||||
if not HAS_PYTZ:
|
||||
CET = None
|
||||
PARIS_IMPLS = (PARIS_ZI,)
|
||||
|
||||
if not HAS_ZONEINFO:
|
||||
PARIS_ZI = None
|
||||
PARIS_IMPLS = (CET,)
|
||||
|
||||
needs_zoneinfo = unittest.skip("Test requires zoneinfo")
|
||||
needs_pytz = unittest.skip('Test requires pytz')
|
||||
else:
|
||||
PARIS_ZI = zoneinfo.ZoneInfo('Europe/Paris')
|
||||
PARIS_IMPLS = (CET, PARIS_ZI)
|
||||
CET = pytz.timezone('Europe/Paris')
|
||||
PARIS_IMPLS = (PARIS_ZI, CET)
|
||||
|
||||
def needs_zoneinfo(f):
|
||||
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()))
|
||||
|
@ -173,13 +190,14 @@ class TimezoneTests(SimpleTestCase):
|
|||
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), tz),
|
||||
datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=CEST))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
timezone.make_aware(CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET)
|
||||
|
||||
if HAS_ZONEINFO:
|
||||
if HAS_PYTZ:
|
||||
with self.assertRaises(ValueError):
|
||||
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=PARIS_ZI), PARIS_ZI)
|
||||
timezone.make_aware(CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET)
|
||||
|
||||
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),
|
||||
|
@ -192,7 +210,6 @@ class TimezoneTests(SimpleTestCase):
|
|||
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)
|
||||
|
||||
@needs_zoneinfo
|
||||
def test_make_naive_zoneinfo(self):
|
||||
self.assertEqual(
|
||||
timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=PARIS_ZI), PARIS_ZI),
|
||||
|
@ -204,6 +221,8 @@ 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)
|
||||
|
@ -217,7 +236,6 @@ class TimezoneTests(SimpleTestCase):
|
|||
self.assertEqual(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
|
||||
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
|
||||
|
||||
@needs_zoneinfo
|
||||
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)
|
||||
|
@ -232,6 +250,8 @@ 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)
|
||||
|
@ -245,7 +265,6 @@ class TimezoneTests(SimpleTestCase):
|
|||
self.assertEqual(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
|
||||
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
|
||||
|
||||
@needs_zoneinfo
|
||||
def test_make_aware_zoneinfo_non_existent(self):
|
||||
# 2:30 never happened due to DST
|
||||
non_existent = datetime.datetime(2015, 3, 29, 2, 30)
|
||||
|
@ -260,6 +279,15 @@ class TimezoneTests(SimpleTestCase):
|
|||
self.assertEqual(std.utcoffset(), datetime.timedelta(hours=1))
|
||||
self.assertEqual(dst.utcoffset(), datetime.timedelta(hours=2))
|
||||
|
||||
def test_make_aware_is_dst_deprecation_warning(self):
|
||||
msg = (
|
||||
'The is_dst argument to make_aware(), used by the Trunc() '
|
||||
'database functions and QuerySet.datetimes(), is deprecated as it '
|
||||
'has no effect with zoneinfo time zones.'
|
||||
)
|
||||
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
|
||||
timezone.make_aware(datetime.datetime(2011, 9, 1, 13, 20, 30), EAT, is_dst=True)
|
||||
|
||||
def test_get_timezone_name(self):
|
||||
"""
|
||||
The _get_timezone_name() helper must return the offset for fixed offset
|
||||
|
@ -271,15 +299,15 @@ class TimezoneTests(SimpleTestCase):
|
|||
# datetime.timezone, fixed offset with and without `name`.
|
||||
(datetime.timezone(datetime.timedelta(hours=10)), 'UTC+10:00'),
|
||||
(datetime.timezone(datetime.timedelta(hours=10), name='Etc/GMT-10'), 'Etc/GMT-10'),
|
||||
# pytz, named and fixed offset.
|
||||
(pytz.timezone('Europe/Madrid'), 'Europe/Madrid'),
|
||||
(pytz.timezone('Etc/GMT-10'), '+10'),
|
||||
# zoneinfo, named and fixed offset.
|
||||
(zoneinfo.ZoneInfo('Europe/Madrid'), 'Europe/Madrid'),
|
||||
(zoneinfo.ZoneInfo('Etc/GMT-10'), '+10'),
|
||||
]
|
||||
if HAS_ZONEINFO:
|
||||
if HAS_PYTZ:
|
||||
tests += [
|
||||
# zoneinfo, named and fixed offset.
|
||||
(zoneinfo.ZoneInfo('Europe/Madrid'), 'Europe/Madrid'),
|
||||
(zoneinfo.ZoneInfo('Etc/GMT-10'), '+10'),
|
||||
# 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):
|
||||
|
@ -288,10 +316,6 @@ class TimezoneTests(SimpleTestCase):
|
|||
def test_get_default_timezone(self):
|
||||
self.assertEqual(timezone.get_default_timezone_name(), 'America/Chicago')
|
||||
|
||||
def test_get_default_timezone_utc(self):
|
||||
with override_settings(USE_TZ=True, TIME_ZONE='UTC'):
|
||||
self.assertIs(timezone.get_default_timezone(), timezone.utc)
|
||||
|
||||
def test_fixedoffset_timedelta(self):
|
||||
delta = datetime.timedelta(hours=1)
|
||||
self.assertEqual(timezone.get_fixed_timezone(delta).utcoffset(None), delta)
|
||||
|
|
Loading…
Reference in New Issue