From f34285a8da1304d07fbf02d529d7e8ac8afc897a Mon Sep 17 00:00:00 2001 From: anishnya Date: Sun, 27 Jun 2021 12:04:06 -0700 Subject: [PATCH 1/4] Added dynamic humanize support and fix a big related to humanize --- arrow/arrow.py | 15 +++++++++++++-- arrow/locales.py | 21 +++++++++++++++++++-- tests/test_arrow.py | 20 ++++++++++++++++++++ tests/test_locales.py | 3 +++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/arrow/arrow.py b/arrow/arrow.py index d01fa894..a0257746 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -1122,6 +1122,7 @@ def humanize( locale: str = DEFAULT_LOCALE, only_distance: bool = False, granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto", + dynamic: bool = False, ) -> str: """Returns a localized, humanized representation of a relative difference in time. @@ -1264,7 +1265,11 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: if _frame in granularity: value = sign * _delta / self._SECS_MAP[_frame] _delta %= self._SECS_MAP[_frame] - if trunc(abs(value)) != 1: + + # If user chooses dynamic and the display value is 0 don't subtract + if dynamic and trunc(abs(value)) == 0: + pass + elif trunc(abs(value)) != 1: timeframes.append( (cast(TimeFrameLiteral, _frame + "s"), value) ) @@ -1285,12 +1290,18 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: for frame in frames: delta = gather_timeframes(delta, frame) - if len(timeframes) < len(granularity): + if len(timeframes) < len(granularity) and not dynamic: raise ValueError( "Invalid level of granularity. " "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." ) + # Needed for the case of dynamic usage (could end up with only one frame unit) + if len(timeframes) == 1: + return locale.describe( + timeframes[0][0], delta, only_distance=only_distance + ) + return locale.describe_multi(timeframes, only_distance=only_distance) except KeyError as e: diff --git a/arrow/locales.py b/arrow/locales.py index 30221fda..86422da0 100644 --- a/arrow/locales.py +++ b/arrow/locales.py @@ -173,7 +173,17 @@ def describe_multi( humanized = " ".join(parts) if not only_distance: - humanized = self._format_relative(humanized, *timeframes[-1]) + + # Needed to determine the correct relative string to use + timeframe_value = 0 + + for _unit_name, unit_value in timeframes: + if trunc(unit_value) != 0: + timeframe_value = trunc(unit_value) + break + + # Note it doesn't matter the timeframe unit we use on the call, only the value + humanized = self._format_relative(humanized, "seconds", timeframe_value) return humanized @@ -3320,8 +3330,15 @@ def describe_multi( """ humanized = "" + relative_delta = 0 + for index, (timeframe, delta) in enumerate(timeframes): last_humanized = self._format_timeframe(timeframe, trunc(delta)) + + # A check for the relative timeframe unit + if trunc(delta) != 0: + relative_delta = trunc(delta) + if index == 0: humanized = last_humanized elif index == len(timeframes) - 1: # Must have at least 2 items @@ -3333,7 +3350,7 @@ def describe_multi( humanized += ", " + last_humanized if not only_distance: - humanized = self._format_relative(humanized, timeframe, delta) + humanized = self._format_relative(humanized, timeframe, relative_delta) return humanized diff --git a/tests/test_arrow.py b/tests/test_arrow.py index ba251270..41f51c30 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2285,6 +2285,26 @@ def test_no_floats_multi_gran(self): ) assert humanize_string == "916 минути 40 няколко секунди назад" + # Dynamic Humanize Tests + + def test_dynamic_on(self): + arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) + later = arw.shift(seconds=3630) + humanize_string = arw.humanize( + later, granularity=["second", "hour", "day", "month", "year"], dynamic=True + ) + + assert humanize_string == "an hour and 30 seconds ago" + + def test_dynamic_off(self): + arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) + later = arw.shift(seconds=3600) + humanize_string = arw.humanize( + later, + granularity=["second", "hour", "day", "month", "year"], + ) + assert humanize_string == "0 years 0 months 0 days an hour and 0 seconds ago" + @pytest.mark.usefixtures("time_2013_01_01") class TestArrowHumanizeTestsWithLocale: diff --git a/tests/test_locales.py b/tests/test_locales.py index 176980ef..c05eac4f 100644 --- a/tests/test_locales.py +++ b/tests/test_locales.py @@ -732,6 +732,9 @@ def test_describe_multi(self): assert describe(seconds60) == "בעוד דקה" assert describe(seconds60, only_distance=True) == "דקה" + fulltestend0 = [("years", 5), ("weeks", 1), ("hours", 1), ("minutes", 0)] + assert describe(fulltestend0) == "בעוד 5 שנים, שבוע, שעה ו־0 דקות" + @pytest.mark.usefixtures("lang_locale") class TestMarathiLocale: From 4732ae00df7d2b1d59ecd25b76bb09455b658a6f Mon Sep 17 00:00:00 2001 From: Anish Nyayachavadi Date: Sat, 7 Aug 2021 13:25:06 -0700 Subject: [PATCH 2/4] Added Test Cases for Improved Coverage --- arrow/arrow.py | 8 ++++++++ tests/test_arrow.py | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/arrow/arrow.py b/arrow/arrow.py index a0257746..ffe2b4ed 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -1278,6 +1278,7 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: return _delta delta = float(delta_second) + frames: Tuple[TimeFrameLiteral, ...] = ( "year", "month", @@ -1296,6 +1297,13 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month' or 'year'." ) + # Needed to see if there are no units output an error + if not timeframes and dynamic: + raise ValueError( + "All provided granulairty values produced an output of zero. " + "Consider using smaller granularities, or set the dynamic flag to False. " + ) + # Needed for the case of dynamic usage (could end up with only one frame unit) if len(timeframes) == 1: return locale.describe( diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 41f51c30..402fd619 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2286,7 +2286,6 @@ def test_no_floats_multi_gran(self): assert humanize_string == "916 минути 40 няколко секунди назад" # Dynamic Humanize Tests - def test_dynamic_on(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) later = arw.shift(seconds=3630) @@ -2296,6 +2295,20 @@ def test_dynamic_on(self): assert humanize_string == "an hour and 30 seconds ago" + def test_dynamic_on_one_granularity(self): + arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) + later = arw.shift(seconds=0) + humanize_string = arw.humanize(later, granularity=["hour"], dynamic=True) + + assert humanize_string == "in 0 hours" + + def test_dynamic_on_zero_output(self): + arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) + later = arw.shift(seconds=0) + + with pytest.raises(ValueError): + arw.humanize(later, granularity=["hour", "second"], dynamic=True) + def test_dynamic_off(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) later = arw.shift(seconds=3600) From 9ce1d98a8e75db839aa5875f0e01dbe7a48f9154 Mon Sep 17 00:00:00 2001 From: Anish Nyayachavadi Date: Sat, 7 Aug 2021 13:35:02 -0700 Subject: [PATCH 3/4] Fixed Codecov issue --- tests/test_arrow.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 402fd619..9cdc23bb 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2297,10 +2297,12 @@ def test_dynamic_on(self): def test_dynamic_on_one_granularity(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) - later = arw.shift(seconds=0) - humanize_string = arw.humanize(later, granularity=["hour"], dynamic=True) + later = arw.shift(seconds=3600) + humanize_string = arw.humanize( + later, granularity=["hour", "second"], dynamic=True + ) - assert humanize_string == "in 0 hours" + assert humanize_string == "in an hour" def test_dynamic_on_zero_output(self): arw = arrow.Arrow(2013, 1, 1, 0, 0, 0) From cf9ea8790c73569cb5b3d38bb5cf017fd2ce300e Mon Sep 17 00:00:00 2001 From: Anish Nyayachavadi <55898433+anishnya@users.noreply.github.com> Date: Sat, 14 Aug 2021 00:48:04 -0700 Subject: [PATCH 4/4] Fixed spelling error within error message Co-authored-by: Kris Fremen --- arrow/arrow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow/arrow.py b/arrow/arrow.py index ffe2b4ed..1117d67e 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -1300,7 +1300,7 @@ def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: # Needed to see if there are no units output an error if not timeframes and dynamic: raise ValueError( - "All provided granulairty values produced an output of zero. " + "All provided granularity values produced an output of zero. " "Consider using smaller granularities, or set the dynamic flag to False. " )