diff --git a/django/utils/timesince.py b/django/utils/timesince.py index 455788e7d72..a69e55498a7 100644 --- a/django/utils/timesince.py +++ b/django/utils/timesince.py @@ -4,8 +4,15 @@ from django.utils.translation import ungettext, ugettext def timesince(d, now=None): """ - Takes two datetime objects and returns the time between then and now - as a nicely formatted string, e.g "10 minutes" + Takes two datetime objects and returns the time between d and now + as a nicely formatted string, e.g. "10 minutes". If d occurs after now, + then "0 minutes" is returned. + + Units used are years, months, weeks, days, hours, and minutes. + Seconds and microseconds are ignored. Up to two adjacent units will be + displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are + possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not. + Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since """ chunks = ( @@ -32,6 +39,9 @@ def timesince(d, now=None): # ignore microsecond part of 'd' since we removed it from 'now' delta = now - (d - datetime.timedelta(0, 0, d.microsecond)) since = delta.days * 24 * 60 * 60 + delta.seconds + if since <= 0: + # d is in the future compared to now, stop processing. + return u'0 ' + ugettext('minutes') for i, (seconds, name) in enumerate(chunks): count = since // seconds if count != 0: diff --git a/docs/templates.txt b/docs/templates.txt index 9adf15731b7..cd436a987d7 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -1275,17 +1275,23 @@ 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 ``{{ comment_date|timesince:blog_date }}`` would return "8 hours". +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. + timeuntil ~~~~~~~~~ Similar to ``timesince``, except that it measures the time from now until the given date or datetime. For example, if today is 1 June 2006 and ``conference_date`` is a date instance holding 29 June 2006, then -``{{ conference_date|timeuntil }}`` will return "28 days". +``{{ conference_date|timeuntil }}`` will return "4 weeks". 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 "7 days". +2006, then ``{{ conference_date|timeuntil:from_date }}`` will return "1 week". + +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. title ~~~~~ diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 11dd0921564..0c851d5f78c 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -771,6 +771,10 @@ class Templates(unittest.TestCase): # Check that timezone is respected 'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'), + # Check times in the future. + 'timesince07' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=1, seconds=10)}, '0 minutes'), + 'timesince08' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(days=1, minutes=1)}, '0 minutes'), + ### TIMEUNTIL TAG ################################################## # Default compare with datetime.now() 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), @@ -781,6 +785,10 @@ class Templates(unittest.TestCase): 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'), 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'), + # Check times in the past. + 'timeuntil07' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(minutes=1, seconds=10)}, '0 minutes'), + 'timeuntil08' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(days=1, minutes=1)}, '0 minutes'), + ### URL TAG ######################################################## # Successes 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 258aea697ef..fe0b226adc0 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -6,6 +6,8 @@ from unittest import TestCase from django.utils import html +from timesince import timesince_tests + class TestUtilsHtml(TestCase): def check_output(self, function, value, output=None): @@ -113,3 +115,11 @@ class TestUtilsHtml(TestCase): ) for value, output in items: self.check_output(f, value, output) + +__test__ = { + 'timesince_tests': timesince_tests, +} + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/tests/regressiontests/utils/timesince.py b/tests/regressiontests/utils/timesince.py new file mode 100644 index 00000000000..30200be6e9e --- /dev/null +++ b/tests/regressiontests/utils/timesince.py @@ -0,0 +1,77 @@ +timesince_tests = """ +>>> from datetime import datetime, timedelta +>>> from django.utils.timesince import timesince + +>>> t = datetime(2007, 8, 14, 13, 46, 0) + +>>> onemicrosecond = timedelta(microseconds=1) +>>> onesecond = timedelta(seconds=1) +>>> oneminute = timedelta(minutes=1) +>>> onehour = timedelta(hours=1) +>>> oneday = timedelta(days=1) +>>> oneweek = timedelta(days=7) +>>> onemonth = timedelta(days=30) +>>> oneyear = timedelta(days=365) + +# equal datetimes. +>>> timesince(t, t) +u'0 minutes' + +# Microseconds and seconds are ignored. +>>> timesince(t, t+onemicrosecond) +u'0 minutes' +>>> timesince(t, t+onesecond) +u'0 minutes' + +# Test other units. +>>> timesince(t, t+oneminute) +u'1 minute' +>>> timesince(t, t+onehour) +u'1 hour' +>>> timesince(t, t+oneday) +u'1 day' +>>> timesince(t, t+oneweek) +u'1 week' +>>> timesince(t, t+onemonth) +u'1 month' +>>> timesince(t, t+oneyear) +u'1 year' + +# Test multiple units. +>>> timesince(t, t+2*oneday+6*onehour) +u'2 days, 6 hours' +>>> timesince(t, t+2*oneweek+2*oneday) +u'2 weeks, 2 days' + +# If the two differing units aren't adjacent, only the first unit is displayed. +>>> timesince(t, t+2*oneweek+3*onehour+4*oneminute) +u'2 weeks' +>>> timesince(t, t+4*oneday+5*oneminute) +u'4 days' + +# When the second date occurs before the first, we should always get 0 minutes. +>>> timesince(t, t-onemicrosecond) +u'0 minutes' +>>> timesince(t, t-onesecond) +u'0 minutes' +>>> timesince(t, t-oneminute) +u'0 minutes' +>>> timesince(t, t-onehour) +u'0 minutes' +>>> timesince(t, t-oneday) +u'0 minutes' +>>> timesince(t, t-oneweek) +u'0 minutes' +>>> timesince(t, t-onemonth) +u'0 minutes' +>>> timesince(t, t-oneyear) +u'0 minutes' +>>> timesince(t, t-2*oneday-6*onehour) +u'0 minutes' +>>> timesince(t, t-2*oneweek-2*oneday) +u'0 minutes' +>>> timesince(t, t-2*oneweek-3*onehour-4*oneminute) +u'0 minutes' +>>> timesince(t, t-4*oneday-5*oneminute) +u'0 minutes' +"""