|
@@ -13,23 +13,26 @@ def humanize_duration(value):
|
|
|
"""
|
|
"""
|
|
|
Express a timedelta in a human-friendly format. Example: 1h 5m 23s. Durations of a second or
|
|
Express a timedelta in a human-friendly format. Example: 1h 5m 23s. Durations of a second or
|
|
|
more are rounded to whole seconds; shorter durations are rounded to the millisecond (e.g.
|
|
more are rounded to whole seconds; shorter durations are rounded to the millisecond (e.g.
|
|
|
- 0.43s). Returns an empty string for None; zero and negative durations render as "0s".
|
|
|
|
|
|
|
+ 0.43s). A negative duration is rendered with a leading minus sign, so that an anomalous value
|
|
|
|
|
+ remains recognizable as one. Returns an empty string for None; zero renders as "0s".
|
|
|
"""
|
|
"""
|
|
|
if value is None:
|
|
if value is None:
|
|
|
return ''
|
|
return ''
|
|
|
|
|
|
|
|
- # Negative durations (which can result from clock skew) are clamped to zero
|
|
|
|
|
- total_seconds = max(value.total_seconds(), 0)
|
|
|
|
|
|
|
+ total_seconds = value.total_seconds()
|
|
|
|
|
+ magnitude = abs(total_seconds)
|
|
|
|
|
|
|
|
# Render sub-second durations to the millisecond, as rounding them to whole seconds would
|
|
# Render sub-second durations to the millisecond, as rounding them to whole seconds would
|
|
|
# report every short-lived duration as zero. Trailing zeros are stripped.
|
|
# report every short-lived duration as zero. Trailing zeros are stripped.
|
|
|
- if 0 < total_seconds < 1:
|
|
|
|
|
- milliseconds = f'{total_seconds:.3f}'.rstrip('0').rstrip('.')
|
|
|
|
|
- if milliseconds != '0':
|
|
|
|
|
- return f'{milliseconds}s'
|
|
|
|
|
|
|
+ if 0 < magnitude < 1:
|
|
|
|
|
+ rendered = f'{magnitude:.3f}'.rstrip('0').rstrip('.')
|
|
|
|
|
+ # A magnitude below a millisecond has no representation here, so fall through to "0s".
|
|
|
|
|
+ # Rounding up to a whole second (e.g. 0.9996) likewise falls through, to "1s".
|
|
|
|
|
+ if rendered not in ('0', '1'):
|
|
|
|
|
+ return f'-{rendered}s' if total_seconds < 0 else f'{rendered}s'
|
|
|
|
|
|
|
|
# Round to whole seconds and decompose
|
|
# Round to whole seconds and decompose
|
|
|
- days, remainder = divmod(round(total_seconds), 86400)
|
|
|
|
|
|
|
+ days, remainder = divmod(round(magnitude), 86400)
|
|
|
hours, remainder = divmod(remainder, 3600)
|
|
hours, remainder = divmod(remainder, 3600)
|
|
|
minutes, seconds = divmod(remainder, 60)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
|
|
|
|
|
@@ -42,7 +45,12 @@ def humanize_duration(value):
|
|
|
ret += f'{minutes}m '
|
|
ret += f'{minutes}m '
|
|
|
if seconds or not ret:
|
|
if seconds or not ret:
|
|
|
ret += f'{seconds}s'
|
|
ret += f'{seconds}s'
|
|
|
- return ret.strip()
|
|
|
|
|
|
|
+ ret = ret.strip()
|
|
|
|
|
+
|
|
|
|
|
+ # Zero carries no sign, however the original value was signed
|
|
|
|
|
+ if total_seconds < 0 and ret != '0s':
|
|
|
|
|
+ ret = f'-{ret}'
|
|
|
|
|
+ return ret
|
|
|
|
|
|
|
|
|
|
|
|
|
def enum_key(value):
|
|
def enum_key(value):
|