Kaydet (Commit) dd3a8838 authored tarafından Warren Smith's avatar Warren Smith

Fixed #20693 -- Add timezone support to built-in time filter.

Modified django.utils.dateformat module, moving __init__() method and
timezone-related format methods from DateFormat class to TimeFormat
base class. Modified timezone-related format methods to return an
empty string when timezone is inappropriate for input value.
üst fa572666
...@@ -38,8 +38,19 @@ class Formatter(object): ...@@ -38,8 +38,19 @@ class Formatter(object):
return ''.join(pieces) return ''.join(pieces)
class TimeFormat(Formatter): class TimeFormat(Formatter):
def __init__(self, t):
self.data = t def __init__(self, obj):
self.data = obj
self.timezone = None
# We only support timezone when formatting datetime objects,
# not date objects (timezone information not appropriate),
# or time objects (against established django policy).
if isinstance(obj, datetime.datetime):
if is_naive(obj):
self.timezone = LocalTimezone(obj)
else:
self.timezone = obj.tzinfo
def a(self): def a(self):
"'a.m.' or 'p.m.'" "'a.m.' or 'p.m.'"
...@@ -57,6 +68,25 @@ class TimeFormat(Formatter): ...@@ -57,6 +68,25 @@ class TimeFormat(Formatter):
"Swatch Internet time" "Swatch Internet time"
raise NotImplementedError raise NotImplementedError
def e(self):
"""
Timezone name.
If timezone information is not available, this method returns
an empty string.
"""
if not self.timezone:
return ""
try:
if hasattr(self.data, 'tzinfo') and self.data.tzinfo:
# Have to use tzinfo.tzname and not datetime.tzname
# because datatime.tzname does not expect Unicode
return self.data.tzinfo.tzname(self.data) or ""
except NotImplementedError:
pass
return ""
def f(self): def f(self):
""" """
Time, in 12-hour hours and minutes, with minutes left off if they're Time, in 12-hour hours and minutes, with minutes left off if they're
...@@ -92,6 +122,21 @@ class TimeFormat(Formatter): ...@@ -92,6 +122,21 @@ class TimeFormat(Formatter):
"Minutes; i.e. '00' to '59'" "Minutes; i.e. '00' to '59'"
return '%02d' % self.data.minute return '%02d' % self.data.minute
def O(self):
"""
Difference to Greenwich time in hours; e.g. '+0200', '-0430'.
If timezone information is not available, this method returns
an empty string.
"""
if not self.timezone:
return ""
seconds = self.Z()
sign = '-' if seconds < 0 else '+'
seconds = abs(seconds)
return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60)
def P(self): def P(self):
""" """
Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
...@@ -109,24 +154,48 @@ class TimeFormat(Formatter): ...@@ -109,24 +154,48 @@ class TimeFormat(Formatter):
"Seconds; i.e. '00' to '59'" "Seconds; i.e. '00' to '59'"
return '%02d' % self.data.second return '%02d' % self.data.second
def T(self):
"""
Time zone of this machine; e.g. 'EST' or 'MDT'.
If timezone information is not available, this method returns
an empty string.
"""
if not self.timezone:
return ""
name = self.timezone.tzname(self.data) if self.timezone else None
if name is None:
name = self.format('O')
return six.text_type(name)
def u(self): def u(self):
"Microseconds; i.e. '000000' to '999999'" "Microseconds; i.e. '000000' to '999999'"
return '%06d' %self.data.microsecond return '%06d' %self.data.microsecond
def Z(self):
"""
Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
timezones west of UTC is always negative, and for those east of UTC is
always positive.
If timezone information is not available, this method returns
an empty string.
"""
if not self.timezone:
return ""
offset = self.timezone.utcoffset(self.data)
# `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)
# Positive offsets have days=0
return offset.days * 86400 + offset.seconds
class DateFormat(TimeFormat): class DateFormat(TimeFormat):
year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] year_days = [None, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]
def __init__(self, dt):
# Accepts either a datetime or date object.
self.data = dt
self.timezone = None
if isinstance(dt, datetime.datetime):
if is_naive(dt):
self.timezone = LocalTimezone(dt)
else:
self.timezone = dt.tzinfo
def b(self): def b(self):
"Month, textual, 3 letters, lowercase; e.g. 'jan'" "Month, textual, 3 letters, lowercase; e.g. 'jan'"
return MONTHS_3[self.data.month] return MONTHS_3[self.data.month]
...@@ -146,17 +215,6 @@ class DateFormat(TimeFormat): ...@@ -146,17 +215,6 @@ class DateFormat(TimeFormat):
"Day of the week, textual, 3 letters; e.g. 'Fri'" "Day of the week, textual, 3 letters; e.g. 'Fri'"
return WEEKDAYS_ABBR[self.data.weekday()] return WEEKDAYS_ABBR[self.data.weekday()]
def e(self):
"Timezone name if available"
try:
if hasattr(self.data, 'tzinfo') and self.data.tzinfo:
# Have to use tzinfo.tzname and not datetime.tzname
# because datatime.tzname does not expect Unicode
return self.data.tzinfo.tzname(self.data) or ""
except NotImplementedError:
pass
return ""
def E(self): def E(self):
"Alternative month names as required by some locales. Proprietary extension." "Alternative month names as required by some locales. Proprietary extension."
return MONTHS_ALT[self.data.month] return MONTHS_ALT[self.data.month]
...@@ -204,13 +262,6 @@ class DateFormat(TimeFormat): ...@@ -204,13 +262,6 @@ class DateFormat(TimeFormat):
"ISO 8601 year number matching the ISO week number (W)" "ISO 8601 year number matching the ISO week number (W)"
return self.data.isocalendar()[0] return self.data.isocalendar()[0]
def O(self):
"Difference to Greenwich time in hours; e.g. '+0200', '-0430'"
seconds = self.Z()
sign = '-' if seconds < 0 else '+'
seconds = abs(seconds)
return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60)
def r(self): def r(self):
"RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'" "RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
return self.format('D, j M Y H:i:s O') return self.format('D, j M Y H:i:s O')
...@@ -232,13 +283,6 @@ class DateFormat(TimeFormat): ...@@ -232,13 +283,6 @@ class DateFormat(TimeFormat):
"Number of days in the given month; i.e. '28' to '31'" "Number of days in the given month; i.e. '28' to '31'"
return '%02d' % calendar.monthrange(self.data.year, self.data.month)[1] return '%02d' % calendar.monthrange(self.data.year, self.data.month)[1]
def T(self):
"Time zone of this machine; e.g. 'EST' or 'MDT'"
name = self.timezone.tzname(self.data) if self.timezone else None
if name is None:
name = self.format('O')
return six.text_type(name)
def U(self): def U(self):
"Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)" "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
if isinstance(self.data, datetime.datetime) and is_aware(self.data): if isinstance(self.data, datetime.datetime) and is_aware(self.data):
...@@ -291,26 +335,13 @@ class DateFormat(TimeFormat): ...@@ -291,26 +335,13 @@ class DateFormat(TimeFormat):
doy += 1 doy += 1
return doy return doy
def Z(self):
"""
Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
timezones west of UTC is always negative, and for those east of UTC is
always positive.
"""
if not self.timezone:
return 0
offset = self.timezone.utcoffset(self.data)
# `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)
# Positive offsets have days=0
return offset.days * 86400 + offset.seconds
def format(value, format_string): def format(value, format_string):
"Convenience function" "Convenience function"
df = DateFormat(value) df = DateFormat(value)
return df.format(format_string) return df.format(format_string)
def time_format(value, format_string): def time_format(value, format_string):
"Convenience function" "Convenience function"
tf = TimeFormat(value) tf = TimeFormat(value)
......
...@@ -360,6 +360,13 @@ def get_filter_tests(): ...@@ -360,6 +360,13 @@ def get_filter_tests():
# Ticket 19370: Make sure |date doesn't blow up on a midnight time object # Ticket 19370: Make sure |date doesn't blow up on a midnight time object
'date08': (r'{{ t|date:"H:i" }}', {'t': time(0, 1)}, '00:01'), 'date08': (r'{{ t|date:"H:i" }}', {'t': time(0, 1)}, '00:01'),
'date09': (r'{{ t|date:"H:i" }}', {'t': time(0, 0)}, '00:00'), 'date09': (r'{{ t|date:"H:i" }}', {'t': time(0, 0)}, '00:00'),
# Ticket 20693: Add timezone support to built-in time template filter
'time01': (r'{{ dt|time:"e:O:T:Z" }}', {'dt': now_tz_i}, '+0315:+0315:+0315:11700'),
'time02': (r'{{ dt|time:"e:T" }}', {'dt': now}, ':' + now_tz.tzinfo.tzname(now_tz)),
'time03': (r'{{ t|time:"P:e:O:T:Z" }}', {'t': time(4, 0, tzinfo=FixedOffset(30))}, '4 a.m.::::'),
'time04': (r'{{ t|time:"P:e:O:T:Z" }}', {'t': time(4, 0)}, '4 a.m.::::'),
'time05': (r'{{ d|time:"P:e:O:T:Z" }}', {'d': today}, ''),
'time06': (r'{{ obj|time:"P:e:O:T:Z" }}', {'obj': 'non-datetime-value'}, ''),
# Tests for #11687 and #16676 # Tests for #11687 and #16676
'add01': (r'{{ i|add:"5" }}', {'i': 2000}, '2005'), 'add01': (r'{{ i|add:"5" }}', {'i': 2000}, '2005'),
......
...@@ -127,10 +127,16 @@ class DateFormatTests(unittest.TestCase): ...@@ -127,10 +127,16 @@ class DateFormatTests(unittest.TestCase):
wintertime = datetime(2005, 10, 30, 4, 00) wintertime = datetime(2005, 10, 30, 4, 00)
timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456)
# 3h30m to the west of UTC
tz = FixedOffset(-3*60 - 30)
aware_dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
if self.tz_tests: if self.tz_tests:
self.assertEqual(dateformat.format(my_birthday, 'O'), '+0100') self.assertEqual(dateformat.format(my_birthday, 'O'), '+0100')
self.assertEqual(dateformat.format(my_birthday, 'r'), 'Sun, 8 Jul 1979 22:00:00 +0100') self.assertEqual(dateformat.format(my_birthday, 'r'), 'Sun, 8 Jul 1979 22:00:00 +0100')
self.assertEqual(dateformat.format(my_birthday, 'T'), 'CET') self.assertEqual(dateformat.format(my_birthday, 'T'), 'CET')
self.assertEqual(dateformat.format(my_birthday, 'e'), '')
self.assertEqual(dateformat.format(aware_dt, 'e'), '-0330')
self.assertEqual(dateformat.format(my_birthday, 'U'), '300315600') self.assertEqual(dateformat.format(my_birthday, 'U'), '300315600')
self.assertEqual(dateformat.format(timestamp, 'u'), '123456') self.assertEqual(dateformat.format(timestamp, 'u'), '123456')
self.assertEqual(dateformat.format(my_birthday, 'Z'), '3600') self.assertEqual(dateformat.format(my_birthday, 'Z'), '3600')
...@@ -140,7 +146,4 @@ class DateFormatTests(unittest.TestCase): ...@@ -140,7 +146,4 @@ class DateFormatTests(unittest.TestCase):
self.assertEqual(dateformat.format(wintertime, 'O'), '+0100') self.assertEqual(dateformat.format(wintertime, 'O'), '+0100')
# Ticket #16924 -- We don't need timezone support to test this # Ticket #16924 -- We don't need timezone support to test this
# 3h30m to the west of UTC self.assertEqual(dateformat.format(aware_dt, 'O'), '-0330')
tz = FixedOffset(-3*60 - 30)
dt = datetime(2009, 5, 16, 5, 30, 30, tzinfo=tz)
self.assertEqual(dateformat.format(dt, 'O'), '-0330')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment