summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2016-10-07 21:06:49 -0400
committerTim Graham <timograham@gmail.com>2016-10-27 08:53:20 -0400
commit414ad25b090a63eaaf297b1164c8f7d814a710a2 (patch)
treee3d0e6fdffee0793baad41a339c2d63db742c33a
parentd84ffcc22b2a0dbc0a44a67d56da7e3647e2f87a (diff)
Fixed #27327 -- Simplified time zone handling by requiring pytz.
-rw-r--r--django/db/backends/base/base.py12
-rw-r--r--django/db/backends/mysql/features.py13
-rw-r--r--django/db/backends/oracle/features.py6
-rw-r--r--django/db/backends/sqlite3/base.py7
-rw-r--r--django/db/backends/sqlite3/features.py9
-rw-r--r--django/db/backends/sqlite3/operations.py15
-rw-r--r--django/db/models/functions/datetime.py2
-rw-r--r--django/templatetags/tz.py15
-rw-r--r--django/utils/timezone.py120
-rw-r--r--docs/internals/contributing/writing-code/coding-style.txt4
-rw-r--r--docs/internals/contributing/writing-code/unit-tests.txt2
-rw-r--r--docs/ref/models/querysets.txt6
-rw-r--r--docs/ref/settings.txt31
-rw-r--r--docs/ref/utils.txt29
-rw-r--r--docs/releases/1.11.txt21
-rw-r--r--docs/spelling_wordlist1
-rw-r--r--docs/topics/i18n/timezones.txt48
-rw-r--r--setup.py1
-rw-r--r--tests/admin_widgets/tests.py9
-rw-r--r--tests/cache/tests.py15
-rw-r--r--tests/datetimes/tests.py7
-rw-r--r--tests/db_functions/test_datetime.py9
-rw-r--r--tests/file_storage/tests.py18
-rw-r--r--tests/humanize_tests/tests.py8
-rw-r--r--tests/requirements/base.txt1
-rw-r--r--tests/schema/tests.py4
-rw-r--r--tests/syndication_tests/tests.py5
-rw-r--r--tests/timezones/tests.py32
-rw-r--r--tests/utils_tests/test_dateformat.py21
-rw-r--r--tests/utils_tests/test_timezone.py64
30 files changed, 109 insertions, 426 deletions
diff --git a/django/db/backends/base/base.py b/django/db/backends/base/base.py
index 595d191dce..81f53685ca 100644
--- a/django/db/backends/base/base.py
+++ b/django/db/backends/base/base.py
@@ -4,6 +4,8 @@ import warnings
from collections import deque
from contextlib import contextmanager
+import pytz
+
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
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.six.moves import _thread as thread
-try:
- import pytz
-except ImportError:
- pytz = None
-
NO_DB_ALIAS = '__no_db__'
@@ -128,7 +125,6 @@ class BaseDatabaseWrapper(object):
elif self.settings_dict['TIME_ZONE'] is None:
return timezone.utc
else:
- # Only this branch requires pytz.
return pytz.timezone(self.settings_dict['TIME_ZONE'])
@cached_property
@@ -207,10 +203,6 @@ class BaseDatabaseWrapper(object):
raise ImproperlyConfigured(
"Connection '%s' cannot set TIME_ZONE because its engine "
"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):
"""
diff --git a/django/db/backends/mysql/features.py b/django/db/backends/mysql/features.py
index f8c0ddea00..81494efa63 100644
--- a/django/db/backends/mysql/features.py
+++ b/django/db/backends/mysql/features.py
@@ -3,11 +3,6 @@ from django.utils.functional import cached_property
from .base import Database
-try:
- import pytz
-except ImportError:
- pytz = None
-
class DatabaseFeatures(BaseDatabaseFeatures):
empty_fetchmany_value = ()
@@ -56,14 +51,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
@cached_property
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.
with self.connection.cursor() as cursor:
cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1")
diff --git a/django/db/backends/oracle/features.py b/django/db/backends/oracle/features.py
index 956f7a051b..f09dee8425 100644
--- a/django/db/backends/oracle/features.py
+++ b/django/db/backends/oracle/features.py
@@ -1,11 +1,6 @@
from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.utils import InterfaceError
-try:
- import pytz
-except ImportError:
- pytz = None
-
class DatabaseFeatures(BaseDatabaseFeatures):
empty_fetchmany_value = ()
@@ -19,7 +14,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_subqueries_in_group_by = False
supports_transactions = True
supports_timezones = False
- has_zoneinfo_database = pytz is not None
supports_bitwise_or = False
has_native_duration_field = True
can_defer_constraint_checks = True
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index ff78adaffa..66ad27842b 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -11,6 +11,8 @@ import decimal
import re
import warnings
+import pytz
+
from django.conf import settings
from django.db import utils
from django.db.backends import utils as backend_utils
@@ -24,11 +26,6 @@ from django.utils.encoding import force_text
from django.utils.safestring import SafeBytes
try:
- import pytz
-except ImportError:
- pytz = None
-
-try:
try:
from pysqlite2 import dbapi2 as Database
except ImportError:
diff --git a/django/db/backends/sqlite3/features.py b/django/db/backends/sqlite3/features.py
index bee26f2da4..43bec87c99 100644
--- a/django/db/backends/sqlite3/features.py
+++ b/django/db/backends/sqlite3/features.py
@@ -7,11 +7,6 @@ from django.utils.functional import cached_property
from .base import Database
-try:
- import pytz
-except ImportError:
- pytz = None
-
class DatabaseFeatures(BaseDatabaseFeatures):
# SQLite cannot handle us only partially reading from a cursor's result set
@@ -78,7 +73,3 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_support = False
cursor.execute('DROP TABLE STDDEV_TEST')
return has_support
-
- @cached_property
- def has_zoneinfo_database(self):
- return pytz is not None
diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py
index 7f6de932ad..47a26b5165 100644
--- a/django/db/backends/sqlite3/operations.py
+++ b/django/db/backends/sqlite3/operations.py
@@ -4,7 +4,7 @@ import datetime
import uuid
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.backends import utils as backend_utils
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.duration import duration_string
-try:
- import pytz
-except ImportError:
- pytz = None
-
class DatabaseOperations(BaseDatabaseOperations):
def bulk_batch_size(self, fields, objs):
@@ -77,27 +72,19 @@ class DatabaseOperations(BaseDatabaseOperations):
# cause a collision with a 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):
- self._require_pytz()
return "django_datetime_cast_date(%s, %%s)" % 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]
def datetime_extract_sql(self, lookup_type, field_name, tzname):
# Same comment as in date_extract_sql.
- self._require_pytz()
return "django_datetime_extract('%s', %s, %%s)" % (
lookup_type.lower(), field_name), [tzname]
def datetime_trunc_sql(self, lookup_type, field_name, tzname):
# Same comment as in date_trunc_sql.
- self._require_pytz()
return "django_datetime_trunc('%s', %s, %%s)" % (
lookup_type.lower(), field_name), [tzname]
diff --git a/django/db/models/functions/datetime.py b/django/db/models/functions/datetime.py
index 9cbdcb0231..6294bc3b94 100644
--- a/django/db/models/functions/datetime.py
+++ b/django/db/models/functions/datetime.py
@@ -191,7 +191,7 @@ class TruncBase(TimezoneMixin, Transform):
if value is None:
raise ValueError(
"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 = timezone.make_aware(value, self.tzinfo)
diff --git a/django/templatetags/tz.py b/django/templatetags/tz.py
index 68e40ebb19..4088ba7eb8 100644
--- a/django/templatetags/tz.py
+++ b/django/templatetags/tz.py
@@ -1,14 +1,10 @@
from datetime import datetime, tzinfo
+import pytz
+
from django.template import Library, Node, TemplateSyntaxError
from django.utils import six, timezone
-try:
- import pytz
-except ImportError:
- pytz = None
-
-
register = Library()
@@ -44,7 +40,6 @@ def do_timezone(value, arg):
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.
- If it is a time zone name, pytz is required.
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
if isinstance(arg, tzinfo):
tz = arg
- elif isinstance(arg, six.string_types) and pytz is not None:
+ elif isinstance(arg, six.string_types):
try:
tz = pytz.timezone(arg)
except pytz.UnknownTimeZoneError:
@@ -156,8 +151,8 @@ def timezone_tag(parser, token):
Enables a given time zone just for this block.
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.
- If it is ``None``, the default time zone is used within the block.
+ time zone name, or ``None``. If it is ``None``, the default time zone is
+ used within the block.
Sample usage::
diff --git a/django/utils/timezone.py b/django/utils/timezone.py
index e2c6967607..82c14f4f96 100644
--- a/django/utils/timezone.py
+++ b/django/utils/timezone.py
@@ -1,24 +1,16 @@
"""
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 threading import local
+import pytz
+
from django.conf import settings
from django.utils import lru_cache, six
from django.utils.decorators import ContextDecorator
-try:
- import pytz
-except ImportError:
- pytz = None
-
-
__all__ = [
'utc', 'get_fixed_timezone',
'get_default_timezone', 'get_default_timezone_name',
@@ -34,26 +26,6 @@ __all__ = [
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):
"""
Fixed offset in minutes east from UTC. Taken from Python's docs.
@@ -78,79 +50,7 @@ class FixedOffset(tzinfo):
def dst(self, dt):
return ZERO
-
-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 = pytz.utc
"""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.
"""
- if isinstance(settings.TIME_ZONE, six.string_types) and pytz is not None:
- return pytz.timezone(settings.TIME_ZONE)
- else:
- # This relies on os.environ['TZ'] being set to settings.TIME_ZONE.
- return LocalTimezone()
+ return pytz.timezone(settings.TIME_ZONE)
# 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.
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):
_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)
else:
raise ValueError("Invalid timezone: %r" % timezone)
@@ -257,8 +153,8 @@ class override(ContextDecorator):
on exit.
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.
- If it is ``None``, Django enables the default time zone.
+ time zone name, or ``None``. If it is ``None``, Django enables the default
+ time zone.
"""
def __init__(self, timezone):
self.timezone = timezone
diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt
index 137ab3e67f..39f49f10e6 100644
--- a/docs/internals/contributing/writing-code/coding-style.txt
+++ b/docs/internals/contributing/writing-code/coding-style.txt
@@ -117,9 +117,9 @@ Imports
# try/except
try:
- import pytz
+ import yaml
except ImportError:
- pytz = None
+ yaml = None
CONSTANT = 'foo'
diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt
index d5fdb72561..bb77eced7d 100644
--- a/docs/internals/contributing/writing-code/unit-tests.txt
+++ b/docs/internals/contributing/writing-code/unit-tests.txt
@@ -232,7 +232,7 @@ dependencies:
* numpy_
* Pillow_
* PyYAML_
-* pytz_
+* pytz_ (required)
* setuptools_
* memcached_, plus a :ref:`supported Python binding <memcached>`
* mock_ (for Python 2)
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index f1c3864377..aa25cd2c0e 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -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
``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`_).
- Oracle: no requirements (see `Choosing a Time Zone File`_).
- - MySQL: install pytz_ and 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/static/datatype-datetime.html#DATATYPE-TIMEZONES
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 223362c0f6..cf4da849c3 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -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
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
(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.
@@ -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.
-.. _pytz: http://pytz.sourceforge.net/
-
.. setting:: USER
``USER``
@@ -2478,8 +2474,8 @@ See also :setting:`DATE_INPUT_FORMATS` and :setting:`DATETIME_INPUT_FORMATS`.
Default: ``'America/Chicago'``
-A string representing the time zone for this installation, or ``None``. See
-the `list of time zones`_.
+A string representing the time zone for this installation. See the `list of
+time zones`_.
.. note::
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
to interpret datetimes entered in forms.
-Django sets the ``os.environ['TZ']`` variable to the time zone you specify in
-the :setting:`TIME_ZONE` setting. Thus, all your views and models will
+On Unix environments (where :func:`time.tzset` is implemented), Django sets the
+``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``
-environment variable under the following conditions:
-
-* If you're using the manual configuration option as described in
- :ref:`manually configuring settings
- <settings-without-django-settings-module>`, or
-
-* 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.
+environment variable if you're using the manual configuration option as
+described in :ref:`manually configuring settings
+<settings-without-django-settings-module>`. 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::
Django cannot reliably use alternate time zones in a Windows environment.
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index 45ffd80fc4..320f5db795 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -966,7 +966,7 @@ appropriate entities.
Sets the :ref:`current time zone <default-current-time-zone>`. The
``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()
@@ -1043,21 +1043,18 @@ appropriate entities.
:class:`~datetime.datetime`. If ``timezone`` is set to ``None``, it
defaults to the :ref:`current time zone <default-current-time-zone>`.
- When pytz_ is installed, the exception ``pytz.AmbiguousTimeError``
- will be raised if you try to make ``value`` aware during a DST transition
- where the same time occurs twice (when reverting from DST). Setting
- ``is_dst`` to ``True`` or ``False`` will avoid the exception by choosing if
- the time is pre-transition or post-transition respectively.
+ The ``pytz.AmbiguousTimeError`` exception is raised if you try to make
+ ``value`` aware during a DST transition where the same time occurs twice
+ (when reverting from DST). Setting ``is_dst`` to ``True`` or ``False`` will
+ avoid the exception by choosing if the time is pre-transition or
+ post-transition respectively.
- When pytz_ is installed, the exception ``pytz.NonExistentTimeError``
- will be raised if you try to make ``value`` aware during a DST transition
- such that the time never occurred (when entering into DST). Setting
- ``is_dst`` to ``True`` or ``False`` will avoid the exception by moving the
- hour backwards or forwards by 1 respectively. For example, ``is_dst=True``
- would change a non-existent time of 2:30 to 1:30 and ``is_dst=False``
- would change the time to 3:30.
-
- ``is_dst`` has no effect when ``pytz`` is not installed.
+ The ``pytz.NonExistentTimeError`` exception is raised if you try to make
+ ``value`` aware during a DST transition such that the time never occurred
+ (when entering into DST). Setting ``is_dst`` to ``True`` or ``False`` will
+ avoid the exception by moving the hour backwards or forwards by 1
+ respectively. For example, ``is_dst=True`` would change a non-existent
+ time of 2:30 to 1:30 and ``is_dst=False`` would change the time to 3:30.
.. function:: make_naive(value, timezone=None)
@@ -1066,8 +1063,6 @@ appropriate entities.
aware :class:`~datetime.datetime`. If ``timezone`` is set to ``None``, it
defaults to the :ref:`current time zone <default-current-time-zone>`.
-.. _pytz: http://pytz.sourceforge.net/
-
``django.utils.translation``
============================
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index 653de07caf..27f7481fa6 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -468,6 +468,27 @@ To prevent typos from passing silently,
arguments are model fields. This should be backwards-incompatible only in the
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
-------------
diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist
index 8218fe0219..13553bc5f1 100644
--- a/docs/spelling_wordlist
+++ b/docs/spelling_wordlist
@@ -622,7 +622,6 @@ Pygments
pysqlite
pythonic
Pythonista
-pytz
qs
Québec
queryset
diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt
index a5848bf33e..ecabefb421 100644
--- a/docs/topics/i18n/timezones.txt
+++ b/docs/topics/i18n/timezones.txt
@@ -26,14 +26,12 @@ to this problem is to use UTC in the code and use local time only when
interacting with end users.
Time zone support is disabled by default. To enable it, set :setting:`USE_TZ =
-True <USE_TZ>` in your settings file. Installing pytz_ is highly recommended,
-but may not be mandatory depending on your particular database backend,
-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:
+True <USE_TZ>` in your settings file. Time zone support uses pytz_, which is
+installed when you install Django.
-.. code-block:: console
+.. versionchanged:: 1.11
- $ pip install pytz
+ Older versions don't require ``pytz`` or install it automatically.
.. 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.
Unfortunately, during DST transitions, some datetimes don't exist or are
-ambiguous. In such situations, pytz_ raises an exception. Other
-:class:`~datetime.tzinfo` implementations, such as the local time zone used as
-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.
+ambiguous. In such situations, pytz_ raises an exception. 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 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.
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::
@@ -405,9 +400,8 @@ Code
----
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
-work. If you create naive datetime objects in your code, Django makes them
-aware when necessary.
+file. At this point, things should mostly work. If you create naive datetime
+objects in your code, Django makes them aware when necessary.
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
@@ -523,22 +517,7 @@ Setup
one year is 2011-02-28 or 2011-03-01, which depends on your business
requirements.)
-3. **Should I install pytz?**
-
- 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?**
+3. **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
time zone for this database in the :setting:`DATABASES` setting.
@@ -653,8 +632,8 @@ Troubleshooting
>>> local.date()
datetime.date(2012, 3, 3)
-4. **I get an error** "``Are time zone definitions for your database and pytz
- installed?``" **pytz is installed, so I guess the problem is my database?**
+4. **I get an error** "``Are time zone definitions for your database
+ installed?``"
If you are using MySQL, see the :ref:`mysql-time-zone-definitions` section
of the MySQL notes for instructions on loading time zone definitions.
@@ -700,8 +679,7 @@ Usage
>>> timezone.localtime(timezone.now())
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
- ``"Europe/Paris"``.
+ In this example, the current time zone is ``"Europe/Paris"``.
3. **How can I see all available time zones?**
diff --git a/setup.py b/setup.py
index 2d7080a131..88e3f72468 100644
--- a/setup.py
+++ b/setup.py
@@ -47,6 +47,7 @@ setup(
entry_points={'console_scripts': [
'django-admin = django.core.management:execute_from_command_line',
]},
+ install_requires=['pytz'],
extras_require={
"bcrypt": ["bcrypt"],
"argon2": ["argon2-cffi >= 16.1.0"],
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 057771bfa4..443e6c6e18 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -5,7 +5,8 @@ import gettext
import os
from datetime import datetime, timedelta
from importlib import import_module
-from unittest import skipIf
+
+import pytz
from django import forms
from django.conf import settings
@@ -23,11 +24,6 @@ from django.utils import six, translation
from . import models
from .widgetadmin import site as widget_admin_site
-try:
- import pytz
-except ImportError:
- pytz = None
-
class TestDataMixin(object):
@@ -794,7 +790,6 @@ class DateTimePickerSeleniumTests(AdminWidgetSeleniumTestCase):
self.wait_for_text('#calendarin0 caption', expected_caption)
-@skipIf(pytz is None, "this test requires pytz")
@override_settings(TIME_ZONE='Asia/Singapore')
class DateTimePickerShortcutsSeleniumTests(AdminWidgetSeleniumTestCase):
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 122017b54e..832727d53c 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -1832,17 +1832,16 @@ class CacheI18nTest(TestCase):
@override_settings(USE_I18N=False, USE_L10N=False, USE_TZ=True)
def test_cache_key_with_non_ascii_tzname(self):
- # Regression test for #17476
- class CustomTzName(timezone.UTC):
- name = ''
-
- def tzname(self, dt):
- return self.name
+ # Timezone-dependent cache keys should use ASCII characters only
+ # (#17476). The implementation here is a bit odd (timezone.utc is an
+ # instance, not a class), but it simulates the correct conditions.
+ class CustomTzName(timezone.utc):
+ pass
request = self.factory.get(self.path)
response = HttpResponse()
- with timezone.override(CustomTzName()):
- CustomTzName.name = 'Hora estándar de Argentina'.encode('UTF-8') # UTF-8 string
+ with timezone.override(CustomTzName):
+ CustomTzName.zone = 'Hora estándar de Argentina'.encode('UTF-8') # UTF-8 string
sanitized_name = 'Hora_estndar_de_Argentina'
self.assertIn(
sanitized_name, learn_cache_key(request, response),
diff --git a/tests/datetimes/tests.py b/tests/datetimes/tests.py
index efef456c43..273333e61c 100644
--- a/tests/datetimes/tests.py
+++ b/tests/datetimes/tests.py
@@ -1,18 +1,12 @@
from __future__ import unicode_literals
import datetime
-from unittest import skipIf
from django.test import TestCase, override_settings
from django.utils import timezone
from .models import Article, Category, Comment
-try:
- import pytz
-except ImportError:
- pytz = None
-
class DateTimesTests(TestCase):
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)
def test_21432(self):
now = timezone.localtime(timezone.now().replace(microsecond=0))
diff --git a/tests/db_functions/test_datetime.py b/tests/db_functions/test_datetime.py
index 1b44c9a658..f9a9a54c1c 100644
--- a/tests/db_functions/test_datetime.py
+++ b/tests/db_functions/test_datetime.py
@@ -1,7 +1,8 @@
from __future__ import unicode_literals
from datetime import datetime
-from unittest import skipIf
+
+import pytz
from django.conf import settings
from django.db import connection
@@ -16,11 +17,6 @@ from django.utils import timezone
from .models import DTModel
-try:
- import pytz
-except ImportError:
- pytz = None
-
def microsecond_support(value):
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())))
-@skipIf(pytz is None, "this test requires pytz")
@override_settings(USE_TZ=True, TIME_ZONE='UTC')
class DateFunctionWithTimeZoneTests(DateFunctionTests):
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index acb663ef7a..5492f105f7 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -32,12 +32,6 @@ from django.utils.six.moves.urllib.request import urlopen
from .models import Storage, temp_storage, temp_storage_location
-try:
- import pytz
-except ImportError:
- pytz = None
-
-
FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
@@ -99,10 +93,6 @@ class FileSystemStorageTests(unittest.TestCase):
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):
storage_class = FileSystemStorage
@@ -157,7 +147,6 @@ class FileStorageTests(SimpleTestCase):
# different.
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)):
# At this point the system TZ is +1 and the Django TZ
# is -5. The following will be aware in UTC.
@@ -191,7 +180,6 @@ class FileStorageTests(SimpleTestCase):
# different.
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)):
# At this point the system TZ is +1 and the Django TZ
# is -5.
@@ -220,7 +208,6 @@ class FileStorageTests(SimpleTestCase):
_dt = timezone.make_aware(dt, now_in_algiers.tzinfo)
self.assertLess(abs(_dt - now_in_algiers), timedelta(seconds=2))
- @requires_pytz
def test_file_get_accessed_time(self):
"""
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.assertLess(timezone.now() - self.storage.get_accessed_time(f_name), timedelta(seconds=2))
- @requires_pytz
@requires_tz_support
def test_file_get_accessed_time_timezone(self):
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.assertLess(datetime.now() - self.storage.accessed_time(f_name), timedelta(seconds=2))
- @requires_pytz
def test_file_get_created_time(self):
"""
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.assertLess(timezone.now() - self.storage.get_created_time(f_name), timedelta(seconds=2))
- @requires_pytz
@requires_tz_support
def test_file_get_created_time_timezone(self):
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.assertLess(datetime.now() - self.storage.created_time(f_name), timedelta(seconds=2))
- @requires_pytz
def test_file_get_modified_time(self):
"""
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.assertLess(timezone.now() - self.storage.get_modified_time(f_name), timedelta(seconds=2))
- @requires_pytz
@requires_tz_support
def test_file_get_modified_time_timezone(self):
self._test_file_time_getter(self.storage.get_modified_time)
diff --git a/tests/humanize_tests/tests.py b/tests/humanize_tests/tests.py
index 22ae426954..a3cc512239 100644
--- a/tests/humanize_tests/tests.py
+++ b/tests/humanize_tests/tests.py
@@ -2,7 +2,6 @@ from __future__ import unicode_literals
import datetime
from decimal import Decimal
-from unittest import skipIf
from django.contrib.humanize.templatetags import humanize
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.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
# 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.
@@ -174,7 +167,6 @@ class HumanizeTests(SimpleTestCase):
# As 24h of difference they will never be the same
self.assertNotEqual(naturalday_one, naturalday_two)
- @skipIf(pytz is None, "this test requires pytz")
def test_naturalday_uses_localtime(self):
# Regression for #18504
# This is 2012-03-08HT19:30:00-06:00 in America/Chicago
diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt
index 374b836fad..f9cb570c62 100644
--- a/tests/requirements/base.txt
+++ b/tests/requirements/base.txt
@@ -8,7 +8,6 @@ Pillow
PyYAML
# pylibmc/libmemcached can't be built on Windows.
pylibmc; sys.platform != 'win32'
-pytz > dev
selenium
sqlparse
tblib
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 9aeada3e67..567a1f86da 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -22,7 +22,7 @@ from django.test import (
TransactionTestCase, mock, skipIfDBFeature, skipUnlessDBFeature,
)
from django.test.utils import CaptureQueriesContext
-from django.utils.timezone import UTC
+from django.utils import timezone
from .fields import (
CustomManyToManyField, InheritedManyToManyField, MediumBlobField,
@@ -2187,7 +2187,7 @@ class SchemaTests(TransactionTestCase):
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_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_tz.now = mock.MagicMock(return_value=now_tz)
# Create the table
diff --git a/tests/syndication_tests/tests.py b/tests/syndication_tests/tests.py
index 6f4655838a..60285efea7 100644
--- a/tests/syndication_tests/tests.py
+++ b/tests/syndication_tests/tests.py
@@ -16,11 +16,6 @@ from django.utils.feedgenerator import (
from .models import Article, Entry
-try:
- import pytz
-except ImportError:
- pytz = None
-
TZ = timezone.get_default_timezone()
diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py
index e72fff069b..d95877d227 100644
--- a/tests/timezones/tests.py
+++ b/tests/timezones/tests.py
@@ -8,6 +8,8 @@ from contextlib import contextmanager
from unittest import SkipTest, skipIf
from xml.dom.minidom import parseString
+import pytz
+
from django.contrib.auth.models import User
from django.core import serializers
from django.core.exceptions import ImproperlyConfigured
@@ -33,13 +35,6 @@ from .models import (
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)
# who don't have Daylight Saving Time, so we can represent them easily
# 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__gt=dt2).count(), 0)
- @requires_pytz
def test_query_filter_with_pytz_timezones(self):
tz = pytz.timezone('Europe/Paris')
dt = datetime.datetime(2011, 9, 1, 12, 20, 30, tzinfo=tz)
@@ -908,7 +902,6 @@ class TemplateTests(SimpleTestCase):
expected = results[k1][k2]
self.assertEqual(actual, expected, '%s / %s: %r != %r' % (k1, k2, actual, expected))
- @requires_pytz
def test_localtime_filters_with_pytz(self):
"""
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"
)
- @requires_pytz
def test_timezone_templatetag_with_pytz(self):
"""
Test the {% timezone %} templatetag with pytz.
@@ -996,7 +988,7 @@ class TemplateTests(SimpleTestCase):
def test_timezone_templatetag_invalid_argument(self):
with self.assertRaises(TemplateSyntaxError):
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'}))
@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 }}")
- self.assertEqual(tpl.render(Context()), "Africa/Nairobi" if pytz else "EAT")
+ self.assertEqual(tpl.render(Context()), "Africa/Nairobi")
with timezone.override(UTC):
self.assertEqual(tpl.render(Context()), "UTC")
@@ -1019,7 +1011,6 @@ class TemplateTests(SimpleTestCase):
with timezone.override(UTC):
self.assertEqual(tpl.render(Context({'tz': ICT})), "+0700")
- @requires_pytz
def test_get_current_timezone_templatetag_with_pytz(self):
"""
Test the {% get_current_timezone %} templatetag with pytz.
@@ -1048,7 +1039,7 @@ class TemplateTests(SimpleTestCase):
context = Context()
self.assertEqual(tpl.render(context), "")
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
def test_date_and_time_template_filters(self):
@@ -1068,15 +1059,6 @@ class TemplateTests(SimpleTestCase):
with timezone.override(ICT):
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
def test_now_template_tag_uses_current_time_zone(self):
# Regression for #17343
@@ -1094,7 +1076,6 @@ class LegacyFormsTests(TestCase):
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 9, 1, 13, 20, 30))
- @requires_pytz
def test_form_with_non_existent_time(self):
form = EventForm({'dt': '2011-03-27 02:30:00'})
with timezone.override(pytz.timezone('Europe/Paris')):
@@ -1102,7 +1083,6 @@ class LegacyFormsTests(TestCase):
self.assertTrue(form.is_valid())
self.assertEqual(form.cleaned_data['dt'], datetime.datetime(2011, 3, 27, 2, 30, 0))
- @requires_pytz
def test_form_with_ambiguous_time(self):
form = EventForm({'dt': '2011-10-30 02:30:00'})
with timezone.override(pytz.timezone('Europe/Paris')):
@@ -1141,7 +1121,6 @@ class NewFormsTests(TestCase):
# Datetime inputs formats don't allow providing a time zone.
self.assertFalse(form.is_valid())
- @requires_pytz
def test_form_with_non_existent_time(self):
with timezone.override(pytz.timezone('Europe/Paris')):
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):
with timezone.override(pytz.timezone('Europe/Paris')):
form = EventForm({'dt': '2011-10-30 02:30:00'})
diff --git a/tests/utils_tests/test_dateformat.py b/tests/utils_tests/test_dateformat.py
index bda69fb4cd..bd99b31a42 100644
--- a/tests/utils_tests/test_dateformat.py
+++ b/tests/utils_tests/test_dateformat.py
@@ -1,8 +1,6 @@
from __future__ import unicode_literals
-import sys
from datetime import date, datetime
-from unittest import skipIf
from django.test import SimpleTestCase, override_settings
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,
)
-try:
- import pytz
-except ImportError:
- pytz = None
-
@override_settings(TIME_ZONE='Europe/Copenhagen')
class DateFormatTests(SimpleTestCase):
@@ -36,18 +29,16 @@ class DateFormatTests(SimpleTestCase):
dt = datetime(2009, 5, 16, 5, 30, 30)
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):
- # dt is ambiguous in Europe/Copenhagen. LocalTimezone guesses the
- # offset (and gets it wrong 50% of the time) while pytz refuses the
- # temptation to guess. In any case, this shouldn't crash.
+ # dt is ambiguous in Europe/Copenhagen. pytz raises an exception for
+ # the ambiguity, which results in an empty string.
dt = datetime(2015, 10, 25, 2, 30, 0)
# Try all formatters that involve self.timezone.
- self.assertEqual(format(dt, 'I'), '0' if pytz is None else '')
- self.assertEqual(format(dt, 'O'), '+0100' if pytz is None else '')
- self.assertEqual(format(dt, 'T'), 'CET' if pytz is None else '')
- self.assertEqual(format(dt, 'Z'), '3600' if pytz is None else '')
+ self.assertEqual(format(dt, 'I'), '')
+ self.assertEqual(format(dt, 'O'), '')
+ self.assertEqual(format(dt, 'T'), '')
+ self.assertEqual(format(dt, 'Z'), '')
@requires_tz_support
def test_datetime_with_local_tzinfo(self):
diff --git a/tests/utils_tests/test_timezone.py b/tests/utils_tests/test_timezone.py
index 1be1d63b2c..2fe5445c48 100644
--- a/tests/utils_tests/test_timezone.py
+++ b/tests/utils_tests/test_timezone.py
@@ -1,21 +1,12 @@
-import copy
import datetime
-import pickle
import sys
-import unittest
+
+import pytz
from django.test import SimpleTestCase, mock, override_settings
from django.utils import timezone
-try:
- 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")
+CET = pytz.timezone("Europe/Paris")
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok
@@ -24,33 +15,6 @@ PY36 = sys.version_info >= (3, 6)
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):
with override_settings(USE_TZ=True):
self.assertTrue(timezone.is_aware(timezone.now()))
@@ -133,18 +97,6 @@ class TimezoneTests(SimpleTestCase):
finally:
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):
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)))
@@ -175,7 +127,6 @@ class TimezoneTests(SimpleTestCase):
with self.assertRaisesMessage(ValueError, 'astimezone() cannot be applied to a naive datetime'):
timezone.make_naive(*args)
- @requires_pytz
def test_make_aware2(self):
self.assertEqual(
timezone.make_aware(datetime.datetime(2011, 9, 1, 12, 20, 30), CET),
@@ -183,7 +134,6 @@ class TimezoneTests(SimpleTestCase):
with self.assertRaises(ValueError):
timezone.make_aware(CET.localize(datetime.datetime(2011, 9, 1, 12, 20, 30)), CET)
- @requires_pytz
def test_make_aware_pytz(self):
self.assertEqual(
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):
timezone.make_naive(datetime.datetime(2011, 9, 1, 12, 20, 30), CET)
- @requires_pytz
def test_make_aware_pytz_ambiguous(self):
# 2:30 happens twice, once before DST ends and once after
ambiguous = datetime.datetime(2015, 10, 25, 2, 30)
@@ -216,7 +165,6 @@ class TimezoneTests(SimpleTestCase):
self.assertEqual(std.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
self.assertEqual(dst.tzinfo.utcoffset(dst), datetime.timedelta(hours=2))
- @requires_pytz
def test_make_aware_pytz_non_existent(self):
# 2:30 never happened due to DST
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.tzinfo.utcoffset(std), datetime.timedelta(hours=1))
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))