From afb4f6079a34b8f7c70a72b47c4e9dc821b1ef39 Mon Sep 17 00:00:00 2001 From: Zephyrkul <23347632+Zephyrkul@users.noreply.github.com> Date: Sun, 14 Apr 2024 13:56:42 -0500 Subject: [PATCH] `humanize_timedelta` improvements (#6350) Co-authored-by: TrustyJAID Co-authored-by: zephyrkul --- redbot/core/utils/chat_formatting.py | 48 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/redbot/core/utils/chat_formatting.py b/redbot/core/utils/chat_formatting.py index afcfc2d12..278b9a35e 100644 --- a/redbot/core/utils/chat_formatting.py +++ b/redbot/core/utils/chat_formatting.py @@ -519,7 +519,11 @@ def format_perms_list(perms: discord.Permissions) -> str: def humanize_timedelta( - *, timedelta: Optional[datetime.timedelta] = None, seconds: Optional[SupportsInt] = None + *, + timedelta: Optional[datetime.timedelta] = None, + seconds: Optional[SupportsInt] = None, + negative_format: Optional[str] = None, + maximum_units: Optional[int] = None, ) -> str: """ Get a locale aware human timedelta representation. @@ -535,6 +539,11 @@ def humanize_timedelta( A timedelta object seconds: Optional[SupportsInt] A number of seconds + negative_format: Optional[str] + How to format negative timedeltas, using %-formatting rules. + Defaults to "negative %s" + maximum_units: Optional[int] + The maximum number of different units to output in the final string. Returns ------- @@ -544,15 +553,33 @@ def humanize_timedelta( Raises ------ ValueError - The function was called with neither a number of seconds nor a timedelta object + The function was called with neither a number of seconds nor a timedelta object, + or with a maximum_units less than 1. + + Examples + -------- + .. testsetup:: + + from datetime import timedelta + from redbot.core.utils.chat_formatting import humanize_timedelta + + .. doctest:: + + >>> humanize_timedelta(seconds=314) + '5 minutes, 14 seconds' + >>> humanize_timedelta(timedelta=timedelta(minutes=3.14), maximum_units=1) + '3 minutes' + >>> humanize_timedelta(timedelta=timedelta(days=-3.14), negative_format="%s ago", maximum_units=3) + '3 days, 3 hours, 21 minutes ago' """ try: obj = seconds if seconds is not None else timedelta.total_seconds() except AttributeError: raise ValueError("You must provide either a timedelta or a number of seconds") + if maximum_units is not None and maximum_units < 1: + raise ValueError("maximum_units must be >= 1") - seconds = int(obj) periods = [ (_("year"), _("years"), 60 * 60 * 24 * 365), (_("month"), _("months"), 60 * 60 * 24 * 30), @@ -561,8 +588,17 @@ def humanize_timedelta( (_("minute"), _("minutes"), 60), (_("second"), _("seconds"), 1), ] - + seconds = int(obj) + if seconds < 0: + seconds = -seconds + if negative_format and "%s" not in negative_format: + negative_format = negative_format + " %s" + else: + negative_format = negative_format or (_("negative") + " %s") + else: + negative_format = "%s" strings = [] + maximum_units = maximum_units or len(periods) for period_name, plural_period_name, period_seconds in periods: if seconds >= period_seconds: period_value, seconds = divmod(seconds, period_seconds) @@ -570,8 +606,10 @@ def humanize_timedelta( continue unit = plural_period_name if period_value > 1 else period_name strings.append(f"{period_value} {unit}") + if len(strings) == maximum_units: + break - return ", ".join(strings) + return negative_format % humanize_list(strings, style="unit") def humanize_number(val: Union[int, float], override_locale=None) -> str: