summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Park <timpark0807@gmail.com>2020-07-02 23:01:45 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-07-16 09:44:28 +0200
commit8fa9a6d29efe2622872b4788190ea7c1bcb92019 (patch)
tree56525b346db10e2d1297923e881a4ece91e7651e
parentbde33bdd51b58a63d3e50fab687815968383b86d (diff)
Fixed #31623 -- Allowed specifying number of adjacent time units in timesince()/timeuntil().
-rw-r--r--django/utils/timesince.py36
-rw-r--r--docs/releases/3.2.txt4
-rw-r--r--tests/utils_tests/test_timesince.py32
3 files changed, 56 insertions, 16 deletions
diff --git a/django/utils/timesince.py b/django/utils/timesince.py
index 3ee70ead21..a9e6b61959 100644
--- a/django/utils/timesince.py
+++ b/django/utils/timesince.py
@@ -24,26 +24,30 @@ TIMESINCE_CHUNKS = (
)
-def timesince(d, now=None, reversed=False, time_strings=None):
+def timesince(d, now=None, reversed=False, time_strings=None, depth=2):
"""
Take two datetime objects and return the time between d and now as a nicely
formatted string, e.g. "10 minutes". If d occurs after now, return
"0 minutes".
Units used are years, months, weeks, days, hours, and minutes.
- Seconds and microseconds are ignored. Up to two adjacent units will be
+ Seconds and microseconds are ignored. Up to `depth` 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.
`time_strings` is an optional dict of strings to replace the default
TIME_STRINGS dict.
+ `depth` is an optional integer to control the number of adjacent time
+ units returned.
+
Adapted from
https://web.archive.org/web/20060617175230/http://blog.natbat.co.uk/archive/2003/Jun/14/time_since
"""
if time_strings is None:
time_strings = TIME_STRINGS
-
+ if depth <= 0:
+ raise ValueError('depth must be greater than 0.')
# Convert datetime.date to datetime.datetime for comparison.
if not isinstance(d, datetime.datetime):
d = datetime.datetime(d.year, d.month, d.day)
@@ -74,18 +78,24 @@ def timesince(d, now=None, reversed=False, time_strings=None):
count = since // seconds
if count != 0:
break
- result = avoid_wrapping(time_strings[name] % count)
- if i + 1 < len(TIMESINCE_CHUNKS):
- # Now get the second item
- seconds2, name2 = TIMESINCE_CHUNKS[i + 1]
- count2 = (since - (seconds * count)) // seconds2
- if count2 != 0:
- result += gettext(', ') + avoid_wrapping(time_strings[name2] % count2)
- return result
+ else:
+ return avoid_wrapping(time_strings['minute'] % 0)
+ result = []
+ current_depth = 0
+ while i < len(TIMESINCE_CHUNKS) and current_depth < depth:
+ seconds, name = TIMESINCE_CHUNKS[i]
+ count = since // seconds
+ if count == 0:
+ break
+ result.append(avoid_wrapping(time_strings[name] % count))
+ since -= seconds * count
+ current_depth += 1
+ i += 1
+ return gettext(', ').join(result)
-def timeuntil(d, now=None, time_strings=None):
+def timeuntil(d, now=None, time_strings=None, depth=2):
"""
Like timesince, but return a string measuring the time until the given time.
"""
- return timesince(d, now, reversed=True, time_strings=time_strings)
+ return timesince(d, now, reversed=True, time_strings=time_strings, depth=depth)
diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt
index aa25b0f511..2f3045f971 100644
--- a/docs/releases/3.2.txt
+++ b/docs/releases/3.2.txt
@@ -298,7 +298,9 @@ URLs
Utilities
~~~~~~~~~
-* ...
+* The new ``depth`` parameter of ``django.utils.timesince.timesince()`` and
+ ``django.utils.timesince.timeuntil()`` functions allows specifying the number
+ of adjacent time units to return.
Validators
~~~~~~~~~~
diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py
index e373b83f43..d30bf00ae9 100644
--- a/tests/utils_tests/test_timesince.py
+++ b/tests/utils_tests/test_timesince.py
@@ -1,13 +1,13 @@
import datetime
-import unittest
+from django.test import TestCase
from django.test.utils import requires_tz_support
from django.utils import timezone, translation
from django.utils.timesince import timesince, timeuntil
from django.utils.translation import npgettext_lazy
-class TimesinceTests(unittest.TestCase):
+class TimesinceTests(TestCase):
def setUp(self):
self.t = datetime.datetime(2007, 8, 14, 13, 46, 0)
@@ -140,3 +140,31 @@ class TimesinceTests(unittest.TestCase):
t = datetime.datetime(1007, 8, 14, 13, 46, 0)
self.assertEqual(timesince(t, self.t), '1000\xa0years')
self.assertEqual(timeuntil(self.t, t), '1000\xa0years')
+
+ def test_depth(self):
+ t = self.t + self.oneyear + self.onemonth + self.oneweek + self.oneday + self.onehour
+ tests = [
+ (t, 1, '1\xa0year'),
+ (t, 2, '1\xa0year, 1\xa0month'),
+ (t, 3, '1\xa0year, 1\xa0month, 1\xa0week'),
+ (t, 4, '1\xa0year, 1\xa0month, 1\xa0week, 1\xa0day'),
+ (t, 5, '1\xa0year, 1\xa0month, 1\xa0week, 1\xa0day, 1\xa0hour'),
+ (t, 6, '1\xa0year, 1\xa0month, 1\xa0week, 1\xa0day, 1\xa0hour'),
+ (self.t + self.onehour, 5, '1\xa0hour'),
+ (self.t + (4 * self.oneminute), 3, '4\xa0minutes'),
+ (self.t + self.onehour + self.oneminute, 1, '1\xa0hour'),
+ (self.t + self.oneday + self.onehour, 1, '1\xa0day'),
+ (self.t + self.oneweek + self.oneday, 1, '1\xa0week'),
+ (self.t + self.onemonth + self.oneweek, 1, '1\xa0month'),
+ (self.t + self.oneyear + self.onemonth, 1, '1\xa0year'),
+ (self.t + self.oneyear + self.oneweek + self.oneday, 3, '1\xa0year'),
+ ]
+ for value, depth, expected in tests:
+ with self.subTest():
+ self.assertEqual(timesince(self.t, value, depth=depth), expected)
+ self.assertEqual(timeuntil(value, self.t, depth=depth), expected)
+
+ def test_depth_invalid(self):
+ msg = 'depth must be greater than 0.'
+ with self.assertRaisesMessage(ValueError, msg):
+ timesince(self.t, self.t, depth=0)