Skip to content

Commit

Permalink
Humanizegranularity (arrow-py#388)
Browse files Browse the repository at this point in the history
* Main functionnalities

* New feature : Granularity with time

* Included decriptive error with tests

* Corrected a bug

Arrow don't supports week granularity
  • Loading branch information
galaplexus authored and andrewelkins committed Dec 31, 2016
1 parent 549204c commit a20b22b
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 45 deletions.
110 changes: 67 additions & 43 deletions arrow/arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from datetime import datetime, timedelta, tzinfo
from dateutil import tz as dateutil_tz
from dateutil.relativedelta import relativedelta
from math import trunc
import calendar
import sys
import warnings
Expand Down Expand Up @@ -699,14 +700,14 @@ def format(self, fmt='YYYY-MM-DD HH:mm:ssZZ', locale='en_us'):
return formatter.DateTimeFormatter(locale).format(self._datetime, fmt)


def humanize(self, other=None, locale='en_us', only_distance=False):
def humanize(self, other=None, locale='en_us', only_distance=False, granularity='auto'):
''' Returns a localized, humanized representation of a relative difference in time.
:param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
:param locale: (optional) a ``str`` specifying a locale. Defaults to 'en_us'.
:param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
:param granularity: (optinal) defines the precision of the output. Set it to strings 'second', 'minute', 'hour', 'day', 'month' or 'year'.
Usage::
>>> earlier = arrow.utcnow().replace(hours=-2)
Expand Down Expand Up @@ -741,48 +742,71 @@ def humanize(self, other=None, locale='en_us', only_distance=False):
sign = -1 if delta < 0 else 1
diff = abs(delta)
delta = diff

if granularity=='auto':
if diff < 10:
return locale.describe('now', only_distance=only_distance)

if diff < 45:
seconds = sign * delta
return locale.describe('seconds', seconds, only_distance=only_distance)

elif diff < 90:
return locale.describe('minute', sign, only_distance=only_distance)
elif diff < 2700:
minutes = sign * int(max(delta / 60, 2))
return locale.describe('minutes', minutes, only_distance=only_distance)

elif diff < 5400:
return locale.describe('hour', sign, only_distance=only_distance)
elif diff < 79200:
hours = sign * int(max(delta / 3600, 2))
return locale.describe('hours', hours, only_distance=only_distance)

elif diff < 129600:
return locale.describe('day', sign, only_distance=only_distance)
elif diff < 2160000:
days = sign * int(max(delta / 86400, 2))
return locale.describe('days', days, only_distance=only_distance)

elif diff < 3888000:
return locale.describe('month', sign, only_distance=only_distance)
elif diff < 29808000:
self_months = self._datetime.year * 12 + self._datetime.month
other_months = dt.year * 12 + dt.month

months = sign * int(max(abs(other_months - self_months), 2))

return locale.describe('months', months, only_distance=only_distance)

elif diff < 47260800:
return locale.describe('year', sign, only_distance=only_distance)
else:
years = sign * int(max(delta / 31536000, 2))
return locale.describe('years', years, only_distance=only_distance)

if diff < 10:
return locale.describe('now', only_distance=only_distance)

if diff < 45:
return locale.describe('seconds', sign, only_distance=only_distance)

elif diff < 90:
return locale.describe('minute', sign, only_distance=only_distance)
elif diff < 2700:
minutes = sign * int(max(delta / 60, 2))
return locale.describe('minutes', minutes, only_distance=only_distance)

elif diff < 5400:
return locale.describe('hour', sign, only_distance=only_distance)
elif diff < 79200:
hours = sign * int(max(delta / 3600, 2))
return locale.describe('hours', hours, only_distance=only_distance)

elif diff < 129600:
return locale.describe('day', sign, only_distance=only_distance)
elif diff < 2160000:
days = sign * int(max(delta / 86400, 2))
return locale.describe('days', days, only_distance=only_distance)

elif diff < 3888000:
return locale.describe('month', sign, only_distance=only_distance)
elif diff < 29808000:
self_months = self._datetime.year * 12 + self._datetime.month
other_months = dt.year * 12 + dt.month

months = sign * int(max(abs(other_months - self_months), 2))

return locale.describe('months', months, only_distance=only_distance)

elif diff < 47260800:
return locale.describe('year', sign, only_distance=only_distance)
else:
years = sign * int(max(delta / 31536000, 2))
return locale.describe('years', years, only_distance=only_distance)


if granularity == 'second':
delta = sign * delta
if(abs(delta) < 2):
return locale.describe('now', only_distance=only_distance)
elif granularity == 'minute':
delta = sign * delta / float(60)
elif granularity == 'hour':
delta = sign * delta / float(60*60)
elif granularity == 'day':
delta = sign * delta / float(60*60*24)
elif granularity == 'month':
delta = sign * delta / float(60*60*24*30.5)
elif granularity == 'year':
delta = sign * delta / float(60*60*24*365.25)
else:
raise AttributeError('Error. Could not understand your level of granularity. Please select between \
"second", "minute", "hour", "day", "week", "month" or "year"')

if(trunc(abs(delta)) != 1):
granularity += 's'
return locale.describe(granularity, delta, only_distance=False)
# math

def __add__(self, other):
Expand All @@ -805,7 +829,7 @@ def __sub__(self, other):

elif isinstance(other, Arrow):
return self._datetime - other._datetime

# print granularity
raise TypeError()

def __rsub__(self, other):
Expand Down
4 changes: 2 additions & 2 deletions arrow/locales.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import inspect
import sys
from math import trunc


def get_locale(name):
Expand Down Expand Up @@ -169,8 +170,7 @@ def _name_to_ordinal(self, lst):
return dict(map(lambda i: (i[1].lower(), i[0] + 1), enumerate(lst[1:])))

def _format_timeframe(self, timeframe, delta):

return self.timeframes[timeframe].format(abs(delta))
return self.timeframes[timeframe].format(trunc(abs(delta)))

def _format_relative(self, humanized, timeframe, delta):

Expand Down
54 changes: 54 additions & 0 deletions tests/arrow_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,60 @@ def setUp(self):
self.datetime = datetime(2013, 1, 1)
self.now = arrow.Arrow.utcnow()

def test_granularity(self):

assertEqual(self.now.humanize(granularity = 'second'), 'just now')

later1 = self.now.shift(seconds=1)
assertEqual(self.now.humanize(later1, granularity = 'second'), 'just now')
assertEqual(later1.humanize(self.now, granularity = 'second'), 'just now')
assertEqual(self.now.humanize(later1, granularity = 'minute'), '0 minutes ago')
assertEqual(later1.humanize(self.now, granularity = 'minute'), 'in 0 minutes')

later100 = self.now.shift(seconds=100)
assertEqual(self.now.humanize(later100, granularity = 'second'), 'seconds ago')
assertEqual(later100.humanize(self.now, granularity = 'second'), 'in seconds')
assertEqual(self.now.humanize(later100, granularity = 'minute'), 'a minute ago')
assertEqual(later100.humanize(self.now, granularity = 'minute'), 'in a minute')
assertEqual(self.now.humanize(later100, granularity = 'hour'), '0 hours ago')
assertEqual(later100.humanize(self.now, granularity = 'hour'), 'in 0 hours')

later4000 = self.now.shift(seconds=4000)
assertEqual(self.now.humanize(later4000, granularity = 'minute'), '66 minutes ago')
assertEqual(later4000.humanize(self.now, granularity = 'minute'), 'in 66 minutes')
assertEqual(self.now.humanize(later4000, granularity = 'hour'), 'an hour ago')
assertEqual(later4000.humanize(self.now, granularity = 'hour'), 'in an hour')
assertEqual(self.now.humanize(later4000, granularity = 'day'), '0 days ago')
assertEqual(later4000.humanize(self.now, granularity = 'day'), 'in 0 days')

later105 = self.now.shift(seconds=10 ** 5)
assertEqual(self.now.humanize(later105, granularity = 'hour'), '27 hours ago')
assertEqual(later105.humanize(self.now, granularity = 'hour'), 'in 27 hours')
assertEqual(self.now.humanize(later105, granularity = 'day'), 'a day ago')
assertEqual(later105.humanize(self.now, granularity = 'day'), 'in a day')
assertEqual(self.now.humanize(later105, granularity = 'month'), '0 months ago')
assertEqual(later105.humanize(self.now, granularity = 'month'), 'in 0 months')

later106 = self.now.shift(seconds=3 * 10 ** 6)
assertEqual(self.now.humanize(later106, granularity = 'day'), '34 days ago')
assertEqual(later106.humanize(self.now, granularity = 'day'), 'in 34 days')
assertEqual(self.now.humanize(later106, granularity = 'month'), 'a month ago')
assertEqual(later106.humanize(self.now, granularity = 'month'), 'in a month')
assertEqual(self.now.humanize(later106, granularity = 'year'), '0 years ago')
assertEqual(later106.humanize(self.now, granularity = 'year'), 'in 0 years')

later506 = self.now.shift(seconds=50 * 10 ** 6)
assertEqual(self.now.humanize(later506, granularity = 'month'), '18 months ago')
assertEqual(later506.humanize(self.now, granularity = 'month'), 'in 18 months')
assertEqual(self.now.humanize(later506, granularity = 'year'), 'a year ago')
assertEqual(later506.humanize(self.now, granularity = 'year'), 'in a year')

later108 = self.now.shift(seconds=10 ** 8)
assertEqual(self.now.humanize(later108, granularity = 'year'), '3 years ago')
assertEqual(later108.humanize(self.now, granularity = 'year'), 'in 3 years')
with assertRaises(AttributeError):
self.now.humanize(later108, granularity = 'years')

def test_seconds(self):

later = self.now.shift(seconds=10)
Expand Down

0 comments on commit a20b22b

Please sign in to comment.