From 8c0fb7cbebf7355d187a9e476c31b66d8188215b Mon Sep 17 00:00:00 2001 From: kevin <5299031+kevin9327@users.noreply.github.com> Date: Wed, 4 Nov 2026 04:11:54 +0900 Subject: [PATCH 1/2] Keep the sign of negative sub-second durations in in_words() --- src/pendulum/duration.py | 2 +- src/pendulum/interval.py | 2 +- tests/duration/test_in_words.py | 6 ++++++ tests/interval/test_in_words.py | 8 ++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/pendulum/duration.py b/src/pendulum/duration.py index d6cc0657d..5022e0424 100644 --- a/src/pendulum/duration.py +++ b/src/pendulum/duration.py @@ -275,7 +275,7 @@ def in_words(self, locale: str | None = None, separator: str = " ") -> str: count: int | str = 0 if self.microseconds != 0: unit = f"units.second.{loaded_locale.plural(0)}" - count = f"{abs(self.microseconds) / 1e6:.2f}" + count = f"{self.microseconds / 1e6:.2f}" else: unit = f"units.microsecond.{loaded_locale.plural(0)}" translation = loaded_locale.translation(unit) diff --git a/src/pendulum/interval.py b/src/pendulum/interval.py index c5e0713a7..d2ab70fba 100644 --- a/src/pendulum/interval.py +++ b/src/pendulum/interval.py @@ -278,7 +278,7 @@ def in_words(self, locale: str | None = None, separator: str = " ") -> str: count: str | int = 0 if abs(self.microseconds) > 0: unit = f"units.second.{loaded_locale.plural(1)}" - count = f"{abs(self.microseconds) / 1e6:.2f}" + count = f"{self.microseconds / 1e6:.2f}" else: unit = f"units.microsecond.{loaded_locale.plural(0)}" diff --git a/tests/duration/test_in_words.py b/tests/duration/test_in_words.py index 13982c33d..af6529e2f 100644 --- a/tests/duration/test_in_words.py +++ b/tests/duration/test_in_words.py @@ -65,6 +65,12 @@ def test_subseconds(): assert pi.in_words() == "0.12 seconds" +def test_negative_subseconds(): + pi = pendulum.duration(microseconds=-123456) + + assert pi.in_words() == "-0.12 seconds" + + def test_subseconds_with_seconds(): pi = pendulum.duration(seconds=12, microseconds=123456) assert pi.in_words() == "12 seconds" diff --git a/tests/interval/test_in_words.py b/tests/interval/test_in_words.py index f60e20d04..2144877c5 100644 --- a/tests/interval/test_in_words.py +++ b/tests/interval/test_in_words.py @@ -56,6 +56,14 @@ def test_subseconds(): assert interval.in_words() == "0.12 second" +def test_negative_subseconds(): + start_date = pendulum.datetime(2012, 1, 1) + interval = pendulum.interval( + start=start_date.add(microseconds=123456), end=start_date + ) + assert interval.in_words() == "-0.12 second" + + def test_subseconds_with_seconds(): start_date = pendulum.datetime(2012, 1, 1) interval = pendulum.interval( From 61be691e9a18d57106c39b66a73fa8aa7e4084c7 Mon Sep 17 00:00:00 2001 From: kevin <5299031+kevin9327@users.noreply.github.com> Date: Tue, 10 Nov 2026 07:03:58 +0900 Subject: [PATCH 2/2] Keep rounded-to-zero sub-second durations unsigned in in_words() Values between -0.005 s and 0 were formatting as "-0.00" after the sign fix. Round to two decimals first and add 0.0 so -0.0 becomes 0.0. --- src/pendulum/duration.py | 3 ++- src/pendulum/interval.py | 3 ++- tests/duration/test_in_words.py | 6 ++++++ tests/interval/test_in_words.py | 8 ++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pendulum/duration.py b/src/pendulum/duration.py index 5022e0424..fae1a43dd 100644 --- a/src/pendulum/duration.py +++ b/src/pendulum/duration.py @@ -275,7 +275,8 @@ def in_words(self, locale: str | None = None, separator: str = " ") -> str: count: int | str = 0 if self.microseconds != 0: unit = f"units.second.{loaded_locale.plural(0)}" - count = f"{self.microseconds / 1e6:.2f}" + value = round(self.microseconds / 1e6, 2) + count = f"{value + 0.0:.2f}" # +0.0 turns -0.0 into 0.0 else: unit = f"units.microsecond.{loaded_locale.plural(0)}" translation = loaded_locale.translation(unit) diff --git a/src/pendulum/interval.py b/src/pendulum/interval.py index d2ab70fba..d98e47863 100644 --- a/src/pendulum/interval.py +++ b/src/pendulum/interval.py @@ -278,7 +278,8 @@ def in_words(self, locale: str | None = None, separator: str = " ") -> str: count: str | int = 0 if abs(self.microseconds) > 0: unit = f"units.second.{loaded_locale.plural(1)}" - count = f"{self.microseconds / 1e6:.2f}" + value = round(self.microseconds / 1e6, 2) + count = f"{value + 0.0:.2f}" # +0.0 turns -0.0 into 0.0 else: unit = f"units.microsecond.{loaded_locale.plural(0)}" diff --git a/tests/duration/test_in_words.py b/tests/duration/test_in_words.py index af6529e2f..0ca557f80 100644 --- a/tests/duration/test_in_words.py +++ b/tests/duration/test_in_words.py @@ -71,6 +71,12 @@ def test_negative_subseconds(): assert pi.in_words() == "-0.12 seconds" +def test_negative_subseconds_round_to_zero(): + pi = pendulum.duration(microseconds=-1000) + + assert pi.in_words() == "0.00 seconds" + + def test_subseconds_with_seconds(): pi = pendulum.duration(seconds=12, microseconds=123456) assert pi.in_words() == "12 seconds" diff --git a/tests/interval/test_in_words.py b/tests/interval/test_in_words.py index 2144877c5..b4183bc49 100644 --- a/tests/interval/test_in_words.py +++ b/tests/interval/test_in_words.py @@ -64,6 +64,14 @@ def test_negative_subseconds(): assert interval.in_words() == "-0.12 second" +def test_negative_subseconds_round_to_zero(): + start_date = pendulum.datetime(2012, 1, 1) + interval = pendulum.interval( + start=start_date.add(microseconds=1000), end=start_date + ) + assert interval.in_words() == "0.00 second" + + def test_subseconds_with_seconds(): start_date = pendulum.datetime(2012, 1, 1) interval = pendulum.interval(