diff --git a/arrow/arrow.py b/arrow/arrow.py index ad95cacd..d6ed7a88 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -75,6 +75,7 @@ "day", "week", "month", + "quarter", "year", ] @@ -132,6 +133,7 @@ class Arrow: _SECS_PER_DAY: Final[int] = 60 * 60 * 24 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5 + _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = { @@ -141,6 +143,7 @@ class Arrow: "day": _SECS_PER_DAY, "week": _SECS_PER_WEEK, "month": _SECS_PER_MONTH, + "quarter": _SECS_PER_QUARTER, "year": _SECS_PER_YEAR, } @@ -1245,12 +1248,14 @@ def humanize( 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 == "year": delta = sign * delta_second / self._SECS_PER_YEAR else: raise ValueError( "Invalid level of granularity. " - "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." + "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) if trunc(abs(delta)) != 1: @@ -1275,6 +1280,7 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: delta = float(delta_second) frames: Tuple[TimeFrameLiteral, ...] = ( "year", + "quarter", "month", "week", "day", @@ -1288,7 +1294,7 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: if len(timeframes) < len(granularity): raise ValueError( "Invalid level of granularity. " - "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." + "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." ) return locale.describe_multi(timeframes, only_distance=only_distance) diff --git a/arrow/locales.py b/arrow/locales.py index 0b33b8c1..d6d5c486 100644 --- a/arrow/locales.py +++ b/arrow/locales.py @@ -35,6 +35,8 @@ "weeks", "month", "months", + "quarter", + "quarters", "year", "years", ] @@ -98,6 +100,8 @@ class Locale: "weeks": "", "month": "", "months": "", + "quarter": "", + "quarters": "", "year": "", "years": "", } @@ -314,6 +318,8 @@ class EnglishLocale(Locale): "weeks": "{0} weeks", "month": "a month", "months": "{0} months", + "quarter": "a quarter", + "quarters": "{0} quarters", "year": "a year", "years": "{0} years", } diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 0314ffae..589f9114 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -1935,9 +1935,29 @@ def test_granularity(self): 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" + 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" assert later506.humanize(self.now, granularity="year") == "in a year" + assert self.now.humanize(later1, granularity="quarter") == "0 quarters ago" + assert later1.humanize(self.now, granularity="quarter") == "in 0 quarters" + later107 = self.now.shift(seconds=10 ** 7) + assert self.now.humanize(later107, granularity="quarter") == "a quarter ago" + assert later107.humanize(self.now, granularity="quarter") == "in a quarter" + later207 = self.now.shift(seconds=2 * 10 ** 7) + assert self.now.humanize(later207, granularity="quarter") == "2 quarters ago" + assert later207.humanize(self.now, granularity="quarter") == "in 2 quarters" + later307 = self.now.shift(seconds=3 * 10 ** 7) + assert self.now.humanize(later307, granularity="quarter") == "3 quarters ago" + assert later307.humanize(self.now, granularity="quarter") == "in 3 quarters" + later377 = self.now.shift(seconds=3.7 * 10 ** 7) + assert self.now.humanize(later377, granularity="quarter") == "4 quarters ago" + assert later377.humanize(self.now, granularity="quarter") == "in 4 quarters" + later407 = self.now.shift(seconds=4 * 10 ** 7) + assert self.now.humanize(later407, granularity="quarter") == "5 quarters ago" + assert later407.humanize(self.now, granularity="quarter") == "in 5 quarters" + later108 = self.now.shift(seconds=10 ** 8) assert self.now.humanize(later108, granularity="year") == "3 years ago" assert later108.humanize(self.now, granularity="year") == "in 3 years"