From 89f77b15a27d57db6ff0b8bf013decf3d970574e Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Sat, 7 Aug 2021 20:41:21 -0700 Subject: [PATCH 1/5] 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 #1014 --- arrow/arrow.py | 10 ++++++++-- docs/index.rst | 2 ++ tests/test_arrow.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 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/docs/index.rst b/docs/index.rst index 43895b04..8fce2070 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -242,6 +242,8 @@ Indicate a specific time granularity (or multiple): 'an hour and 6 minutes ago' >>> future.humanize(present, only_distance=True, granularity=["hour", "minute"]) 'an hour and 6 minutes' + >>> future.humanize(present, granularity="all") + 'in 0 years 0 months 0 weeks 0 days an hour 6 minutes and 0 seconds' Support for a growing number of locales (see ``locales.py`` for supported languages): 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) From 06dd6308eaa76d82f4c27f0da3fbff336cdbc623 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 23 Dec 2021 20:22:25 -0800 Subject: [PATCH 2/5] Fix missing quarters in 'all' granularity tests --- tests/test_arrow.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 339912d6..1aac9ff5 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2056,41 +2056,41 @@ def test_multiple_granularity(self): 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" + == "in 0 years 0 quarters 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" + == "0 years 0 quarters 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" + == "in 0 years 0 quarters 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" + == "3 years 0 quarters 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" + == "in 3 years 0 quarters 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" + == "3 years 0 quarters 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" + == "in 0 years 0 quarters 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" + == "in 0 years 0 quarters 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" ) with pytest.raises(ValueError): From b2d289478b06bcea7feb536943e7daa64b4a0ab6 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 23 Dec 2021 20:34:29 -0800 Subject: [PATCH 3/5] Fix inconsistent 'all' granularity test results The assertions only worked for certain dates and times. For example, a difference of 2 months could result in "1 month and 29 days" rather than "2 months" depending on the start time. Since utcnow() was used, assertions could fail if ran at a later date. Fix this by using a fixed time for test data rather than utcnow(). --- tests/test_arrow.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 1aac9ff5..b9d3f533 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2059,42 +2059,42 @@ def test_all_granularity(self): == "in 0 years 0 quarters 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) + later105 = self.arrow.shift(seconds=10 ** 5) assert ( - self.now.humanize(later105, granularity="all") + self.arrow.humanize(later105, granularity="all") == "0 years 0 quarters 0 months 0 weeks a day 3 hours 46 minutes and 40 seconds ago" ) assert ( - later105.humanize(self.now, granularity="all") + later105.humanize(self.arrow, granularity="all") == "in 0 years 0 quarters 0 months 0 weeks a day 3 hours 46 minutes and 40 seconds" ) - later108 = self.now.shift(seconds=10 ** 8) + later108 = self.arrow.shift(seconds=10 ** 8) assert ( - self.now.humanize(later108, granularity="all") + self.arrow.humanize(later108, granularity="all") == "3 years 0 quarters 2 months 0 weeks a day 9 hours 46 minutes and 40 seconds ago" ) assert ( - later108.humanize(self.now, granularity="all") + later108.humanize(self.arrow, granularity="all") == "in 3 years 0 quarters 2 months 0 weeks a day 9 hours 46 minutes and 40 seconds" ) assert ( - self.now.humanize(later108, granularity="all", only_distance=True) + self.arrow.humanize(later108, granularity="all", only_distance=True) == "3 years 0 quarters 2 months 0 weeks a day 9 hours 46 minutes and 40 seconds" ) - later_two_months = self.now.shift(months=2) + later_two_months = self.arrow.shift(days=61) assert ( - self.now.humanize(later_two_months, granularity="all") + self.arrow.humanize(later_two_months, granularity="all") == "in 0 years 0 quarters 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") + later_two_months.humanize(self.arrow, granularity="all") == "in 0 years 0 quarters 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" ) with pytest.raises(ValueError): - self.now.humanize(later108, granularity=["all", "year"]) + self.arrow.humanize(later108, granularity=["all", "year"]) def test_seconds(self): From ea3c1ae5920a1fed752fa63a9bec2877bb0b43b9 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 23 Dec 2021 20:38:11 -0800 Subject: [PATCH 4/5] Fix relative affix for 'all' granularity test data Since #996 was fixed by #1077, the test no longer needs to use the incorrect data to work around the bug. --- tests/test_arrow.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_arrow.py b/tests/test_arrow.py index b9d3f533..e19df8aa 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2057,7 +2057,7 @@ def test_all_granularity(self): assert ( self.now.humanize(granularity="all") == "in 0 years 0 quarters 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.arrow.shift(seconds=10 ** 5) assert ( @@ -2086,8 +2086,8 @@ def test_all_granularity(self): later_two_months = self.arrow.shift(days=61) assert ( self.arrow.humanize(later_two_months, granularity="all") - == "in 0 years 0 quarters 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" - ) # TODO: this should be "ago"; change this when #997 is merged + == "0 years 0 quarters 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds ago" + ) assert ( later_two_months.humanize(self.arrow, granularity="all") == "in 0 years 0 quarters 2 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" From 804eece6ceb94ebca1935fa9845b0c94c8b5b0bf Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Thu, 23 Dec 2021 20:46:41 -0800 Subject: [PATCH 5/5] Fix time-sensitive assertion in 'all' granularity test --- tests/test_arrow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_arrow.py b/tests/test_arrow.py index e19df8aa..ab4dc3c6 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2055,7 +2055,7 @@ def test_multiple_granularity(self): def test_all_granularity(self): assert ( - self.now.humanize(granularity="all") + self.now.humanize(self.now, granularity="all") == "in 0 years 0 quarters 0 months 0 weeks 0 days 0 hours 0 minutes and 0 seconds" )