Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions humanfriendly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,13 +429,14 @@ def format_timespan(num_seconds, detailed=False, max_units=3):
'1 year, 2 days and 3 hours'
"""
num_seconds = coerce_seconds(num_seconds)
if num_seconds < 60 and not detailed:
if abs(num_seconds) < 60 and not detailed:
# Fast path.
return pluralize(round_number(num_seconds), 'second')
else:
# Slow path.
is_negative = num_seconds < 0
result = []
num_seconds = decimal.Decimal(str(num_seconds))
num_seconds = decimal.Decimal(str(abs(num_seconds)))
relevant_units = list(reversed(time_units[0 if detailed else 3:]))
for unit in relevant_units:
# Extract the unit count from the remaining time.
Expand All @@ -454,13 +455,16 @@ def format_timespan(num_seconds, detailed=False, max_units=3):
result.append(pluralize(count, unit['singular'], unit['plural']))
if len(result) == 1:
# A single count/unit combination.
return result[0]
formatted = result[0]
else:
if not detailed:
# Remove `insignificant' data from the formatted timespan.
result = result[:max_units]
# Format the timespan in a readable way.
return concatenate(result)
formatted = concatenate(result)
# Apply a single leading minus sign for negative timespans instead of
# letting each individual unit carry its own sign.
return ('-' + formatted) if is_negative else formatted


def parse_timespan(timespan):
Expand Down
6 changes: 6 additions & 0 deletions humanfriendly/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ def test_format_timespan(self):
assert '5 minutes and 0.3 seconds' == format_timespan(300.300)
assert '1 second and 15 milliseconds' == format_timespan(1.015, detailed=True)
assert '10 seconds and 15 milliseconds' == format_timespan(10.015, detailed=True)
# Negative timespans should get a single leading minus sign instead
# of a minus sign on every individual unit.
assert '-1 hour, 1 minute and 40 seconds' == format_timespan(-3700, detailed=True)
assert '-1 minute and 30 seconds' == format_timespan(-90, detailed=True)
assert '-30 seconds' == format_timespan(-30, detailed=False)
assert '-1 hour, 1 minute and 40 seconds' == format_timespan(-3700, detailed=False)
assert '1 microsecond and 50 nanoseconds' == format_timespan(0.00000105, detailed=True)
# Test the datetime.timedelta support:
# https://github.com/xolox/python-humanfriendly/issues/27
Expand Down