diff --git a/pillar/web/utils/__init__.py b/pillar/web/utils/__init__.py index 60bb9334..bf08fb23 100644 --- a/pillar/web/utils/__init__.py +++ b/pillar/web/utils/__init__.py @@ -59,24 +59,54 @@ def pretty_date(time=None, detail=False, now=None): 'just now', etc """ - from datetime import datetime + import datetime # Normalize the 'time' parameter so it's always a datetime. if type(time) is int: - time = datetime.fromtimestamp(time, tz=pillarsdk.utils.utc) + time = datetime.datetime.fromtimestamp(time, tz=pillarsdk.utils.utc) elif time is None: time = now - now = now or datetime.now(tz=time.tzinfo) - diff = now - time + now = now or datetime.datetime.now(tz=time.tzinfo) + diff = now - time # TODO: flip the sign, so that future = positive and past = negative. - second_diff = diff.seconds + second_diff = diff.seconds # Always positive, so -1 second = -1 day + 23h59m59s day_diff = diff.days - if day_diff < 0: - return time + if day_diff < 0 and time.year != now.year: + # "16 Jul 2018" + pretty = time.strftime("%d %b %Y") - if day_diff == 0: + elif day_diff < -21 and time.year == now.year: + # "16 Jul" + pretty = time.strftime("%d %b") + + elif day_diff < -7: + week_count = -day_diff // 7 + if week_count == 1: + pretty = "in 1 week" + else: + pretty = "in %s weeks" % week_count + + elif day_diff < -1: + # "next Tuesday" + pretty = 'next %s' % time.strftime("%A") + elif day_diff == -1: + # Compute the actual number of seconds in the future, positively. + seconds = 24 * 3600 - second_diff + if seconds < 10: + return 'just now' + if seconds < 60: + return 'in %ss' % seconds + if seconds < 120: + return 'in a minute' + if seconds < 3600: + return 'in %im' % (seconds // 60) + if seconds < 7200: + return 'in an hour' + if seconds < 86400: + return 'in %ih' % (seconds // 3600) + elif day_diff == 0: if second_diff < 10: return "just now" if second_diff < 60: @@ -84,21 +114,21 @@ def pretty_date(time=None, detail=False, now=None): if second_diff < 120: return "a minute ago" if second_diff < 3600: - return str(second_diff / 60 ) + "m ago" + return str(second_diff // 60) + "m ago" if second_diff < 7200: return "an hour ago" if second_diff < 86400: - return str(second_diff / 3600) + "h ago" + return str(second_diff // 3600) + "h ago" - if day_diff == 1: + elif day_diff == 1: pretty = "yesterday" elif day_diff <= 7: - # "Tuesday" - pretty = time.strftime("%A") + # "last Tuesday" + pretty = 'last %s' % time.strftime("%A") elif day_diff <= 22: - week_count = day_diff/7 + week_count = day_diff // 7 if week_count == 1: pretty = "1 week ago" else: diff --git a/tests/test_web/test_utils.py b/tests/test_web/test_utils.py index 389c1a0d..a770568c 100644 --- a/tests/test_web/test_utils.py +++ b/tests/test_web/test_utils.py @@ -1,7 +1,11 @@ # -*- encoding: utf-8 -*- -import unittest +from __future__ import absolute_import +import unittest +import datetime + +from bson import tz_util from pillar.web import utils @@ -35,3 +39,67 @@ class IsValidIdTest(unittest.TestCase): # unicode variant of valid 12-byte str object self.assertFalse(utils.is_valid_id(u'beef€67890')) + + +class PrettyDateTest(unittest.TestCase): + def test_past(self): + from pillar.web.utils import pretty_date + + now = datetime.datetime(2016, 11, 8, 11, 46, 30, 0, tz_util.utc) # a Tuesday + + def pd(**diff): + return pretty_date(now - datetime.timedelta(**diff), now=now) + + self.assertEqual('just now', pd(seconds=5)) + self.assertEqual('5m ago', pd(minutes=5)) + self.assertEqual('last Tuesday', pd(days=7)) + self.assertEqual('1 week ago', pd(days=8)) + self.assertEqual('2 weeks ago', pd(days=14)) + self.assertEqual('08 Oct', pd(days=31)) + self.assertEqual('08 Oct 2015', pd(days=31 + 366)) + + def test_future(self): + from pillar.web.utils import pretty_date + + def pd(**diff): + return pretty_date(now + datetime.timedelta(**diff), now=now) + + now = datetime.datetime(2016, 11, 8, 11, 46, 30, 0, tz_util.utc) # a Tuesday + self.assertEqual('just now', pd(seconds=5)) + self.assertEqual('in 5m', pd(minutes=5)) + self.assertEqual('next Tuesday', pd(days=7)) + self.assertEqual('in 1 week', pd(days=8)) + self.assertEqual('in 2 weeks', pd(days=14)) + self.assertEqual('08 Dec', pd(days=30)) + self.assertEqual('08 Dec 2017', pd(days=30 + 365)) + + def test_past_with_time(self): + from pillar.web.utils import pretty_date + + now = datetime.datetime(2016, 11, 8, 11, 46, 30, 0, tz_util.utc) # a Tuesday + + def pd(**diff): + return pretty_date(now - datetime.timedelta(**diff), detail=True, now=now) + + self.assertEqual('just now', pd(seconds=5)) + self.assertEqual('5m ago', pd(minutes=5)) + self.assertEqual('last Tuesday at 11:46', pd(days=7)) + self.assertEqual('1 week ago at 11:46', pd(days=8)) + self.assertEqual('2 weeks ago at 11:46', pd(days=14)) + self.assertEqual('08 Oct at 11:46', pd(days=31)) + self.assertEqual('08 Oct 2015 at 11:46', pd(days=31 + 366)) + + def test_future_with_time(self): + from pillar.web.utils import pretty_date + + def pd(**diff): + return pretty_date(now + datetime.timedelta(**diff), detail=True, now=now) + + now = datetime.datetime(2016, 11, 8, 11, 46, 30, 0, tz_util.utc) # a Tuesday + self.assertEqual('just now', pd(seconds=5)) + self.assertEqual('in 5m', pd(minutes=5)) + self.assertEqual('next Tuesday at 11:46', pd(days=7)) + self.assertEqual('in 1 week at 11:46', pd(days=8)) + self.assertEqual('in 2 weeks at 11:46', pd(days=14)) + self.assertEqual('08 Dec at 11:46', pd(days=30)) + self.assertEqual('08 Dec 2017 at 11:46', pd(days=30 + 365))