Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions arrow/arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1276,10 +1276,25 @@ def humanize(
delta = sign * delta_second / self._SECS_PER_DAY
elif granularity == "week":
delta = sign * delta_second / self._SECS_PER_WEEK
elif granularity == "month":
delta = sign * delta_second / self._SECS_PER_MONTH
elif granularity == "quarter":
delta = sign * delta_second / self._SECS_PER_QUARTER
elif granularity in ("month", "quarter"):
# Count calendar months, as the "auto" granularity does, so
# that e.g. Jan 1 to Mar 1 is 2 months rather than 59 / 30.5.
# The partial month is a fraction of the next calendar month,
# which keeps it below 1 and keeps the sign of a 0 delta.
start, end = sorted((self._datetime, dt))
calendar_diff = relativedelta(end, start)
calendar_months = (
calendar_diff.years * self._MONTHS_PER_YEAR
+ calendar_diff.months
)
month_start = start + relativedelta(months=calendar_months)
month_end = start + relativedelta(months=calendar_months + 1)
elapsed_months = calendar_months + (end - month_start) / (
month_end - month_start
)
if granularity == "quarter":
elapsed_months /= self._MONTHS_PER_QUARTER
delta = sign * elapsed_months
elif granularity == "year":
delta = sign * delta_second / self._SECS_PER_YEAR
else:
Expand Down
31 changes: 29 additions & 2 deletions tests/test_arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1991,8 +1991,9 @@ def test_granularity(self):
later506 = self.now.shift(seconds=50 * 10**6)
assert self.now.humanize(later506, granularity="week") == "82 weeks ago"
assert later506.humanize(self.now, granularity="week") == "in 82 weeks"
assert self.now.humanize(later506, granularity="month") == "18 months ago"
assert later506.humanize(self.now, granularity="month") == "in 18 months"
# 2013-01-01 to 2014-08-02 is 19 calendar months
assert self.now.humanize(later506, granularity="month") == "19 months ago"
assert later506.humanize(self.now, granularity="month") == "in 19 months"
assert self.now.humanize(later506, granularity="quarter") == "6 quarters ago"
assert later506.humanize(self.now, granularity="quarter") == "in 6 quarters"
assert self.now.humanize(later506, granularity="year") == "a year ago"
Expand Down Expand Up @@ -2037,6 +2038,32 @@ def test_granularity(self):
with pytest.raises(ValueError):
self.now.humanize(later108, granularity="years")

def test_granularity_calendar_months(self):
start = arrow.Arrow(2023, 1, 31)

for months, expected in [(1, "a month"), (2, "2 months"), (6, "6 months")]:
end = start.shift(months=months)
assert end.humanize(start, granularity="month") == f"in {expected}"
assert start.humanize(end, granularity="month") == f"{expected} ago"

for months, expected in [
(3, "a quarter"),
(6, "2 quarters"),
(18, "6 quarters"),
]:
end = start.shift(months=months)
assert end.humanize(start, granularity="quarter") == f"in {expected}"
assert start.humanize(end, granularity="quarter") == f"{expected} ago"

# Less than a calendar month, even though it is more than 30.5 days
end = arrow.Arrow(2023, 1, 31, 23, 59)
assert end.humanize(arrow.Arrow(2023, 1, 1), granularity="month") == (
"in 0 months"
)
assert arrow.Arrow(2023, 1, 1).humanize(end, granularity="month") == (
"0 months ago"
)

def test_multiple_granularity(self):
assert self.now.humanize(granularity="second") == "just now"
assert self.now.humanize(granularity=["second"]) == "just now"
Expand Down
Loading