Skip to content

Commit

Permalink
Fixed #17262 -- Refactored tzinfo implementations.
Browse files Browse the repository at this point in the history
This commit deprecates django.utils.tzinfo in favor of the more recent
django.utils.timezone which was introduced when Django gained support
for time zones.
  • Loading branch information
aaugustin committed Sep 9, 2013
1 parent 9d70032 commit ec2778b
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 21 deletions.
60 changes: 48 additions & 12 deletions django/utils/timezone.py
Expand Up @@ -18,7 +18,8 @@
from django.utils import six

__all__ = [
'utc', 'get_default_timezone', 'get_current_timezone',
'utc', 'get_fixed_timezone',
'get_default_timezone', 'get_current_timezone',
'activate', 'deactivate', 'override',
'is_naive', 'is_aware', 'make_aware', 'make_naive',
]
Expand Down Expand Up @@ -47,19 +48,45 @@ def tzname(self, dt):
def dst(self, dt):
return ZERO

class FixedOffset(tzinfo):
"""
Fixed offset in minutes east from UTC. Taken from Python's docs.
Kept as close as possible to the reference version. __init__ was changed
to make its arguments optional, according to Python's requirement that
tzinfo subclasses can be instantiated without arguments.
"""

def __init__(self, offset=None, name=None):
if offset is not None:
self.__offset = timedelta(minutes=offset)
if name is not None:
self.__name = name

def utcoffset(self, dt):
return self.__offset

def tzname(self, dt):
return self.__name

def dst(self, dt):
return ZERO

class ReferenceLocalTimezone(tzinfo):
"""
Local time implementation taken from Python's docs.
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 identical to the reference version. Subclasses contain improvements.
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):
# This code is moved in __init__ to execute it as late as possible
# See get_default_timezone().
self.STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight:
self.DSTOFFSET = timedelta(seconds=-_time.altzone)
Expand All @@ -68,9 +95,6 @@ def __init__(self):
self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET
tzinfo.__init__(self)

def __repr__(self):
return "<LocalTimezone>"

def utcoffset(self, dt):
if self._isdst(dt):
return self.DSTOFFSET
Expand All @@ -84,8 +108,7 @@ def dst(self, dt):
return ZERO

def tzname(self, dt):
is_dst = False if dt is None else self._isdst(dt)
return _time.tzname[is_dst]
return _time.tzname[self._isdst(dt)]

def _isdst(self, dt):
tt = (dt.year, dt.month, dt.day,
Expand All @@ -103,6 +126,10 @@ class LocalTimezone(ReferenceLocalTimezone):
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)
Expand All @@ -116,6 +143,17 @@ def _isdst(self, dt):
utc = pytz.utc if pytz else UTC()
"""UTC time zone as a tzinfo instance."""

def get_fixed_timezone(offset):
"""
Returns a tzinfo instance with a fixed offset from UTC.
"""
if isinstance(offset, timedelta):
offset = offset.seconds // 60
sign = '-' if offset < 0 else '+'
hhmm = '%02d%02d' % divmod(abs(offset), 60)
name = sign + hhmm
return FixedOffset(offset, name)

# In order to avoid accessing the settings at compile time,
# wrap the expression in a function and cache the result.
_localtime = None
Expand All @@ -125,8 +163,6 @@ def get_default_timezone():
Returns the default time zone as a tzinfo instance.
This is the time zone defined by settings.TIME_ZONE.
See also :func:`get_current_timezone`.
"""
global _localtime
if _localtime is None:
Expand Down
17 changes: 16 additions & 1 deletion django/utils/tzinfo.py
Expand Up @@ -2,11 +2,18 @@

from __future__ import unicode_literals

import time
from datetime import timedelta, tzinfo
import time
import warnings

from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING

warnings.warn(
"django.utils.tzinfo will be removed in Django 1.9. "
"Use django.utils.timezone instead.",
PendingDeprecationWarning)


# Python's doc say: "A tzinfo subclass must have an __init__() method that can
# be called with no arguments". FixedOffset and LocalTimezone don't honor this
# requirement. Defining __getinitargs__ is sufficient to fix copy/deepcopy as
Expand All @@ -15,6 +22,10 @@
class FixedOffset(tzinfo):
"Fixed offset in minutes east from UTC."
def __init__(self, offset):
warnings.warn(
"django.utils.tzinfo.FixedOffset will be removed in Django 1.9. "
"Use django.utils.timezone.get_fixed_timezone instead.",
PendingDeprecationWarning)
if isinstance(offset, timedelta):
self.__offset = offset
offset = self.__offset.seconds // 60
Expand Down Expand Up @@ -48,6 +59,10 @@ def dst(self, dt):
class LocalTimezone(tzinfo):
"Proxy timezone information from time module."
def __init__(self, dt):
warnings.warn(
"django.utils.tzinfo.LocalTimezone will be removed in Django 1.9. "
"Use django.utils.timezone.get_default_timezone instead.",
PendingDeprecationWarning)
tzinfo.__init__(self)
self.__dt = dt
self._tzname = self.tzname(dt)
Expand Down
2 changes: 2 additions & 0 deletions docs/internals/deprecation.txt
Expand Up @@ -417,6 +417,8 @@ these changes.

* ``django.utils.importlib`` will be removed.

* ``django.utils.tzinfo`` will be removed.

* ``django.utils.unittest`` will be removed.

* The ``syncdb`` command will be removed.
Expand Down
20 changes: 20 additions & 0 deletions docs/ref/utils.txt
Expand Up @@ -927,6 +927,17 @@ For a complete discussion on the usage of the following see the

:class:`~datetime.tzinfo` instance that represents UTC.

.. function:: get_fixed_timezone(offset)

.. versionadded:: 1.7

Returns a :class:`~datetime.tzinfo` instance that represents a time zone
with a fixed offset from UTC.

``offset`` is a :class:`datetime.timedelta` or an integer number of
minutes. Use positive values for time zones east of UTC and negative
values for west of UTC.

.. function:: get_default_timezone()

Returns a :class:`~datetime.tzinfo` instance that represents the
Expand Down Expand Up @@ -1021,13 +1032,22 @@ For a complete discussion on the usage of the following see the
``django.utils.tzinfo``
=======================

.. deprecated:: 1.7
Use :mod:`~django.utils.timezone` instead.

.. module:: django.utils.tzinfo
:synopsis: Implementation of ``tzinfo`` classes for use with ``datetime.datetime``.

.. class:: FixedOffset

Fixed offset in minutes east from UTC.

.. deprecated:: 1.7
Use :func:`~django.utils.timezone.get_fixed_timezone` instead.

.. class:: LocalTimezone

Proxy timezone information from time module.

.. deprecated:: 1.7
Use :func:`~django.utils.timezone.get_default_timezone` instead.
19 changes: 19 additions & 0 deletions docs/releases/1.7.txt
Expand Up @@ -308,6 +308,16 @@ For apps with migrations, ``allow_migrate`` will now get passed
without custom attributes, methods or managers. Make sure your ``allow_migrate``
methods are only referring to fields or other items in ``model._meta``.

pytz may be required
~~~~~~~~~~~~~~~~~~~~

If your project handles datetimes before 1970 or after 2037 and Django raises
a :exc:`~exceptions.ValueError` when encountering them, you will have to
install pytz_. You may be affected by this problem if you use Django's time
zone-related date formats or :mod:`django.contrib.syndication`.

.. _pytz: https://pypi.python.org/pypi/pytz/

Miscellaneous
~~~~~~~~~~~~~

Expand Down Expand Up @@ -389,6 +399,15 @@ Features deprecated in 1.7
respectively :mod:`logging.config` and :mod:`importlib` provided for Python
versions prior to 2.7. They have been deprecated.

``django.utils.tzinfo``
~~~~~~~~~~~~~~~~~~~~~~~

``django.utils.tzinfo`` provided two :class:`~datetime.tzinfo` subclasses,
``LocalTimezone`` and ``FixedOffset``. They've been deprecated in favor of
more correct alternatives provided by :mod:`django.utils.timezone`,
:func:`django.utils.timezone.get_default_timezone` and
:func:`django.utils.timezone.get_fixed_timezone`.

``django.utils.unittest``
~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
5 changes: 2 additions & 3 deletions tests/timezones/tests.py
Expand Up @@ -25,7 +25,6 @@
from django.test.utils import override_settings
from django.utils import six
from django.utils import timezone
from django.utils.tzinfo import FixedOffset

from .forms import EventForm, EventSplitForm, EventModelForm
from .models import Event, MaybeEvent, Session, SessionEvent, Timestamp, AllDayEvent
Expand All @@ -40,8 +39,8 @@
# 10:20:30 in UTC and 17:20:30 in ICT.

UTC = timezone.utc
EAT = FixedOffset(180) # Africa/Nairobi
ICT = FixedOffset(420) # Asia/Bangkok
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok

TZ_SUPPORT = hasattr(time, 'tzset')

Expand Down
5 changes: 2 additions & 3 deletions tests/utils_tests/test_timezone.py
Expand Up @@ -6,11 +6,10 @@
from django.test.utils import override_settings
from django.utils import six
from django.utils import timezone
from django.utils.tzinfo import FixedOffset


EAT = FixedOffset(180) # Africa/Nairobi
ICT = FixedOffset(420) # Asia/Bangkok
EAT = timezone.get_fixed_timezone(180) # Africa/Nairobi
ICT = timezone.get_fixed_timezone(420) # Asia/Bangkok


class TimezoneTests(unittest.TestCase):
Expand Down
10 changes: 8 additions & 2 deletions tests/utils_tests/test_tzinfo.py
Expand Up @@ -4,10 +4,16 @@
import pickle
import time
import unittest
import warnings

from django.utils.tzinfo import FixedOffset, LocalTimezone
from django.test.utils import IgnorePendingDeprecationWarningsMixin

class TzinfoTests(unittest.TestCase):
# Swallow the import-time warning to test the deprecated implementation.
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
from django.utils.tzinfo import FixedOffset, LocalTimezone

class TzinfoTests(IgnorePendingDeprecationWarningsMixin, unittest.TestCase):

@classmethod
def setUpClass(cls):
Expand Down

0 comments on commit ec2778b

Please sign in to comment.