Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #7201 -- Fixed the timeuntil filter to work correctly with time…

…zone-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
  • Loading branch information...
commit 3111d7f60b67d5aefa4d891d1d9a86ba56d7607c 1 parent 61957df
@malcolmt malcolmt authored
View
1  AUTHORS
@@ -88,6 +88,7 @@ answer newbie questions, and generally made Django that much better:
Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com>
Trevor Caira <trevor@caira.com>
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
+ Jeremy Carbaugh <jcarbaugh@gmail.com>
Graham Carlyle <graham.carlyle@maplecroft.net>
Antonio Cavedoni <http://cavedoni.com/>
C8E
View
18 django/template/defaultfilters.py
@@ -646,20 +646,24 @@ def timesince(value, arg=None):
from django.utils.timesince import timesince
if not value:
return u''
- if arg:
- return timesince(value, arg)
- return timesince(value)
+ try:
+ if arg:
+ return timesince(value, arg)
+ return timesince(value)
+ except (ValueError, TypeError):
+ return u''
timesince.is_safe = False
def timeuntil(value, arg=None):
"""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
if not value:
return u''
- if arg:
- return timesince(arg, value)
- return timesince(datetime.now(), value)
+ try:
+ return timeuntil(value, arg)
+ except (ValueError, TypeError):
+ return u''
timeuntil.is_safe = False
###################
View
22 django/utils/timesince.py
@@ -28,15 +28,12 @@ def timesince(d, now=None):
# Convert datetime.date to datetime.datetime for comparison
if d.__class__ is not datetime.datetime:
d = datetime.datetime(d.year, d.month, d.day)
- if now:
- t = now.timetuple()
- else:
- t = time.localtime()
- if d.tzinfo:
- tz = LocalTimezone(d)
- else:
- tz = None
- now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz)
+
+ if not now:
+ if d.tzinfo:
+ now = datetime.datetime.now(LocalTimezone(d))
+ else:
+ now = datetime.datetime.now()
# ignore microsecond part of 'd' since we removed it from 'now'
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
the given time.
"""
- if now == None:
- now = datetime.datetime.now()
+ if not now:
+ if d.tzinfo:
+ now = datetime.datetime.now(LocalTimezone(d))
+ else:
+ now = datetime.datetime.now()
return timesince(now, d)
View
4 docs/ref/templates/builtins.txt
@@ -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,
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
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
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
date that is in the past relative to the comparison point.
View
14 tests/regressiontests/templates/filters.py
@@ -9,7 +9,7 @@
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
# These two classes are used to test auto-escaping of __unicode__ output.
@@ -27,6 +27,7 @@ def __unicode__(self):
def get_filter_tests():
now = datetime.now()
now_tz = datetime.now(LocalTimezone(now))
+ now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone
return {
# Default compare with datetime.now()
'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-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()
'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'),
@@ -61,6 +70,9 @@ def get_filter_tests():
'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'),
+ # 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-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"&lt;a&gt;\&#39; <a>\'"),
View
14 tests/regressiontests/utils/timesince.py
@@ -1,6 +1,7 @@
"""
>>> 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)
@@ -74,4 +75,15 @@
u'0 minutes'
>>> timesince(t, t-4*oneday-5*oneminute)
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'
"""
Please sign in to comment.
Something went wrong with that request. Please try again.