From ec2778b445546f624d3b3a1f2118e751b10bb2e7 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sun, 8 Sep 2013 02:04:31 -0500 Subject: [PATCH] Fixed #17262 -- Refactored tzinfo implementations. 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. --- django/utils/timezone.py | 60 ++++++++++++++++++++++++------ django/utils/tzinfo.py | 17 ++++++++- docs/internals/deprecation.txt | 2 + docs/ref/utils.txt | 20 ++++++++++ docs/releases/1.7.txt | 19 ++++++++++ tests/timezones/tests.py | 5 +-- tests/utils_tests/test_timezone.py | 5 +-- tests/utils_tests/test_tzinfo.py | 10 ++++- 8 files changed, 117 insertions(+), 21 deletions(-) diff --git a/django/utils/timezone.py b/django/utils/timezone.py index 93ef4dfd57f..da6f971c50e 100644 --- a/django/utils/timezone.py +++ b/django/utils/timezone.py @@ -18,7 +18,8 @@ from django.conf import settings 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', ] @@ -47,19 +48,45 @@ class UTC(tzinfo): 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) @@ -68,9 +95,6 @@ class ReferenceLocalTimezone(tzinfo): self.DSTDIFF = self.DSTOFFSET - self.STDOFFSET tzinfo.__init__(self) - def __repr__(self): - return "" - def utcoffset(self, dt): if self._isdst(dt): return self.DSTOFFSET @@ -84,8 +108,7 @@ class ReferenceLocalTimezone(tzinfo): 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, @@ -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) @@ -116,6 +143,17 @@ class LocalTimezone(ReferenceLocalTimezone): 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 @@ -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: diff --git a/django/utils/tzinfo.py b/django/utils/tzinfo.py index fd221ea48be..62c8e07a66e 100644 --- a/django/utils/tzinfo.py +++ b/django/utils/tzinfo.py @@ -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 @@ -15,6 +22,10 @@ from django.utils.encoding import force_str, force_text, DEFAULT_LOCALE_ENCODING 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 @@ -48,6 +59,10 @@ class FixedOffset(tzinfo): 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) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 307f4dec646..054e3b32fc8 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -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. diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index a093de18b7f..59a501cf82d 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -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 @@ -1021,6 +1032,9 @@ 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``. @@ -1028,6 +1042,12 @@ For a complete discussion on the usage of the following see the 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. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index fdba5a80a53..c49ad86c6c4 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -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 ~~~~~~~~~~~~~ @@ -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`` ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py index 9390eb93dfd..e9d29f704be 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -25,7 +25,6 @@ from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature 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 @@ -40,8 +39,8 @@ from .models import Event, MaybeEvent, Session, SessionEvent, Timestamp, AllDayE # 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') diff --git a/tests/utils_tests/test_timezone.py b/tests/utils_tests/test_timezone.py index 6d5f9a74828..de80afe3254 100644 --- a/tests/utils_tests/test_timezone.py +++ b/tests/utils_tests/test_timezone.py @@ -6,11 +6,10 @@ import unittest 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): diff --git a/tests/utils_tests/test_tzinfo.py b/tests/utils_tests/test_tzinfo.py index 1867743fef9..31fd7c4a500 100644 --- a/tests/utils_tests/test_tzinfo.py +++ b/tests/utils_tests/test_tzinfo.py @@ -4,10 +4,16 @@ import os 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):