Fixed #27327 -- Simplified time zone handling by requiring pytz.

This commit is contained in:
Tim Graham 2016-10-07 21:06:49 -04:00
parent d84ffcc22b
commit 414ad25b09
30 changed files with 109 additions and 426 deletions

View File

@ -4,6 +4,8 @@ import warnings
from collections import deque from collections import deque
from contextlib import contextmanager from contextlib import contextmanager
import pytz
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS
@ -16,11 +18,6 @@ from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.six.moves import _thread as thread from django.utils.six.moves import _thread as thread
try:
import pytz
except ImportError:
pytz = None
NO_DB_ALIAS = '__no_db__' NO_DB_ALIAS = '__no_db__'
@ -128,7 +125,6 @@ class BaseDatabaseWrapper(object):
elif self.settings_dict['TIME_ZONE'] is None: elif self.settings_dict['TIME_ZONE'] is None:
return timezone.utc return timezone.utc
else: else:
# Only this branch requires pytz.
return pytz.timezone(self.settings_dict['TIME_ZONE']) return pytz.timezone(self.settings_dict['TIME_ZONE'])
@cached_property @cached_property
@ -207,10 +203,6 @@ class BaseDatabaseWrapper(object):
raise ImproperlyConfigured( raise ImproperlyConfigured(
"Connection '%s' cannot set TIME_ZONE because its engine " "Connection '%s' cannot set TIME_ZONE because its engine "
"handles time zones conversions natively." % self.alias) "handles time zones conversions natively." % self.alias)
elif pytz is None:
raise ImproperlyConfigured(
"Connection '%s' cannot set TIME_ZONE because pytz isn't "
"installed." % self.alias)
def ensure_connection(self): def ensure_connection(self):
""" """

View File

@ -3,11 +3,6 @@ from django.utils.functional import cached_property
from .base import Database from .base import Database
try:
import pytz
except ImportError:
pytz = None
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
empty_fetchmany_value = () empty_fetchmany_value = ()
@ -56,14 +51,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
@cached_property @cached_property
def has_zoneinfo_database(self): def has_zoneinfo_database(self):
# MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects
# abbreviations (eg. EAT). When pytz isn't installed and the current
# time zone is LocalTimezone (the only sensible value in this
# context), the current time zone name will be an abbreviation. As a
# consequence, MySQL cannot perform time zone conversions reliably.
if pytz is None:
return False
# Test if the time zone definitions are installed. # Test if the time zone definitions are installed.
with self.connection.cursor() as cursor: with self.connection.cursor() as cursor:
cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1") cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")

View File

@ -1,11 +1,6 @@
from django.db.backends.base.features import BaseDatabaseFeatures from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.utils import InterfaceError from django.db.utils import InterfaceError
try:
import pytz
except ImportError:
pytz = None
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
empty_fetchmany_value = () empty_fetchmany_value = ()
@ -19,7 +14,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_subqueries_in_group_by = False supports_subqueries_in_group_by = False
supports_transactions = True supports_transactions = True
supports_timezones = False supports_timezones = False
has_zoneinfo_database = pytz is not None
supports_bitwise_or = False supports_bitwise_or = False
has_native_duration_field = True has_native_duration_field = True
can_defer_constraint_checks = True can_defer_constraint_checks = True

View File

@ -11,6 +11,8 @@ import decimal
import re import re
import warnings import warnings
import pytz
from django.conf import settings from django.conf import settings
from django.db import utils from django.db import utils
from django.db.backends import utils as backend_utils from django.db.backends import utils as backend_utils
@ -23,11 +25,6 @@ from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.safestring import SafeBytes from django.utils.safestring import SafeBytes
try:
import pytz
except ImportError:
pytz = None
try: try:
try: try:
from pysqlite2 import dbapi2 as Database from pysqlite2 import dbapi2 as Database

View File

@ -7,11 +7,6 @@ from django.utils.functional import cached_property
from .base import Database from .base import Database
try:
import pytz
except ImportError:
pytz = None
class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set # SQLite cannot handle us only partially reading from a cursor's result set
@ -78,7 +73,3 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_support = False has_support = False
cursor.execute('DROP TABLE STDDEV_TEST') cursor.execute('DROP TABLE STDDEV_TEST')
return has_support return has_support
@cached_property
def has_zoneinfo_database(self):
return pytz is not None

View File

@ -4,7 +4,7 @@ import datetime
import uuid import uuid
from django.conf import settings from django.conf import settings
from django.core.exceptions import FieldError, ImproperlyConfigured from django.core.exceptions import FieldError
from django.db import utils from django.db import utils
from django.db.backends import utils as backend_utils from django.db.backends import utils as backend_utils
from django.db.backends.base.operations import BaseDatabaseOperations from django.db.backends.base.operations import BaseDatabaseOperations
@ -13,11 +13,6 @@ from django.utils import six, timezone
from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.dateparse import parse_date, parse_datetime, parse_time
from django.utils.duration import duration_string from django.utils.duration import duration_string
try:
import pytz
except ImportError:
pytz = None
class DatabaseOperations(BaseDatabaseOperations): class DatabaseOperations(BaseDatabaseOperations):
def bulk_batch_size(self, fields, objs): def bulk_batch_size(self, fields, objs):
@ -77,27 +72,19 @@ class DatabaseOperations(BaseDatabaseOperations):
# cause a collision with a field name). # cause a collision with a field name).
return "django_time_trunc('%s', %s)" % (lookup_type.lower(), field_name) return "django_time_trunc('%s', %s)" % (lookup_type.lower(), field_name)
def _require_pytz(self):
if settings.USE_TZ and pytz is None:
raise ImproperlyConfigured("This query requires pytz, but it isn't installed.")
def datetime_cast_date_sql(self, field_name, tzname): def datetime_cast_date_sql(self, field_name, tzname):
self._require_pytz()
return "django_datetime_cast_date(%s, %%s)" % field_name, [tzname] return "django_datetime_cast_date(%s, %%s)" % field_name, [tzname]
def datetime_cast_time_sql(self, field_name, tzname): def datetime_cast_time_sql(self, field_name, tzname):
self._require_pytz()
return "django_datetime_cast_time(%s, %%s)" % field_name, [tzname] return "django_datetime_cast_time(%s, %%s)" % field_name, [tzname]
def datetime_extract_sql(self, lookup_type, field_name, tzname): def datetime_extract_sql(self, lookup_type, field_name, tzname):
# Same comment as in date_extract_sql. # Same comment as in date_extract_sql.
self._require_pytz()
return "django_datetime_extract('%s', %s, %%s)" % ( return "django_datetime_extract('%s', %s, %%s)" % (
lookup_type.lower(), field_name), [tzname] lookup_type.lower(), field_name), [tzname]
def datetime_trunc_sql(self, lookup_type, field_name, tzname): def datetime_trunc_sql(self, lookup_type, field_name, tzname):
# Same comment as in date_trunc_sql. # Same comment as in date_trunc_sql.
self._require_pytz()
return "django_datetime_trunc('%s', %s, %%s)" % ( return "django_datetime_trunc('%s', %s, %%s)" % (
lookup_type.lower(), field_name), [tzname] lookup_type.lower(), field_name), [tzname]

View File

@ -191,7 +191,7 @@ class TruncBase(TimezoneMixin, Transform):
if value is None: if value is None:
raise ValueError( raise ValueError(
"Database returned an invalid datetime value. " "Database returned an invalid datetime value. "
"Are time zone definitions for your database and pytz installed?" "Are time zone definitions for your database installed?"
) )
value = value.replace(tzinfo=None) value = value.replace(tzinfo=None)
value = timezone.make_aware(value, self.tzinfo) value = timezone.make_aware(value, self.tzinfo)

View File

@ -1,14 +1,10 @@
from datetime import datetime, tzinfo from datetime import datetime, tzinfo
import pytz
from django.template import Library, Node, TemplateSyntaxError from django.template import Library, Node, TemplateSyntaxError
from django.utils import six, timezone from django.utils import six, timezone
try:
import pytz
except ImportError:
pytz = None
register = Library() register = Library()
@ -44,7 +40,6 @@ def do_timezone(value, arg):
Converts a datetime to local time in a given time zone. Converts a datetime to local time in a given time zone.
The argument must be an instance of a tzinfo subclass or a time zone name. The argument must be an instance of a tzinfo subclass or a time zone name.
If it is a time zone name, pytz is required.
Naive datetimes are assumed to be in local time in the default time zone. Naive datetimes are assumed to be in local time in the default time zone.
""" """
@ -64,7 +59,7 @@ def do_timezone(value, arg):
# Obtain a tzinfo instance # Obtain a tzinfo instance
if isinstance(arg, tzinfo): if isinstance(arg, tzinfo):
tz = arg tz = arg
elif isinstance(arg, six.string_types) and pytz is not None: elif isinstance(arg, six.string_types):
try: try:
tz = pytz.timezone(arg) tz = pytz.timezone(arg)
except pytz.UnknownTimeZoneError: except pytz.UnknownTimeZoneError:
@ -156,8 +151,8 @@ def timezone_tag(parser, token):
Enables a given time zone just for this block. Enables a given time zone just for this block.
The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
time zone name, or ``None``. If is it a time zone name, pytz is required. time zone name, or ``None``. If it is ``None``, the default time zone is
If it is ``None``, the default time zone is used within the block. used within the block.
Sample usage:: Sample usage::

View File

@ -1,24 +1,16 @@
""" """
Timezone-related classes and functions. Timezone-related classes and functions.
This module uses pytz when it's available and fallbacks when it isn't.
""" """
import sys
import time as _time
from datetime import datetime, timedelta, tzinfo from datetime import datetime, timedelta, tzinfo
from threading import local from threading import local
import pytz
from django.conf import settings from django.conf import settings
from django.utils import lru_cache, six from django.utils import lru_cache, six
from django.utils.decorators import ContextDecorator from django.utils.decorators import ContextDecorator
try:
import pytz
except ImportError:
pytz = None
__all__ = [ __all__ = [
'utc', 'get_fixed_timezone', 'utc', 'get_fixed_timezone',
'get_default_timezone', 'get_default_timezone_name', 'get_default_timezone', 'get_default_timezone_name',
@ -34,26 +26,6 @@ __all__ = [
ZERO = timedelta(0) ZERO = timedelta(0)
class UTC(tzinfo):
"""
UTC implementation taken from Python's docs.
Used only when pytz isn't available.
"""
def __repr__(self):
return "<UTC>"
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
class FixedOffset(tzinfo): class FixedOffset(tzinfo):
""" """
Fixed offset in minutes east from UTC. Taken from Python's docs. Fixed offset in minutes east from UTC. Taken from Python's docs.
@ -78,79 +50,7 @@ class FixedOffset(tzinfo):
def dst(self, dt): def dst(self, dt):
return ZERO return ZERO
utc = pytz.utc
class ReferenceLocalTimezone(tzinfo):
"""
Local time. Taken from Python's docs.
Used only when pytz isn't available, and most likely inaccurate. If you're
having trouble with this class, don't waste your time, just install pytz.
Kept as close as possible to the reference version. __init__ was added to
delay the computation of STDOFFSET, DSTOFFSET and DSTDIFF which is
performed at import time in the example.
Subclasses contain further improvements.
"""
def __init__(self):
self.STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight:
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
else:
self.DSTOFFSET = self.STDOFFSET
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
tzinfo.__init__(self)
def utcoffset(self, dt):
if self._isdst(dt):
return self.DSTOFFSET
else:
return self.STDOFFSET
def dst(self, dt):
if self._isdst(dt):
return self.DSTDIFF
else:
return ZERO
def tzname(self, dt):
return _time.tzname[self._isdst(dt)]
def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
dt.weekday(), 0, 0)
stamp = _time.mktime(tt)
tt = _time.localtime(stamp)
return tt.tm_isdst > 0
class LocalTimezone(ReferenceLocalTimezone):
"""
Slightly improved local time implementation focusing on correctness.
It still crashes on dates before 1970 or after 2038, but at least the
error message is helpful.
"""
def tzname(self, dt):
is_dst = False if dt is None else self._isdst(dt)
return _time.tzname[is_dst]
def _isdst(self, dt):
try:
return super(LocalTimezone, self)._isdst(dt)
except (OverflowError, ValueError) as exc:
exc_type = type(exc)
exc_value = exc_type(
"Unsupported value: %r. You should install pytz." % dt)
exc_value.__cause__ = exc
if not hasattr(exc, '__traceback__'):
exc.__traceback__ = sys.exc_info()[2]
six.reraise(exc_type, exc_value, sys.exc_info()[2])
utc = pytz.utc if pytz else UTC()
"""UTC time zone as a tzinfo instance.""" """UTC time zone as a tzinfo instance."""
@ -175,11 +75,7 @@ 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 isinstance(settings.TIME_ZONE, six.string_types) and pytz is not None: return pytz.timezone(settings.TIME_ZONE)
return pytz.timezone(settings.TIME_ZONE)
else:
# This relies on os.environ['TZ'] being set to settings.TIME_ZONE.
return LocalTimezone()
# This function exists for consistency with get_current_timezone_name # This function exists for consistency with get_current_timezone_name
@ -228,11 +124,11 @@ def activate(timezone):
Sets the time zone for the current thread. Sets the time zone for the current thread.
The ``timezone`` argument must be an instance of a tzinfo subclass or a The ``timezone`` argument must be an instance of a tzinfo subclass or a
time zone name. If it is a time zone name, pytz is required. time zone name.
""" """
if isinstance(timezone, tzinfo): if isinstance(timezone, tzinfo):
_active.value = timezone _active.value = timezone
elif isinstance(timezone, six.string_types) and pytz is not None: elif isinstance(timezone, six.string_types):
_active.value = pytz.timezone(timezone) _active.value = pytz.timezone(timezone)
else: else:
raise ValueError("Invalid timezone: %r" % timezone) raise ValueError("Invalid timezone: %r" % timezone)
@ -257,8 +153,8 @@ class override(ContextDecorator):
on exit. on exit.
The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a The ``timezone`` argument must be an instance of a ``tzinfo`` subclass, a
time zone name, or ``None``. If is it a time zone name, pytz is required. time zone name, or ``None``. If it is ``None``, Django enables the default
If it is ``None``, Django enables the default time zone. time zone.
""" """
def __init__(self, timezone): def __init__(self, timezone):
self.timezone = timezone self.timezone = timezone

View File

@ -117,9 +117,9 @@ Imports
# try/except # try/except
try: try:
import pytz import yaml
except ImportError: except ImportError:
pytz = None yaml = None
CONSTANT = 'foo' CONSTANT = 'foo'

View File

@ -232,7 +232,7 @@ dependencies:
* numpy_ * numpy_
* Pillow_ * Pillow_
* PyYAML_ * PyYAML_
* pytz_ * pytz_ (required)
* setuptools_ * setuptools_
* memcached_, plus a :ref:`supported Python binding <memcached>` * memcached_, plus a :ref:`supported Python binding <memcached>`
* mock_ (for Python 2) * mock_ (for Python 2)

View File

@ -758,11 +758,11 @@ object. If it's ``None``, Django uses the :ref:`current time zone
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: install pytz_ — conversions are actually performed in Python. - SQLite: no requirements. Conversions are performed in Python with pytz_
(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: install pytz_ and load the time zone tables with - MySQL: load the time zone tables with `mysql_tzinfo_to_sql`_.
`mysql_tzinfo_to_sql`_.
.. _pytz: http://pytz.sourceforge.net/ .. _pytz: http://pytz.sourceforge.net/
.. _Time Zones: https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-TIMEZONES .. _Time Zones: https://www.postgresql.org/docs/current/static/datatype-datetime.html#DATATYPE-TIMEZONES

View File

@ -607,8 +607,6 @@ This allows interacting with third-party databases that store datetimes in
local time rather than UTC. To avoid issues around DST changes, you shouldn't local time rather than UTC. To avoid issues around DST changes, you shouldn't
set this option for databases managed by Django. set this option for databases managed by Django.
Setting this option requires installing pytz_.
When :setting:`USE_TZ` is ``True`` and the database doesn't support time zones When :setting:`USE_TZ` is ``True`` and the database doesn't support time zones
(e.g. SQLite, MySQL, Oracle), Django reads and writes datetimes in local time (e.g. SQLite, MySQL, Oracle), Django reads and writes datetimes in local time
according to this option if it is set and in UTC if it isn't. according to this option if it is set and in UTC if it isn't.
@ -618,8 +616,6 @@ PostgreSQL), it is an error to set this option.
When :setting:`USE_TZ` is ``False``, it is an error to set this option. When :setting:`USE_TZ` is ``False``, it is an error to set this option.
.. _pytz: http://pytz.sourceforge.net/
.. setting:: USER .. setting:: USER
``USER`` ``USER``
@ -2478,8 +2474,8 @@ See also :setting:`DATE_INPUT_FORMATS` and :setting:`DATETIME_INPUT_FORMATS`.
Default: ``'America/Chicago'`` Default: ``'America/Chicago'``
A string representing the time zone for this installation, or ``None``. See A string representing the time zone for this installation. See the `list of
the `list of time zones`_. time zones`_.
.. note:: .. note::
Since Django was first released with the :setting:`TIME_ZONE` set to Since Django was first released with the :setting:`TIME_ZONE` set to
@ -2496,22 +2492,15 @@ will store all datetimes. When :setting:`USE_TZ` is ``True``, this is the
default time zone that Django will use to display datetimes in templates and default time zone that Django will use to display datetimes in templates and
to interpret datetimes entered in forms. to interpret datetimes entered in forms.
Django sets the ``os.environ['TZ']`` variable to the time zone you specify in On Unix environments (where :func:`time.tzset` is implemented), Django sets the
the :setting:`TIME_ZONE` setting. Thus, all your views and models will ``os.environ['TZ']`` variable to the time zone you specify in the
:setting:`TIME_ZONE` setting. Thus, all your views and models will
automatically operate in this time zone. However, Django won't set the ``TZ`` automatically operate in this time zone. However, Django won't set the ``TZ``
environment variable under the following conditions: environment variable if you're using the manual configuration option as
described in :ref:`manually configuring settings
* If you're using the manual configuration option as described in <settings-without-django-settings-module>`. If Django doesn't set the ``TZ``
:ref:`manually configuring settings environment variable, it's up to you to ensure your processes are running in
<settings-without-django-settings-module>`, or the correct environment.
* If you specify ``TIME_ZONE = None``. This will cause Django to fall back to
using the system timezone. However, this is discouraged when :setting:`USE_TZ
= True <USE_TZ>`, because it makes conversions between local time and UTC
less reliable.
If Django doesn't set the ``TZ`` environment variable, it's up to you
to ensure your processes are running in the correct environment.
.. note:: .. note::
Django cannot reliably use alternate time zones in a Windows environment. Django cannot reliably use alternate time zones in a Windows environment.

View File

@ -966,7 +966,7 @@ appropriate entities.
Sets the :ref:`current time zone <default-current-time-zone>`. The Sets the :ref:`current time zone <default-current-time-zone>`. The
``timezone`` argument must be an instance of a :class:`~datetime.tzinfo` ``timezone`` argument must be an instance of a :class:`~datetime.tzinfo`
subclass or, if pytz_ is available, a time zone name. subclass or a time zone name.
.. function:: deactivate() .. function:: deactivate()
@ -1043,21 +1043,18 @@ 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 pytz_ is installed, the exception ``pytz.AmbiguousTimeError`` The ``pytz.AmbiguousTimeError`` exception is raised if you try to make
will be raised if you try to make ``value`` aware during a DST transition ``value`` aware during a DST transition where the same time occurs twice
where the same time occurs twice (when reverting from DST). Setting (when reverting from DST). Setting ``is_dst`` to ``True`` or ``False`` will
``is_dst`` to ``True`` or ``False`` will avoid the exception by choosing if avoid the exception by choosing if the time is pre-transition or
the time is pre-transition or post-transition respectively. post-transition respectively.
When pytz_ is installed, the exception ``pytz.NonExistentTimeError`` The ``pytz.NonExistentTimeError`` exception is raised if you try to make
will be raised if you try to make ``value`` aware during a DST transition ``value`` aware during a DST transition such that the time never occurred
such that the time never occurred (when entering into DST). Setting (when entering into DST). Setting ``is_dst`` to ``True`` or ``False`` will
``is_dst`` to ``True`` or ``False`` will avoid the exception by moving the avoid the exception by moving the hour backwards or forwards by 1
hour backwards or forwards by 1 respectively. For example, ``is_dst=True`` respectively. For example, ``is_dst=True`` would change a non-existent
would change a non-existent time of 2:30 to 1:30 and ``is_dst=False`` time of 2:30 to 1:30 and ``is_dst=False`` would change the time to 3:30.
would change the time to 3:30.
``is_dst`` has no effect when ``pytz`` is not installed.
.. function:: make_naive(value, timezone=None) .. function:: make_naive(value, timezone=None)
@ -1066,8 +1063,6 @@ appropriate entities.
aware :class:`~datetime.datetime`. If ``timezone`` is set to ``None``, it aware :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>`.
.. _pytz: http://pytz.sourceforge.net/
``django.utils.translation`` ``django.utils.translation``
============================ ============================

View File

@ -468,6 +468,27 @@ To prevent typos from passing silently,
arguments are model fields. This should be backwards-incompatible only in the arguments are model fields. This should be backwards-incompatible only in the
fact that it might expose a bug in your project. fact that it might expose a bug in your project.
``pytz`` is a required dependency and support for ``settings.TIME_ZONE = None`` is removed
------------------------------------------------------------------------------------------
To simplify Django's timezone handling, ``pytz`` is now a required dependency.
It's automatically installed along with Django.
Support for ``settings.TIME_ZONE = None`` is removed as the behavior isn't
commonly used and is questionably useful. If you want to automatically detect
the timezone based on the system timezone, you can use `tzlocal
<https://pypi.python.org/pypi/tzlocal>`_::
from tzlocal import get_localzone
TIME_ZONE = get_localzone().zone
This works similar to ``settings.TIME_ZONE = None`` except that it also sets
``os.environ['TZ']``. `Let us know
<https://groups.google.com/d/topic/django-developers/OAV3FChfuPM/discussion>`__
if there's a use case where you find you can't adapt your code to set a
``TIME_ZONE``.
Miscellaneous Miscellaneous
------------- -------------

View File

@ -622,7 +622,6 @@ Pygments
pysqlite pysqlite
pythonic pythonic
Pythonista Pythonista
pytz
qs qs
Québec Québec
queryset queryset

View File

@ -26,14 +26,12 @@ 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 =
True <USE_TZ>` in your settings file. Installing pytz_ is highly recommended, True <USE_TZ>` in your settings file. Time zone support uses pytz_, which is
but may not be mandatory depending on your particular database backend, installed when you install Django.
operating system and time zone. If you encounter an exception querying dates
or times, please try installing it before filing a bug. It's as simple as:
.. code-block:: console .. versionchanged:: 1.11
$ pip install pytz Older versions don't require ``pytz`` or install it automatically.
.. note:: .. note::
@ -113,11 +111,8 @@ 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. Other ambiguous. In such situations, pytz_ raises an exception. That's why you should
:class:`~datetime.tzinfo` implementations, such as the local time zone used as always create aware datetime objects when time zone support is enabled.
a fallback when pytz_ isn't installed, may raise an exception or return
inaccurate results. That's why you should always create aware datetime objects
when time zone support is enabled.
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
@ -360,7 +355,7 @@ For example::
Forces conversion of a single value to an arbitrary timezone. Forces conversion of a single value to an arbitrary timezone.
The argument must be an instance of a :class:`~datetime.tzinfo` subclass or a The argument must be an instance of a :class:`~datetime.tzinfo` subclass or a
time zone name. If it is a time zone name, pytz_ is required. time zone name.
For example:: For example::
@ -405,9 +400,8 @@ Code
---- ----
The first step is to add :setting:`USE_TZ = True <USE_TZ>` to your settings The first step is to add :setting:`USE_TZ = True <USE_TZ>` to your settings
file and install pytz_ (if possible). At this point, things should mostly file. At this point, things should mostly work. If you create naive datetime
work. If you create naive datetime objects in your code, Django makes them objects in your code, Django makes them aware when necessary.
aware when necessary.
However, these conversions may fail around DST transitions, which means you However, these conversions may fail around DST transitions, which means you
aren't getting the full benefits of time zone support yet. Also, you're likely aren't getting the full benefits of time zone support yet. Also, you're likely
@ -523,22 +517,7 @@ Setup
one year is 2011-02-28 or 2011-03-01, which depends on your business one year is 2011-02-28 or 2011-03-01, which depends on your business
requirements.) requirements.)
3. **Should I install pytz?** 3. **How do I interact with a database that stores datetimes in local time?**
Yes. Django has a policy of not requiring external dependencies, and for
this reason pytz_ is optional. However, it's much safer to install it.
As soon as you activate time zone support, Django needs a definition of the
default time zone. When pytz is available, Django loads this definition
from the `tz database`_. This is the most accurate solution. Otherwise, it
relies on the difference between local time and UTC, as reported by the
operating system, to compute conversions. This is less reliable, especially
around DST transitions.
Furthermore, if you want to support users in more than one time zone, pytz
is the reference for time zone definitions.
4. **How do I interact with a database that stores datetimes in local time?**
Set the :setting:`TIME_ZONE <DATABASE-TIME_ZONE>` option to the appropriate Set the :setting:`TIME_ZONE <DATABASE-TIME_ZONE>` option to the appropriate
time zone for this database in the :setting:`DATABASES` setting. time zone for this database in the :setting:`DATABASES` setting.
@ -653,8 +632,8 @@ Troubleshooting
>>> local.date() >>> local.date()
datetime.date(2012, 3, 3) datetime.date(2012, 3, 3)
4. **I get an error** "``Are time zone definitions for your database and pytz 4. **I get an error** "``Are time zone definitions for your database
installed?``" **pytz is installed, so I guess the problem is my database?** installed?``"
If you are using MySQL, see the :ref:`mysql-time-zone-definitions` section If you are using MySQL, see the :ref:`mysql-time-zone-definitions` section
of the MySQL notes for instructions on loading time zone definitions. of the MySQL notes for instructions on loading time zone definitions.
@ -700,8 +679,7 @@ Usage
>>> 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=<DstTzInfo 'Europe/Paris' CET+1:00:00 STD>)
In this example, pytz_ is installed and the current time zone is In this example, the current time zone is ``"Europe/Paris"``.
``"Europe/Paris"``.
3. **How can I see all available time zones?** 3. **How can I see all available time zones?**

View File

@ -47,6 +47,7 @@ setup(
entry_points={'console_scripts': [ entry_points={'console_scripts': [
'django-admin = django.core.management:execute_from_command_line', 'django-admin = django.core.management:execute_from_command_line',
]}, ]},
install_requires=['pytz'],
extras_require={ extras_require={
"bcrypt": ["bcrypt"], "bcrypt": ["bcrypt"],
"argon2": ["argon2-cffi >= 16.1.0"], "argon2": ["argon2-cffi >= 16.1.0"],

View File

@ -5,7 +5,8 @@ import gettext
import os import os
from datetime import datetime, timedelta from datetime import datetime, timedelta
from importlib import import_module from importlib import import_module
from unittest import skipIf
import pytz
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -23,11 +24,6 @@ from django.utils import six, translation
from . import models from . import models
from .widgetadmin import site as widget_admin_site from .widgetadmin import site as widget_admin_site
try:
import pytz
except ImportError:
pytz = None
class TestDataMixin(object): class TestDataMixin(object):
@ -794,7 +790,6 @@ class DateTimePickerSeleniumTests(AdminWidgetSeleniumTestCase):
self.wait_for_text('#calendarin0 caption', expected_caption) self.wait_for_text('#calendarin0 caption', expected_caption)
@skipIf(pytz is None, "this test requires pytz")
@override_settings(TIME_ZONE='Asia/Singapore') @override_settings(TIME_ZONE='Asia/Singapore')
class DateTimePickerShortcutsSeleniumTests(AdminWidgetSeleniumTestCase): class DateTimePickerShortcutsSeleniumTests(AdminWidgetSeleniumTestCase):

15
tests/cache/tests.py vendored
View File

@ -1832,17 +1832,16 @@ class CacheI18nTest(TestCase):
@override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True) @override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True)
def test_cache_key_with_non_ascii_tzname(self): def test_cache_key_with_non_ascii_tzname(self):
# Regression test for #17476 # Timezone-dependent cache keys should use ASCII characters only
class CustomTzName(timezone.UTC): # (#17476). The implementation here is a bit odd (timezone.utc is an
name = '' # instance, not a class), but it simulates the correct conditions.
class CustomTzName(timezone.utc):
def tzname(self, dt): pass
return self.name
request = self.factory.get(self.path) request = self.factory.get(self.path)
response = HttpResponse() response = HttpResponse()
with timezone.override(CustomTzName()): with timezone.override(CustomTzName):
CustomTzName.name = 'Hora estándar de Argentina'.encode('UTF-8') # UTF-8 string CustomTzName.zone = 'Hora estándar de Argentina'.encode('UTF-8') # UTF-8 string
sanitized_name = 'Hora_estndar_de_Argentina' sanitized_name = 'Hora_estndar_de_Argentina'
self.assertIn( self.assertIn(
sanitized_name, learn_cache_key(request, response), sanitized_name, learn_cache_key(request, response),

View File

@ -1,18 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
from unittest import skipIf
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.utils import timezone from django.utils import timezone
from .models import Article, Category, Comment from .models import Article, Category, Comment
try:
import pytz
except ImportError:
pytz = None
class DateTimesTests(TestCase): class DateTimesTests(TestCase):
def test_related_model_traverse(self): def test_related_model_traverse(self):
@ -84,7 +78,6 @@ class DateTimesTests(TestCase):
], ],
) )
@skipIf(pytz is None, "this test requires pytz")
@override_settings(USE_TZ=True) @override_settings(USE_TZ=True)
def test_21432(self): def test_21432(self):
now = timezone.localtime(timezone.now().replace(microsecond=0)) now = timezone.localtime(timezone.now().replace(microsecond=0))

View File

@ -1,7 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from datetime import datetime from datetime import datetime
from unittest import skipIf
import pytz
from django.conf import settings from django.conf import settings
from django.db import connection from django.db import connection
@ -16,11 +17,6 @@ from django.utils import timezone
from .models import DTModel from .models import DTModel
try:
import pytz
except ImportError:
pytz = None
def microsecond_support(value): def microsecond_support(value):
return value if connection.features.supports_microsecond_precision else value.replace(microsecond=0) return value if connection.features.supports_microsecond_precision else value.replace(microsecond=0)
@ -659,7 +655,6 @@ class DateFunctionTests(TestCase):
list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField()))) list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField())))
@skipIf(pytz is None, "this test requires pytz")
@override_settings(USE_TZ=True, TIME_ZONE='UTC') @override_settings(USE_TZ=True, TIME_ZONE='UTC')
class DateFunctionWithTimeZoneTests(DateFunctionTests): class DateFunctionWithTimeZoneTests(DateFunctionTests):

View File

@ -32,12 +32,6 @@ from django.utils.six.moves.urllib.request import urlopen
from .models import Storage, temp_storage, temp_storage_location from .models import Storage, temp_storage, temp_storage_location
try:
import pytz
except ImportError:
pytz = None
FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
@ -99,10 +93,6 @@ class FileSystemStorageTests(unittest.TestCase):
storage.url(storage.base_url) storage.url(storage.base_url)
# Tests for TZ-aware time methods need pytz.
requires_pytz = unittest.skipIf(pytz is None, "this test requires pytz")
class FileStorageTests(SimpleTestCase): class FileStorageTests(SimpleTestCase):
storage_class = FileSystemStorage storage_class = FileSystemStorage
@ -157,7 +147,6 @@ class FileStorageTests(SimpleTestCase):
# different. # different.
now_in_algiers = timezone.make_aware(datetime.now()) now_in_algiers = timezone.make_aware(datetime.now())
# Use a fixed offset timezone so we don't need pytz.
with timezone.override(timezone.get_fixed_timezone(-300)): with timezone.override(timezone.get_fixed_timezone(-300)):
# At this point the system TZ is +1 and the Django TZ # At this point the system TZ is +1 and the Django TZ
# is -5. The following will be aware in UTC. # is -5. The following will be aware in UTC.
@ -191,7 +180,6 @@ class FileStorageTests(SimpleTestCase):
# different. # different.
now_in_algiers = timezone.make_aware(datetime.now()) now_in_algiers = timezone.make_aware(datetime.now())
# Use a fixed offset timezone so we don't need pytz.
with timezone.override(timezone.get_fixed_timezone(-300)): with timezone.override(timezone.get_fixed_timezone(-300)):
# At this point the system TZ is +1 and the Django TZ # At this point the system TZ is +1 and the Django TZ
# is -5. # is -5.
@ -220,7 +208,6 @@ class FileStorageTests(SimpleTestCase):
_dt = timezone.make_aware(dt, now_in_algiers.tzinfo) _dt = timezone.make_aware(dt, now_in_algiers.tzinfo)
self.assertLess(abs(_dt - now_in_algiers), timedelta(seconds=2)) self.assertLess(abs(_dt - now_in_algiers), timedelta(seconds=2))
@requires_pytz
def test_file_get_accessed_time(self): def test_file_get_accessed_time(self):
""" """
File storage returns a Datetime object for the last accessed time of File storage returns a Datetime object for the last accessed time of
@ -236,7 +223,6 @@ class FileStorageTests(SimpleTestCase):
self.assertEqual(atime, datetime.fromtimestamp(os.path.getatime(self.storage.path(f_name)))) self.assertEqual(atime, datetime.fromtimestamp(os.path.getatime(self.storage.path(f_name))))
self.assertLess(timezone.now() - self.storage.get_accessed_time(f_name), timedelta(seconds=2)) self.assertLess(timezone.now() - self.storage.get_accessed_time(f_name), timedelta(seconds=2))
@requires_pytz
@requires_tz_support @requires_tz_support
def test_file_get_accessed_time_timezone(self): def test_file_get_accessed_time_timezone(self):
self._test_file_time_getter(self.storage.get_accessed_time) self._test_file_time_getter(self.storage.get_accessed_time)
@ -256,7 +242,6 @@ class FileStorageTests(SimpleTestCase):
self.assertEqual(atime, datetime.fromtimestamp(os.path.getatime(self.storage.path(f_name)))) self.assertEqual(atime, datetime.fromtimestamp(os.path.getatime(self.storage.path(f_name))))
self.assertLess(datetime.now() - self.storage.accessed_time(f_name), timedelta(seconds=2)) self.assertLess(datetime.now() - self.storage.accessed_time(f_name), timedelta(seconds=2))
@requires_pytz
def test_file_get_created_time(self): def test_file_get_created_time(self):
""" """
File storage returns a datetime for the creation time of a file. File storage returns a datetime for the creation time of a file.
@ -271,7 +256,6 @@ class FileStorageTests(SimpleTestCase):
self.assertEqual(ctime, datetime.fromtimestamp(os.path.getctime(self.storage.path(f_name)))) self.assertEqual(ctime, datetime.fromtimestamp(os.path.getctime(self.storage.path(f_name))))
self.assertLess(timezone.now() - self.storage.get_created_time(f_name), timedelta(seconds=2)) self.assertLess(timezone.now() - self.storage.get_created_time(f_name), timedelta(seconds=2))
@requires_pytz
@requires_tz_support @requires_tz_support
def test_file_get_created_time_timezone(self): def test_file_get_created_time_timezone(self):
self._test_file_time_getter(self.storage.get_created_time) self._test_file_time_getter(self.storage.get_created_time)
@ -291,7 +275,6 @@ class FileStorageTests(SimpleTestCase):
self.assertEqual(ctime, datetime.fromtimestamp(os.path.getctime(self.storage.path(f_name)))) self.assertEqual(ctime, datetime.fromtimestamp(os.path.getctime(self.storage.path(f_name))))
self.assertLess(datetime.now() - self.storage.created_time(f_name), timedelta(seconds=2)) self.assertLess(datetime.now() - self.storage.created_time(f_name), timedelta(seconds=2))
@requires_pytz
def test_file_get_modified_time(self): def test_file_get_modified_time(self):
""" """
File storage returns a datetime for the last modified time of a file. File storage returns a datetime for the last modified time of a file.
@ -306,7 +289,6 @@ class FileStorageTests(SimpleTestCase):
self.assertEqual(mtime, datetime.fromtimestamp(os.path.getmtime(self.storage.path(f_name)))) self.assertEqual(mtime, datetime.fromtimestamp(os.path.getmtime(self.storage.path(f_name))))
self.assertLess(timezone.now() - self.storage.get_modified_time(f_name), timedelta(seconds=2)) self.assertLess(timezone.now() - self.storage.get_modified_time(f_name), timedelta(seconds=2))
@requires_pytz
@requires_tz_support @requires_tz_support
def test_file_get_modified_time_timezone(self): def test_file_get_modified_time_timezone(self):
self._test_file_time_getter(self.storage.get_modified_time) self._test_file_time_getter(self.storage.get_modified_time)

View File

@ -2,7 +2,6 @@ from __future__ import unicode_literals
import datetime import datetime
from decimal import Decimal from decimal import Decimal
from unittest import skipIf
from django.contrib.humanize.templatetags import humanize from django.contrib.humanize.templatetags import humanize
from django.template import Context, Template, defaultfilters from django.template import Context, Template, defaultfilters
@ -12,12 +11,6 @@ from django.utils.html import escape
from django.utils.timezone import get_fixed_timezone, utc from django.utils.timezone import get_fixed_timezone, utc
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
try:
import pytz
except ImportError:
pytz = None
# Mock out datetime in some tests so they don't fail occasionally when they # Mock out datetime in some tests so they don't fail occasionally when they
# run too slow. Use a fixed datetime for datetime.now(). DST change in # run too slow. Use a fixed datetime for datetime.now(). DST change in
# America/Chicago (the default time zone) happened on March 11th in 2012. # America/Chicago (the default time zone) happened on March 11th in 2012.
@ -174,7 +167,6 @@ class HumanizeTests(SimpleTestCase):
# As 24h of difference they will never be the same # As 24h of difference they will never be the same
self.assertNotEqual(naturalday_one, naturalday_two) self.assertNotEqual(naturalday_one, naturalday_two)
@skipIf(pytz is None, "this test requires pytz")
def test_naturalday_uses_localtime(self): def test_naturalday_uses_localtime(self):
# Regression for #18504 # Regression for #18504
# This is 2012-03-08HT19:30:00-06:00 in America/Chicago # This is 2012-03-08HT19:30:00-06:00 in America/Chicago

View File

@ -8,7 +8,6 @@ Pillow
PyYAML PyYAML
# pylibmc/libmemcached can't be built on Windows. # pylibmc/libmemcached can't be built on Windows.
pylibmc; sys.platform != 'win32' pylibmc; sys.platform != 'win32'
pytz > dev
selenium selenium
sqlparse sqlparse
tblib tblib

View File

@ -22,7 +22,7 @@ from django.test import (
TransactionTestCase, mock, skipIfDBFeature, skipUnlessDBFeature, TransactionTestCase, mock, skipIfDBFeature, skipUnlessDBFeature,
) )
from django.test.utils import CaptureQueriesContext from django.test.utils import CaptureQueriesContext
from django.utils.timezone import UTC from django.utils import timezone
from .fields import ( from .fields import (
CustomManyToManyField, InheritedManyToManyField, MediumBlobField, CustomManyToManyField, InheritedManyToManyField, MediumBlobField,
@ -2187,7 +2187,7 @@ class SchemaTests(TransactionTestCase):
TimeField if auto_now or auto_add_now is set (#25005). TimeField if auto_now or auto_add_now is set (#25005).
""" """
now = datetime.datetime(month=1, day=1, year=2000, hour=1, minute=1) now = datetime.datetime(month=1, day=1, year=2000, hour=1, minute=1)
now_tz = datetime.datetime(month=1, day=1, year=2000, hour=1, minute=1, tzinfo=UTC()) now_tz = datetime.datetime(month=1, day=1, year=2000, hour=1, minute=1, tzinfo=timezone.utc)
mocked_datetime.now = mock.MagicMock(return_value=now) mocked_datetime.now = mock.MagicMock(return_value=now)
mocked_tz.now = mock.MagicMock(return_value=now_tz) mocked_tz.now = mock.MagicMock(return_value=now_tz)
# Create the table # Create the table

View File

@ -16,11 +16,6 @@ from django.utils.feedgenerator import (
from .models import Article, Entry from .models import Article, Entry
try:
import pytz
except ImportError:
pytz = None
TZ = timezone.get_default_timezone() TZ = timezone.get_default_timezone()

View File

@ -8,6 +8,8 @@ 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
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import serializers from django.core import serializers
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
@ -33,13 +35,6 @@ from .models import (
AllDayEvent, Event, MaybeEvent, Session, SessionEvent, Timestamp, AllDayEvent, Event, MaybeEvent, Session, SessionEvent, Timestamp,
) )
try:
import pytz
except ImportError:
pytz = None
requires_pytz = skipIf(pytz is None, "this test requires pytz")
# These tests use the EAT (Eastern Africa Time) and ICT (Indochina Time) # These tests use the EAT (Eastern Africa Time) and ICT (Indochina Time)
# who don't have Daylight Saving Time, so we can represent them easily # who don't have Daylight Saving Time, so we can represent them easily
# with FixedOffset, and use them directly as tzinfo in the constructors. # with FixedOffset, and use them directly as tzinfo in the constructors.
@ -363,7 +358,6 @@ class NewDatabaseTests(TestCase):
self.assertEqual(Event.objects.filter(dt__gte=dt2).count(), 1) self.assertEqual(Event.objects.filter(dt__gte=dt2).count(), 1)
self.assertEqual(Event.objects.filter(dt__gt=dt2).count(), 0) self.assertEqual(Event.objects.filter(dt__gt=dt2).count(), 0)
@requires_pytz
def test_query_filter_with_pytz_timezones(self): def test_query_filter_with_pytz_timezones(self):
tz = pytz.timezone('Europe/Paris') tz = pytz.timezone('Europe/Paris')
dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=tz) dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=tz)
@ -908,7 +902,6 @@ class TemplateTests(SimpleTestCase):
expected = results[k1][k2] expected = results[k1][k2]
self.assertEqual(actual, expected, '%s / %s: %r != %r' % (k1, k2, actual, expected)) self.assertEqual(actual, expected, '%s / %s: %r != %r' % (k1, k2, actual, expected))
@requires_pytz
def test_localtime_filters_with_pytz(self): def test_localtime_filters_with_pytz(self):
""" """
Test the |localtime, |utc, and |timezone filters with pytz. Test the |localtime, |utc, and |timezone filters with pytz.
@ -976,7 +969,6 @@ class TemplateTests(SimpleTestCase):
"2011-09-01T13:20:30+03:00|2011-09-01T17:20:30+07:00|2011-09-01T13:20:30+03:00" "2011-09-01T13:20:30+03:00|2011-09-01T17:20:30+07:00|2011-09-01T13:20:30+03:00"
) )
@requires_pytz
def test_timezone_templatetag_with_pytz(self): def test_timezone_templatetag_with_pytz(self):
""" """
Test the {% timezone %} templatetag with pytz. Test the {% timezone %} templatetag with pytz.
@ -996,7 +988,7 @@ class TemplateTests(SimpleTestCase):
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(ValueError if pytz is None else pytz.UnknownTimeZoneError): with 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.startswith('win'), "Windows uses non-standard time zone names") @skipIf(sys.platform.startswith('win'), "Windows uses non-standard time zone names")
@ -1006,7 +998,7 @@ class TemplateTests(SimpleTestCase):
""" """
tpl = Template("{% load tz %}{% get_current_timezone as time_zone %}{{ time_zone }}") tpl = Template("{% load tz %}{% get_current_timezone as time_zone %}{{ time_zone }}")
self.assertEqual(tpl.render(Context()), "Africa/Nairobi" if pytz else "EAT") self.assertEqual(tpl.render(Context()), "Africa/Nairobi")
with timezone.override(UTC): with timezone.override(UTC):
self.assertEqual(tpl.render(Context()), "UTC") self.assertEqual(tpl.render(Context()), "UTC")
@ -1019,7 +1011,6 @@ class TemplateTests(SimpleTestCase):
with timezone.override(UTC): with timezone.override(UTC):
self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700") self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700")
@requires_pytz
def test_get_current_timezone_templatetag_with_pytz(self): def test_get_current_timezone_templatetag_with_pytz(self):
""" """
Test the {% get_current_timezone %} templatetag with pytz. Test the {% get_current_timezone %} templatetag with pytz.
@ -1048,7 +1039,7 @@ class TemplateTests(SimpleTestCase):
context = Context() context = Context()
self.assertEqual(tpl.render(context), "") self.assertEqual(tpl.render(context), "")
request_context = RequestContext(HttpRequest(), processors=[context_processors.tz]) request_context = RequestContext(HttpRequest(), processors=[context_processors.tz])
self.assertEqual(tpl.render(request_context), "Africa/Nairobi" if pytz else "EAT") self.assertEqual(tpl.render(request_context), "Africa/Nairobi")
@requires_tz_support @requires_tz_support
def test_date_and_time_template_filters(self): def test_date_and_time_template_filters(self):
@ -1068,15 +1059,6 @@ class TemplateTests(SimpleTestCase):
with timezone.override(ICT): with timezone.override(ICT):
self.assertEqual(tpl.render(ctx), "2011-09-01 at 20:20:20") self.assertEqual(tpl.render(ctx), "2011-09-01 at 20:20:20")
def test_localtime_with_time_zone_setting_set_to_none(self):
# Regression for #17274
tpl = Template("{% load tz %}{{ dt }}")
ctx = Context({'dt': datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=EAT)})
with self.settings(TIME_ZONE=None):
# the actual value depends on the system time zone of the host
self.assertTrue(tpl.render(ctx).startswith("2011"))
@requires_tz_support @requires_tz_support
def test_now_template_tag_uses_current_time_zone(self): def test_now_template_tag_uses_current_time_zone(self):
# Regression for #17343 # Regression for #17343
@ -1094,7 +1076,6 @@ class LegacyFormsTests(TestCase):
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30)) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30))
@requires_pytz
def test_form_with_non_existent_time(self): def test_form_with_non_existent_time(self):
form = EventForm({'dt': '2011-03-27 02:30:00'}) form = EventForm({'dt': '2011-03-27 02:30:00'})
with timezone.override(pytz.timezone('Europe/Paris')): with timezone.override(pytz.timezone('Europe/Paris')):
@ -1102,7 +1083,6 @@ class LegacyFormsTests(TestCase):
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 3, 27, 2, 30, 0)) self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 3, 27, 2, 30, 0))
@requires_pytz
def test_form_with_ambiguous_time(self): def test_form_with_ambiguous_time(self):
form = EventForm({'dt': '2011-10-30 02:30:00'}) form = EventForm({'dt': '2011-10-30 02:30:00'})
with timezone.override(pytz.timezone('Europe/Paris')): with timezone.override(pytz.timezone('Europe/Paris')):
@ -1141,7 +1121,6 @@ class NewFormsTests(TestCase):
# Datetime inputs formats don't allow providing a time zone. # Datetime inputs formats don't allow providing a time zone.
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
@requires_pytz
def test_form_with_non_existent_time(self): def test_form_with_non_existent_time(self):
with timezone.override(pytz.timezone('Europe/Paris')): with timezone.override(pytz.timezone('Europe/Paris')):
form = EventForm({'dt': '2011-03-27 02:30:00'}) form = EventForm({'dt': '2011-03-27 02:30:00'})
@ -1153,7 +1132,6 @@ class NewFormsTests(TestCase):
] ]
) )
@requires_pytz
def test_form_with_ambiguous_time(self): def test_form_with_ambiguous_time(self):
with timezone.override(pytz.timezone('Europe/Paris')): with timezone.override(pytz.timezone('Europe/Paris')):
form = EventForm({'dt': '2011-10-30 02:30:00'}) form = EventForm({'dt': '2011-10-30 02:30:00'})

View File

@ -1,8 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from datetime import date, datetime from datetime import date, datetime
from unittest import skipIf
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
from django.test.utils import TZ_SUPPORT, requires_tz_support from django.test.utils import TZ_SUPPORT, requires_tz_support
@ -12,11 +10,6 @@ from django.utils.timezone import (
get_default_timezone, get_fixed_timezone, make_aware, utc, get_default_timezone, get_fixed_timezone, make_aware, utc,
) )
try:
import pytz
except ImportError:
pytz = None
@override_settings(TIME_ZONE='Europe/Copenhagen') @override_settings(TIME_ZONE='Europe/Copenhagen')
class DateFormatTests(SimpleTestCase): class DateFormatTests(SimpleTestCase):
@ -36,18 +29,16 @@ class DateFormatTests(SimpleTestCase):
dt = datetime(2009, 5, 16, 5, 30, 30) dt = datetime(2009, 5, 16, 5, 30, 30)
self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt) self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt)
@skipIf(sys.platform.startswith('win') and not pytz, "Test requires pytz on Windows")
def test_naive_ambiguous_datetime(self): def test_naive_ambiguous_datetime(self):
# dt is ambiguous in Europe/Copenhagen. LocalTimezone guesses the # dt is ambiguous in Europe/Copenhagen. pytz raises an exception for
# offset (and gets it wrong 50% of the time) while pytz refuses the # the ambiguity, which results in an empty string.
# temptation to guess. In any case, this shouldn't crash.
dt = datetime(2015, 10, 25, 2, 30, 0) dt = datetime(2015, 10, 25, 2, 30, 0)
# Try all formatters that involve self.timezone. # Try all formatters that involve self.timezone.
self.assertEqual(format(dt, 'I'), '0' if pytz is None else '') self.assertEqual(format(dt, 'I'), '')
self.assertEqual(format(dt, 'O'), '+0100' if pytz is None else '') self.assertEqual(format(dt, 'O'), '')
self.assertEqual(format(dt, 'T'), 'CET' if pytz is None else '') self.assertEqual(format(dt, 'T'), '')
self.assertEqual(format(dt, 'Z'), '3600' if pytz is None else '') self.assertEqual(format(dt, 'Z'), '')
@requires_tz_support @requires_tz_support
def test_datetime_with_local_tzinfo(self): def test_datetime_with_local_tzinfo(self):

View File

@ -1,21 +1,12 @@
import copy
import datetime import datetime
import pickle
import sys import sys
import unittest
import pytz
from django.test import SimpleTestCase, mock, override_settings from django.test import SimpleTestCase, mock, override_settings
from django.utils import timezone from django.utils import timezone
try: CET = pytz.timezone("Europe/Paris")
import pytz
except ImportError:
pytz = None
requires_pytz = unittest.skipIf(pytz is None, "this test requires pytz")
if pytz is not None:
CET = pytz.timezone("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
@ -24,33 +15,6 @@ PY36 = sys.version_info >= (3, 6)
class TimezoneTests(SimpleTestCase): class TimezoneTests(SimpleTestCase):
def test_localtime(self):
now = datetime.datetime.utcnow().replace(tzinfo=timezone.utc)
local_tz = timezone.LocalTimezone()
with timezone.override(local_tz):
local_now = timezone.localtime(now)
self.assertEqual(local_now.tzinfo, local_tz)
local_now = timezone.localtime(now, timezone=local_tz)
self.assertEqual(local_now.tzinfo, local_tz)
def test_localtime_naive(self):
now = datetime.datetime.now()
if PY36:
self.assertEqual(timezone.localtime(now), now.replace(tzinfo=timezone.LocalTimezone()))
else:
with self.assertRaisesMessage(ValueError, 'astimezone() cannot be applied to a naive datetime'):
timezone.localtime(now)
def test_localtime_out_of_range(self):
local_tz = timezone.LocalTimezone()
long_ago = datetime.datetime(1900, 1, 1, tzinfo=timezone.utc)
try:
timezone.localtime(long_ago, local_tz)
except (OverflowError, ValueError) as exc:
self.assertIn("install pytz", exc.args[0])
else:
self.skipTest("Failed to trigger an OverflowError or ValueError")
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()))
@ -133,18 +97,6 @@ class TimezoneTests(SimpleTestCase):
finally: finally:
timezone.deactivate() timezone.deactivate()
def test_copy(self):
self.assertIsInstance(copy.copy(timezone.UTC()), timezone.UTC)
self.assertIsInstance(copy.copy(timezone.LocalTimezone()), timezone.LocalTimezone)
def test_deepcopy(self):
self.assertIsInstance(copy.deepcopy(timezone.UTC()), timezone.UTC)
self.assertIsInstance(copy.deepcopy(timezone.LocalTimezone()), timezone.LocalTimezone)
def test_pickling_unpickling(self):
self.assertIsInstance(pickle.loads(pickle.dumps(timezone.UTC())), timezone.UTC)
self.assertIsInstance(pickle.loads(pickle.dumps(timezone.LocalTimezone())), timezone.LocalTimezone)
def test_is_aware(self): def test_is_aware(self):
self.assertTrue(timezone.is_aware(datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT))) self.assertTrue(timezone.is_aware(datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)))
self.assertFalse(timezone.is_aware(datetime.datetime(2011, 9, 1, 13, 20, 30))) self.assertFalse(timezone.is_aware(datetime.datetime(2011, 9, 1, 13, 20, 30)))
@ -175,7 +127,6 @@ class TimezoneTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, 'astimezone() cannot be applied to a naive datetime'): with self.assertRaisesMessage(ValueError, 'astimezone() cannot be applied to a naive datetime'):
timezone.make_naive(*args) timezone.make_naive(*args)
@requires_pytz
def test_make_aware2(self): def test_make_aware2(self):
self.assertEqual( self.assertEqual(
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), CET), timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), CET),
@ -183,7 +134,6 @@ class TimezoneTests(SimpleTestCase):
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)
@requires_pytz
def test_make_aware_pytz(self): def test_make_aware_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),
@ -202,7 +152,6 @@ class TimezoneTests(SimpleTestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30), CET) timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30), CET)
@requires_pytz
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)
@ -216,7 +165,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))
@requires_pytz
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)
@ -229,9 +177,3 @@ class TimezoneTests(SimpleTestCase):
self.assertEqual(std - dst, datetime.timedelta(hours=1)) self.assertEqual(std - dst, datetime.timedelta(hours=1))
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))
# round trip to UTC then back to CET
std = timezone.localtime(timezone.localtime(std, timezone.UTC()), CET)
dst = timezone.localtime(timezone.localtime(dst, timezone.UTC()), CET)
self.assertEqual((std.hour, std.minute), (3, 30))
self.assertEqual((dst.hour, dst.minute), (1, 30))