Repository: sopel-irc/sopel

Issue:
tools.time: `seconds_to_human()` doesn't handle 0 well
### Description
`tools.time.seconds_to_human()` returns just `" ago"` when passed something that `int()` parses to 0 (i.e. -0.999... through 0.999...)

### Expected behavior
A sensible return value would be `"0 seconds ago"`.

Other values might also make sense (`"just now"`, `"very recently"`, etc.) but they don't necessarily work as well in the same contexts as the expected return format for non-zero inputs.

### Environment
- Sopel `.version`: 7.0.1


Discussion:

Resolving patch:
diff --git a/sopel/tools/time.py b/sopel/tools/time.py
--- a/sopel/tools/time.py
+++ b/sopel/tools/time.py
@@ -226,30 +226,35 @@ def seconds_to_human(secs):
     secs = int(secs)
     secs = abs(secs)
 
-    years = secs // 31536000
-    months = (secs - years * 31536000) // 2635200
-    days = (secs - years * 31536000 - months * 2635200) // 86400
-    hours = (secs - years * 31536000 - months * 2635200 - days * 86400) // 3600
-    minutes = (secs - years * 31536000 - months * 2635200 - days * 86400 - hours * 3600) // 60
-    seconds = secs - years * 31536000 - months * 2635200 - days * 86400 - hours * 3600 - minutes * 60
-
-    years_text = "year{}".format("s" if years != 1 else "")
-    months_text = "month{}".format("s" if months != 1 else "")
-    days_text = "day{}".format("s" if days != 1 else "")
-    hours_text = "hour{}".format("s" if hours != 1 else "")
-    minutes_text = "minute{}".format("s" if minutes != 1 else "")
-    seconds_text = "second{}".format("s" if seconds != 1 else "")
-
-    result = ", ".join(filter(lambda x: bool(x), [
-        "{0} {1}".format(years, years_text) if years else "",
-        "{0} {1}".format(months, months_text) if months else "",
-        "{0} {1}".format(days, days_text) if days else "",
-        "{0} {1}".format(hours, hours_text) if hours else "",
-        "{0} {1}".format(minutes, minutes_text) if minutes else "",
-        "{0} {1}".format(seconds, seconds_text) if seconds else ""
-    ]))
-    # Granularity
-    result = ", ".join(result.split(", ")[:2])
+    if secs == 0:
+        # zero is a special case that the algorithm below won't handle correctly (#1841)
+        result = "0 seconds"
+    else:
+        years = secs // 31536000
+        months = (secs - years * 31536000) // 2635200
+        days = (secs - years * 31536000 - months * 2635200) // 86400
+        hours = (secs - years * 31536000 - months * 2635200 - days * 86400) // 3600
+        minutes = (secs - years * 31536000 - months * 2635200 - days * 86400 - hours * 3600) // 60
+        seconds = secs - years * 31536000 - months * 2635200 - days * 86400 - hours * 3600 - minutes * 60
+
+        years_text = "year{}".format("s" if years != 1 else "")
+        months_text = "month{}".format("s" if months != 1 else "")
+        days_text = "day{}".format("s" if days != 1 else "")
+        hours_text = "hour{}".format("s" if hours != 1 else "")
+        minutes_text = "minute{}".format("s" if minutes != 1 else "")
+        seconds_text = "second{}".format("s" if seconds != 1 else "")
+
+        result = ", ".join(filter(lambda x: bool(x), [
+            "{0} {1}".format(years, years_text) if years else "",
+            "{0} {1}".format(months, months_text) if months else "",
+            "{0} {1}".format(days, days_text) if days else "",
+            "{0} {1}".format(hours, hours_text) if hours else "",
+            "{0} {1}".format(minutes, minutes_text) if minutes else "",
+            "{0} {1}".format(seconds, seconds_text) if seconds else ""
+        ]))
+        # Granularity
+        result = ", ".join(result.split(", ")[:2])
+
     if future is False:
         result += " ago"
     else:


Test patch: