Fixed #7201 -- Fixed the timeuntil filter to work correctly with timezone-aware

times. Patch from Jeremy Carbaugh.

This is backwards incompatible in the sense that previously, if you tried to
compare timezone-aware and timezone-naive values, you got an incorrect result.
Now you get an empty string. So your previously incorrect code returns a
different incorrect result.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8579 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2008-08-26 08:08:55 +00:00
parent 61957df17f
commit 3111d7f60b
6 changed files with 53 additions and 20 deletions

View File

@ -88,6 +88,7 @@ answer newbie questions, and generally made Django that much better:
Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com> Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com>
Trevor Caira <trevor@caira.com> Trevor Caira <trevor@caira.com>
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com> Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
Jeremy Carbaugh <jcarbaugh@gmail.com>
Graham Carlyle <graham.carlyle@maplecroft.net> Graham Carlyle <graham.carlyle@maplecroft.net>
Antonio Cavedoni <http://cavedoni.com/> Antonio Cavedoni <http://cavedoni.com/>
C8E C8E

View File

@ -646,20 +646,24 @@ def timesince(value, arg=None):
from django.utils.timesince import timesince from django.utils.timesince import timesince
if not value: if not value:
return u'' return u''
if arg: try:
return timesince(value, arg) if arg:
return timesince(value) return timesince(value, arg)
return timesince(value)
except (ValueError, TypeError):
return u''
timesince.is_safe = False timesince.is_safe = False
def timeuntil(value, arg=None): def timeuntil(value, arg=None):
"""Formats a date as the time until that date (i.e. "4 days, 6 hours").""" """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
from django.utils.timesince import timesince from django.utils.timesince import timeuntil
from datetime import datetime from datetime import datetime
if not value: if not value:
return u'' return u''
if arg: try:
return timesince(arg, value) return timeuntil(value, arg)
return timesince(datetime.now(), value) except (ValueError, TypeError):
return u''
timeuntil.is_safe = False timeuntil.is_safe = False
################### ###################

View File

@ -28,15 +28,12 @@ def timesince(d, now=None):
# Convert datetime.date to datetime.datetime for comparison # Convert datetime.date to datetime.datetime for comparison
if d.__class__ is not datetime.datetime: if d.__class__ is not datetime.datetime:
d = datetime.datetime(d.year, d.month, d.day) d = datetime.datetime(d.year, d.month, d.day)
if now:
t = now.timetuple() if not now:
else: if d.tzinfo:
t = time.localtime() now = datetime.datetime.now(LocalTimezone(d))
if d.tzinfo: else:
tz = LocalTimezone(d) now = datetime.datetime.now()
else:
tz = None
now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz)
# ignore microsecond part of 'd' since we removed it from 'now' # ignore microsecond part of 'd' since we removed it from 'now'
delta = now - (d - datetime.timedelta(0, 0, d.microsecond)) delta = now - (d - datetime.timedelta(0, 0, d.microsecond))
@ -62,6 +59,9 @@ def timeuntil(d, now=None):
Like timesince, but returns a string measuring the time until Like timesince, but returns a string measuring the time until
the given time. the given time.
""" """
if now == None: if not now:
now = datetime.datetime.now() if d.tzinfo:
now = datetime.datetime.now(LocalTimezone(d))
else:
now = datetime.datetime.now()
return timesince(now, d) return timesince(now, d)

View File

@ -1332,6 +1332,8 @@ For example, if ``blog_date`` is a date instance representing midnight on 1
June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006, June 2006, and ``comment_date`` is a date instance for 08:00 on 1 June 2006,
then ``{{ blog_date|timesince:comment_date }}`` would return "8 hours". then ``{{ blog_date|timesince:comment_date }}`` would return "8 hours".
Comparing offset-naive and offset-aware datetimes will return an empty string.
Minutes is the smallest unit used, and "0 minutes" will be returned for any Minutes is the smallest unit used, and "0 minutes" will be returned for any
date that is in the future relative to the comparison point. date that is in the future relative to the comparison point.
@ -1349,6 +1351,8 @@ Takes an optional argument that is a variable containing the date to use as
the comparison point (instead of *now*). If ``from_date`` contains 22 June the comparison point (instead of *now*). If ``from_date`` contains 22 June
2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week". 2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week".
Comparing offset-naive and offset-aware datetimes will return an empty string.
Minutes is the smallest unit used, and "0 minutes" will be returned for any Minutes is the smallest unit used, and "0 minutes" will be returned for any
date that is in the past relative to the comparison point. date that is in the past relative to the comparison point.

View File

@ -9,7 +9,7 @@ consistent.
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils.tzinfo import LocalTimezone from django.utils.tzinfo import LocalTimezone, FixedOffset
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
# These two classes are used to test auto-escaping of __unicode__ output. # These two classes are used to test auto-escaping of __unicode__ output.
@ -27,6 +27,7 @@ class SafeClass:
def get_filter_tests(): def get_filter_tests():
now = datetime.now() now = datetime.now()
now_tz = datetime.now(LocalTimezone(now)) now_tz = datetime.now(LocalTimezone(now))
now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone
return { return {
# Default compare with datetime.now() # Default compare with datetime.now()
'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
@ -46,6 +47,14 @@ def get_filter_tests():
'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'), 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'),
'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'), 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'),
# Ensures that differing timezones are calculated correctly
'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'),
'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'),
'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'),
'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'),
'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''),
'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''),
# Default compare with datetime.now() # Default compare with datetime.now()
'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
@ -61,6 +70,9 @@ def get_filter_tests():
'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'), 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'),
'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'), 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'),
# Ensures that differing timezones are calculated correctly
'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'),
'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'),
'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"), 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"),
'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"&lt;a&gt;\&#39; <a>\'"), 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"&lt;a&gt;\&#39; <a>\'"),

View File

@ -1,6 +1,7 @@
""" """
>>> from datetime import datetime, timedelta >>> from datetime import datetime, timedelta
>>> from django.utils.timesince import timesince >>> from django.utils.timesince import timesince, timeuntil
>>> from django.utils.tzinfo import LocalTimezone, FixedOffset
>>> t = datetime(2007, 8, 14, 13, 46, 0) >>> t = datetime(2007, 8, 14, 13, 46, 0)
@ -74,4 +75,15 @@ u'0 minutes'
u'0 minutes' u'0 minutes'
>>> timesince(t, t-4*oneday-5*oneminute) >>> timesince(t, t-4*oneday-5*oneminute)
u'0 minutes' u'0 minutes'
# When using two different timezones.
>>> now = datetime.now()
>>> now_tz = datetime.now(LocalTimezone(now))
>>> now_tz_i = datetime.now(FixedOffset((3 * 60) + 15))
>>> timesince(now)
u'0 minutes'
>>> timesince(now_tz)
u'0 minutes'
>>> timeuntil(now_tz, now_tz_i)
u'0 minutes'
""" """