From 2506875da84239f3dafc2f386f77e0988769f101 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 7 Aug 2021 20:41:21 -0700 Subject: [PATCH] Add an "all" granularity to humanize It's more convenient to pass a simple short string than to pass a list of 7 strings. Resolve #997 --- arrow/arrow.py | 10 ++++++++-- tests/test_arrow.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/arrow/arrow.py b/arrow/arrow.py index d01fa894..cc792051 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -69,6 +69,7 @@ _GRANULARITY = Literal[ "auto", + "all", "second", "minute", "hour", @@ -1130,7 +1131,8 @@ def humanize( :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: (optional) defines the precision of the output. Set it to strings 'second', 'minute', - 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings + 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings. + Set it to 'all' to include all possible units in the granularity. Usage:: @@ -1228,7 +1230,7 @@ def humanize( years = sign * max(delta_second // self._SECS_PER_YEAR, 2) return locale.describe("years", years, only_distance=only_distance) - elif isinstance(granularity, str): + elif isinstance(granularity, str) and granularity != "all": granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment] if granularity == "second": @@ -1282,6 +1284,10 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: "minute", "second", ) + + if granularity == "all": + granularity = cast(List[_GRANULARITY], frames) + for frame in frames: delta = gather_timeframes(delta, frame) diff --git a/tests/test_arrow.py b/tests/test_arrow.py index cef9ee6d..bcc5f109 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2032,6 +2032,49 @@ def test_multiple_granularity(self): == "a minute and 2 seconds ago" ) + def test_all_granularity(self): + assert ( + self.now.humanize(granularity="all") + == "in 0 years 0 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" + ) # TODO: this should be "ago"; change this when #997 is merged + + later105 = self.now.shift(seconds=10 ** 5) + assert ( + self.now.humanize(later105, granularity="all") + == "0 years 0 months 0 weeks a day 3 hours 46 minutes and 40 seconds ago" + ) + assert ( + later105.humanize(self.now, granularity="all") + == "in 0 years 0 months 0 weeks a day 3 hours 46 minutes and 40 seconds" + ) + + later108 = self.now.shift(seconds=10 ** 8) + assert ( + self.now.humanize(later108, granularity="all") + == "3 years 2 months 0 weeks a day 9 hours 46 minutes and 40 seconds ago" + ) + assert ( + later108.humanize(self.now, granularity="all") + == "in 3 years 2 months 0 weeks a day 9 hours 46 minutes and 40 seconds" + ) + assert ( + self.now.humanize(later108, granularity="all", only_distance=True) + == "3 years 2 months 0 weeks a day 9 hours 46 minutes and 40 seconds" + ) + + later_two_months = self.now.shift(months=2) + assert ( + self.now.humanize(later_two_months, granularity="all") + == "in 0 years 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" + ) # TODO: this should be "ago"; change this when #997 is merged + assert ( + later_two_months.humanize(self.now, granularity="all") + == "in 0 years 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" + ) + + with pytest.raises(ValueError): + self.now.humanize(later108, granularity=["all", "year"]) + def test_seconds(self): later = self.now.shift(seconds=10)