Skip to content

Commit 218a86e

Browse files
authored
Fix handling of negative time deltas (#173)
Fixes #18. Fixes #171. The [Python docs say](https://docs.python.org/3/library/datetime.html#datetime.timedelta): > String representations of [timedelta](https://docs.python.org/3/library/datetime.html#datetime.timedelta) objects are normalized similarly to their internal representation. This leads to somewhat unusual results for negative timedeltas. For example: ```pycon >>> timedelta(hours=-5) datetime.timedelta(days=-1, seconds=68400) >>> print(_) -1 day, 19:00:00 ``` However, we assumed all components have the same sign, and used absolute values of some of those, leading to inconsistent behaviour (on Linux and macOS; it still worked on Windows). Instead, we should convert the whole `timedelta` into its absolute value, and then use its components directly. Also test all the `timedelta`s with positive and negative values.
2 parents 1399e04 + ec99f29 commit 218a86e

3 files changed

Lines changed: 18 additions & 15 deletions

File tree

.pre-commit-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ repos:
2828
hooks:
2929
- id: mypy
3030
additional_dependencies: [pytest, types-freezegun, types-setuptools]
31-
args: [--strict, --pretty, --show-error-codes]
31+
args: [--strict, --pretty, --show-error-codes, .]
32+
pass_filenames: false
3233

3334
- repo: https://github.com/tox-dev/pyproject-fmt
3435
rev: 1.7.0

src/humanize/time.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,13 @@ def naturaldelta(
141141

142142
use_months = months
143143

144-
seconds = abs(delta.seconds)
145-
days = abs(delta.days)
146-
years = days // 365
147-
days = days % 365
144+
delta = abs(delta)
145+
years = delta.days // 365
146+
days = delta.days % 365
148147
num_months = int(days // 30.5)
149148

150149
if not years and days < 1:
151-
if seconds == 0:
150+
if delta.seconds == 0:
152151
if min_unit == Unit.MICROSECONDS and delta.microseconds < 1000:
153152
return (
154153
_ngettext("%d microsecond", "%d microseconds", delta.microseconds)
@@ -165,24 +164,24 @@ def naturaldelta(
165164
)
166165
return _("a moment")
167166

168-
if seconds == 1:
167+
if delta.seconds == 1:
169168
return _("a second")
170169

171-
if seconds < 60:
172-
return _ngettext("%d second", "%d seconds", seconds) % seconds
170+
if delta.seconds < 60:
171+
return _ngettext("%d second", "%d seconds", delta.seconds) % delta.seconds
173172

174-
if 60 <= seconds < 120:
173+
if 60 <= delta.seconds < 120:
175174
return _("a minute")
176175

177-
if 120 <= seconds < 3600:
178-
minutes = seconds // 60
176+
if 120 <= delta.seconds < 3600:
177+
minutes = delta.seconds // 60
179178
return _ngettext("%d minute", "%d minutes", minutes) % minutes
180179

181-
if 3600 <= seconds < 3600 * 2:
180+
if 3600 <= delta.seconds < 3600 * 2:
182181
return _("an hour")
183182

184-
if 3600 < seconds:
185-
hours = seconds // 3600
183+
if 3600 < delta.seconds:
184+
hours = delta.seconds // 3600
186185
return _ngettext("%d hour", "%d hours", hours) % hours
187186

188187
elif years == 0:

tests/test_time.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def test_naturaldelta_nomonths(test_input: dt.timedelta, expected: str) -> None:
9595
(1, "a second"),
9696
(23.5, "23 seconds"),
9797
(30, "30 seconds"),
98+
(dt.timedelta(microseconds=13), "a moment"),
9899
(dt.timedelta(minutes=1, seconds=30), "a minute"),
99100
(dt.timedelta(minutes=2), "2 minutes"),
100101
(dt.timedelta(hours=1, minutes=30, seconds=30), "an hour"),
@@ -129,6 +130,8 @@ def test_naturaldelta_nomonths(test_input: dt.timedelta, expected: str) -> None:
129130
)
130131
def test_naturaldelta(test_input: float | dt.timedelta, expected: str) -> None:
131132
assert humanize.naturaldelta(test_input) == expected
133+
if not isinstance(test_input, str):
134+
assert humanize.naturaldelta(-test_input) == expected
132135

133136

134137
@freeze_time("2020-02-02")

0 commit comments

Comments
 (0)