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

From: [email-masked] (GNU bug Tracking System)
Date: Sun, 02 Feb 2025 16:51:02 +0000

Your bug report

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

which was filed against the emacs package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
75849: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=75849
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#75849: 30.0.93; format-seconds: sometimes broken for negative arguments
From: Paul Eggert <[email-masked]>
To: Eli Zaretskii <[email-masked]>
Cc: [email-masked], "N. Jackson" <[email-masked]>
Date: Sun, 2 Feb 2025 08:50:15 -0800
List-Id: <debbugs-submit.debbugs.gnu.org>

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

Subject: 30.0.93; format-seconds: sometimes broken for negative arguments
From: "N. Jackson" <[email-masked]>
To: [email-masked]
Date: Sun, 26 Jan 2025 03:12:47 +0000
List-Id: <debbugs-submit.debbugs.gnu.org>

With a negative argument, the function format-seconds sometimes
gives incorrect results.

For example:

  (format-seconds "%dd %hh %mm %ss" -20)  ; => "-1d 23h 59m 40s" (incorrect)

But it works correctly when the argument is positive and it works
correctly with the same negative agument but a simpler format
string:

  (format-seconds "%dd %hh %mm %ss" 20)   ; => "0d 0h 0m 20s" (correct)
  (format-seconds "%ss" 20)               ; => "20s" (correct)
  (format-seconds "%ss" -20)              ; => "-20s" (correct)

This bug also exists on master.  (I tested just now, after commit
35d39278599caf30eb4bfbd83118ffe15d2bc705 of Jan 25 21:18:00 2025
+0100).

In GNU Emacs 30.0.93 (build 1, x86_64-pc-linux-gnu, GTK+ Version
 3.24.43, cairo version 1.18.0) of 2024-12-20 built on fedora
Windowing system distributor 'The X.Org Foundation', version 11.0.12014000
System Description: Fedora Linux 40 (Xfce)

Configured features:
ACL CAIRO DBUS FREETYPE GIF GLIB GMP GNUTLS GPM GSETTINGS HARFBUZZ
JPEG LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 M17N_FLT MODULES
NATIVE_COMP NOTIFY INOTIFY PDUMPER PNG RSVG SECCOMP SOUND SQLITE3
THREADS TIFF TOOLKIT_SCROLL_BARS WEBP X11 XDBE XIM XINPUT2 XPM GTK3
ZLIB

Important settings:
  value of $LANG: en_CA.utf8
  value of $XMODIFIERS: @im=none
  locale-coding-system: utf-8-unix

Major mode: Group

Minor modes in effect:
  hl-line-mode: t
  gnus-undo-mode: t
  yas-global-mode: t
  yas-minor-mode: t
  savehist-mode: t
  save-place-mode: t
  electric-pair-mode: t
  display-time-mode: t
  display-battery-mode: t
  delete-selection-mode: t
  cua-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  minibuffer-regexp-mode: t
  buffer-read-only: t
  size-indication-mode: t
  column-number-mode: t
  line-number-mode: t
  global-visual-line-mode: t
  visual-line-mode: t
  transient-mark-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  temp-buffer-resize-mode: t
  abbrev-mode: t

Load-path shadows:
None found.

Features:
(shadow emacsbug mailalias smtpmail solar cal-dst holidays
holiday-loaddefs cal-iso mule-util cal-move bbdb-message sort
gnus-cite mail-extr textsec uni-scripts idna-mapping ucs-normalize
uni-confusable textsec-check gnus-async gnus-bcklg misearch
multi-isearch gnus-kill qp gnus-ml disp-table hl-line nndraft nnmh
utf-7 epa-file network-stream nsm nnfolder bbdb-gnus bbdb-mua nnnil
gnus-agent gnus-srvr gnus-score score-mode nnvirtual gnus-msg
gnus-art mm-uu mml2015 mm-view mml-smime smime gnutls dig nntp
gnus-cache gnus-sum shr pixel-fill kinsoku url-file svg dom
gnus-group gnus-undo gnus-start gnus-dbus gnus-cloud nnimap nnmail
mail-source utf7 nnoo parse-time iso8601 gnus-spec gnus-int
gnus-range message sendmail yank-media puny dired dired-loaddefs
rfc822 mml mml-sec epa epg rfc6068 epg-config mm-decode mm-bodies
mm-encode mail-parse rfc2231 rfc2047 rfc2045 ietf-drums gmm-utils
mailheader gnus-win gnus nnheader gnus-util mail-utils range mm-util
mail-prsvr org-habit wheatgrass-theme display-fill-column-indicator
display-line-numbers flyspell ispell kmacro mines derived cookie1
gamegrid transpar expand-region text-mode-expansions
the-org-mode-expansions python-el-fgallina-expansions
er-basic-expansions expand-region-core expand-region-custom hydra
advice lv compile text-property-search org-clock comp-run
comp-common org-agenda org-element org-persist xdg org-id
org-element-ast inline avl-tree generator org-refile org org-macro
org-pcomplete org-list org-footnote org-faces org-entities time-date
noutline outline ob-shell shell pcomplete ob-R ob-python python
project compat ob-plantuml ob-org ob-gnuplot ob-ditaa ob-calc
calc-store calc-trail calc-ext calc calc-loaddefs rect calc-macs
ob-awk ob-dot ob-maxima ob ob-tangle org-src sh-script smie treesit
executable ob-ref ob-lob ob-table ob-exp ob-comint comint ansi-osc
ansi-color ring ob-emacs-lisp ob-core ob-eval org-cycle org-table
org-keys oc org-loaddefs thingatpt find-func ol org-fold
org-fold-core org-compat org-version org-macs bbdb-anniv diary-lib
diary-loaddefs cal-menu calendar cal-loaddefs bbdb-com crm
mailabbrev bbdb bbdb-site timezone recentf tree-widget cus-edit pp
wid-edit ido format-spec modus-vivendi-theme modus-themes
yasnippet-classic-snippets cl-extra yasnippet help-mode savehist
saveplace company pcase elec-pair time battery dbus xml desktop
frameset delsel cua-base cus-load ace-window-autoloads
auctex-autoloads tex-site avy-autoloads bbdb-autoloads
cdlatex-autoloads company-autoloads csv-mode-autoloads
debbugs-autoloads ess-autoloads expand-region-autoloads
geiser-autoloads info orderless-autoloads rx sql-indent-autoloads
yasnippet-autoloads package browse-url url url-proxy url-privacy
url-expand url-methods url-history url-cookie generate-lisp-file
url-domsuf url-util mailcap url-handlers url-parse auth-source
cl-seq eieio eieio-core cl-macs icons password-cache json subr-x map
byte-opt gv bytecomp byte-compile url-vars cl-loaddefs cl-lib rmc
iso-transl tooltip cconv eldoc paren electric uniquify ediff-hook
vc-hooks lisp-float-type elisp-mode mwheel term/x-win x-win
term/common-win x-dnd touch-screen tool-bar dnd fontset image
regexp-opt fringe tabulated-list replace newcomment text-mode
lisp-mode prog-mode register page tab-bar menu-bar rfn-eshadow
isearch easymenu timer select scroll-bar mouse jit-lock font-lock
syntax font-core term/tty-colors frame minibuffer nadvice seq simple
cl-generic indonesian philippine cham georgian utf-8-lang misc-lang
vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms
cp51932 hebrew greek romanian slovak czech european ethiopic indian
cyrillic chinese composite emoji-zwj charscript charprop case-table
epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded
button loaddefs theme-loaddefs faces cus-face macroexp files window
text-properties overlay sha1 md5 base64 format env code-pages mule
custom widget keymap hashtable-print-readable backquote threads
dbusbind inotify dynamic-setting system-font-setting
font-render-setting cairo gtk x-toolkit xinput2 x multi-tty
move-toolbar make-network-process native-compile emacs)

Memory information:
((conses 16 807911 185147) (symbols 48 35500 2)
 (strings 32 181596 16153) (string-bytes 1 5822983) (vectors 16 71382)
 (vector-slots 8 917693 93308) (floats 8 938 970)
 (intervals 56 3347 2404) (buffers 984 23))