Subject: Re: bug#75849: 30.0.93; format-seconds: sometimes broken for negative arguments

From: Paul Eggert <[email-masked]>
Date: Sun, 2 Feb 2025 08:50:15 -0800

On 2025-01-30 04:10, Eli Zaretskii wrote:
> Paul, could you please take a look at this?

I installed the attached which I think fixes this particular problem.

I hope we can find someone else to maintain format-seconds, by the way. 
That code is not my cup of tea.

From 6b40dbda6ad6d677c69263785ff4db7010840b6e Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sat, 1 Feb 2025 22:20:04 -0800
Subject: [PATCH] Improve format-seconds on negative args

* lisp/calendar/time-date.el (format-seconds):
Work better with negative seconds (Bug#75849).
* test/lisp/calendar/time-date-tests.el (test-format-seconds): Test it.
---
 lisp/calendar/time-date.el            | 13 ++++----
 test/lisp/calendar/time-date-tests.el | 43 ++++++++++++++++-----------
 2 files changed, 33 insertions(+), 23 deletions(-)

diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index 09b4cfb0edf..1d792952f98 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -305,7 +305,7 @@ format-seconds
                  ("x")))
         (case-fold-search t)
         spec match usedunits zeroflag larger prev name unit num
-        leading-zeropos trailing-zeropos fraction
+	leading-zeropos trailing-zeropos fraction minus
         chop-leading chop-trailing)
     (while (string-match "%\\.?[0-9]*\\(,[0-9]\\)?\\(.\\)" string start)
       (setq start (match-end 0)
@@ -327,8 +327,11 @@ format-seconds
       (error "Units are not in decreasing order of size"))
     (unless (numberp seconds)
       (setq seconds (float-time seconds)))
-    (setq fraction (mod seconds 1)
-          seconds (round seconds))
+    (setq minus (when (< seconds 0) "-") ; Treat -0.0 like 0.0.
+	  seconds (abs seconds)
+	  seconds (let ((s (floor seconds)))
+		    (setq fraction (- seconds s))
+		    s))
     (dolist (u units)
       (setq spec (car u)
             name (cadr u)
@@ -392,8 +395,8 @@ format-seconds
       ;; string in full.
       (when (equal string "")
         (setq string pre)))
-    (setq string (replace-regexp-in-string "%[zx]" "" string)))
-  (string-trim (string-replace "%%" "%" string)))
+    (setq string (replace-regexp-in-string "%[zx]" "" string))
+    (concat minus (string-trim (string-replace "%%" "%" string)))))
 
 (defvar seconds-to-string
   (list (list 1 "ms" 0.001)
diff --git a/test/lisp/calendar/time-date-tests.el b/test/lisp/calendar/time-date-tests.el
index 3a69a0c7b18..b8d3381528e 100644
--- a/test/lisp/calendar/time-date-tests.el
+++ b/test/lisp/calendar/time-date-tests.el
@@ -106,24 +106,31 @@ test-days-in-month
   (should-error (date-days-in-month 2020 'foo)))
 
 (ert-deftest test-format-seconds ()
-  (should (equal (format-seconds "%y %d %h %m %s %%" 0) "0 0 0 0 0 %"))
-  (should (equal (format-seconds "%y %d %h %m %s %%" 9999999) "0 115 17 46 39 %"))
-  (should (equal (format-seconds "%y %d %h %m %z %s %%" 1) "1 %"))
-  (should (equal (format-seconds "%mm %ss" 66) "1m 6s"))
-  (should (equal (format-seconds "%mm %5ss" 66) "1m     6s"))
-  (should (equal (format-seconds "%mm %.5ss" 66.4) "1m 00006s"))
-
-  (should (equal (format-seconds "%mm %,1ss" 66.4) "1m 6.4s"))
-  (should (equal (format-seconds "%mm %5,1ss" 66.4) "1m   6.4s"))
-  (should (equal (format-seconds "%mm %.5,1ss" 66.4) "1m 006.4s"))
-
-  (should (equal (format-seconds "%hh %z%x%mm %ss" (* 60 2)) "2m"))
-  (should (equal (format-seconds "%hh %z%mm %ss" (* 60 2)) "2m 0s"))
-  (should (equal (format-seconds "%hh %x%mm %ss" (* 60 2)) "0h 2m"))
-  (should (equal (format-seconds "%hh %x%mm %ss" 0) "0h 0m 0s"))
-  ;; Bug#70322
-  (should (equal (format-seconds "%y %z%d %h %m %s %%" 9999999) "115 17 46 39 %"))
-  (should (equal (format-seconds "%Y, %D, %H, %M, %z%S" 0) "0 seconds")))
+  (let ((format-seconds-list
+	 '(("%y %d %h %m %s %%" 0 "0 0 0 0 0 %")
+	   ("%y %d %h %m %s %%" 0 "0 0 0 0 0 %")
+	   ("%y %d %h %m %s %%" 9999999 "0 115 17 46 39 %")
+	   ("%y %d %h %m %z %s %%" 1 "1 %")
+	   ("%mm %ss" 66 "1m 6s")
+	   ("%mm %5ss" 66 "1m     6s")
+	   ("%mm %.5ss" 66.4 "1m 00006s")
+	   ("%mm %,1ss" 66.4 "1m 6.4s")
+	   ("%mm %5,1ss" 66.4 "1m   6.4s")
+	   ("%mm %.5,1ss" 66.4 "1m 006.4s")
+	   ("%hh %z%x%mm %ss" 120 "2m")
+	   ("%hh %z%mm %ss" 120 "2m 0s")
+	   ("%hh %x%mm %ss" 120 "0h 2m")
+	   ("%hh %x%mm %ss" 0 "0h 0m 0s")
+	   ("%y %z%d %h %m %s %%" 9999999 "115 17 46 39 %")
+	   ("%Y, %D, %H, %M, %z%S" 0 "0 seconds"))))
+    (dolist (fs format-seconds-list)
+      (let ((string (nth 0 fs))
+	    (seconds (nth 1 fs))
+	    (expected (nth 2 fs)))
+      (should (equal (format-seconds string seconds) expected))
+      (when (< 0 seconds)
+	(should (equal (format-seconds string (- seconds))
+		       (concat "-" expected))))))))
 
 (ert-deftest test-ordinal ()
   (should (equal (date-ordinal-to-time 2008 271)
-- 
2.45.2