Skip to content

Commit 32d399b

Browse files
author
Daniel Gillet
committed
Do not keep fractional remainders for months in precisedelta
Although 1 month is 30.5 days on average, we want 31 days to be one month, and not remainder of 0.5 days which would not be intuitive.
1 parent efa5e60 commit 32d399b

File tree

2 files changed

+12
-17
lines changed

2 files changed

+12
-17
lines changed

src/humanize/time.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,10 @@ def _quotient_and_remainder(
379379
if unit in suppress:
380380
return 0, value
381381

382-
return divmod(value, divisor)
382+
# Convert the remainder back to integer is necessary for months. 1 month is 30.5
383+
# days on average, but if we have 31 days, we want to count is as a whole month,
384+
# and not as 1 month plus a remainder of 0.5 days.
385+
return tuple(int(x) for x in divmod(value, divisor))
383386

384387

385388
def _suitable_minimum_unit(min_unit: Unit, suppress: Iterable[Unit]) -> Unit:

tests/test_time.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -563,14 +563,14 @@ def test_precisedelta_one_unit_enough(
563563
"minutes",
564564
"0 minutes",
565565
),
566-
(dt.timedelta(days=31), "seconds", "1 month and 12 hours"),
567-
(dt.timedelta(days=32), "seconds", "1 month, 1 day and 12 hours"),
566+
(dt.timedelta(days=31), "seconds", "1 month"),
567+
(dt.timedelta(days=32), "seconds", "1 month and 1 day"),
568568
(dt.timedelta(days=62), "seconds", "2 months and 1 day"),
569-
(dt.timedelta(days=92), "seconds", "3 months and 12 hours"),
570-
(dt.timedelta(days=31), "days", "1 month and 0.50 days"),
571-
(dt.timedelta(days=32), "days", "1 month and 1.50 days"),
569+
(dt.timedelta(days=92), "seconds", "3 months"),
570+
(dt.timedelta(days=31), "days", "1 month"),
571+
(dt.timedelta(days=32), "days", "1 month and 1 day"),
572572
(dt.timedelta(days=62), "days", "2 months and 1 day"),
573-
(dt.timedelta(days=92), "days", "3 months and 0.50 days"),
573+
(dt.timedelta(days=92), "days", "3 months"),
574574
],
575575
)
576576
def test_precisedelta_multiple_units(
@@ -620,19 +620,11 @@ def test_precisedelta_multiple_units(
620620
"5 days and 4.50 hours",
621621
),
622622
(dt.timedelta(days=5, hours=4, seconds=30 * 60), "days", "%0.2f", "5.19 days"),
623-
# 1 month is 30.5 days. Remaining 0.5 days is rounded down for both formats
623+
# 1 month is 30.5 days but remainder is always rounded down.
624624
(dt.timedelta(days=31), "days", "%d", "1 month"),
625625
(dt.timedelta(days=31), "days", "%.0f", "1 month"),
626-
# But adding a tiny amount will reveal a difference between %d and %.0f
627-
# %d will truncate while %.0f will round to the nearest number.
628-
(dt.timedelta(days=31.01), "days", "%d", "1 month"),
629-
(dt.timedelta(days=31.01), "days", "%.0f", "1 month and 1 day"),
630-
(dt.timedelta(days=31.99), "days", "%d", "1 month and 1 day"),
631-
# 1 month is 30.5 days. Remaining 1.5 days is truncated for %d.
632-
# For format %.0f, there is a tie, so it's rounded to the nearest even number,
633-
# which is 2. See https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules
634626
(dt.timedelta(days=32), "days", "%d", "1 month and 1 day"),
635-
(dt.timedelta(days=32), "days", "%.0f", "1 month and 2 days"),
627+
(dt.timedelta(days=32), "days", "%.0f", "1 month and 1 day"),
636628
(dt.timedelta(days=62), "days", "%d", "2 months and 1 day"),
637629
(dt.timedelta(days=92), "days", "%d", "3 months"),
638630
(dt.timedelta(days=120), "months", "%0.2f", "3.93 months"),

0 commit comments

Comments
 (0)