Skip to content

Commit 05cd9b4

Browse files
authored
fix humanize month limits (#1224)
* fix month humanize * remove comment and extra coverage test as month humanize now works properly.
1 parent ea756ea commit 05cd9b4

File tree

2 files changed

+34
-24
lines changed

2 files changed

+34
-24
lines changed

arrow/arrow.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class Arrow:
123123
]
124124
_ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS]
125125
_MONTHS_PER_QUARTER: Final[int] = 3
126+
_MONTHS_PER_YEAR: Final[int] = 12
126127
_SECS_PER_MINUTE: Final[int] = 60
127128
_SECS_PER_HOUR: Final[int] = 60 * 60
128129
_SECS_PER_DAY: Final[int] = 60 * 60 * 24
@@ -1189,6 +1190,7 @@ def humanize(
11891190

11901191
elif diff < self._SECS_PER_MINUTE * 2:
11911192
return locale.describe("minute", sign, only_distance=only_distance)
1193+
11921194
elif diff < self._SECS_PER_HOUR:
11931195
minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2)
11941196
return locale.describe(
@@ -1197,36 +1199,54 @@ def humanize(
11971199

11981200
elif diff < self._SECS_PER_HOUR * 2:
11991201
return locale.describe("hour", sign, only_distance=only_distance)
1202+
12001203
elif diff < self._SECS_PER_DAY:
12011204
hours = sign * max(delta_second // self._SECS_PER_HOUR, 2)
12021205
return locale.describe("hours", hours, only_distance=only_distance)
1203-
elif diff < self._SECS_PER_DAY * 2:
1206+
1207+
calendar_diff = (
1208+
relativedelta(dt, self._datetime)
1209+
if self._datetime < dt
1210+
else relativedelta(self._datetime, dt)
1211+
)
1212+
calendar_months = (
1213+
calendar_diff.years * self._MONTHS_PER_YEAR + calendar_diff.months
1214+
)
1215+
1216+
# For months, if more than 2 weeks, count as a full month
1217+
if calendar_diff.days > 14:
1218+
calendar_months += 1
1219+
1220+
calendar_months = min(calendar_months, self._MONTHS_PER_YEAR)
1221+
1222+
if diff < self._SECS_PER_DAY * 2:
12041223
return locale.describe("day", sign, only_distance=only_distance)
1224+
12051225
elif diff < self._SECS_PER_WEEK:
12061226
days = sign * max(delta_second // self._SECS_PER_DAY, 2)
12071227
return locale.describe("days", days, only_distance=only_distance)
12081228

1229+
elif calendar_months >= 1 and diff < self._SECS_PER_YEAR:
1230+
if calendar_months == 1:
1231+
return locale.describe(
1232+
"month", sign, only_distance=only_distance
1233+
)
1234+
else:
1235+
months = sign * calendar_months
1236+
return locale.describe(
1237+
"months", months, only_distance=only_distance
1238+
)
1239+
12091240
elif diff < self._SECS_PER_WEEK * 2:
12101241
return locale.describe("week", sign, only_distance=only_distance)
1242+
12111243
elif diff < self._SECS_PER_MONTH:
12121244
weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2)
12131245
return locale.describe("weeks", weeks, only_distance=only_distance)
12141246

1215-
elif diff < self._SECS_PER_MONTH * 2:
1216-
return locale.describe("month", sign, only_distance=only_distance)
1217-
elif diff < self._SECS_PER_YEAR:
1218-
# TODO revisit for humanization during leap years
1219-
self_months = self._datetime.year * 12 + self._datetime.month
1220-
other_months = dt.year * 12 + dt.month
1221-
1222-
months = sign * max(abs(other_months - self_months), 2)
1223-
1224-
return locale.describe(
1225-
"months", months, only_distance=only_distance
1226-
)
1227-
12281247
elif diff < self._SECS_PER_YEAR * 2:
12291248
return locale.describe("year", sign, only_distance=only_distance)
1249+
12301250
else:
12311251
years = sign * max(delta_second // self._SECS_PER_YEAR, 2)
12321252
return locale.describe("years", years, only_distance=only_distance)

tests/test_arrow.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,25 +2058,15 @@ def test_weeks(self):
20582058
assert self.now.humanize(later, only_distance=True) == "2 weeks"
20592059
assert later.humanize(self.now, only_distance=True) == "2 weeks"
20602060

2061-
@pytest.mark.xfail(reason="known issue with humanize month limits")
20622061
def test_month(self):
20632062
later = self.now.shift(months=1)
20642063

2065-
# TODO this test now returns "4 weeks ago", we need to fix this to be correct on a per month basis
20662064
assert self.now.humanize(later) == "a month ago"
20672065
assert later.humanize(self.now) == "in a month"
20682066

20692067
assert self.now.humanize(later, only_distance=True) == "a month"
20702068
assert later.humanize(self.now, only_distance=True) == "a month"
20712069

2072-
def test_month_plus_4_days(self):
2073-
# TODO needed for coverage, remove when month limits are fixed
2074-
later = self.now.shift(months=1, days=4)
2075-
2076-
assert self.now.humanize(later) == "a month ago"
2077-
assert later.humanize(self.now) == "in a month"
2078-
2079-
@pytest.mark.xfail(reason="known issue with humanize month limits")
20802070
def test_months(self):
20812071
later = self.now.shift(months=2)
20822072
earlier = self.now.shift(months=-2)

0 commit comments

Comments
 (0)