Browse Source

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

Tim Graham 8 years ago
parent
commit
414ad25b09

+ 2 - 10
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):
         """

+ 0 - 13
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")

+ 0 - 6
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

+ 2 - 5
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
@@ -23,11 +25,6 @@ from django.utils.deprecation import RemovedInDjango20Warning
 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

+ 0 - 9
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

+ 1 - 14
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]
 

+ 1 - 1
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)

+ 5 - 10
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::
 

+ 8 - 112
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

+ 2 - 2
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'
 

+ 1 - 1
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)

+ 3 - 3
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

+ 10 - 21
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.

+ 12 - 17
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``
 ============================
 

+ 21 - 0
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
 -------------
 

+ 0 - 1
docs/spelling_wordlist

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

+ 13 - 35
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?**
 

+ 1 - 0
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"],

+ 2 - 7
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):
 

+ 7 - 8
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),

+ 0 - 7
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))

+ 2 - 7
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):
 

+ 0 - 18
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)

+ 0 - 8
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

+ 0 - 1
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

+ 2 - 2
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

+ 0 - 5
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()
 
 

+ 5 - 27
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'})

+ 6 - 15
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):

+ 3 - 61
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))