From 1014ba026e879e56e0f265a8d9f54e6f39843348 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 7 Nov 2015 16:12:13 +0100 Subject: [PATCH] Fixed debug view crash during autumn DST change. This only happens if USE_TZ = False and pytz is installed (perhaps not the most logical combination, but who am I to jugde?) Refs #23714 which essentially fixed the same problem when USE_TZ = True. Thanks Florian and Carl for insisting until I wrote a complete patch. --- django/utils/dateformat.py | 34 +++++++++++++++++++++++----- docs/releases/1.8.7.txt | 3 +++ tests/utils_tests/test_dateformat.py | 17 ++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index f4de4e7f7c..ea55eaaf9d 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -136,6 +136,8 @@ class TimeFormat(Formatter): return "" seconds = self.Z() + if seconds == "": + return "" sign = '-' if seconds < 0 else '+' seconds = abs(seconds) return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60) @@ -167,7 +169,14 @@ class TimeFormat(Formatter): if not self.timezone: return "" - name = self.timezone.tzname(self.data) if self.timezone else None + name = None + try: + name = self.timezone.tzname(self.data) + except Exception: + # pytz raises AmbiguousTimeError during the autumn DST change. + # This happens mainly when __init__ receives a naive datetime + # and sets self.timezone = get_default_timezone(). + pass if name is None: name = self.format('O') return six.text_type(name) @@ -188,7 +197,14 @@ class TimeFormat(Formatter): if not self.timezone: return "" - offset = self.timezone.utcoffset(self.data) + try: + offset = self.timezone.utcoffset(self.data) + except Exception: + # pytz raises AmbiguousTimeError during the autumn DST change. + # This happens mainly when __init__ receives a naive datetime + # and sets self.timezone = get_default_timezone(). + return "" + # `offset` is a datetime.timedelta. For negative values (to the west of # UTC) only days can be negative (days=-1) and seconds are always # positive. e.g. UTC-1 -> timedelta(days=-1, seconds=82800, microseconds=0) @@ -228,10 +244,16 @@ class DateFormat(TimeFormat): def I(self): "'1' if Daylight Savings Time, '0' otherwise." - if self.timezone and self.timezone.dst(self.data): - return '1' - else: - return '0' + try: + if self.timezone and self.timezone.dst(self.data): + return '1' + else: + return '0' + except Exception: + # pytz raises AmbiguousTimeError during the autumn DST change. + # This happens mainly when __init__ receives a naive datetime + # and sets self.timezone = get_default_timezone(). + return '' def j(self): "Day of the month without leading zeros; i.e. '1' to '31'" diff --git a/docs/releases/1.8.7.txt b/docs/releases/1.8.7.txt index 6db055b5fc..9aed739c01 100644 --- a/docs/releases/1.8.7.txt +++ b/docs/releases/1.8.7.txt @@ -9,4 +9,7 @@ Django 1.8.7 fixes several bugs in 1.8.6. Bugfixes ======== +* Fixed a crash of the debug view during the autumn DST change when + :setting:`USE_TZ` is ``False`` and ``pytz`` is installed. + * ... diff --git a/tests/utils_tests/test_dateformat.py b/tests/utils_tests/test_dateformat.py index faf4b6605b..918cc2a518 100644 --- a/tests/utils_tests/test_dateformat.py +++ b/tests/utils_tests/test_dateformat.py @@ -10,6 +10,11 @@ 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): @@ -29,6 +34,18 @@ class DateFormatTests(SimpleTestCase): dt = datetime(2009, 5, 16, 5, 30, 30) self.assertEqual(datetime.fromtimestamp(int(format(dt, 'U'))), dt) + 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 = 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 '') + @requires_tz_support def test_datetime_with_local_tzinfo(self): ltz = get_default_timezone()