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"
|
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 = (
|
USE_L10N_DEPRECATED_MSG = (
|
||||||
'The USE_L10N setting is deprecated. Starting with Django 5.0, localized '
|
'The USE_L10N setting is deprecated. Starting with Django 5.0, localized '
|
||||||
'formatting of data will always be enabled. For example Django will '
|
'formatting of data will always be enabled. For example Django will '
|
||||||
|
@ -196,6 +203,9 @@ class Settings:
|
||||||
category=RemovedInDjango50Warning,
|
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:
|
if hasattr(time, 'tzset') and self.TIME_ZONE:
|
||||||
# When we can, attempt to validate the timezone. If we can't find
|
# When we can, attempt to validate the timezone. If we can't find
|
||||||
# this file, no check happens and it's harmless.
|
# this file, no check happens and it's harmless.
|
||||||
|
@ -245,6 +255,8 @@ class UserSettingsHolder:
|
||||||
if name == 'USE_L10N':
|
if name == 'USE_L10N':
|
||||||
warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
|
warnings.warn(USE_L10N_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
if name == 'USE_DEPRECATED_PYTZ':
|
||||||
|
warnings.warn(USE_DEPRECATED_PYTZ_DEPRECATED_MSG, RemovedInDjango50Warning)
|
||||||
|
|
||||||
def __delattr__(self, name):
|
def __delattr__(self, name):
|
||||||
self._deleted.add(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.
|
# If you set this to True, Django will use timezone-aware datetimes.
|
||||||
USE_TZ = False
|
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:
|
# Language code for this installation. All choices can be found here:
|
||||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
|
||||||
from django.contrib.admin.utils import (
|
from django.contrib.admin.utils import (
|
||||||
display_for_field, display_for_value, get_fields_from_path,
|
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]
|
field = get_fields_from_path(cl.model, field_name)[-1]
|
||||||
if isinstance(field, models.DateTimeField):
|
if isinstance(field, models.DateTimeField):
|
||||||
dates_or_datetimes = 'datetimes'
|
dates_or_datetimes = 'datetimes'
|
||||||
qs_kwargs = {'is_dst': True}
|
qs_kwargs = {'is_dst': True} if settings.USE_DEPRECATED_PYTZ else {}
|
||||||
else:
|
else:
|
||||||
dates_or_datetimes = 'dates'
|
dates_or_datetimes = 'dates'
|
||||||
qs_kwargs = {}
|
qs_kwargs = {}
|
||||||
|
|
|
@ -6,7 +6,10 @@ import warnings
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import pytz
|
try:
|
||||||
|
import zoneinfo
|
||||||
|
except ImportError:
|
||||||
|
from backports import zoneinfo
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
@ -23,6 +26,14 @@ from django.utils.functional import cached_property
|
||||||
NO_DB_ALIAS = '__no_db__'
|
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:
|
class BaseDatabaseWrapper:
|
||||||
"""Represent a database connection."""
|
"""Represent a database connection."""
|
||||||
# Mapping of Field objects to their column types.
|
# Mapping of Field objects to their column types.
|
||||||
|
@ -135,7 +146,7 @@ class BaseDatabaseWrapper:
|
||||||
elif self.settings_dict['TIME_ZONE'] is None:
|
elif self.settings_dict['TIME_ZONE'] is None:
|
||||||
return timezone.utc
|
return timezone.utc
|
||||||
else:
|
else:
|
||||||
return pytz.timezone(self.settings_dict['TIME_ZONE'])
|
return timezone_constructor(self.settings_dict['TIME_ZONE'])
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def timezone_name(self):
|
def timezone_name(self):
|
||||||
|
|
|
@ -14,12 +14,12 @@ import warnings
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from sqlite3 import dbapi2 as Database
|
from sqlite3 import dbapi2 as Database
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.db.backends import utils as backend_utils
|
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 import timezone
|
||||||
from django.utils.asyncio import async_unsafe
|
from django.utils.asyncio import async_unsafe
|
||||||
from django.utils.dateparse import parse_datetime, parse_time
|
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):
|
except (TypeError, ValueError):
|
||||||
return None
|
return None
|
||||||
if conn_tzname:
|
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:
|
if tzname is not None and tzname != conn_tzname:
|
||||||
sign_index = tzname.find('+') + tzname.find('-') + 1
|
sign_index = tzname.find('+') + tzname.find('-') + 1
|
||||||
if sign_index > -1:
|
if sign_index > -1:
|
||||||
|
@ -441,7 +441,7 @@ def _sqlite_datetime_parse(dt, tzname=None, conn_tzname=None):
|
||||||
hours, minutes = offset.split(':')
|
hours, minutes = offset.split(':')
|
||||||
offset_delta = datetime.timedelta(hours=int(hours), minutes=int(minutes))
|
offset_delta = datetime.timedelta(hours=int(hours), minutes=int(minutes))
|
||||||
dt += offset_delta if sign == '+' else -offset_delta
|
dt += offset_delta if sign == '+' else -offset_delta
|
||||||
dt = timezone.localtime(dt, pytz.timezone(tzname))
|
dt = timezone.localtime(dt, timezone_constructor(tzname))
|
||||||
return dt
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class DatetimeDatetimeSerializer(BaseSerializer):
|
||||||
imports = ["import datetime"]
|
imports = ["import datetime"]
|
||||||
if self.value.tzinfo is not None:
|
if self.value.tzinfo is not None:
|
||||||
imports.append("from django.utils.timezone import utc")
|
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):
|
class DecimalSerializer(BaseSerializer):
|
||||||
|
|
|
@ -188,7 +188,9 @@ class TruncBase(TimezoneMixin, Transform):
|
||||||
kind = None
|
kind = None
|
||||||
tzinfo = 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.tzinfo = tzinfo
|
||||||
self.is_dst = is_dst
|
self.is_dst = is_dst
|
||||||
super().__init__(expression, output_field=output_field, **extra)
|
super().__init__(expression, output_field=output_field, **extra)
|
||||||
|
@ -264,7 +266,9 @@ class TruncBase(TimezoneMixin, Transform):
|
||||||
|
|
||||||
class Trunc(TruncBase):
|
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
|
self.kind = kind
|
||||||
super().__init__(
|
super().__init__(
|
||||||
expression, output_field=output_field, tzinfo=tzinfo,
|
expression, output_field=output_field, tzinfo=tzinfo,
|
||||||
|
|
|
@ -916,7 +916,9 @@ class QuerySet:
|
||||||
'datefield', flat=True
|
'datefield', flat=True
|
||||||
).distinct().filter(plain_field__isnull=False).order_by(('-' if order == 'DESC' else '') + 'datefield')
|
).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
|
Return a list of datetime objects representing all available
|
||||||
datetimes for the given field_name, scoped to 'kind'.
|
datetimes for the given field_name, scoped to 'kind'.
|
||||||
|
|
|
@ -1,13 +1,37 @@
|
||||||
from datetime import datetime, tzinfo
|
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.template import Library, Node, TemplateSyntaxError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
register = Library()
|
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
|
# HACK: datetime instances cannot be assigned new attributes. Define a subclass
|
||||||
# in order to define new attributes in do_timezone().
|
# in order to define new attributes in do_timezone().
|
||||||
class datetimeobject(datetime):
|
class datetimeobject(datetime):
|
||||||
|
@ -61,8 +85,8 @@ def do_timezone(value, arg):
|
||||||
tz = arg
|
tz = arg
|
||||||
elif isinstance(arg, str):
|
elif isinstance(arg, str):
|
||||||
try:
|
try:
|
||||||
tz = pytz.timezone(arg)
|
tz = timezone_constructor(arg)
|
||||||
except pytz.UnknownTimeZoneError:
|
except UnknownTimezoneException:
|
||||||
return ''
|
return ''
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
|
@ -3,13 +3,21 @@ Timezone-related classes and functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
try:
|
||||||
|
import zoneinfo
|
||||||
|
except ImportError:
|
||||||
|
from backports import zoneinfo
|
||||||
|
|
||||||
from contextlib import ContextDecorator
|
from contextlib import ContextDecorator
|
||||||
from datetime import datetime, timedelta, timezone, tzinfo
|
from datetime import datetime, timedelta, timezone, tzinfo
|
||||||
|
|
||||||
import pytz
|
|
||||||
from asgiref.local import Local
|
from asgiref.local import Local
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'utc', 'get_fixed_timezone',
|
'utc', 'get_fixed_timezone',
|
||||||
|
@ -20,14 +28,11 @@ __all__ = [
|
||||||
'is_aware', 'is_naive', 'make_aware', 'make_naive',
|
'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)
|
utc = timezone.utc
|
||||||
# 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),)
|
|
||||||
|
|
||||||
|
|
||||||
def get_fixed_timezone(offset):
|
def get_fixed_timezone(offset):
|
||||||
|
@ -49,7 +54,10 @@ def get_default_timezone():
|
||||||
|
|
||||||
This is the time zone defined by settings.TIME_ZONE.
|
This is the time zone defined by settings.TIME_ZONE.
|
||||||
"""
|
"""
|
||||||
|
if settings.USE_DEPRECATED_PYTZ:
|
||||||
|
import pytz
|
||||||
return pytz.timezone(settings.TIME_ZONE)
|
return pytz.timezone(settings.TIME_ZONE)
|
||||||
|
return zoneinfo.ZoneInfo(settings.TIME_ZONE)
|
||||||
|
|
||||||
|
|
||||||
# This function exists for consistency with get_current_timezone_name
|
# This function exists for consistency with get_current_timezone_name
|
||||||
|
@ -94,7 +102,11 @@ def activate(timezone):
|
||||||
if isinstance(timezone, tzinfo):
|
if isinstance(timezone, tzinfo):
|
||||||
_active.value = timezone
|
_active.value = timezone
|
||||||
elif isinstance(timezone, str):
|
elif isinstance(timezone, str):
|
||||||
|
if settings.USE_DEPRECATED_PYTZ:
|
||||||
|
import pytz
|
||||||
_active.value = pytz.timezone(timezone)
|
_active.value = pytz.timezone(timezone)
|
||||||
|
else:
|
||||||
|
_active.value = zoneinfo.ZoneInfo(timezone)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid timezone: %r" % timezone)
|
raise ValueError("Invalid timezone: %r" % timezone)
|
||||||
|
|
||||||
|
@ -229,8 +241,17 @@ def is_naive(value):
|
||||||
return value.utcoffset() is None
|
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."""
|
"""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:
|
if timezone is None:
|
||||||
timezone = get_current_timezone()
|
timezone = get_current_timezone()
|
||||||
if _is_pytz_zone(timezone):
|
if _is_pytz_zone(timezone):
|
||||||
|
@ -255,13 +276,45 @@ def make_naive(value, timezone=None):
|
||||||
return value.astimezone(timezone).replace(tzinfo=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):
|
def _is_pytz_zone(tz):
|
||||||
"""Checks if a zone is a pytz zone."""
|
"""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)
|
return isinstance(tz, _PYTZ_BASE_CLASSES)
|
||||||
|
|
||||||
|
|
||||||
def _datetime_ambiguous_or_imaginary(dt, tz):
|
def _datetime_ambiguous_or_imaginary(dt, tz):
|
||||||
if _is_pytz_zone(tz):
|
if _is_pytz_zone(tz):
|
||||||
|
import pytz
|
||||||
try:
|
try:
|
||||||
tz.utcoffset(dt)
|
tz.utcoffset(dt)
|
||||||
except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError):
|
except (pytz.AmbiguousTimeError, pytz.NonExistentTimeError):
|
||||||
|
|
|
@ -36,6 +36,24 @@ details on these changes.
|
||||||
|
|
||||||
* The ``USE_L10N`` setting will be removed.
|
* 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:
|
.. _deprecation-removed-in-4.1:
|
||||||
|
|
||||||
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``.
|
of the date referenced by ``lookup_name`` as an ``IntegerField``.
|
||||||
Django usually uses the databases' extract function, so you may use any
|
Django usually uses the databases' extract function, so you may use any
|
||||||
``lookup_name`` that your database supports. A ``tzinfo`` subclass, usually
|
``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
|
Given the datetime ``2015-06-15 23:30:01.000321+00:00``, the built-in
|
||||||
``lookup_name``\s return:
|
``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
|
the Melbourne timezone (UTC +10:00), which changes the day, weekday, and hour
|
||||||
values that are returned::
|
values that are returned::
|
||||||
|
|
||||||
>>> import pytz
|
>>> import zoneinfo
|
||||||
>>> melb = pytz.timezone('Australia/Melbourne') # UTC+10:00
|
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne') # UTC+10:00
|
||||||
>>> with timezone.override(melb):
|
>>> with timezone.override(melb):
|
||||||
... Experiment.objects.annotate(
|
... Experiment.objects.annotate(
|
||||||
... day=ExtractDay('start_datetime'),
|
... day=ExtractDay('start_datetime'),
|
||||||
|
@ -466,8 +467,8 @@ values that are returned::
|
||||||
Explicitly passing the timezone to the ``Extract`` function behaves in the same
|
Explicitly passing the timezone to the ``Extract`` function behaves in the same
|
||||||
way, and takes priority over an active timezone::
|
way, and takes priority over an active timezone::
|
||||||
|
|
||||||
>>> import pytz
|
>>> import zoneinfo
|
||||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||||
>>> Experiment.objects.annotate(
|
>>> Experiment.objects.annotate(
|
||||||
... day=ExtractDay('start_datetime', tzinfo=melb),
|
... day=ExtractDay('start_datetime', tzinfo=melb),
|
||||||
... weekday=ExtractWeekDay('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
|
``TimeField()``, or ``DateField()``. It returns a datetime, date, or time
|
||||||
depending on ``output_field``, with fields up to ``kind`` set to their minimum
|
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``
|
value. If ``output_field`` is omitted, it will default to the ``output_field``
|
||||||
of ``expression``. A ``tzinfo`` subclass, usually provided by ``pytz``, can be
|
of ``expression``. A ``tzinfo`` subclass, usually provided by :mod:`zoneinfo`,
|
||||||
passed to truncate a value in a specific timezone.
|
can be passed to truncate a value in a specific timezone.
|
||||||
|
|
||||||
The ``is_dst`` parameter indicates whether or not ``pytz`` should interpret
|
.. deprecated:: 4.0
|
||||||
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 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
|
Given the datetime ``2015-06-15 14:30:50.000321+00:00``, the built-in ``kind``\s
|
||||||
return:
|
return:
|
||||||
|
@ -607,6 +612,10 @@ Usage example::
|
||||||
|
|
||||||
.. attribute:: kind = 'quarter'
|
.. 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
|
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
|
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
|
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
|
2014-01-01 1
|
||||||
2015-01-01 2
|
2015-01-01 2
|
||||||
|
|
||||||
>>> import pytz
|
>>> import zoneinfo
|
||||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||||
>>> experiments_per_month = Experiment.objects.annotate(
|
>>> experiments_per_month = Experiment.objects.annotate(
|
||||||
... month=TruncMonth('start_datetime', tzinfo=melb)).values('month').annotate(
|
... month=TruncMonth('start_datetime', tzinfo=melb)).values('month').annotate(
|
||||||
... experiments=Count('id'))
|
... experiments=Count('id'))
|
||||||
|
@ -691,6 +700,10 @@ truncate function. It's also registered as a transform on ``DateTimeField`` as
|
||||||
|
|
||||||
.. attribute:: kind = 'second'
|
.. 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
|
These are logically equivalent to ``Trunc('datetime_field', kind)``. They
|
||||||
truncate all parts of the date up to ``kind`` and allow grouping or filtering
|
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
|
datetimes with less precision. ``expression`` must have an ``output_field`` of
|
||||||
|
@ -704,10 +717,10 @@ Usage example::
|
||||||
... TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond,
|
... TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond,
|
||||||
... )
|
... )
|
||||||
>>> from django.utils import timezone
|
>>> from django.utils import timezone
|
||||||
>>> import pytz
|
>>> import zoneinfo
|
||||||
>>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc)
|
>>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc)
|
||||||
>>> Experiment.objects.create(start_datetime=start1, start_date=start1.date())
|
>>> Experiment.objects.create(start_datetime=start1, start_date=start1.date())
|
||||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||||
>>> Experiment.objects.annotate(
|
>>> Experiment.objects.annotate(
|
||||||
... date=TruncDate('start_datetime'),
|
... date=TruncDate('start_datetime'),
|
||||||
... day=TruncDay('start_datetime', tzinfo=melb),
|
... day=TruncDay('start_datetime', tzinfo=melb),
|
||||||
|
@ -716,10 +729,10 @@ Usage example::
|
||||||
... second=TruncSecond('start_datetime'),
|
... second=TruncSecond('start_datetime'),
|
||||||
... ).values('date', 'day', 'hour', 'minute', 'second').get()
|
... ).values('date', 'day', 'hour', 'minute', 'second').get()
|
||||||
{'date': datetime.date(2014, 6, 15),
|
{'date': datetime.date(2014, 6, 15),
|
||||||
'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=<DstTzInfo 'Australia/Melbourne' AEST+10:00:00 STD>),
|
'day': datetime.datetime(2014, 6, 16, 0, 0, tzinfo=zoneinfo.ZoneInfo('Australia/Melbourne')),
|
||||||
'hour': 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=zoneinfo.ZoneInfo('Australia/Melbourne')),
|
||||||
'minute': 'minute': datetime.datetime(2014, 6, 15, 14, 30, tzinfo=<UTC>),
|
'minute': 'minute': datetime.datetime(2014, 6, 15, 14, 30, tzinfo=zoneinfo.ZoneInfo('UTC')),
|
||||||
'second': datetime.datetime(2014, 6, 15, 14, 30, 50, tzinfo=<UTC>)
|
'second': datetime.datetime(2014, 6, 15, 14, 30, 50, tzinfo=zoneinfo.ZoneInfo('UTC'))
|
||||||
}
|
}
|
||||||
|
|
||||||
``TimeField`` truncation
|
``TimeField`` truncation
|
||||||
|
@ -740,6 +753,10 @@ Usage example::
|
||||||
|
|
||||||
.. attribute:: kind = 'second'
|
.. 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
|
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
|
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
|
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
|
14:00:00 2
|
||||||
17:00:00 1
|
17:00:00 1
|
||||||
|
|
||||||
>>> import pytz
|
>>> import zoneinfo
|
||||||
>>> melb = pytz.timezone('Australia/Melbourne')
|
>>> melb = zoneinfo.ZoneInfo('Australia/Melbourne')
|
||||||
>>> experiments_per_hour = Experiment.objects.annotate(
|
>>> experiments_per_hour = Experiment.objects.annotate(
|
||||||
... hour=TruncHour('start_datetime', tzinfo=melb),
|
... hour=TruncHour('start_datetime', tzinfo=melb),
|
||||||
... ).values('hour').annotate(experiments=Count('id'))
|
... ).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``),
|
ambiguous datetimes in daylight saving time. By default (when ``is_dst=None``),
|
||||||
``pytz`` raises an exception for such datetimes.
|
``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:
|
.. _database-time-zone-definitions:
|
||||||
|
|
||||||
.. note::
|
.. 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
|
As a consequence, your database must be able to interpret the value of
|
||||||
``tzinfo.tzname(None)``. This translates into the following requirements:
|
``tzinfo.tzname(None)``. This translates into the following requirements:
|
||||||
|
|
||||||
- SQLite: no requirements. Conversions are performed in Python with pytz_
|
- SQLite: no requirements. Conversions are performed in Python.
|
||||||
(installed when you install Django).
|
|
||||||
- PostgreSQL: no requirements (see `Time Zones`_).
|
- PostgreSQL: no requirements (see `Time Zones`_).
|
||||||
- Oracle: no requirements (see `Choosing a Time Zone File`_).
|
- Oracle: no requirements (see `Choosing a Time Zone File`_).
|
||||||
- MySQL: load the time zone tables with `mysql_tzinfo_to_sql`_.
|
- 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
|
.. _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/
|
.. _Choosing a Time Zone File: https://docs.oracle.com/en/database/oracle/
|
||||||
oracle-database/18/nlspg/datetime-data-types-and-time-zone-support.html
|
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
|
otherwise, you should leave this option unset. It's best to store datetimes
|
||||||
in UTC because it avoids ambiguous or nonexistent datetimes during daylight
|
in UTC because it avoids ambiguous or nonexistent datetimes during daylight
|
||||||
saving time changes. Also, receiving datetimes in UTC keeps datetime
|
saving time changes. Also, receiving datetimes in UTC keeps datetime
|
||||||
arithmetic simple — there's no need for the ``normalize()`` method provided
|
arithmetic simple — there's no need to consider potential offset changes
|
||||||
by pytz.
|
over a DST transition.
|
||||||
|
|
||||||
* If you're connecting to a third-party database that stores datetimes in a
|
* 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
|
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.
|
as ``date_trunc``, because their results depend on the time zone.
|
||||||
|
|
||||||
However, this has a downside: receiving all datetimes in local time makes
|
However, this has a downside: receiving all datetimes in local time makes
|
||||||
datetime arithmetic more tricky — you must call the ``normalize()`` method
|
datetime arithmetic more tricky — you must account for possible offset
|
||||||
provided by pytz after each operation.
|
changes over DST transitions.
|
||||||
|
|
||||||
Consider converting to local time explicitly with ``AT TIME ZONE`` in raw SQL
|
Consider converting to local time explicitly with ``AT TIME ZONE`` in raw SQL
|
||||||
queries instead of setting the ``TIME_ZONE`` option.
|
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
|
.. _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
|
.. setting:: USE_I18N
|
||||||
|
|
||||||
``USE_I18N``
|
``USE_I18N``
|
||||||
|
|
|
@ -941,25 +941,31 @@ appropriate entities.
|
||||||
:class:`~datetime.datetime`. If ``timezone`` is set to ``None``, it
|
:class:`~datetime.datetime`. If ``timezone`` is set to ``None``, it
|
||||||
defaults to the :ref:`current time zone <default-current-time-zone>`.
|
defaults to the :ref:`current time zone <default-current-time-zone>`.
|
||||||
|
|
||||||
When using ``pytz``, the ``pytz.AmbiguousTimeError`` exception is raised if
|
.. deprecated:: 4.0
|
||||||
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.
|
|
||||||
|
|
||||||
When using ``pytz``, the ``pytz.NonExistentTimeError`` exception is raised
|
When using ``pytz``, the ``pytz.AmbiguousTimeError`` exception is
|
||||||
if you try to make ``value`` aware during a DST transition such that the
|
raised if you try to make ``value`` aware during a DST transition where
|
||||||
time never occurred. For example, if the 2:00 hour is skipped during a DST
|
the same time occurs twice (when reverting from DST). Setting
|
||||||
transition, trying to make 2:30 aware in that time zone will raise an
|
``is_dst`` to ``True`` or ``False`` will avoid the exception by
|
||||||
exception. To avoid that you can use ``is_dst`` to specify how
|
choosing if the time is pre-transition or post-transition respectively.
|
||||||
``make_aware()`` should interpret such a nonexistent time. If
|
|
||||||
``is_dst=True`` then the above time would be interpreted as 2:30 DST time
|
When using ``pytz``, the ``pytz.NonExistentTimeError`` exception is
|
||||||
(equivalent to 1:30 local time). Conversely, if ``is_dst=False`` the time
|
raised if you try to make ``value`` aware during a DST transition such
|
||||||
would be interpreted as 2:30 standard time (equivalent to 3:30 local time).
|
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
|
The ``is_dst`` parameter has no effect when using non-``pytz`` timezone
|
||||||
implementations.
|
implementations.
|
||||||
|
|
||||||
|
The ``is_dst`` parameter is deprecated and will be removed in Django
|
||||||
|
5.0.
|
||||||
|
|
||||||
.. function:: make_naive(value, timezone=None)
|
.. function:: make_naive(value, timezone=None)
|
||||||
|
|
||||||
Returns a naive :class:`~datetime.datetime` that represents in
|
Returns a naive :class:`~datetime.datetime` that represents in
|
||||||
|
|
|
@ -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
|
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
|
Functional unique constraints
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
@ -595,11 +635,37 @@ Miscellaneous
|
||||||
* The default value of the ``USE_L10N`` setting is changed to ``True``. See the
|
* The default value of the ``USE_L10N`` setting is changed to ``True``. See the
|
||||||
:ref:`Localization section <use_l10n_deprecation>` above for more details.
|
: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:
|
.. _deprecated-features-4.0:
|
||||||
|
|
||||||
Features deprecated in 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
|
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
|
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,
|
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.
|
you're likely to encounter errors twice a year, when the transitions happen.
|
||||||
(The pytz_ documentation discusses `these issues`_ in greater detail.) This
|
This probably doesn't matter for your blog, but it's a problem if you over bill
|
||||||
probably doesn't matter for your blog, but it's a problem if you over-bill or
|
or under bill your customers by one hour, twice a year, every year. The
|
||||||
under-bill your customers by one hour, twice a year, every year. The solution
|
solution to this problem is to use UTC in the code and use local time only when
|
||||||
to this problem is to use UTC in the code and use local time only when
|
|
||||||
interacting with end users.
|
interacting with end users.
|
||||||
|
|
||||||
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
|
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.
|
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
|
Time zone support uses :mod:`zoneinfo`, which is part of the Python standard
|
||||||
Django; Django also supports the use of other time zone implementations like
|
library from Python 3.9. The ``backports.zoneinfo`` package is automatically
|
||||||
:mod:`zoneinfo` by passing :class:`~datetime.tzinfo` objects directly to
|
installed alongside Django if you are using Python 3.8.
|
||||||
functions in :mod:`django.utils.timezone`.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.2
|
.. versionchanged:: 3.2
|
||||||
|
|
||||||
Support for non-``pytz`` timezone implementations was added.
|
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::
|
.. note::
|
||||||
|
|
||||||
The default :file:`settings.py` file created by :djadmin:`django-admin
|
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,
|
Dealing with aware datetime objects isn't always intuitive. For instance,
|
||||||
the ``tzinfo`` argument of the standard datetime constructor doesn't work
|
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
|
reliably for time zones with DST. Using UTC is generally safe; if you're
|
||||||
using other time zones, you should review the `pytz`_ documentation
|
using other time zones, you should review the :mod:`zoneinfo`
|
||||||
carefully.
|
documentation carefully.
|
||||||
|
|
||||||
.. note::
|
.. 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.
|
:ref:`default time zone <default-current-time-zone>` and raises a warning.
|
||||||
|
|
||||||
Unfortunately, during DST transitions, some datetimes don't exist or are
|
Unfortunately, during DST transitions, some datetimes don't exist or are
|
||||||
ambiguous. In such situations, pytz_ raises an exception. That's why you should
|
ambiguous. That's why you should always create aware datetime objects when time
|
||||||
always create aware datetime objects when time zone support is enabled.
|
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 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
|
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
|
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
|
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
|
the time zone of their primary audience or UTC.
|
||||||
list of time zones per country, that you can use to pre-select the most likely
|
:func:`zoneinfo.available_timezones` provides a set of available timezones that
|
||||||
choices.
|
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
|
Here's an example that stores the current timezone in the session. (It skips
|
||||||
error handling entirely for the sake of simplicity.)
|
error handling entirely for the sake of simplicity.)
|
||||||
|
|
||||||
Add the following middleware to :setting:`MIDDLEWARE`::
|
Add the following middleware to :setting:`MIDDLEWARE`::
|
||||||
|
|
||||||
import pytz
|
import zoneinfo
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
@ -183,7 +189,7 @@ Add the following middleware to :setting:`MIDDLEWARE`::
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
tzname = request.session.get('django_timezone')
|
tzname = request.session.get('django_timezone')
|
||||||
if tzname:
|
if tzname:
|
||||||
timezone.activate(pytz.timezone(tzname))
|
timezone.activate(zoneinfo.ZoneInfo(tzname))
|
||||||
else:
|
else:
|
||||||
timezone.deactivate()
|
timezone.deactivate()
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
@ -192,12 +198,19 @@ Create a view that can set the current timezone::
|
||||||
|
|
||||||
from django.shortcuts import redirect, render
|
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):
|
def set_timezone(request):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
request.session['django_timezone'] = request.POST['timezone']
|
request.session['django_timezone'] = request.POST['timezone']
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
else:
|
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:
|
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 %}
|
{% csrf_token %}
|
||||||
<label for="timezone">Time zone:</label>
|
<label for="timezone">Time zone:</label>
|
||||||
<select name="timezone">
|
<select name="timezone">
|
||||||
{% for tz in timezones %}
|
{% for city, tz in timezones %}
|
||||||
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ tz }}</option>
|
<option value="{{ tz }}"{% if tz == TIME_ZONE %} selected{% endif %}>{{ city }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="submit" value="Set">
|
<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
|
forms in the :ref:`current time zone <default-current-time-zone>` and returns
|
||||||
aware datetime objects in ``cleaned_data``.
|
aware datetime objects in ``cleaned_data``.
|
||||||
|
|
||||||
If the current time zone raises an exception for datetimes that don't exist or
|
Converted datetimes that don't exist or are ambiguous because they fall in a
|
||||||
are ambiguous because they fall in a DST transition (the timezones provided by
|
DST transition will be reported as invalid values.
|
||||||
pytz_ do this), such datetimes will be reported as invalid values.
|
|
||||||
|
|
||||||
.. _time-zones-in-templates:
|
.. _time-zones-in-templates:
|
||||||
|
|
||||||
|
@ -583,20 +595,20 @@ Troubleshooting
|
||||||
None of this is true in a time zone aware environment::
|
None of this is true in a time zone aware environment::
|
||||||
|
|
||||||
>>> import datetime
|
>>> import datetime
|
||||||
>>> import pytz
|
>>> import zoneinfo
|
||||||
>>> paris_tz = pytz.timezone("Europe/Paris")
|
>>> paris_tz = zoneinfo.ZoneInfo("Europe/Paris")
|
||||||
>>> new_york_tz = pytz.timezone("America/New_York")
|
>>> new_york_tz = zoneinfo.ZoneInfo("America/New_York")
|
||||||
>>> paris = paris_tz.localize(datetime.datetime(2012, 3, 3, 1, 30))
|
>>> paris = datetime.datetime(2012, 3, 3, 1, 30, tzinfo=paris_tz)
|
||||||
# This is the correct way to convert between time zones with pytz.
|
# This is the correct way to convert between time zones.
|
||||||
>>> new_york = new_york_tz.normalize(paris.astimezone(new_york_tz))
|
>>> new_york = paris.astimezone(new_york_tz)
|
||||||
>>> paris == new_york, paris.date() == new_york.date()
|
>>> paris == new_york, paris.date() == new_york.date()
|
||||||
(True, False)
|
(True, False)
|
||||||
>>> paris - new_york, paris.date() - new_york.date()
|
>>> paris - new_york, paris.date() - new_york.date()
|
||||||
(datetime.timedelta(0), datetime.timedelta(1))
|
(datetime.timedelta(0), datetime.timedelta(1))
|
||||||
>>> paris
|
>>> 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
|
>>> 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
|
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
|
the time zone in which it is represented. But the real problem is more
|
||||||
|
@ -621,14 +633,13 @@ Troubleshooting
|
||||||
will be the current timezone::
|
will be the current timezone::
|
||||||
|
|
||||||
>>> from django.utils import 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
|
# 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.
|
# you would obtain the current time zone in the general case.
|
||||||
>>> current_tz = timezone.get_current_timezone()
|
>>> current_tz = timezone.get_current_timezone()
|
||||||
# Again, this is the correct way to convert between time zones with pytz.
|
>>> local = paris.astimezone(current_tz)
|
||||||
>>> local = current_tz.normalize(paris.astimezone(current_tz))
|
|
||||||
>>> local
|
>>> 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()
|
>>> local.date()
|
||||||
datetime.date(2012, 3, 3)
|
datetime.date(2012, 3, 3)
|
||||||
|
|
||||||
|
@ -645,18 +656,14 @@ Usage
|
||||||
``"Europe/Helsinki"`` **time zone. How do I turn that into an aware
|
``"Europe/Helsinki"`` **time zone. How do I turn that into an aware
|
||||||
datetime?**
|
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
|
>>> from django.utils.dateparse import parse_datetime
|
||||||
>>> naive = parse_datetime("2012-02-21 10:28:45")
|
>>> naive = parse_datetime("2012-02-21 10:28:45")
|
||||||
>>> import pytz
|
>>> naive.replace(tzinfo=zoneinfo.ZoneInfo("Europe/Helsinki"))
|
||||||
>>> pytz.timezone("Europe/Helsinki").localize(naive, is_dst=None)
|
datetime.datetime(2012, 2, 21, 10, 28, 45, tzinfo=zoneinfo.ZoneInfo(key='Europe/Helsinki'))
|
||||||
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.
|
|
||||||
|
|
||||||
#. **How can I obtain the local time in the current time zone?**
|
#. **How can I obtain the local time in the current time zone?**
|
||||||
|
|
||||||
|
@ -677,19 +684,14 @@ Usage
|
||||||
|
|
||||||
>>> from django.utils import timezone
|
>>> from django.utils import timezone
|
||||||
>>> timezone.localtime(timezone.now())
|
>>> 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"``.
|
In this example, the current time zone is ``"Europe/Paris"``.
|
||||||
|
|
||||||
#. **How can I see all available time zones?**
|
#. **How can I see all available time zones?**
|
||||||
|
|
||||||
pytz_ provides helpers_, including a list of current time zones and a list
|
:func:`zoneinfo.available_timezones` provides the set of all valid keys for
|
||||||
of all available time zones -- some of which are only of historical
|
IANA time zones available to your system. See the docs for usage
|
||||||
interest. :mod:`zoneinfo` also provides similar functionality via
|
considerations.
|
||||||
:func:`zoneinfo.available_timezones`.
|
|
||||||
|
|
||||||
.. _pytz: http://pytz.sourceforge.net/
|
.. _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
|
zip_safe = false
|
||||||
install_requires =
|
install_requires =
|
||||||
asgiref >= 3.3.2
|
asgiref >= 3.3.2
|
||||||
pytz
|
backports.zoneinfo; python_version<"3.9"
|
||||||
sqlparse >= 0.2.2
|
sqlparse >= 0.2.2
|
||||||
|
tzdata; sys_platform == 'win32'
|
||||||
|
|
||||||
[options.entry_points]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
|
|
|
@ -5,15 +5,15 @@ import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import parse_qsl, urljoin, urlparse
|
from urllib.parse import parse_qsl, urljoin, urlparse
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
|
||||||
from backports import zoneinfo
|
from backports import zoneinfo
|
||||||
except ImportError:
|
|
||||||
zoneinfo = None
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
pytz = None
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin import AdminSite, ModelAdmin
|
from django.contrib.admin import AdminSite, ModelAdmin
|
||||||
|
@ -73,11 +73,11 @@ MULTIPART_ENCTYPE = 'enctype="multipart/form-data"'
|
||||||
|
|
||||||
def make_aware_datetimes(dt, iana_key):
|
def make_aware_datetimes(dt, iana_key):
|
||||||
"""Makes one aware datetime for each supported time zone provider."""
|
"""Makes one aware datetime for each supported time zone provider."""
|
||||||
yield pytz.timezone(iana_key).localize(dt, is_dst=None)
|
|
||||||
|
|
||||||
if zoneinfo is not None:
|
|
||||||
yield dt.replace(tzinfo=zoneinfo.ZoneInfo(iana_key))
|
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:
|
class AdminFieldExtractionMixin:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,7 +4,10 @@ import re
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
import pytz
|
try:
|
||||||
|
import zoneinfo
|
||||||
|
except ImportError:
|
||||||
|
from backports import zoneinfo
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -967,8 +970,8 @@ class DateTimePickerShortcutsSeleniumTests(AdminWidgetSeleniumTestCase):
|
||||||
error_margin = timedelta(seconds=10)
|
error_margin = timedelta(seconds=10)
|
||||||
|
|
||||||
# If we are neighbouring a DST, we add an hour of error margin.
|
# If we are neighbouring a DST, we add an hour of error margin.
|
||||||
tz = pytz.timezone('America/Chicago')
|
tz = zoneinfo.ZoneInfo('America/Chicago')
|
||||||
utc_now = datetime.now(pytz.utc)
|
utc_now = datetime.now(zoneinfo.ZoneInfo('UTC'))
|
||||||
tz_yesterday = (utc_now - timedelta(days=1)).astimezone(tz).tzname()
|
tz_yesterday = (utc_now - timedelta(days=1)).astimezone(tz).tzname()
|
||||||
tz_tomorrow = (utc_now + timedelta(days=1)).astimezone(tz).tzname()
|
tz_tomorrow = (utc_now + timedelta(days=1)).astimezone(tz).tzname()
|
||||||
if tz_yesterday != tz_tomorrow:
|
if tz_yesterday != tz_tomorrow:
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import datetime
|
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 import timezone
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
|
||||||
from .models import Article, Category, Comment
|
from .models import Article, Category, Comment
|
||||||
|
|
||||||
|
@ -91,7 +96,9 @@ class DateTimesTests(TestCase):
|
||||||
qs = Article.objects.datetimes('pub_date', 'second')
|
qs = Article.objects.datetimes('pub_date', 'second')
|
||||||
self.assertEqual(qs[0], now)
|
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):
|
def test_datetimes_ambiguous_and_invalid_times(self):
|
||||||
sao = pytz.timezone('America/Sao_Paulo')
|
sao = pytz.timezone('America/Sao_Paulo')
|
||||||
utc = pytz.UTC
|
utc = pytz.UTC
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
|
import unittest
|
||||||
from datetime import datetime, timedelta, timezone as datetime_timezone
|
from datetime import datetime, timedelta, timezone as datetime_timezone
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
|
||||||
from backports import zoneinfo
|
from backports import zoneinfo
|
||||||
except ImportError:
|
|
||||||
zoneinfo = None
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
pytz = None
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
|
@ -23,15 +24,24 @@ from django.db.models.functions import (
|
||||||
TruncYear,
|
TruncYear,
|
||||||
)
|
)
|
||||||
from django.test import (
|
from django.test import (
|
||||||
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
TestCase, ignore_warnings, override_settings, skipIfDBFeature,
|
||||||
|
skipUnlessDBFeature,
|
||||||
)
|
)
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
|
||||||
from ..models import Author, DTModel, Fan
|
from ..models import Author, DTModel, Fan
|
||||||
|
|
||||||
ZONE_CONSTRUCTORS = (pytz.timezone,)
|
HAS_PYTZ = pytz is not None
|
||||||
if zoneinfo is not None:
|
if not HAS_PYTZ:
|
||||||
ZONE_CONSTRUCTORS += (zoneinfo.ZoneInfo,)
|
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):
|
def truncate_to(value, kind, tzinfo=None):
|
||||||
|
@ -98,8 +108,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 10)
|
start_datetime = datetime(2015, 6, 15, 14, 10)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10)
|
end_datetime = datetime(2016, 6, 15, 14, 10)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
|
||||||
|
@ -135,8 +145,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 10)
|
start_datetime = datetime(2015, 6, 15, 14, 10)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10)
|
end_datetime = datetime(2016, 6, 15, 14, 10)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
|
||||||
|
@ -158,8 +168,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 10)
|
start_datetime = datetime(2015, 6, 15, 14, 10)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10)
|
end_datetime = datetime(2016, 6, 15, 14, 10)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_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)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_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)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -319,8 +329,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -339,8 +349,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -359,14 +369,14 @@ class DateFunctionTests(TestCase):
|
||||||
def test_extract_iso_year_func_boundaries(self):
|
def test_extract_iso_year_func_boundaries(self):
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
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_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
|
||||||
week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
|
week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
|
||||||
week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
|
week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_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, is_dst=False)
|
week_52_day_2014 = timezone.make_aware(week_52_day_2014)
|
||||||
week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
|
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]
|
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_2014 = self.create_model(week_52_day_2014, end_datetime)
|
||||||
obj_1_iso_2015 = self.create_model(week_1_day_2014_2015, 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)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -417,8 +427,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -437,8 +447,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -458,8 +468,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 8, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 8, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -477,13 +487,13 @@ class DateFunctionTests(TestCase):
|
||||||
def test_extract_quarter_func_boundaries(self):
|
def test_extract_quarter_func_boundaries(self):
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
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)
|
last_quarter_2014 = datetime(2014, 12, 31, 13, 0)
|
||||||
first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
|
first_quarter_2015 = datetime(2015, 1, 1, 13, 0)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
last_quarter_2014 = timezone.make_aware(last_quarter_2014, is_dst=False)
|
last_quarter_2014 = timezone.make_aware(last_quarter_2014)
|
||||||
first_quarter_2015 = timezone.make_aware(first_quarter_2015, is_dst=False)
|
first_quarter_2015 = timezone.make_aware(first_quarter_2015)
|
||||||
dates = [last_quarter_2014, first_quarter_2015]
|
dates = [last_quarter_2014, first_quarter_2015]
|
||||||
self.create_model(last_quarter_2014, end_datetime)
|
self.create_model(last_quarter_2014, end_datetime)
|
||||||
self.create_model(first_quarter_2015, end_datetime)
|
self.create_model(first_quarter_2015, end_datetime)
|
||||||
|
@ -498,15 +508,15 @@ class DateFunctionTests(TestCase):
|
||||||
def test_extract_week_func_boundaries(self):
|
def test_extract_week_func_boundaries(self):
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
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_52_day_2014 = datetime(2014, 12, 27, 13, 0) # Sunday
|
||||||
week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
|
week_1_day_2014_2015 = datetime(2014, 12, 31, 13, 0) # Wednesday
|
||||||
week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
|
week_53_day_2015 = datetime(2015, 12, 31, 13, 0) # Thursday
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
week_1_day_2014_2015 = timezone.make_aware(week_1_day_2014_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, is_dst=False)
|
week_52_day_2014 = timezone.make_aware(week_52_day_2014)
|
||||||
week_53_day_2015 = timezone.make_aware(week_53_day_2015, is_dst=False)
|
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]
|
days = [week_52_day_2014, week_1_day_2014_2015, week_53_day_2015]
|
||||||
self.create_model(week_53_day_2015, end_datetime)
|
self.create_model(week_53_day_2015, end_datetime)
|
||||||
|
@ -525,8 +535,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -551,8 +561,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -586,8 +596,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -606,8 +616,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -626,8 +636,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -646,8 +656,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_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)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'year')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'year')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -786,10 +796,10 @@ class DateFunctionTests(TestCase):
|
||||||
last_quarter_2015 = truncate_to(datetime(2015, 12, 31, 14, 10, 50, 123), 'quarter')
|
last_quarter_2015 = truncate_to(datetime(2015, 12, 31, 14, 10, 50, 123), 'quarter')
|
||||||
first_quarter_2016 = truncate_to(datetime(2016, 1, 1, 14, 10, 50, 123), 'quarter')
|
first_quarter_2016 = truncate_to(datetime(2016, 1, 1, 14, 10, 50, 123), 'quarter')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
last_quarter_2015 = timezone.make_aware(last_quarter_2015, is_dst=False)
|
last_quarter_2015 = timezone.make_aware(last_quarter_2015)
|
||||||
first_quarter_2016 = timezone.make_aware(first_quarter_2016, is_dst=False)
|
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=start_datetime, end_datetime=end_datetime)
|
||||||
self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
|
self.create_model(start_datetime=end_datetime, end_datetime=start_datetime)
|
||||||
self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
|
self.create_model(start_datetime=last_quarter_2015, end_datetime=end_datetime)
|
||||||
|
@ -825,8 +835,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'month')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'month')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -857,8 +867,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'week')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'week')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -881,8 +891,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -909,8 +919,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -937,8 +947,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 26) # 0 microseconds.
|
start_datetime = datetime(2015, 6, 15, 14, 30, 26) # 0 microseconds.
|
||||||
end_datetime = datetime(2015, 6, 15, 14, 30, 26, 321)
|
end_datetime = datetime(2015, 6, 15, 14, 30, 26, 321)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.assertIs(
|
self.assertIs(
|
||||||
DTModel.objects.filter(
|
DTModel.objects.filter(
|
||||||
|
@ -962,8 +972,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'day')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'day')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -986,8 +996,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'hour')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'hour')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -1018,8 +1028,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'minute')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'minute')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -1050,8 +1060,8 @@ class DateFunctionTests(TestCase):
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'second')
|
end_datetime = truncate_to(datetime(2016, 6, 15, 14, 10, 50, 123), 'second')
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
|
@ -1085,9 +1095,9 @@ class DateFunctionTests(TestCase):
|
||||||
fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
|
fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
|
||||||
fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
|
fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
fan_since_1 = timezone.make_aware(fan_since_1, is_dst=False)
|
fan_since_1 = timezone.make_aware(fan_since_1)
|
||||||
fan_since_2 = timezone.make_aware(fan_since_2, is_dst=False)
|
fan_since_2 = timezone.make_aware(fan_since_2)
|
||||||
fan_since_3 = timezone.make_aware(fan_since_3, is_dst=False)
|
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='Tom', fan_since=fan_since_1)
|
||||||
Fan.objects.create(author=author_1, name='Emma', fan_since=fan_since_2)
|
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)
|
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_2 = datetime(2001, 3, 5)
|
||||||
datetime_3 = datetime(2002, 1, 3)
|
datetime_3 = datetime(2002, 1, 3)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
datetime_1 = timezone.make_aware(datetime_1, is_dst=False)
|
datetime_1 = timezone.make_aware(datetime_1)
|
||||||
datetime_2 = timezone.make_aware(datetime_2, is_dst=False)
|
datetime_2 = timezone.make_aware(datetime_2)
|
||||||
datetime_3 = timezone.make_aware(datetime_3, is_dst=False)
|
datetime_3 = timezone.make_aware(datetime_3)
|
||||||
obj_1 = self.create_model(datetime_1, datetime_3)
|
obj_1 = self.create_model(datetime_1, datetime_3)
|
||||||
obj_2 = self.create_model(datetime_2, datetime_1)
|
obj_2 = self.create_model(datetime_2, datetime_1)
|
||||||
obj_3 = self.create_model(datetime_3, datetime_2)
|
obj_3 = self.create_model(datetime_3, datetime_2)
|
||||||
|
@ -1144,8 +1154,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
def test_extract_func_with_timezone(self):
|
def test_extract_func_with_timezone(self):
|
||||||
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
|
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
|
||||||
end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
|
end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
|
delta_tzinfo_pos = datetime_timezone(timedelta(hours=5))
|
||||||
delta_tzinfo_neg = datetime_timezone(timedelta(hours=-5, minutes=17))
|
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):
|
def test_extract_func_explicit_timezone_priority(self):
|
||||||
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
|
start_datetime = datetime(2015, 6, 15, 23, 30, 1, 321)
|
||||||
end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
|
end_datetime = datetime(2015, 6, 16, 13, 11, 27, 123)
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
|
|
||||||
for melb in self.get_timezones('Australia/Melbourne'):
|
for melb in self.get_timezones('Australia/Melbourne'):
|
||||||
|
@ -1233,8 +1243,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
def test_trunc_timezone_applied_before_truncation(self):
|
def test_trunc_timezone_applied_before_truncation(self):
|
||||||
start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
|
start_datetime = datetime(2016, 1, 1, 1, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
|
|
||||||
for melb, pacific in zip(
|
for melb, pacific in zip(
|
||||||
|
@ -1263,6 +1273,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
self.assertEqual(model.melb_time, melb_start_datetime.time())
|
self.assertEqual(model.melb_time, melb_start_datetime.time())
|
||||||
self.assertEqual(model.pacific_time, pacific_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):
|
def test_trunc_ambiguous_and_invalid_times(self):
|
||||||
sao = pytz.timezone('America/Sao_Paulo')
|
sao = pytz.timezone('America/Sao_Paulo')
|
||||||
utc = timezone.utc
|
utc = timezone.utc
|
||||||
|
@ -1294,8 +1306,8 @@ class DateFunctionWithTimeZoneTests(DateFunctionTests):
|
||||||
"""
|
"""
|
||||||
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
start_datetime = datetime(2015, 6, 15, 14, 30, 50, 321)
|
||||||
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
end_datetime = datetime(2016, 6, 15, 14, 10, 50, 123)
|
||||||
start_datetime = timezone.make_aware(start_datetime, is_dst=False)
|
start_datetime = timezone.make_aware(start_datetime)
|
||||||
end_datetime = timezone.make_aware(end_datetime, is_dst=False)
|
end_datetime = timezone.make_aware(end_datetime)
|
||||||
self.create_model(start_datetime, end_datetime)
|
self.create_model(start_datetime, end_datetime)
|
||||||
self.create_model(end_datetime, start_datetime)
|
self.create_model(end_datetime, start_datetime)
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,16 @@ import sys
|
||||||
import uuid
|
import uuid
|
||||||
from unittest import mock
|
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.more_operations
|
||||||
import custom_migration_operations.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):
|
def test_serialize_fields(self):
|
||||||
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
||||||
self.assertSerializedResultEqual(
|
self.assertSerializedResultEqual(
|
||||||
|
|
|
@ -4,7 +4,10 @@ import unittest
|
||||||
from types import ModuleType, SimpleNamespace
|
from types import ModuleType, SimpleNamespace
|
||||||
from unittest import mock
|
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.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.test import (
|
from django.test import (
|
||||||
|
@ -348,6 +351,21 @@ class SettingsTests(SimpleTestCase):
|
||||||
finally:
|
finally:
|
||||||
del sys.modules['fake_settings_module']
|
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):
|
class TestComplexSettingOverride(SimpleTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -5,15 +5,15 @@ from contextlib import contextmanager
|
||||||
from unittest import SkipTest, skipIf
|
from unittest import SkipTest, skipIf
|
||||||
from xml.dom.minidom import parseString
|
from xml.dom.minidom import parseString
|
||||||
|
|
||||||
import pytz
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
|
||||||
from backports import zoneinfo
|
from backports import zoneinfo
|
||||||
except ImportError:
|
|
||||||
zoneinfo = None
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
pytz = None
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
|
@ -61,9 +61,9 @@ UTC = timezone.utc
|
||||||
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
|
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
|
||||||
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
|
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
|
||||||
|
|
||||||
ZONE_CONSTRUCTORS = (pytz.timezone,)
|
ZONE_CONSTRUCTORS = (zoneinfo.ZoneInfo,)
|
||||||
if zoneinfo is not None:
|
if pytz is not None:
|
||||||
ZONE_CONSTRUCTORS += (zoneinfo.ZoneInfo,)
|
ZONE_CONSTRUCTORS += (pytz.timezone,)
|
||||||
|
|
||||||
|
|
||||||
def get_timezones(key):
|
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__in=(prev, dt, next)).count(), 1)
|
||||||
self.assertEqual(Event.objects.filter(dt__range=(prev, 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):
|
def test_query_convert_timezones(self):
|
||||||
# Connection timezone is equal to the current timezone, datetime
|
# Connection timezone is equal to the current timezone, datetime
|
||||||
# shouldn't be converted.
|
# shouldn't be converted.
|
||||||
|
@ -921,7 +938,7 @@ class TemplateTests(SimpleTestCase):
|
||||||
tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
|
tpl = Template("{% load tz %}{{ dt|timezone:tz }}")
|
||||||
ctx = Context({
|
ctx = Context({
|
||||||
'dt': datetime.datetime(2011, 9, 1, 13, 20, 30),
|
'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")
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||||
|
|
||||||
|
@ -994,10 +1011,14 @@ class TemplateTests(SimpleTestCase):
|
||||||
})
|
})
|
||||||
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
self.assertEqual(tpl.render(ctx), "2011-09-01T12:20:30+02:00")
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango50Warning)
|
||||||
def test_timezone_templatetag_invalid_argument(self):
|
def test_timezone_templatetag_invalid_argument(self):
|
||||||
with self.assertRaises(TemplateSyntaxError):
|
with self.assertRaises(TemplateSyntaxError):
|
||||||
Template("{% load tz %}{% timezone %}{% endtimezone %}").render()
|
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'}))
|
Template("{% load tz %}{% timezone tz %}{% endtimezone %}").render(Context({'tz': 'foobar'}))
|
||||||
|
|
||||||
@skipIf(sys.platform == 'win32', "Windows uses non-standard time zone names")
|
@skipIf(sys.platform == 'win32', "Windows uses non-standard time zone names")
|
||||||
|
|
|
@ -14,7 +14,10 @@ from pathlib import Path
|
||||||
from subprocess import CompletedProcess
|
from subprocess import CompletedProcess
|
||||||
from unittest import mock, skip, skipIf
|
from unittest import mock, skip, skipIf
|
||||||
|
|
||||||
import pytz
|
try:
|
||||||
|
import zoneinfo
|
||||||
|
except ImportError:
|
||||||
|
from backports import zoneinfo
|
||||||
|
|
||||||
import django.__main__
|
import django.__main__
|
||||||
from django.apps.registry import Apps
|
from django.apps.registry import Apps
|
||||||
|
@ -247,7 +250,7 @@ class TestChildArguments(SimpleTestCase):
|
||||||
class TestUtilities(SimpleTestCase):
|
class TestUtilities(SimpleTestCase):
|
||||||
def test_is_django_module(self):
|
def test_is_django_module(self):
|
||||||
for module, expected in (
|
for module, expected in (
|
||||||
(pytz, False),
|
(zoneinfo, False),
|
||||||
(sys, False),
|
(sys, False),
|
||||||
(autoreload, True)
|
(autoreload, True)
|
||||||
):
|
):
|
||||||
|
@ -256,7 +259,7 @@ class TestUtilities(SimpleTestCase):
|
||||||
|
|
||||||
def test_is_django_path(self):
|
def test_is_django_path(self):
|
||||||
for module, expected in (
|
for module, expected in (
|
||||||
(pytz.__file__, False),
|
(zoneinfo.__file__, False),
|
||||||
(contextlib.__file__, False),
|
(contextlib.__file__, False),
|
||||||
(autoreload.__file__, True)
|
(autoreload.__file__, True)
|
||||||
):
|
):
|
||||||
|
|
|
@ -2,41 +2,58 @@ import datetime
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytz
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
pytz = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import zoneinfo
|
import zoneinfo
|
||||||
except ImportError:
|
except ImportError:
|
||||||
try:
|
|
||||||
from backports import zoneinfo
|
from backports import zoneinfo
|
||||||
except ImportError:
|
|
||||||
zoneinfo = None
|
|
||||||
|
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, ignore_warnings, override_settings
|
||||||
from django.utils import timezone
|
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
|
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
|
||||||
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
|
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
|
||||||
UTC = datetime.timezone.utc
|
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:
|
needs_pytz = unittest.skip('Test requires pytz')
|
||||||
PARIS_ZI = None
|
|
||||||
PARIS_IMPLS = (CET,)
|
|
||||||
|
|
||||||
needs_zoneinfo = unittest.skip("Test requires zoneinfo")
|
|
||||||
else:
|
else:
|
||||||
PARIS_ZI = zoneinfo.ZoneInfo('Europe/Paris')
|
CET = pytz.timezone('Europe/Paris')
|
||||||
PARIS_IMPLS = (CET, PARIS_ZI)
|
PARIS_IMPLS = (PARIS_ZI, CET)
|
||||||
|
|
||||||
def needs_zoneinfo(f):
|
def needs_pytz(f):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
|
|
||||||
class TimezoneTests(SimpleTestCase):
|
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):
|
def test_now(self):
|
||||||
with override_settings(USE_TZ=True):
|
with override_settings(USE_TZ=True):
|
||||||
self.assertTrue(timezone.is_aware(timezone.now()))
|
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),
|
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), tz),
|
||||||
datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=CEST))
|
datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=CEST))
|
||||||
|
|
||||||
|
if HAS_PYTZ:
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
timezone.make_aware(CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET)
|
timezone.make_aware(CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET)
|
||||||
|
|
||||||
if HAS_ZONEINFO:
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=PARIS_ZI), PARIS_ZI)
|
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=PARIS_ZI), PARIS_ZI)
|
||||||
|
|
||||||
|
@needs_pytz
|
||||||
def test_make_naive_pytz(self):
|
def test_make_naive_pytz(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
timezone.make_naive(CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET),
|
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'):
|
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)
|
timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30), CET)
|
||||||
|
|
||||||
@needs_zoneinfo
|
|
||||||
def test_make_naive_zoneinfo(self):
|
def test_make_naive_zoneinfo(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=PARIS_ZI), PARIS_ZI),
|
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)
|
datetime.datetime(2011, 9, 1, 12, 20, 30, fold=1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@needs_pytz
|
||||||
|
@ignore_warnings(category=RemovedInDjango50Warning)
|
||||||
def test_make_aware_pytz_ambiguous(self):
|
def test_make_aware_pytz_ambiguous(self):
|
||||||
# 2:30 happens twice, once before DST ends and once after
|
# 2:30 happens twice, once before DST ends and once after
|
||||||
ambiguous = datetime.datetime(2015, 10, 25, 2, 30)
|
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(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
|
||||||
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
|
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
|
||||||
|
|
||||||
@needs_zoneinfo
|
|
||||||
def test_make_aware_zoneinfo_ambiguous(self):
|
def test_make_aware_zoneinfo_ambiguous(self):
|
||||||
# 2:30 happens twice, once before DST ends and once after
|
# 2:30 happens twice, once before DST ends and once after
|
||||||
ambiguous = datetime.datetime(2015, 10, 25, 2, 30)
|
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(std.utcoffset(), datetime.timedelta(hours=1))
|
||||||
self.assertEqual(dst.utcoffset(), datetime.timedelta(hours=2))
|
self.assertEqual(dst.utcoffset(), datetime.timedelta(hours=2))
|
||||||
|
|
||||||
|
@needs_pytz
|
||||||
|
@ignore_warnings(category=RemovedInDjango50Warning)
|
||||||
def test_make_aware_pytz_non_existent(self):
|
def test_make_aware_pytz_non_existent(self):
|
||||||
# 2:30 never happened due to DST
|
# 2:30 never happened due to DST
|
||||||
non_existent = datetime.datetime(2015, 3, 29, 2, 30)
|
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(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
|
||||||
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
|
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
|
||||||
|
|
||||||
@needs_zoneinfo
|
|
||||||
def test_make_aware_zoneinfo_non_existent(self):
|
def test_make_aware_zoneinfo_non_existent(self):
|
||||||
# 2:30 never happened due to DST
|
# 2:30 never happened due to DST
|
||||||
non_existent = datetime.datetime(2015, 3, 29, 2, 30)
|
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(std.utcoffset(), datetime.timedelta(hours=1))
|
||||||
self.assertEqual(dst.utcoffset(), datetime.timedelta(hours=2))
|
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):
|
def test_get_timezone_name(self):
|
||||||
"""
|
"""
|
||||||
The _get_timezone_name() helper must return the offset for fixed offset
|
The _get_timezone_name() helper must return the offset for fixed offset
|
||||||
|
@ -271,16 +299,16 @@ class TimezoneTests(SimpleTestCase):
|
||||||
# datetime.timezone, fixed offset with and without `name`.
|
# datetime.timezone, fixed offset with and without `name`.
|
||||||
(datetime.timezone(datetime.timedelta(hours=10)), 'UTC+10:00'),
|
(datetime.timezone(datetime.timedelta(hours=10)), 'UTC+10:00'),
|
||||||
(datetime.timezone(datetime.timedelta(hours=10), name='Etc/GMT-10'), 'Etc/GMT-10'),
|
(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'),
|
|
||||||
]
|
|
||||||
if HAS_ZONEINFO:
|
|
||||||
tests += [
|
|
||||||
# zoneinfo, named and fixed offset.
|
# zoneinfo, named and fixed offset.
|
||||||
(zoneinfo.ZoneInfo('Europe/Madrid'), 'Europe/Madrid'),
|
(zoneinfo.ZoneInfo('Europe/Madrid'), 'Europe/Madrid'),
|
||||||
(zoneinfo.ZoneInfo('Etc/GMT-10'), '+10'),
|
(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:
|
for tz, expected in tests:
|
||||||
with self.subTest(tz=tz, expected=expected):
|
with self.subTest(tz=tz, expected=expected):
|
||||||
self.assertEqual(timezone._get_timezone_name(tz), expected)
|
self.assertEqual(timezone._get_timezone_name(tz), expected)
|
||||||
|
@ -288,10 +316,6 @@ class TimezoneTests(SimpleTestCase):
|
||||||
def test_get_default_timezone(self):
|
def test_get_default_timezone(self):
|
||||||
self.assertEqual(timezone.get_default_timezone_name(), 'America/Chicago')
|
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):
|
def test_fixedoffset_timedelta(self):
|
||||||
delta = datetime.timedelta(hours=1)
|
delta = datetime.timedelta(hours=1)
|
||||||
self.assertEqual(timezone.get_fixed_timezone(delta).utcoffset(None), delta)
|
self.assertEqual(timezone.get_fixed_timezone(delta).utcoffset(None), delta)
|
||||||
|
|
Loading…
Reference in New Issue