unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#71573: [PATCH] seconds-to-string-approximate
@ 2024-06-15 17:24 JD Smith
  2024-06-15 17:36 ` Eli Zaretskii
                   ` (2 more replies)
  0 siblings, 3 replies; 31+ messages in thread
From: JD Smith @ 2024-06-15 17:24 UTC (permalink / raw)
  To: 71573; +Cc: Adam Porter, jonas

[-- Attachment #1: Type: text/plain, Size: 827 bytes --]

A very useful and widely used time operation is to approximate a given delay or age (in seconds) using a human-readable unit — think "2 hours", "5 days",  "3 weeks", or "7 months".  We have `seconds-to-string', but it provides more precision than is often required, skips some meaningful "human readable" duration units like weeks and months, and uses abbreviated units exclusively.  

For those familiar with magit, the `magit--age' function has provided this capability for quite some time (e.g. for short commit age), and other packages have adapted it.  It would be useful to have a version in core.

This patch provides a `seconds-to-string-approximate' function based loosely on `magit--age' and `seconds-to-string'.  It allows using abbreviated or full units, and can optionally round to the nearest half-unit.


[-- Attachment #2: seconds-to-string-approximate.patch --]
[-- Type: application/octet-stream, Size: 2089 bytes --]

diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index eca80f1e8b6..079001bafe2 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -420,6 +420,37 @@ seconds-to-string
                          (<= (car here) delay)))
              (concat (format "%.2f" (/ delay (car (cddr here)))) (cadr here))))))
 
+(defvar seconds-to-string-approximate
+  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
+    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
+    ("w" "week"   "weeks"   ,(* 60 60 24 7))
+    ("d" "day"    "days"    ,(* 60 60 24))
+    ("h" "hour"   "hours"   ,(* 60 60))
+    ("m" "minute" "minutes" 60)
+    ("s" "second" "seconds" 1))
+  "Formatting used by the function `seconds-to-string-approximate'.")
+;;;###autoload
+(defun seconds-to-string-approximate (delay &optional abbreviate half)
+  "Convert the time interval DELAY in seconds to a string approximation.
+Abbreviate the units if ABBREVIATE is non-nil.  If HALF is non-nil,
+round to the nearest half-unit, otherwise round to the nearest unit."
+  (cond ((> 0 delay)
+         (concat "-" (seconds-to-string-approximate (- delay) abbreviate half)))
+        ((= (round delay (if half 0.5 1.)) 0)
+         (format "0%s" (if abbreviate "s" " seconds")))
+        (t (let ((stsa seconds-to-string-approximate) here cnt)
+             (while (and (setq here (pop stsa)) stsa
+                         (< (/ delay (nth 3 here)) 1)))
+             (setq cnt (round (/ (float delay) (nth 3 here)) (if half 0.5 1.)))
+             (concat
+              (let ((c (if half (/ cnt 2) cnt)))
+                (if (> c 0) (number-to-string c) ""))
+              (if (and half (= (mod cnt 2) 1)) "½" "")
+              (if abbreviate "" " ")
+              (cond (abbreviate (car here))
+                    ((<= cnt (if half 2 1)) (nth 1 here))
+                    (t (nth 2 here))))))))
+
 (defun date-days-in-month (year month)
   "The number of days in MONTH in YEAR."
   (unless (and (numberp month) (<= 1 month 12))

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* bug#71573: [PATCH] seconds-to-string-approximate
  2024-06-15 17:24 bug#71573: [PATCH] seconds-to-string-approximate JD Smith
@ 2024-06-15 17:36 ` Eli Zaretskii
  2024-06-17  6:20 ` bug#71573: Related functions from ts.el Adam Porter
  2024-06-22  8:45 ` bug#71572: [PATCH] seconds-to-string-approximate Eli Zaretskii
  2 siblings, 0 replies; 31+ messages in thread
From: Eli Zaretskii @ 2024-06-15 17:36 UTC (permalink / raw)
  To: JD Smith; +Cc: 71573, adam, jonas

merge 71573 71572
thanks

> Cc: Adam Porter <adam@alphapapa.net>, jonas@bernoul.li
> From: JD Smith <jdtsmith@gmail.com>
> Date: Sat, 15 Jun 2024 13:24:00 -0400
> A very useful and widely used time operation is to approximate a given delay or age (in seconds) using a human-readable unit — think "2 hours", "5 days",  "3 weeks", or "7 months".  We have `seconds-to-string', but it provides more precision than is often required, skips some meaningful "human readable" duration units like weeks and months, and uses abbreviated units exclusively.  
> 
> For those familiar with magit, the `magit--age' function has provided this capability for quite some time (e.g. for short commit age), and other packages have adapted it.  It would be useful to have a version in core.
> 
> This patch provides a `seconds-to-string-approximate' function based loosely on `magit--age' and `seconds-to-string'.  It allows using abbreviated or full units, and can optionally round to the nearest half-unit.

This is a duplicate of bug#71572, merging.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71573: Related functions from ts.el
  2024-06-15 17:24 bug#71573: [PATCH] seconds-to-string-approximate JD Smith
  2024-06-15 17:36 ` Eli Zaretskii
@ 2024-06-17  6:20 ` Adam Porter
  2024-06-22 10:55   ` Stefan Kangas
  2024-06-22  8:45 ` bug#71572: [PATCH] seconds-to-string-approximate Eli Zaretskii
  2 siblings, 1 reply; 31+ messages in thread
From: Adam Porter @ 2024-06-17  6:20 UTC (permalink / raw)
  To: 71573

Hi all,

FWIW, my ts.el timestamp library has the related functions
`ts-human-duration' and `ts-human-format-duration'.  See 
<https://github.com/alphapapa/ts.el/blob/552936017cfdec89f7fc20c254ae6b37c3f22c5b/ts.el#L440-L491> 
and code below.

They work a bit differently, but I've found them very useful in my other
Elisp projects, and my profiling has shown that they perform very well 
relative to, e.g. the existing `format-seconds' function in terms of 
runtime and GC (see benchmarks in source comments).

If any of the code in ts.el would be helpful, I'd be glad to contribute
it to Emacs (some discussion about upstreaming parts of ts.el has also 
been going on in other, Org-related contexts).

--Adam

Elisp follows:

(defun ts-human-duration (seconds)
   "Return plist describing duration SECONDS.
List includes years, days, hours, minutes, and seconds.  This is
a simple calculation that does not account for leap years, leap
seconds, etc."
   ;; TODO: Add weeks.
   (cl-macrolet ((dividef (place divisor)
                          ;; Divide PLACE by DIVISOR, set PLACE to the 
remainder, and return the quotient.
                          `(prog1 (/ ,place ,divisor)
                             (setf ,place (% ,place ,divisor)))))
     (let* ((seconds (floor seconds))
            (years (dividef seconds 31536000))
            (days (dividef seconds 86400))
            (hours (dividef seconds 3600))
            (minutes (dividef seconds 60)))
       (list :years years :days days :hours hours :minutes minutes 
:seconds seconds))))

;; See also the built-in function `format-seconds', which I seem to have
;; overlooked before writing this.  However, a quick benchmark, run
;; 100,000 times, shows that, when controllable formatting is not needed,
;; `ts-human-format-duration' is much faster and generates less garbage:

;; | Form                     | x faster than next | Total runtime | # 
of GCs | Total GC runtime |
;; 
|--------------------------+--------------------+---------------+----------+------------------|
;; | ts-human-format-duration | 5.82               |      0.832945 | 
    3 |         0.574929 |
;; | format-seconds           | slowest            |      4.848253 | 
   17 |         3.288799 |

(cl-defun ts-human-format-duration (seconds &optional abbreviate)
   "Return human-formatted string describing duration SECONDS.
If SECONDS is less than 1, returns \"0 seconds\".  If ABBREVIATE
is non-nil, return a shorter version, without spaces.  This is a
simple calculation that does not account for leap years, leap
seconds, etc."
   ;; FIXME: Doesn't work with negative values, even though 
`ts-human-duration' does.
   (if (< seconds 1)
       (if abbreviate "0s" "0 seconds")
     (cl-macrolet ((format> (place)
                            ;; When PLACE is greater than 0, return 
formatted string using its symbol name.
                            `(when (> ,place 0)
                               (format "%d%s%s" ,place
                                       (if abbreviate "" " ")
                                       (if abbreviate
                                           ,(substring (symbol-name 
place) 0 1)
                                         ,(symbol-name place)))))
                   (join-places (&rest places)
                                ;; Return string joining the names and 
values of PLACES.
                                `(->> (list ,@(cl-loop for place in places
                                                       collect `(format> 
,place)))
                                      -non-nil
                                      (s-join (if abbreviate "" ", ")))))
       (-let* (((&plist :years :days :hours :minutes :seconds) 
(ts-human-duration seconds)))
         (join-places years days hours minutes seconds)))))





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-15 17:24 bug#71573: [PATCH] seconds-to-string-approximate JD Smith
  2024-06-15 17:36 ` Eli Zaretskii
  2024-06-17  6:20 ` bug#71573: Related functions from ts.el Adam Porter
@ 2024-06-22  8:45 ` Eli Zaretskii
  2024-06-22 21:56   ` Adam Porter
  2024-06-22 23:42   ` Paul Eggert
  2 siblings, 2 replies; 31+ messages in thread
From: Eli Zaretskii @ 2024-06-22  8:45 UTC (permalink / raw)
  To: JD Smith, Paul Eggert; +Cc: adam, 71572, jonas

> Cc: Adam Porter <adam@alphapapa.net>, jonas@bernoul.li
> From: JD Smith <jdtsmith@gmail.com>
> Date: Sat, 15 Jun 2024 13:24:00 -0400
> 
> A very useful and widely used time operation is to approximate a given delay or age (in seconds) using a human-readable unit — think "2 hours", "5 days",  "3 weeks", or "7 months".  We have `seconds-to-string', but it provides more precision than is often required, skips some meaningful "human readable" duration units like weeks and months, and uses abbreviated units exclusively.  
> 
> For those familiar with magit, the `magit--age' function has provided this capability for quite some time (e.g. for short commit age), and other packages have adapted it.  It would be useful to have a version in core.
> 
> This patch provides a `seconds-to-string-approximate' function based loosely on `magit--age' and `seconds-to-string'.  It allows using abbreviated or full units, and can optionally round to the nearest half-unit.

Paul, any comments to the patch?





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71573: Related functions from ts.el
  2024-06-17  6:20 ` bug#71573: Related functions from ts.el Adam Porter
@ 2024-06-22 10:55   ` Stefan Kangas
  2024-06-22 21:54     ` Adam Porter
  0 siblings, 1 reply; 31+ messages in thread
From: Stefan Kangas @ 2024-06-22 10:55 UTC (permalink / raw)
  To: Adam Porter, 71573

Adam Porter <adam@alphapapa.net> writes:

> ;; See also the built-in function `format-seconds', which I seem to have
> ;; overlooked before writing this.  However, a quick benchmark, run
> ;; 100,000 times, shows that, when controllable formatting is not needed,
> ;; `ts-human-format-duration' is much faster and generates less garbage:
>
> ;; | Form                     | x faster than next | Total runtime | #
> of GCs | Total GC runtime |
> ;;
> |--------------------------+--------------------+---------------+----------+------------------|
> ;; | ts-human-format-duration | 5.82               |      0.832945 |
>     3 |         0.574929 |
> ;; | format-seconds           | slowest            |      4.848253 |
>    17 |         3.288799 |

Is this used a lot in hot loops?  IOW, is it worth optimizing?

If yes, how about adding something like what you have as an optimization
to `format-seconds` for when the format is very simple?  Would that
remove the need for `ts-human-format-duration'?





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71573: Related functions from ts.el
  2024-06-22 10:55   ` Stefan Kangas
@ 2024-06-22 21:54     ` Adam Porter
  0 siblings, 0 replies; 31+ messages in thread
From: Adam Porter @ 2024-06-22 21:54 UTC (permalink / raw)
  To: Stefan Kangas, 71573

On 6/22/24 05:55, Stefan Kangas wrote:
> Adam Porter <adam@alphapapa.net> writes:
> 
>> ;; See also the built-in function `format-seconds', which I seem to have
>> ;; overlooked before writing this.  However, a quick benchmark, run
>> ;; 100,000 times, shows that, when controllable formatting is not needed,
>> ;; `ts-human-format-duration' is much faster and generates less garbage:
>>
>> ;; | Form                     | x faster than next | Total runtime | #
>> of GCs | Total GC runtime |
>> ;;
>> |--------------------------+--------------------+---------------+----------+------------------|
>> ;; | ts-human-format-duration | 5.82               |      0.832945 |
>>      3 |         0.574929 |
>> ;; | format-seconds           | slowest            |      4.848253 |
>>     17 |         3.288799 |
> 
> Is this used a lot in hot loops?  IOW, is it worth optimizing?

It can be.  Imagine formatting timestamps for thousands of items in a 
vtable.  And imagine that happening frequently, e.g. if the vtable is 
redrawn automatically to account for data having arrived over the network.

> If yes, how about adding something like what you have as an optimization
> to `format-seconds` for when the format is very simple?  Would that
> remove the need for `ts-human-format-duration'?

I don't know what form such an optimization would take.  Perhaps someone 
could profile it and optimize some hot spots in it, but I'll have to 
decline that to-do for now, as my list is much too long already.  :)

BTW, please note that I don't claim that ts-human-format-duration is 
superior to format-seconds, because the latter is different and has some 
additional features.  Rather, ts-human-format-duration is an alternative 
that can sometimes be worth using instead.  I present it as food for 
thought when considering to implement related functionality.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-22  8:45 ` bug#71572: [PATCH] seconds-to-string-approximate Eli Zaretskii
@ 2024-06-22 21:56   ` Adam Porter
  2024-06-22 23:42   ` Paul Eggert
  1 sibling, 0 replies; 31+ messages in thread
From: Adam Porter @ 2024-06-22 21:56 UTC (permalink / raw)
  To: Eli Zaretskii, JD Smith, Paul Eggert; +Cc: 71572, jonas

May I also recommend that the function be benchmarked, and potentially 
that it be profiled and optimized if needed?  A function like this may 
be used on hundreds or even thousands of items in a single operation 
(e.g. formatting a long list of items into a vtable), so it's important 
that it not be too slow.  I don't think I've seen mention of performance 
yet (forgive me if I missed it).





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-22  8:45 ` bug#71572: [PATCH] seconds-to-string-approximate Eli Zaretskii
  2024-06-22 21:56   ` Adam Porter
@ 2024-06-22 23:42   ` Paul Eggert
  2024-06-23  2:16     ` JD Smith
  2024-06-23  5:13     ` Eli Zaretskii
  1 sibling, 2 replies; 31+ messages in thread
From: Paul Eggert @ 2024-06-22 23:42 UTC (permalink / raw)
  To: Eli Zaretskii, JD Smith; +Cc: adam, 71572, jonas

On 6/22/24 04:45, Eli Zaretskii wrote:
>> Cc: Adam Porter <adam@alphapapa.net>, jonas@bernoul.li
>> From: JD Smith <jdtsmith@gmail.com>
>> Date: Sat, 15 Jun 2024 13:24:00 -0400
>>
>> A very useful and widely used time operation is to approximate a given delay or age (in seconds) using a human-readable unit — think "2 hours", "5 days",  "3 weeks", or "7 months".  We have `seconds-to-string', but it provides more precision than is often required, skips some meaningful "human readable" duration units like weeks and months, and uses abbreviated units exclusively.
>>
>> For those familiar with magit, the `magit--age' function has provided this capability for quite some time (e.g. for short commit age), and other packages have adapted it.  It would be useful to have a version in core.
>>
>> This patch provides a `seconds-to-string-approximate' function based loosely on `magit--age' and `seconds-to-string'.  It allows using abbreviated or full units, and can optionally round to the nearest half-unit.
> 
> Paul, any comments to the patch?

For starters:

Why define a new function, instead of adding optional arguments to the 
existing one?

Why not look at what mastodon.el does, as the comment in 
seconds-to-string suggests? For example, mastodon-tl--human-duration 
lets you specify whatever resolution you want, instead of limiting you 
to either 0.5 or 1  as in the proposed patch.

Isn't the master branch was in an long-term sort-of-frozen state, until 
a branch is created for Emacs 30? If so I imagine changes in this area 
should wait.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-22 23:42   ` Paul Eggert
@ 2024-06-23  2:16     ` JD Smith
  2024-07-04  5:29       ` Eli Zaretskii
  2024-06-23  5:13     ` Eli Zaretskii
  1 sibling, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-06-23  2:16 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Adam Porter, 71572, Eli Zaretskii, jonas

[-- Attachment #1: Type: text/plain, Size: 5234 bytes --]



> On Jun 22, 2024, at 7:42 PM, Paul Eggert <eggert@cs.ucla.edu> wrote:
> 
> On 6/22/24 04:45, Eli Zaretskii wrote:
>>> 
>> Paul, any comments to the patch?
> 
> For starters:

Thanks for the comment.

> Why define a new function, instead of adding optional arguments to the existing one?

No real reason; new patch doing so attached.

> Why not look at what mastodon.el does, as the comment in seconds-to-string suggests? For example, mastodon-tl--human-duration lets you specify whatever resolution you want, instead of limiting you to either 0.5 or 1  as in the proposed patch.

I see that mastodon is a package in ELPA, so doesn't satisfy the need in core.  I took a look at this function.  The RESOLUTION mentioned is not equivalent to the HALF argument, it is the minimum resolution in seconds.  So setting it to e.g. 3600 results in truncating to the hour, but changes nothing below the hour.  Setting it to the number of seconds in a year gives something quite similar to magit--age (though I notice the mastodon function truncates, instead of rounds; see, e.g., 2.98y in the table below).

Here's a comparison among:

- the current seconds-to-string
- mastodon-tl--human-duration
- mastodon with a 3600s resolution
- mastodon with 1yr "resolution"
- the new seconds-to-string with option READABLE=t
- new seconds-to-string with abbreviated units and half unit resolution

  Delay (s)      s-to-s            mastodon    mastodon (3600s)    mast (1yr)  s-to-s (rdb)  s-to-s (rdb=abbrev, half)
        0.5    450.00ms               0 sec               0 sec         0 sec     0 seconds  ½s
        1.0       1.03s               1 sec               1 sec         1 sec      1 second  1s
        2.4       2.38s              2 secs              2 secs        2 secs     2 seconds  2½s
        5.5       5.48s              5 secs              5 secs        5 secs     5 seconds  5½s
       12.6      12.59s             12 secs             12 secs       12 secs    13 seconds  12½s
       29.0      28.96s             28 secs             28 secs       28 secs    29 seconds  29s
       66.6      66.62s               1 min               1 min         1 min      1 minute  1m
      153.2       2.55m              2 mins              2 mins        2 mins     3 minutes  2½m
      352.4       5.87m              5 mins              5 mins        5 mins     6 minutes  6m
      810.5      13.51m             13 mins             13 mins       13 mins    14 minutes  13½m
     1864.2      31.07m             31 mins             31 mins       31 mins    31 minutes  31m
     4287.6      71.46m     1 hour, 11 mins              1 hour        1 hour        1 hour  1h
     9861.6       2.74h    2 hours, 44 mins             2 hours       2 hours       3 hours  2½h
    22681.6       6.30h    6 hours, 18 mins             6 hours       6 hours       6 hours  6½h
    52167.8      14.49h   14 hours, 29 mins            14 hours      14 hours      14 hours  14½h
   119985.9       1.39d      1 day, 9 hours      1 day, 9 hours         1 day         1 day  1½d
   275967.5       3.19d     3 days, 4 hours     3 days, 4 hours        3 days        3 days  3d
   634725.2       7.35d              1 week              1 week        1 week        1 week  1w
  1459867.9      16.90d     2 weeks, 2 days     2 weeks, 2 days       2 weeks       2 weeks  2½w
  3357696.2      38.86d     1 month, 1 week     1 month, 1 week       1 month       1 month  1½M
  7722701.2      89.38d   2 months, 4 weeks   2 months, 4 weeks      2 months      3 months  3M
 17762212.9     205.58d   6 months, 3 weeks   6 months, 3 weeks      6 months      7 months  7M
 40853089.6       1.29y    1 year, 3 months    1 year, 3 months        1 year        1 year  1½Y
 93962106.0       2.98y  2 years, 11 months  2 years, 11 months       2 years       3 years  3Y
216112843.8       6.85y  6 years, 10 months  6 years, 10 months       6 years       7 years  7Y
497059540.7      15.75y  15 years, 9 months  15 years, 9 months      15 years      16 years  16Y

The last column is obviously the most compact while still conveying a good amount of information, but the 1yr mastodon and normal READABLE s-to-s are also quite good (effectively equivalent to magit--age) for a quick glance and maintaining ~constant widths.  I do find it awkward to set the RESOLUTION argument to >30 million seconds to achieve this.  It took me a bit to understand what this argument does. 

Code use to produce:

(concat
 (format "%11s  %10s  %18s  %18s  %12s  %12s  %s\n" "Delay (s)" "s-to-s"
         "mastodon" "mastodon (3600s)" "mast (1yr)" "s-to-s (rdb)" "s-to-s (rdb=abbrev, half)")
 (cl-loop for s = 0.45 then (* s 2.3) while (< s (* 365.25 24 3600 22))
          concat (format "%11.1f  %10s  %18s  %18s  %12s  %12s  %s\n" s
                         (seconds-to-string s)
                         (car (mastodon-tl--human-duration s))
                         (car (mastodon-tl--human-duration s 3600))
                         (car (mastodon-tl--human-duration s (* 365.25 24 3600)))
                         (seconds-to-string s t)
                         (seconds-to-string s 'abbrev 'half))))



[-- Attachment #2.1: Type: text/html, Size: 11225 bytes --]

[-- Attachment #2.2: time-data-readable-seconds.patch --]
[-- Type: application/octet-stream, Size: 2217 bytes --]

--- time-date.el	2024-06-22 21:51:21
+++ time-date_new.el	2024-06-22 21:53:13
@@ -406,10 +406,41 @@
         (list (* 3600 24 400) "d" (* 3600.0 24.0))
         (list nil "y" (* 365.25 24 3600)))
   "Formatting used by the function `seconds-to-string'.")
+
+(defvar seconds-to-string-readable
+  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
+    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
+    ("w" "week"   "weeks"   ,(* 60 60 24 7))
+    ("d" "day"    "days"    ,(* 60 60 24))
+    ("h" "hour"   "hours"   ,(* 60 60))
+    ("m" "minute" "minutes" 60)
+    ("s" "second" "seconds" 1))
+  "Formatting used by the function `seconds-to-string' with READABLE set.")
+
 ;;;###autoload
-(defun seconds-to-string (delay)
-  "Convert the time interval in seconds to a short string."
-  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay))))
+(defun seconds-to-string (delay &optional readable half)
+  "Convert the time interval in seconds to a short string.
+If READABLE is non-nil, convert DELAY into a readable string.  If it is
+the value `abbrev', abbreviate the units.  If HALF is set, round to the
+nearest half unit."
+  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay) readable half)))
+        (readable
+         (let ((abbrev (eq readable 'abbrev))
+               (stsa seconds-to-string-readable)
+               here cnt)
+           (if (= (round delay (if half 0.5 1.)) 0)
+               (format "0%s" (if abbrev "s" " seconds"))
+             (while (and (setq here (pop stsa)) stsa
+                         (< (/ delay (nth 3 here)) 1)))
+             (setq cnt (round (/ (float delay) (nth 3 here)) (if half 0.5 1.)))
+             (concat
+              (let ((c (if half (/ cnt 2) cnt)))
+                (if (> c 0) (number-to-string c) ""))
+              (if (and half (= (mod cnt 2) 1)) "½" "")
+              (if abbrev "" " ")
+              (cond (abbrev (car here))
+                    ((<= cnt (if half 2 1)) (nth 1 here))
+                    (t (nth 2 here)))))))
         ((= 0 delay) "0s")
         (t (let ((sts seconds-to-string) here)
              (while (and (car (setq here (pop sts)))

[-- Attachment #2.3: Type: text/html, Size: 212 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-22 23:42   ` Paul Eggert
  2024-06-23  2:16     ` JD Smith
@ 2024-06-23  5:13     ` Eli Zaretskii
  2024-07-03 20:32       ` JD Smith
  1 sibling, 1 reply; 31+ messages in thread
From: Eli Zaretskii @ 2024-06-23  5:13 UTC (permalink / raw)
  To: Paul Eggert; +Cc: adam, 71572, jonas, jdtsmith

> Date: Sat, 22 Jun 2024 19:42:25 -0400
> Cc: 71572@debbugs.gnu.org, adam@alphapapa.net, jonas@bernoul.li
> From: Paul Eggert <eggert@cs.ucla.edu>
> 
> Isn't the master branch was in an long-term sort-of-frozen state, until 
> a branch is created for Emacs 30? If so I imagine changes in this area 
> should wait.

Yes, this is for Emacs 31.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-23  5:13     ` Eli Zaretskii
@ 2024-07-03 20:32       ` JD Smith
  2024-07-04  5:29         ` Eli Zaretskii
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-07-03 20:32 UTC (permalink / raw)
  To: Eli Zaretskii, Paul Eggert, 71572; +Cc: Adam Porter, jonas


> On Jun 23, 2024, at 1:13 AM, Eli Zaretskii <eliz@gnu.org> wrote:
> 
>> Date: Sat, 22 Jun 2024 19:42:25 -0400
>> Cc: 71572@debbugs.gnu.org, adam@alphapapa.net, jonas@bernoul.li
>> From: Paul Eggert <eggert@cs.ucla.edu>
>> 
>> Isn't the master branch was in an long-term sort-of-frozen state, until 
>> a branch is created for Emacs 30? If so I imagine changes in this area 
>> should wait.
> 
> Yes, this is for Emacs 31.

Any further thoughts on this approach?




^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-06-23  2:16     ` JD Smith
@ 2024-07-04  5:29       ` Eli Zaretskii
  2024-07-04  6:04         ` Eli Zaretskii
                           ` (2 more replies)
  0 siblings, 3 replies; 31+ messages in thread
From: Eli Zaretskii @ 2024-07-04  5:29 UTC (permalink / raw)
  To: JD Smith; +Cc: adam, 71572, jonas, eggert

> From: JD Smith <jdtsmith@gmail.com>
> Date: Sat, 22 Jun 2024 22:16:40 -0400
> Cc: Eli Zaretskii <eliz@gnu.org>,
>  71572@debbugs.gnu.org,
>  Adam Porter <adam@alphapapa.net>,
>  jonas@bernoul.li
> 
> > Why not look at what mastodon.el does, as the comment in seconds-to-string suggests? For example, mastodon-tl--human-duration lets you specify whatever resolution you want, instead of limiting you to either 0.5 or 1  as in the proposed patch.
> 
> I see that mastodon is a package in ELPA, so doesn't satisfy the need in core.  I took a look at this function.  The RESOLUTION mentioned is not equivalent to the HALF argument, it is the minimum resolution in seconds.  So setting it to e.g. 3600 results in truncating to the hour, but changes nothing below the hour.  Setting it to the number of seconds in a year gives something quite similar to magit--age (though I notice the mastodon function truncates, instead of rounds; see, e.g., 2.98y in the table below).
> 
> Here's a comparison among:
> 
> - the current seconds-to-string
> - mastodon-tl--human-duration
> - mastodon with a 3600s resolution
> - mastodon with 1yr "resolution"
> - the new seconds-to-string with option READABLE=t
> - new seconds-to-string with abbreviated units and half unit resolution
> 
>   Delay (s)      s-to-s            mastodon    mastodon (3600s)    mast (1yr)  s-to-s (rdb)  s-to-s (rdb=abbrev, half)
>         0.5    450.00ms               0 sec               0 sec         0 sec     0 seconds  ½s
>         1.0       1.03s               1 sec               1 sec         1 sec      1 second  1s
>         2.4       2.38s              2 secs              2 secs        2 secs     2 seconds  2½s
>         5.5       5.48s              5 secs              5 secs        5 secs     5 seconds  5½s
>        12.6      12.59s             12 secs             12 secs       12 secs    13 seconds  12½s
>        29.0      28.96s             28 secs             28 secs       28 secs    29 seconds  29s
>        66.6      66.62s               1 min               1 min         1 min      1 minute  1m
>       153.2       2.55m              2 mins              2 mins        2 mins     3 minutes  2½m
>       352.4       5.87m              5 mins              5 mins        5 mins     6 minutes  6m
>       810.5      13.51m             13 mins             13 mins       13 mins    14 minutes  13½m
>      1864.2      31.07m             31 mins             31 mins       31 mins    31 minutes  31m
>      4287.6      71.46m     1 hour, 11 mins              1 hour        1 hour        1 hour  1h
>      9861.6       2.74h    2 hours, 44 mins             2 hours       2 hours       3 hours  2½h
>     22681.6       6.30h    6 hours, 18 mins             6 hours       6 hours       6 hours  6½h
>     52167.8      14.49h   14 hours, 29 mins            14 hours      14 hours      14 hours  14½h
>    119985.9       1.39d      1 day, 9 hours      1 day, 9 hours         1 day         1 day  1½d
>    275967.5       3.19d     3 days, 4 hours     3 days, 4 hours        3 days        3 days  3d
>    634725.2       7.35d              1 week              1 week        1 week        1 week  1w
>   1459867.9      16.90d     2 weeks, 2 days     2 weeks, 2 days       2 weeks       2 weeks  2½w
>   3357696.2      38.86d     1 month, 1 week     1 month, 1 week       1 month       1 month  1½M
>   7722701.2      89.38d   2 months, 4 weeks   2 months, 4 weeks      2 months      3 months  3M
>  17762212.9     205.58d   6 months, 3 weeks   6 months, 3 weeks      6 months      7 months  7M
>  40853089.6       1.29y    1 year, 3 months    1 year, 3 months        1 year        1 year  1½Y
>  93962106.0       2.98y  2 years, 11 months  2 years, 11 months       2 years       3 years  3Y
> 216112843.8       6.85y  6 years, 10 months  6 years, 10 months       6 years       7 years  7Y
> 497059540.7      15.75y  15 years, 9 months  15 years, 9 months      15 years      16 years  16Y

Basically, this shows that:

 . mastodon truncates where seconds-to-string rounds
 . seconds-to-string lacks the "1 hour 11 min" output format
 . seconds-to-string sometimes produces inaccurate results, as in
   5.5 => 5.48s

The last item worries me: can we fix this, please?
The second item sounds like a useful feature, so maybe an optional
behavior could provide it as well?

Thanks.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-03 20:32       ` JD Smith
@ 2024-07-04  5:29         ` Eli Zaretskii
  0 siblings, 0 replies; 31+ messages in thread
From: Eli Zaretskii @ 2024-07-04  5:29 UTC (permalink / raw)
  To: JD Smith; +Cc: adam, 71572, jonas, eggert

> From: JD Smith <jdtsmith@gmail.com>
> Date: Wed, 3 Jul 2024 16:32:46 -0400
> Cc: Adam Porter <adam@alphapapa.net>,
>  jonas@bernoul.li
> 
> 
> > On Jun 23, 2024, at 1:13 AM, Eli Zaretskii <eliz@gnu.org> wrote:
> > 
> >> Date: Sat, 22 Jun 2024 19:42:25 -0400
> >> Cc: 71572@debbugs.gnu.org, adam@alphapapa.net, jonas@bernoul.li
> >> From: Paul Eggert <eggert@cs.ucla.edu>
> >> 
> >> Isn't the master branch was in an long-term sort-of-frozen state, until 
> >> a branch is created for Emacs 30? If so I imagine changes in this area 
> >> should wait.
> > 
> > Yes, this is for Emacs 31.
> 
> Any further thoughts on this approach?

I've just sent a few.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04  5:29       ` Eli Zaretskii
@ 2024-07-04  6:04         ` Eli Zaretskii
  2024-07-04  7:09         ` Paul Eggert
  2024-07-04 15:27         ` JD Smith
  2 siblings, 0 replies; 31+ messages in thread
From: Eli Zaretskii @ 2024-07-04  6:04 UTC (permalink / raw)
  To: jdtsmith; +Cc: adam, 71572, jonas, eggert

> Cc: adam@alphapapa.net, 71572@debbugs.gnu.org, jonas@bernoul.li,
>  eggert@cs.ucla.edu
> Date: Thu, 04 Jul 2024 08:29:03 +0300
> From: Eli Zaretskii <eliz@gnu.org>
> 
> Basically, this shows that:
> 
>  . mastodon truncates where seconds-to-string rounds
>  . seconds-to-string lacks the "1 hour 11 min" output format
>  . seconds-to-string sometimes produces inaccurate results, as in
>    5.5 => 5.48s
> 
> The last item worries me: can we fix this, please?
> The second item sounds like a useful feature, so maybe an optional
> behavior could provide it as well?

And one more nit: should this function be described in the ELisp
manual?





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04  5:29       ` Eli Zaretskii
  2024-07-04  6:04         ` Eli Zaretskii
@ 2024-07-04  7:09         ` Paul Eggert
  2024-07-06 19:29           ` JD Smith
  2024-07-04 15:27         ` JD Smith
  2 siblings, 1 reply; 31+ messages in thread
From: Paul Eggert @ 2024-07-04  7:09 UTC (permalink / raw)
  To: Eli Zaretskii, JD Smith; +Cc: adam, 71572, jonas

On 7/4/24 06:29, Eli Zaretskii wrote:

> Basically, this shows that:
> 
>   . mastodon truncates where seconds-to-string rounds

For high precision timestamps it's often better to truncate, for various 
reasons. That's what the C code does with timestamps, anyway. 
seconds-to-string historically has rounded several times which of course 
is not best but apparently is good enough for its intended application area.


>   . seconds-to-string lacks the "1 hour 11 min" output format

That format could be confusing with negative delays, e.g., "-1 hour 11 min".

>   . seconds-to-string sometimes produces inaccurate results, as in
>     5.5 => 5.48s

No, it's the other way round: seconds-to-string is more accurate than 
the alternatives. That's merely a misfeature in the test script. 
seconds-to-string is passed the argument 5.475149999999998, and formats 
it as "5.48" whereas the test script formats it as "5.5".


I'm not sold on the "half" argument; seems like a cuteness rather than a 
feature that's all that useful (among other things, it assumes Unicode 
or something like it). What's really going on here is that there's an 
optional argument specifying style and I imagine that style preferences 
will differ (Mastodon style, etc.).

I imagine that style preferences could proliferate. Is there an ISO or 
similar standard for this sort of thing?






^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04  5:29       ` Eli Zaretskii
  2024-07-04  6:04         ` Eli Zaretskii
  2024-07-04  7:09         ` Paul Eggert
@ 2024-07-04 15:27         ` JD Smith
  2024-07-04 15:59           ` Eli Zaretskii
  2024-07-04 16:36           ` Ihor Radchenko
  2 siblings, 2 replies; 31+ messages in thread
From: JD Smith @ 2024-07-04 15:27 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Adam Porter, 71572, jonas, Paul Eggert

[-- Attachment #1: Type: text/plain, Size: 4718 bytes --]


> On Jul 4, 2024, at 1:29 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> 
>> Here's a comparison among:
>> 
>> - the current seconds-to-string
>> - mastodon-tl--human-duration
>> - mastodon with a 3600s resolution
>> - mastodon with 1yr "resolution"
>> - the new seconds-to-string with option READABLE=t
>> - new seconds-to-string with abbreviated units and half unit resolution
<snip>
> 
> Basically, this shows that:
> 
> . mastodon truncates where seconds-to-string rounds

I definitely think rounding makes more sense for a readable unit; consider 2.98y.

> . seconds-to-string lacks the "1 hour 11 min" output format
> . seconds-to-string sometimes produces inaccurate results, as in
>   5.5 => 5.48s
> The last item worries me: can we fix this, please?

The latter issue results simply from my truncation of the initial delay column to 1 decimal digit.  E.g. .45s * 2.3^3 = 5.47515s, so s2s has it right.

> The second item sounds like a useful feature, so maybe an optional
> behavior could provide it as well?

I've expanded the call format to (delay &optional readable abbrev half) , with READABLE='expanded an option to get the "1 hour 11 min" expanded format, i.e. one larger and one smaller unit (if appropriate).  Patch below.

Updated output at the same delays for the various new options (r: readable, e: readable=expanded, a: abbrev, h: half):

   Delay (s)        s2s       s2s-r  s2s-ra  s2s-rah                  s2s-e   s2s-ea  s2s-eah
        0.45   450.00ms   0 seconds      0s       ½s              0 seconds       0s  ½s
        1.03      1.03s    1 second      1s       1s               1 second       1s  1s
        2.38      2.38s   2 seconds      2s      2½s              2 seconds       2s  2½s
        5.48      5.48s   5 seconds      5s      5½s              5 seconds       5s  5½s
       12.59     12.59s  13 seconds     13s     12½s             13 seconds      13s  12½s
       28.96     28.96s  29 seconds     29s      29s             29 seconds      29s  29s
       66.62     66.62s    1 minute      1m       1m     1 minute 7 seconds    1m 7s  1m 6½s
      153.22      2.55m   3 minutes      3m      2½m   2 minutes 33 seconds   2m 33s  2m 33s
      352.40      5.87m   6 minutes      6m       6m   5 minutes 52 seconds   5m 52s  5m 52½s
      810.52     13.51m  14 minutes     14m     13½m  13 minutes 31 seconds  13m 31s  13m 30½s
     1864.19     31.07m  31 minutes     31m      31m   31 minutes 4 seconds   31m 4s  31m 4s
     4287.64     71.46m      1 hour      1h       1h      1 hour 11 minutes   1h 11m  1h 11½m
     9861.58      2.74h     3 hours      3h      2½h     2 hours 44 minutes   2h 44m  2h 44½m
    22681.64      6.30h     6 hours      6h      6½h     6 hours 18 minutes   6h 18m  6h 18m
    52167.76     14.49h    14 hours     14h     14½h    14 hours 29 minutes  14h 29m  14h 29½m
   119985.86      1.39d       1 day      1d      1½d          1 day 9 hours    1d 9h  1d 9½h
   275967.47      3.19d      3 days      3d       3d         3 days 5 hours    3d 5h  3d 4½h
   634725.18      7.35d      1 week      1w       1w                 7 days       7d  1w ½d
  1459867.91     16.90d     2 weeks      2w      2½w         2 weeks 3 days    2w 3d  2w 3d
  3357696.19     38.86d     1 month      1M      1½M         1 month 1 week    1M 1w  1M 1w
  7722701.24     89.38d    3 months      3M       3M       2 months 4 weeks    2M 4w  2M 4w
 17762212.85    205.58d    7 months      7M       7M       6 months 3 weeks    6M 3w  6M 3½w
 40853089.56      1.29y      1 year      1Y      1½Y        1 year 4 months    1Y 4M  1Y 3½M
 93962106.00      2.98y     3 years      3Y       3Y      2 years 12 months   2Y 12M  2Y 11½M
216112843.80      6.85y     7 years      7Y       7Y      6 years 10 months   6Y 10M  6Y 10M
497059540.74     15.75y    16 years     16Y      16Y      15 years 9 months   15Y 9M  15Y 9M

This is produced with:

(concat
 (format "%12s %10s  %10s  %6s  %7s  %21s  %7s  %s\n"
         "Delay (s)" "s2s" "s2s-r" "s2s-ra" "s2s-rah" "s2s-e" "s2s-ea" "s2s-eah" )
 (cl-loop for s = 0.45 then (* s 2.3) while (< s (* 365.25 24 3600 22))
          concat (format "%12.2f %10s  %10s  %6s  %7s  %21s  %7s  %s\n" s
                         (seconds-to-string s)
                         (seconds-to-string s 'readable)
                         (seconds-to-string s 'readable 'abbrev)
                         (seconds-to-string s 'readable 'abbrev 'half)
                         (seconds-to-string s 'expanded)
                         (seconds-to-string s 'expanded 'abbrev)
                         (seconds-to-string s 'expanded 'abbrev 'half))))



[-- Attachment #2.1: Type: text/html, Size: 10366 bytes --]

[-- Attachment #2.2: time-data-readable-seconds-2.patch --]
[-- Type: application/octet-stream, Size: 2991 bytes --]

--- time-date.el	2024-06-22 21:51:21
+++ time-date_new.el	2024-07-04 11:16:22
@@ -406,10 +406,62 @@
         (list (* 3600 24 400) "d" (* 3600.0 24.0))
         (list nil "y" (* 365.25 24 3600)))
   "Formatting used by the function `seconds-to-string'.")
+
+(defvar seconds-to-string-readable
+  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
+    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
+    ("w" "week"   "weeks"   ,(* 60 60 24 7))
+    ("d" "day"    "days"    ,(* 60 60 24))
+    ("h" "hour"   "hours"   ,(* 60 60))
+    ("m" "minute" "minutes" 60)
+    ("s" "second" "seconds" 1))
+  "Formatting used by the function `seconds-to-string' with READABLE set.")
+
 ;;;###autoload
-(defun seconds-to-string (delay)
-  "Convert the time interval in seconds to a short string."
-  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay))))
+(defun seconds-to-string (delay &optional readable abbrev half)
+  "Convert time interval DELAY (in seconds) to a short string.
+By default, the returned string has two decimal precision in the
+smallest unit that is larger than DELAY from the variable
+`seconds-to-string'.  If READABLE is non-nil, convert DELAY into
+a readable string, using the information in the variable
+`seconds-to-string-readable'.  If it is the symbol `expanded',
+use two units to describe DELAY, if appropriate.  E.g. \"1 hour
+32 minutes\".  If ABBREV is non-nil, abbreviate the readable
+units.  If HALF is non-nil, round the smallest unit displayed to
+the nearest half unit."
+  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay) readable half)))
+        (readable
+         (let* ((stsa seconds-to-string-readable)
+		(expanded (eq readable 'expanded))
+		(round-to (if half 0.5 1))
+		(padding (if abbrev "" " "))
+		here cnt cnt-pre here-pre)
+	   (if (= (round delay round-to) 0)
+	       (format "0%s" (if abbrev "s" " seconds"))
+	     (while (and (setq here (pop stsa)) stsa
+			 (< (/ delay (nth 3 here)) 1)))
+	     (or (and expanded stsa
+		      (progn
+			(setq here-pre here here (car stsa)
+			      cnt-pre (floor (/ (float delay) (nth 3 here-pre)))
+			      cnt (round (/ (- (float delay) (* cnt-pre (nth 3 here-pre)))
+					    (nth 3 here))
+					 round-to))
+			(if (> cnt 0) t (setq here-pre nil))))
+	       (setq cnt (round (/ (float delay) (nth 3 here)) round-to)))
+	     (cl-labels
+		 ((unit (cnt here &optional half)
+		    (cond (abbrev (car here))
+			  ((<= cnt (if half 2 1)) (nth 1 here))
+			  (t (nth 2 here)))))
+	       (concat
+		(when here-pre
+		  (concat (number-to-string cnt-pre) padding
+			  (unit cnt-pre here-pre) " "))
+		(let ((c (if half (/ cnt 2) cnt)))
+		  (if (> c 0) (number-to-string c) ""))
+		(if (and half (= (mod cnt 2) 1)) "½" "")
+		padding (unit cnt here half))))))
         ((= 0 delay) "0s")
         (t (let ((sts seconds-to-string) here)
              (while (and (car (setq here (pop sts)))

[-- Attachment #2.3: Type: text/html, Size: 212 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04 15:27         ` JD Smith
@ 2024-07-04 15:59           ` Eli Zaretskii
  2024-07-04 17:16             ` JD Smith
  2024-07-04 16:36           ` Ihor Radchenko
  1 sibling, 1 reply; 31+ messages in thread
From: Eli Zaretskii @ 2024-07-04 15:59 UTC (permalink / raw)
  To: JD Smith; +Cc: adam, 71572, jonas, eggert

> From: JD Smith <jdtsmith@gmail.com>
> Date: Thu, 4 Jul 2024 11:27:41 -0400
> Cc: Paul Eggert <eggert@cs.ucla.edu>,
>  71572@debbugs.gnu.org,
>  Adam Porter <adam@alphapapa.net>,
>  jonas@bernoul.li
> 
> > The second item sounds like a useful feature, so maybe an optional
> > behavior could provide it as well?
> 
> I've expanded the call format to (delay &optional readable abbrev half) , with READABLE='expanded an option to get the "1 hour 11 min" expanded format, i.e. one larger and one smaller unit (if appropriate).  Patch below.

Still no documentation...





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04 15:27         ` JD Smith
  2024-07-04 15:59           ` Eli Zaretskii
@ 2024-07-04 16:36           ` Ihor Radchenko
  2024-07-04 17:23             ` JD Smith
  1 sibling, 1 reply; 31+ messages in thread
From: Ihor Radchenko @ 2024-07-04 16:36 UTC (permalink / raw)
  To: JD Smith; +Cc: Adam Porter, 71572, Eli Zaretskii, jonas, Paul Eggert

JD Smith <jdtsmith@gmail.com> writes:

> The latter issue results simply from my truncation of the initial delay column to 1 decimal digit.  E.g. .45s * 2.3^3 = 5.47515s, so s2s has it right.
>
>> The second item sounds like a useful feature, so maybe an optional
>> behavior could provide it as well?
>
> I've expanded the call format to (delay &optional readable abbrev half) , with READABLE='expanded an option to get the "1 hour 11 min" expanded format, i.e. one larger and one smaller unit (if appropriate).  Patch below.

Have you seen `org-duration-from-minutes'?
In Org mode, we have a rather complex (but flexible) system to customize
the duration format:

(defcustom org-duration-format '(("d" . nil) (special . h:mm))
"Format definition for a duration.

The value can be set to, respectively, the symbols `h:mm:ss' or
`h:mm', which means a duration is expressed as, respectively,
a \"H:MM:SS\" or \"H:MM\" string.

Alternatively, the value can be a list of entries following the
pattern:

  (UNIT . REQUIRED?)

UNIT is a unit string, as defined in `org-duration-units'.  The
time duration is formatted using only the time components that
are specified here.

Units with a zero value are skipped, unless REQUIRED? is non-nil.
In that case, the unit is always used.

The list can also contain one of the following special entries:

  (special . h:mm)
  (special . h:mm:ss)

    Units shorter than an hour are ignored.  The hours and
    minutes part of the duration is expressed unconditionally
    with H:MM, or H:MM:SS, pattern.

  (special . PRECISION)

    A duration is expressed with a single unit, PRECISION being
    the number of decimal places to show.  The unit chosen is the
    first one required or with a non-zero integer part.  If there
    is no such unit, the smallest one is used.

Eventually, if the list contains the symbol `compact', the
duration is expressed in a compact form, without any white space
between units.

For example,

   ((\"d\" . nil) (\"h\" . t) (\"min\" . t))

means a duration longer than a day is expressed in days, hours
and minutes, whereas a duration shorter than a day is always
expressed in hours and minutes, even when shorter than an hour.

On the other hand, the value

  ((\"d\" . nil) (\"min\" . nil))

means a duration longer than a day is expressed in days and
minutes, whereas a duration shorter than a day is expressed
entirely in minutes, even when longer than an hour.

The following format

  ((\"d\" . nil) (special . h:mm))

means that any duration longer than a day is expressed with both
a \"d\" unit and a \"H:MM\" part, whereas a duration shorter than
a day is expressed only as a \"H:MM\" string.

Eventually,

  ((\"d\" . nil) (\"h\" . nil) (special . 2))

expresses a duration longer than a day as a decimal number, with
a 2-digits fractional part, of \"d\" unit.  A duration shorter
than a day uses \"h\" unit instead."

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04 15:59           ` Eli Zaretskii
@ 2024-07-04 17:16             ` JD Smith
  2024-07-04 18:06               ` Eli Zaretskii
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-07-04 17:16 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Adam Porter, 71572, jonas, Paul Eggert



> On Jul 4, 2024, at 11:59 AM, Eli Zaretskii <eliz@gnu.org> wrote:
> 
>> From: JD Smith <jdtsmith@gmail.com>
>> Date: Thu, 4 Jul 2024 11:27:41 -0400
>> Cc: Paul Eggert <eggert@cs.ucla.edu>,
>> 71572@debbugs.gnu.org,
>> Adam Porter <adam@alphapapa.net>,
>> jonas@bernoul.li
>> 
>>> The second item sounds like a useful feature, so maybe an optional
>>> behavior could provide it as well?
>> 
>> I've expanded the call format to (delay &optional readable abbrev half) , with READABLE='expanded an option to get the "1 hour 11 min" expanded format, i.e. one larger and one smaller unit (if appropriate).  Patch below.
> 
> Still no documentation...

It seems sensible to finalize the design before documenting.  Where do you think the documentation should go?




^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04 16:36           ` Ihor Radchenko
@ 2024-07-04 17:23             ` JD Smith
  2024-07-04 17:57               ` Ihor Radchenko
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-07-04 17:23 UTC (permalink / raw)
  To: Ihor Radchenko; +Cc: Adam Porter, 71572, Eli Zaretskii, jonas, Paul Eggert

[-- Attachment #1: Type: text/plain, Size: 748 bytes --]



> On Jul 4, 2024, at 12:36 PM, Ihor Radchenko <yantar92@posteo.net> wrote:
> 
> Have you seen `org-duration-from-minutes'?
> In Org mode, we have a rather complex (but flexible) system to customize
> the duration format:

I had not seen that function, thanks for pointing it out.  Looks useful and rather flexible.  Would you be able to re-use the example code I posted earlier to show how it formats various ages with typical configuration?

For activities (which is the package where the conversation about this need started), you can have ages from seconds to many years, so you need something that accommodates that dynamic range well.  magit--age was the inspiration, and we're in fact just reusing Jonas' configuration variable.

[-- Attachment #2: Type: text/html, Size: 2894 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04 17:23             ` JD Smith
@ 2024-07-04 17:57               ` Ihor Radchenko
  0 siblings, 0 replies; 31+ messages in thread
From: Ihor Radchenko @ 2024-07-04 17:57 UTC (permalink / raw)
  To: JD Smith; +Cc: Adam Porter, 71572, Eli Zaretskii, jonas, Paul Eggert

JD Smith <jdtsmith@gmail.com> writes:

> I had not seen that function, thanks for pointing it out.  Looks useful and rather flexible.  Would you be able to re-use the example code I posted earlier to show how it formats various ages with typical configuration?
>
> For activities (which is the package where the conversation about this need started), you can have ages from seconds to many years, so you need something that accommodates that dynamic range well.  magit--age was the inspiration, and we're in fact just reusing Jonas' configuration variable.

  Delay (s)      s-to-s            mastodon    mastodon (3600s)    mast (1yr)  s-to-s (rdb)                  Org     Org (frac) s-to-s (rdb=abbrev, half)
        0.5    450.00ms               0 sec               0 sec         0 sec           0s                 0min        0.01min   ½s
        1.0       1.03s               1 sec               1 sec         1 sec           1s                 0min        0.02min   1s
        2.4       2.38s              2 secs              2 secs        2 secs           2s                 0min        0.04min   2½s
        5.5       5.48s              5 secs              5 secs        5 secs           5s                 0min        0.09min   5½s
       12.6      12.59s             12 secs             12 secs       12 secs          13s                 0min        0.21min   12½s
       29.0      28.96s             28 secs             28 secs       28 secs          29s                 0min        0.48min   29s
       66.6      66.62s               1 min               1 min         1 min           1m                 1min        1.11min   1m
      153.2       2.55m              2 mins              2 mins        2 mins           3m                 2min        2.55min   2½m
      352.4       5.87m              5 mins              5 mins        5 mins           6m                 5min        5.87min   6m
      810.5      13.51m             13 mins             13 mins       13 mins          14m                13min       13.51min   13½m
     1864.2      31.07m             31 mins             31 mins       31 mins          31m                31min       31.07min   31m
     4287.6      71.46m     1 hour, 11 mins              1 hour        1 hour           1h             1h 11min          1.19h   1h
     9861.6       2.74h    2 hours, 44 mins             2 hours       2 hours           3h             2h 44min          2.74h   2½h
    22681.6       6.30h    6 hours, 18 mins             6 hours       6 hours           6h             6h 18min          6.30h   6½h
    52167.8      14.49h   14 hours, 29 mins            14 hours      14 hours          14h            14h 29min         14.49h   14½h
   119985.9       1.39d      1 day, 9 hours      1 day, 9 hours         1 day           1d          1d 9h 19min          1.39d   1½d
   275967.5       3.19d     3 days, 4 hours     3 days, 4 hours        3 days           3d          3d 4h 39min          3.19d   3d
   634725.2       7.35d              1 week              1 week        1 week           1w          7d 8h 18min          7.35d   1w
  1459867.9      16.90d     2 weeks, 2 days     2 weeks, 2 days       2 weeks           2w        16d 21h 31min         16.90d   2½w
  3357696.2      38.86d     1 month, 1 week     1 month, 1 week       1 month           1M      1m 8d 20h 41min          1.30m   1½M
  7722701.2      89.38d   2 months, 4 weeks   2 months, 4 weeks      2 months           3M      2m 29d 9h 11min          2.98m   3M
 17762212.9     205.58d   6 months, 3 weeks   6 months, 3 weeks      6 months           7M     6m 25d 13h 56min          6.85m   7M
 40853089.6       1.29y    1 year, 3 months    1 year, 3 months        1 year           1Y   1y 3m 17d 14h 4min          1.29y   1½Y
 93962106.0       2.98y  2 years, 11 months  2 years, 11 months       2 years           3Y     2y 11m 27d 35min          2.98y   3Y
216112843.8       6.85y  6 years, 10 months  6 years, 10 months       6 years           7Y  6y 10m 9d 19h 20min          6.85y   7Y
497059540.7      15.75y  15 years, 9 months  15 years, 9 months      15 years          16Y    15y 9m 4d 6h 5min         15.75y   16Y


(concat
 (format "%11s  %10s  %18s  %18s  %12s  %12s %20s %14s %s\n" "Delay (s)" "s-to-s"
	 "mastodon" "mastodon (3600s)" "mast (1yr)" "s-to-s (rdb)" "Org" "Org (frac)" "s-to-s (rdb=abbrev, half)")
 (cl-loop for s = 0.45 then (* s 2.3) while (< s (* 365.25 24 3600 22))
	  concat (format "%11.1f  %10s  %18s  %18s  %12s %12s %20s %14s   %s\n" s
			 (seconds-to-string s)
			 (car (mastodon-tl--human-duration s))
			 (car (mastodon-tl--human-duration s 3600))
			 (car (mastodon-tl--human-duration s (* 365.25 24 3600)))
			 (seconds-to-string-approximate s t)
                         (org-duration-from-minutes (/ s 60) '(("y" . nil) ("m" . nil) ("d" . nil) ("h" . nil) ("min" . nil)))
                         (org-duration-from-minutes (/ s 60) '(("y" . nil) ("m" . nil) ("d" . nil) ("h" . nil) ("min" . nil) (special . 2) compact))
			 (seconds-to-string-approximate s 'abbrev 'half))))

-- 
Ihor Radchenko // yantar92,
Org mode contributor,
Learn more about Org mode at <https://orgmode.org/>.
Support Org development at <https://liberapay.com/org-mode>,
or support my work at <https://liberapay.com/yantar92>





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04 17:16             ` JD Smith
@ 2024-07-04 18:06               ` Eli Zaretskii
  0 siblings, 0 replies; 31+ messages in thread
From: Eli Zaretskii @ 2024-07-04 18:06 UTC (permalink / raw)
  To: JD Smith; +Cc: adam, 71572, jonas, eggert

> From: JD Smith <jdtsmith@gmail.com>
> Date: Thu, 4 Jul 2024 13:16:32 -0400
> Cc: Paul Eggert <eggert@cs.ucla.edu>,
>  71572@debbugs.gnu.org,
>  Adam Porter <adam@alphapapa.net>,
>  jonas@bernoul.li
> 
> > Still no documentation...
> 
> It seems sensible to finalize the design before documenting.  Where do you think the documentation should go?

"Time Calculations", I'd say.

It should also be mentioned in NEWS.

Thanks.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-04  7:09         ` Paul Eggert
@ 2024-07-06 19:29           ` JD Smith
  2024-07-06 21:09             ` Paul Eggert
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-07-06 19:29 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Adam Porter, 71572, Eli Zaretskii, jonas



> On Jul 4, 2024, at 3:09 AM, Paul Eggert <eggert@cs.ucla.edu> wrote:
> 
> On 7/4/24 06:29, Eli Zaretskii wrote:
> 
>>  . seconds-to-string lacks the "1 hour 11 min" output format
> 
> That format could be confusing with negative delays, e.g., "-1 hour 11 min".

Does anyone share this concern for negative delays?  I wouldn't have trouble interpreting that (and negative delays are likely the rare case).

> I'm not sold on the "half" argument; seems like a cuteness rather than a feature that's all that useful (among other things, it assumes Unicode or something like it).

Is this an actual problem, i.e. do we universally avoid unicode in core files?  We could drop the HALF, but it's to me quite useful when rounding to just a few weeks or months.

> What's really going on here is that there's an optional argument specifying style and I imagine that style preferences will differ (Mastodon style, etc.).

There are 1000 different ways to come at this; we currently offer 17 "styles": 16 "readable" plus the original seconds-to-string style.

> Is there an ISO or similar standard for this sort of thing?

I've never seen one if so.

Any other thoughts?






^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-06 19:29           ` JD Smith
@ 2024-07-06 21:09             ` Paul Eggert
  2024-07-11 21:01               ` JD Smith
  0 siblings, 1 reply; 31+ messages in thread
From: Paul Eggert @ 2024-07-06 21:09 UTC (permalink / raw)
  To: JD Smith; +Cc: Adam Porter, 71572, Eli Zaretskii, jonas

On 7/6/24 21:29, JD Smith wrote:
> do we universally avoid unicode in core files?

Not in comments, but even today it's wise to be cautious about 
generating user-visible Unicode characters like "½" when there's a 
simple ASCII substitute like ".5". Plus, why stop with ½? Why not also 
do ¼ and ¾?

It might be better to have an optional precision argument, defaulting to 
0, specifying the number of digits of precision after the decimal point. 
Or something like that.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-06 21:09             ` Paul Eggert
@ 2024-07-11 21:01               ` JD Smith
  2024-11-30 18:58                 ` JD Smith
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-07-11 21:01 UTC (permalink / raw)
  To: 71572; +Cc: Adam Porter, Eli Zaretskii, jonas, Paul Eggert

[-- Attachment #1: Type: text/plain, Size: 5121 bytes --]



> On Jul 6, 2024, at 5:09 PM, Paul Eggert <eggert@cs.ucla.edu> wrote:
> 
> On 7/6/24 21:29, JD Smith wrote:
>> do we universally avoid unicode in core files?
> 
> Not in comments, but even today it's wise to be cautious about generating user-visible Unicode characters like "½" when there's a simple ASCII substitute like ".5". Plus, why stop with ½? Why not also do ¼ and ¾?
> 
> It might be better to have an optional precision argument, defaulting to 0, specifying the number of digits of precision after the decimal point. Or something like that.

Thanks for the feedback.  Attached find an updated patch:

- HALF is dropped.
- PRECISION can now be specified as a whole-number of digits or a float <1.0 (e.g. 0.5)
- NEWS and doc entries.

Users who want ½ can always use precision=0.5 and edit the string after the fact.

Another attached file includes commands to produce and display a simple benchmark, as well as the example output (below).  

I see about a 10x performance difference between the standard seconds-to-string and the "bells and whistles" readable version.  It's still <35µs per delay for me, so formatting thousands of strings at once should be no problem.  Happy to take performance improvement ideas.

Current example:

     Delay (s)        s2s       s2s-r  s2s-ra  s2s-ra1  s2s-rah                  s2s-e   s2s-ea    s2s-ea1      s2s-ea3    s2s-eah
         0.000         0s   0 seconds      0s       0s       0s              0 seconds       0s         0s           0s         0s
         0.450   450.00ms   0 seconds      0s     0.4s     0.5s              0 seconds       0s       0.4s       0.450s       0.5s
         1.035      1.03s    1 second      1s       1s       1s               1 second       1s         1s       1.035s         1s
         2.380      2.38s   2 seconds      2s     2.4s     2.5s              2 seconds       2s       2.4s       2.380s       2.5s
         5.475      5.48s   5 seconds      5s     5.5s     5.5s              5 seconds       5s       5.5s       5.475s       5.5s
        12.593     12.59s  13 seconds     13s    12.6s    12.5s             13 seconds      13s      12.6s      12.593s      12.5s
        28.964     28.96s  29 seconds     29s      29s      29s             29 seconds      29s        29s      28.964s        29s
        66.616     66.62s    1 minute      1m     1.1m       1m     1 minute 7 seconds    1m 7s    1m 6.6s    1m 6.616s    1m 6.5s
       153.217      2.55m   3 minutes      3m     2.6m     2.5m   2 minutes 33 seconds   2m 33s   2m 33.2s   2m 33.217s     2m 33s
       352.399      5.87m   6 minutes      6m     5.9m       6m   5 minutes 52 seconds   5m 52s   5m 52.4s   5m 52.399s   5m 52.5s
       810.519     13.51m  14 minutes     14m    13.5m    13.5m  13 minutes 31 seconds  13m 31s  13m 30.5s  13m 30.519s  13m 30.5s
      1864.193     31.07m  31 minutes     31m    31.1m      31m   31 minutes 4 seconds   31m 4s   31m 4.2s   31m 4.193s     31m 4s
      4287.644     71.46m      1 hour      1h     1.2h       1h      1 hour 11 minutes   1h 11m   1h 11.5m   1h 11.461m   1h 11.5m
      9861.581      2.74h     3 hours      3h     2.7h     2.5h     2 hours 44 minutes   2h 44m   2h 44.4m   2h 44.360m   2h 44.5m
     22681.636      6.30h     6 hours      6h     6.3h     6.5h     6 hours 18 minutes   6h 18m     6h 18m   6h 18.027m     6h 18m
     52167.763     14.49h    14 hours     14h    14.5h    14.5h    14 hours 29 minutes  14h 29m  14h 29.5m  14h 29.463m  14h 29.5m
    119985.856      1.39d       1 day      1d     1.4d     1.5d          1 day 9 hours    1d 9h    1d 9.3h    1d 9.329h    1d 9.5h
    275967.469      3.19d      3 days      3d     3.2d       3d         3 days 5 hours    3d 5h    3d 4.7h    3d 4.658h    3d 4.5h
    634725.178      7.35d      1 week      1w       1w       1w                 1 week       1w    1w 0.3d    1w 0.346d    1w 0.5d
   1459867.909     16.90d     2 weeks      2w     2.4w     2.5w         2 weeks 3 days    2w 3d    2w 2.9d    2w 2.897d      2w 3d
   3357696.192     38.86d     1 month      1M     1.3M     1.5M         1 month 1 week    1M 1w    1M 1.2w    1M 1.204w      1M 1w
   7722701.241     89.38d    3 months      3M     2.9M       3M       2 months 4 weeks    2M 4w    2M 4.1w    2M 4.073w      2M 4w
  17762212.854    205.58d    7 months      7M     6.8M       7M       6 months 3 weeks    6M 3w    6M 3.3w    6M 3.280w    6M 3.5w
  40853089.565      1.29y      1 year      1Y     1.3Y     1.5Y        1 year 4 months    1Y 4M    1Y 3.5M    1Y 3.535M    1Y 3.5M
  93962105.999      2.98y     3 years      3Y       3Y       3Y      2 years 12 months   2Y 12M   2Y 11.7M   2Y 11.730M   2Y 11.5M
 216112843.798      6.85y     7 years      7Y     6.8Y       7Y      6 years 10 months   6Y 10M   6Y 10.2M   6Y 10.180M     6Y 10M
 497059540.736     15.75y    16 years     16Y    15.8Y      16Y      15 years 9 months   15Y 9M     15Y 9M   15Y 9.014M     15Y 9M
1143236943.694     36.23y    36 years     36Y    36.2Y      36Y      36 years 3 months   36Y 3M   36Y 2.7M   36Y 2.733M   36Y 2.5M





[-- Attachment #2.1: Type: text/html, Size: 10907 bytes --]

[-- Attachment #2.2: s2s_test.el --]
[-- Type: application/octet-stream, Size: 1794 bytes --]

(require 'cl-lib)

(defun s2s/example ()
  (interactive)
  (with-temp-buffer-window "s2s/example" nil nil
    (princ
     (concat
      (format "%14s %10s  %10s  %6s  %7s  %7s  %21s  %7s  %9s  %11s  %9s\n"
	      "Delay (s)" "s2s" "s2s-r" "s2s-ra" "s2s-ra1" "s2s-rah" "s2s-e" "s2s-ea" "s2s-ea1"
	      "s2s-ea3" "s2s-eah")
      (cl-loop for s = 0.0 then (if (zerop s) 0.45 (* s 2.3))
	       while (< s (* 365.25 24 3600 40))
	       concat (format "%14.3f %10s  %10s  %6s  %7s  %7s  %21s  %7s  %9s  %11s  %9s\n" s
			      (seconds-to-string s)
			      (seconds-to-string s 'readable)
			      (seconds-to-string s 'readable 'abbrev)
			      (seconds-to-string s 'readable 'abbrev 1)
			      (seconds-to-string s 'readable 'abbrev 0.5)
			      (seconds-to-string s 'expanded)
			      (seconds-to-string s 'expanded 'abbrev)
			      (seconds-to-string s 'expanded 'abbrev 1)
			      (seconds-to-string s 'expanded 'abbrev 3)
			      (seconds-to-string s 'expanded 'abbrev 0.5)))))))

(defun s2s/benchmark ()
  (interactive)
  (let* ((ndelays 100000)
	 (delays (cl-loop for i from 1 to ndelays
			  with max = (* 365.25 24 3600 40)
			  collect (cl-random max)))
	 (bsmpl (benchmark-run nil
		  (cl-loop for d in delays
			   do (seconds-to-string d))))
	 (brdbl (benchmark-run nil
		  (cl-loop for d in delays
			   do (seconds-to-string d t t 0.1)))))
    (with-temp-buffer-window "s2s/benchmarks" nil nil
      (princ "seconds-to-string benchmarks\n")
      (princ (format "  default timing: %0.2fµs\n\t%S\n"
		     (/ (car bsmpl) ndelays 1e-6) bsmpl))
      (princ (format " readable timing: %0.2fµs\n\t%S\n\n"
		     (/ (car brdbl) ndelays 1e-6) brdbl))
      (princ (format "readable/default: %0.2f\n" (/ (car brdbl) (car bsmpl)))))))

[-- Attachment #2.3: Type: text/html, Size: 289 bytes --]

[-- Attachment #2.4: 0001-seconds-to-string-new-optional-arguments-for-readabl.patch --]
[-- Type: application/octet-stream, Size: 5345 bytes --]

From 4767735197fba78672e076737a033921637db1a2 Mon Sep 17 00:00:00 2001
From: JD Smith <93749+jdtsmith@users.noreply.github.com>
Date: Thu, 11 Jul 2024 16:24:17 -0400
Subject: [PATCH] seconds-to-string: new optional arguments for readable
 strings

---
 doc/lispref/os.texi        |  6 +++
 etc/NEWS                   |  7 ++++
 lisp/calendar/time-date.el | 75 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 3ba3da459bf..1e26c83de6a 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -2155,6 +2155,12 @@ Time Calculations
 structure.  For instance, the 120th day in 2004 is April 29th.
 @end defun
 
+@defun seconds-to-string delay &optional readable abbrev precision
+Return a string describing a given @var{delay} (in seconds).  Optional
+arguments can be used to configure a human readable delay using various
+formats.  For example, a delay of 9861.5 seconds with @var{readable} set
+to the symbol @code{expanded} returns "2 hours 44 minutes".
+
 @node Timers
 @section Timers for Delayed Execution
 @cindex timers
diff --git a/etc/NEWS b/etc/NEWS
index f10f9ae4d65..38ab21288a3 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,13 @@ applies, and please also update docstrings as needed.
 \f
 * Changes in Emacs 31.1
 
+** Time & Date
+
++++
+*** 'seconds-to-string' includes new arguments to produce human-readable
+delay strings in a variety of formats, for example "6 months 3 weeks" or
+"5m 52.5s".
+
 \f
 * Editing Changes in Emacs 31.1
 
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index eca80f1e8b6..f80aa2f980d 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -409,11 +409,78 @@ seconds-to-string
         (list (* 3600 24 400) "d" (* 3600.0 24.0))
         (list nil "y" (* 365.25 24 3600)))
   "Formatting used by the function `seconds-to-string'.")
+
+(defvar seconds-to-string-readable
+  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
+    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
+    ("w" "week"   "weeks"   ,(* 60 60 24 7))
+    ("d" "day"    "days"    ,(* 60 60 24))
+    ("h" "hour"   "hours"   ,(* 60 60))
+    ("m" "minute" "minutes" 60)
+    ("s" "second" "seconds" 1))
+  "Formatting used by the function `seconds-to-string' with READABLE set.")
+
 ;;;###autoload
-(defun seconds-to-string (delay)
-  ;; FIXME: There's a similar (tho fancier) function in mastodon.el!
-  "Convert the time interval in seconds to a short string."
-  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay))))
+(defun seconds-to-string (delay &optional readable abbrev precision)
+  "Convert time interval DELAY (in seconds) to a short string.
+By default, the returned string has two decimal precision in the
+smallest unit that is larger than DELAY from the variable
+`seconds-to-string'.  If READABLE is non-nil, convert DELAY into
+a readable string, using the information in the variable
+`seconds-to-string-readable'.  If it is the symbol `expanded',
+use two units to describe DELAY, if appropriate.  E.g. \"1 hour
+32 minutes\".  If ABBREV is non-nil, abbreviate the readable
+units.  If PRECISION is a whole number, round the value
+associated with the smallest displayed unit to that many digits
+after the decimal.  If it a non-negative float less than 1.0,
+round to that value."
+  (cond ((< delay 0)
+	 (concat "-" (seconds-to-string (- delay) readable precision)))
+        (readable
+         (let* ((stsa seconds-to-string-readable)
+		(expanded (eq readable 'expanded))
+		digits
+		(round-to (cond ((wholenump precision)
+				 (setq digits precision)
+				 (expt 10 (- precision)))
+				((and (floatp precision) (< precision 1.))
+				 (setq digits (- (floor (log precision 10))))
+				 precision)
+				(t (setq digits 0) 1)))
+		(dformat (if (> digits 0) (format "%%0.%df" digits)))
+		(padding (if abbrev "" " "))
+		here cnt cnt-pre here-pre cnt-val)
+	   (if (= (round delay round-to) 0)
+	       (format "0%s" (if abbrev "s" " seconds"))
+	     (while (and (setq here (pop stsa)) stsa
+			 (< (/ delay (nth 3 here)) 1)))
+	     (or (and
+		  expanded stsa 	; smaller unit remains
+		  (progn
+		    (setq
+		     here-pre here here (car stsa)
+		     cnt-pre (floor (/ (float delay) (nth 3 here-pre)))
+		     cnt (round
+			  (/ (- (float delay) (* cnt-pre (nth 3 here-pre)))
+			     (nth 3 here))
+			  round-to))
+		    (if (> cnt 0) t (setq cnt cnt-pre here here-pre here-pre nil))))
+		 (setq cnt (round (/ (float delay) (nth 3 here)) round-to)))
+	     (setq cnt-val (* cnt round-to))
+	     (cl-labels
+		 ((unit (val here)
+		    (cond (abbrev (car here))
+			  ((<= (floor val) 1) (nth 1 here))
+			  (t (nth 2 here)))))
+	       (concat
+		(when here-pre
+		  (concat (number-to-string cnt-pre) padding
+			  (unit (* cnt-pre round-to) here-pre) " "))
+		(if (and (> digits 0)
+			 (> (- cnt-val (floor cnt-val)) 0.))
+		    (format dformat cnt-val)
+		  (number-to-string (floor cnt-val)))
+		padding (unit cnt-val here))))))
         ((= 0 delay) "0s")
         (t (let ((sts seconds-to-string) here)
              (while (and (car (setq here (pop sts)))
-- 
2.43.0


[-- Attachment #2.5: Type: text/html, Size: 457 bytes --]

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-07-11 21:01               ` JD Smith
@ 2024-11-30 18:58                 ` JD Smith
  2024-12-07 13:02                   ` Eli Zaretskii
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-11-30 18:58 UTC (permalink / raw)
  To: 71572; +Cc: Adam Porter, Eli Zaretskii, jonas, Paul Eggert

[-- Attachment #1: Type: text/plain, Size: 5579 bytes --]

I was recently reminded of the need for a more capable seconds-to-string.  

Anyone have any additional comments on this proposed patch?  If not, I'd suggest someone with access merges.

JD

> On Jul 11, 2024, at 5:01 PM, JD Smith <jdtsmith@gmail.com> wrote:
> 
> 
> 
>> On Jul 6, 2024, at 5:09 PM, Paul Eggert <eggert@cs.ucla.edu> wrote:
>> 
>> On 7/6/24 21:29, JD Smith wrote:
>>> do we universally avoid unicode in core files?
>> 
>> Not in comments, but even today it's wise to be cautious about generating user-visible Unicode characters like "½" when there's a simple ASCII substitute like ".5". Plus, why stop with ½? Why not also do ¼ and ¾?
>> 
>> It might be better to have an optional precision argument, defaulting to 0, specifying the number of digits of precision after the decimal point. Or something like that.
> 
> Thanks for the feedback.  Attached find an updated patch:
> 
> - HALF is dropped.
> - PRECISION can now be specified as a whole-number of digits or a float <1.0 (e.g. 0.5)
> - NEWS and doc entries.
> 
> Users who want ½ can always use precision=0.5 and edit the string after the fact.
> 
> Another attached file includes commands to produce and display a simple benchmark, as well as the example output (below).  
> 
> I see about a 10x performance difference between the standard seconds-to-string and the "bells and whistles" readable version.  It's still <35µs per delay for me, so formatting thousands of strings at once should be no problem.  Happy to take performance improvement ideas.
> 
> Current example:
> 
>      Delay (s)        s2s       s2s-r  s2s-ra  s2s-ra1  s2s-rah                  s2s-e   s2s-ea    s2s-ea1      s2s-ea3    s2s-eah
>          0.000         0s   0 seconds      0s       0s       0s              0 seconds       0s         0s           0s         0s
>          0.450   450.00ms   0 seconds      0s     0.4s     0.5s              0 seconds       0s       0.4s       0.450s       0.5s
>          1.035      1.03s    1 second      1s       1s       1s               1 second       1s         1s       1.035s         1s
>          2.380      2.38s   2 seconds      2s     2.4s     2.5s              2 seconds       2s       2.4s       2.380s       2.5s
>          5.475      5.48s   5 seconds      5s     5.5s     5.5s              5 seconds       5s       5.5s       5.475s       5.5s
>         12.593     12.59s  13 seconds     13s    12.6s    12.5s             13 seconds      13s      12.6s      12.593s      12.5s
>         28.964     28.96s  29 seconds     29s      29s      29s             29 seconds      29s        29s      28.964s        29s
>         66.616     66.62s    1 minute      1m     1.1m       1m     1 minute 7 seconds    1m 7s    1m 6.6s    1m 6.616s    1m 6.5s
>        153.217      2.55m   3 minutes      3m     2.6m     2.5m   2 minutes 33 seconds   2m 33s   2m 33.2s   2m 33.217s     2m 33s
>        352.399      5.87m   6 minutes      6m     5.9m       6m   5 minutes 52 seconds   5m 52s   5m 52.4s   5m 52.399s   5m 52.5s
>        810.519     13.51m  14 minutes     14m    13.5m    13.5m  13 minutes 31 seconds  13m 31s  13m 30.5s  13m 30.519s  13m 30.5s
>       1864.193     31.07m  31 minutes     31m    31.1m      31m   31 minutes 4 seconds   31m 4s   31m 4.2s   31m 4.193s     31m 4s
>       4287.644     71.46m      1 hour      1h     1.2h       1h      1 hour 11 minutes   1h 11m   1h 11.5m   1h 11.461m   1h 11.5m
>       9861.581      2.74h     3 hours      3h     2.7h     2.5h     2 hours 44 minutes   2h 44m   2h 44.4m   2h 44.360m   2h 44.5m
>      22681.636      6.30h     6 hours      6h     6.3h     6.5h     6 hours 18 minutes   6h 18m     6h 18m   6h 18.027m     6h 18m
>      52167.763     14.49h    14 hours     14h    14.5h    14.5h    14 hours 29 minutes  14h 29m  14h 29.5m  14h 29.463m  14h 29.5m
>     119985.856      1.39d       1 day      1d     1.4d     1.5d          1 day 9 hours    1d 9h    1d 9.3h    1d 9.329h    1d 9.5h
>     275967.469      3.19d      3 days      3d     3.2d       3d         3 days 5 hours    3d 5h    3d 4.7h    3d 4.658h    3d 4.5h
>     634725.178      7.35d      1 week      1w       1w       1w                 1 week       1w    1w 0.3d    1w 0.346d    1w 0.5d
>    1459867.909     16.90d     2 weeks      2w     2.4w     2.5w         2 weeks 3 days    2w 3d    2w 2.9d    2w 2.897d      2w 3d
>    3357696.192     38.86d     1 month      1M     1.3M     1.5M         1 month 1 week    1M 1w    1M 1.2w    1M 1.204w      1M 1w
>    7722701.241     89.38d    3 months      3M     2.9M       3M       2 months 4 weeks    2M 4w    2M 4.1w    2M 4.073w      2M 4w
>   17762212.854    205.58d    7 months      7M     6.8M       7M       6 months 3 weeks    6M 3w    6M 3.3w    6M 3.280w    6M 3.5w
>   40853089.565      1.29y      1 year      1Y     1.3Y     1.5Y        1 year 4 months    1Y 4M    1Y 3.5M    1Y 3.535M    1Y 3.5M
>   93962105.999      2.98y     3 years      3Y       3Y       3Y      2 years 12 months   2Y 12M   2Y 11.7M   2Y 11.730M   2Y 11.5M
>  216112843.798      6.85y     7 years      7Y     6.8Y       7Y      6 years 10 months   6Y 10M   6Y 10.2M   6Y 10.180M     6Y 10M
>  497059540.736     15.75y    16 years     16Y    15.8Y      16Y      15 years 9 months   15Y 9M     15Y 9M   15Y 9.014M     15Y 9M
> 1143236943.694     36.23y    36 years     36Y    36.2Y      36Y      36 years 3 months   36Y 3M   36Y 2.7M   36Y 2.733M   36Y 2.5M
> 
> <s2s_test.el>
> <0001-seconds-to-string-new-optional-arguments-for-readabl.patch>
> 
> 


[-- Attachment #2: Type: text/html, Size: 12360 bytes --]

^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-11-30 18:58                 ` JD Smith
@ 2024-12-07 13:02                   ` Eli Zaretskii
  2024-12-07 17:52                     ` JD Smith
  0 siblings, 1 reply; 31+ messages in thread
From: Eli Zaretskii @ 2024-12-07 13:02 UTC (permalink / raw)
  To: JD Smith; +Cc: adam, 71572, jonas, eggert

> From: JD Smith <jdtsmith@gmail.com>
> Date: Sat, 30 Nov 2024 13:58:52 -0500
> Cc: Eli Zaretskii <eliz@gnu.org>,
>  Adam Porter <adam@alphapapa.net>,
>  jonas@bernoul.li,
>  Paul Eggert <eggert@cs.ucla.edu>
> 
> I was recently reminded of the need for a more capable seconds-to-string.  
> 
> Anyone have any additional comments on this proposed patch?  If not, I'd suggest someone with access
> merges.

A few minor nits below, and then we can install:

> --- a/doc/lispref/os.texi
> +++ b/doc/lispref/os.texi
> @@ -2155,6 +2155,12 @@ Time Calculations
>  structure.  For instance, the 120th day in 2004 is April 29th.
>  @end defun
>  
> +@defun seconds-to-string delay &optional readable abbrev precision
> +Return a string describing a given @var{delay} (in seconds).  Optional
> +arguments can be used to configure a human readable delay using various
> +formats.  For example, a delay of 9861.5 seconds with @var{readable} set
> +to the symbol @code{expanded} returns "2 hours 44 minutes".

The strings should be in @samp (and then you can drop the quotes).

> +** Time & Date
> +
> ++++
> +*** 'seconds-to-string' includes new arguments to produce human-readable
> +delay strings in a variety of formats, for example "6 months 3 weeks" or
> +"5m 52.5s".

This should have a heading line, which is a single complete sentence.

> +(defvar seconds-to-string-readable
> +  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
> +    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
> +    ("w" "week"   "weeks"   ,(* 60 60 24 7))
> +    ("d" "day"    "days"    ,(* 60 60 24))
> +    ("h" "hour"   "hours"   ,(* 60 60))
> +    ("m" "minute" "minutes" 60)
> +    ("s" "second" "seconds" 1))
> +  "Formatting used by the function `seconds-to-string' with READABLE set.")

Please exp.lain in more detail the format of this list's elements, so
that people could modify it more easily in the future.

> +(defun seconds-to-string (delay &optional readable abbrev precision)
> +  "Convert time interval DELAY (in seconds) to a short string.
                                                    ^^^^^^^^^^^^
Why "short"?

> +By default, the returned string has two decimal precision in the
> +smallest unit that is larger than DELAY from the variable
> +`seconds-to-string'.

I couldn't parse this sentence.  "Two decimal precision"? is that a
typo?

Thanks.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-12-07 13:02                   ` Eli Zaretskii
@ 2024-12-07 17:52                     ` JD Smith
  2024-12-07 19:17                       ` john muhl
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-12-07 17:52 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Adam Porter, 71572, Jonas Bernoulli, Paul Eggert

[-- Attachment #1: Type: text/plain, Size: 592 bytes --]



> On Dec 7, 2024, at 8:02 AM, Eli Zaretskii <eliz@gnu.org> wrote:
> 
>> From: JD Smith <jdtsmith@gmail.com>
>> Date: Sat, 30 Nov 2024 13:58:52 -0500
>> Cc: Eli Zaretskii <eliz@gnu.org>,
>> Adam Porter <adam@alphapapa.net>,
>> jonas@bernoul.li,
>> Paul Eggert <eggert@cs.ucla.edu>
>> 
>> I was recently reminded of the need for a more capable seconds-to-string.  
>> 
>> Anyone have any additional comments on this proposed patch?  If not, I'd suggest someone with access
>> merges.
> 
> A few minor nits below, and then we can install:

Thanks.  Updated patch below.


[-- Attachment #2: 0001-seconds-to-string-new-optional-arguments-for-readabl.patch --]
[-- Type: application/octet-stream, Size: 5688 bytes --]

From 99f52d767e67d5ce929a2696749b3db635bd302d Mon Sep 17 00:00:00 2001
From: JD Smith <93749+jdtsmith@users.noreply.github.com>
Date: Thu, 11 Jul 2024 16:24:17 -0400
Subject: [PATCH] seconds-to-string: new optional arguments for readable
 strings

---
 doc/lispref/os.texi        |  6 +++
 etc/NEWS                   |  7 ++++
 lisp/calendar/time-date.el | 81 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 90 insertions(+), 4 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 3ba3da459bf..ac9d5acdf3d 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -2155,6 +2155,12 @@ Time Calculations
 structure.  For instance, the 120th day in 2004 is April 29th.
 @end defun
 
+@defun seconds-to-string delay &optional readable abbrev precision
+Return a string describing a given @var{delay} (in seconds).  Optional
+arguments can be used to configure a human readable delay using various
+formats.  For example, a delay of 9861.5 seconds with @var{readable} set
+to the symbol @code{expanded} returns @samp{2 hours 44 minutes}.
+
 @node Timers
 @section Timers for Delayed Execution
 @cindex timers
diff --git a/etc/NEWS b/etc/NEWS
index f10f9ae4d65..1fd2a9404bb 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,13 @@ applies, and please also update docstrings as needed.
 \f
 * Changes in Emacs 31.1
 
+** Time & Date
+
++++
+*** 'seconds-to-string' includes new formatting options.
+Options are provided to produce human-readable delay strings in a
+variety of formats, for example "6 months 3 weeks" or "5m 52.5s".
+
 \f
 * Editing Changes in Emacs 31.1
 
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index eca80f1e8b6..60573f814ac 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -409,11 +409,84 @@ seconds-to-string
         (list (* 3600 24 400) "d" (* 3600.0 24.0))
         (list nil "y" (* 365.25 24 3600)))
   "Formatting used by the function `seconds-to-string'.")
+
+(defvar seconds-to-string-readable
+  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
+    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
+    ("w" "week"   "weeks"   ,(* 60 60 24 7))
+    ("d" "day"    "days"    ,(* 60 60 24))
+    ("h" "hour"   "hours"   ,(* 60 60))
+    ("m" "minute" "minutes" 60)
+    ("s" "second" "seconds" 1))
+  "Formatting used by the function `seconds-to-string' with READABLE set.
+The format is an alist, with string keys ABBREV-UNIT, and elements like:
+
+  (ABBREV-UNIT UNIT UNIT-PLURAL SECS)
+
+where UNIT is a unit of time, ABBREV-UNIT is the abreviated form of
+UNIT, UNIT-PLURAL is the plural form of UNIT, and SECS is the number of
+seconds per UNIT.")
+
 ;;;###autoload
-(defun seconds-to-string (delay)
-  ;; FIXME: There's a similar (tho fancier) function in mastodon.el!
-  "Convert the time interval in seconds to a short string."
-  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay))))
+(defun seconds-to-string (delay &optional readable abbrev precision)
+  "Convert time interval DELAY (in seconds) to a string.
+By default, the returned string is formatted as a float in the smallest
+unit from the variable `seconds-to-string' that is longer than DELAY,
+and a precision of two.  If READABLE is non-nil, convert DELAY into a
+readable string, using the information provided in the variable
+`seconds-to-string-readable'.  If it is the symbol `expanded', use two
+units to describe DELAY, if appropriate.  E.g. \"1 hour 32 minutes\".
+If ABBREV is non-nil, abbreviate the readable units.  If PRECISION is a
+whole number, round the value associated with the smallest displayed
+unit to that many digits after the decimal.  If it a non-negative float
+less than 1.0, round to that value."
+  (cond ((< delay 0)
+	 (concat "-" (seconds-to-string (- delay) readable precision)))
+        (readable
+         (let* ((stsa seconds-to-string-readable)
+		(expanded (eq readable 'expanded))
+		digits
+		(round-to (cond ((wholenump precision)
+				 (setq digits precision)
+				 (expt 10 (- precision)))
+				((and (floatp precision) (< precision 1.))
+				 (setq digits (- (floor (log precision 10))))
+				 precision)
+				(t (setq digits 0) 1)))
+		(dformat (if (> digits 0) (format "%%0.%df" digits)))
+		(padding (if abbrev "" " "))
+		here cnt cnt-pre here-pre cnt-val)
+	   (if (= (round delay round-to) 0)
+	       (format "0%s" (if abbrev "s" " seconds"))
+	     (while (and (setq here (pop stsa)) stsa
+			 (< (/ delay (nth 3 here)) 1)))
+	     (or (and
+		  expanded stsa 	; smaller unit remains
+		  (progn
+		    (setq
+		     here-pre here here (car stsa)
+		     cnt-pre (floor (/ (float delay) (nth 3 here-pre)))
+		     cnt (round
+			  (/ (- (float delay) (* cnt-pre (nth 3 here-pre)))
+			     (nth 3 here))
+			  round-to))
+		    (if (> cnt 0) t (setq cnt cnt-pre here here-pre here-pre nil))))
+		 (setq cnt (round (/ (float delay) (nth 3 here)) round-to)))
+	     (setq cnt-val (* cnt round-to))
+	     (cl-labels
+		 ((unit (val here)
+		    (cond (abbrev (car here))
+			  ((<= (floor val) 1) (nth 1 here))
+			  (t (nth 2 here)))))
+	       (concat
+		(when here-pre
+		  (concat (number-to-string cnt-pre) padding
+			  (unit (* cnt-pre round-to) here-pre) " "))
+		(if (and (> digits 0)
+			 (> (- cnt-val (floor cnt-val)) 0.))
+		    (format dformat cnt-val)
+		  (number-to-string (floor cnt-val)))
+		padding (unit cnt-val here))))))
         ((= 0 delay) "0s")
         (t (let ((sts seconds-to-string) here)
              (while (and (car (setq here (pop sts)))
-- 
2.45.2


^ permalink raw reply related	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-12-07 17:52                     ` JD Smith
@ 2024-12-07 19:17                       ` john muhl
  2024-12-08 19:52                         ` JD Smith
  0 siblings, 1 reply; 31+ messages in thread
From: john muhl @ 2024-12-07 19:17 UTC (permalink / raw)
  To: JD Smith; +Cc: Adam Porter, 71572, Eli Zaretskii, Jonas Bernoulli, Paul Eggert

JD Smith <jdtsmith@gmail.com> writes:

>> On Dec 7, 2024, at 8:02 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>> 
>>> From: JD Smith <jdtsmith@gmail.com>
>>> Date: Sat, 30 Nov 2024 13:58:52 -0500
>>> Cc: Eli Zaretskii <eliz@gnu.org>,
>>> Adam Porter <adam@alphapapa.net>,
>>> jonas@bernoul.li,
>>> Paul Eggert <eggert@cs.ucla.edu>
>>> 
>>> I was recently reminded of the need for a more capable seconds-to-string.  
>>> 
>>> Anyone have any additional comments on this proposed patch? If not,
>>> I'd suggest someone with access
>>> merges.
>> 
>> A few minor nits below, and then we can install:
>
> Thanks.  Updated patch below.

Thanks for working on this. I gave a quick try and noticed some
amounts aren’t pluralized how I expect; e.g.

  (seconds-to-string 36541462 'expanded nil 1)
  ;; "1 year 1.9 month"

  (seconds-to-string 73082924 'expanded nil 2)
  ;; "2 year 3.79 months"

  (seconds-to-string 2511822 'expanded nil 3)
  ;; "4 week 1.072 day"

I would expect those to output:

  1 year 1.9 months
  2 years 3.79 months
  4 weeks 1.072 days

According to the Chicago Manual of Style all fractional values are
plural, even 1.0, 2.0 &c.

The incorrect “2 year”, “4 week” only happen when PRECISION is
non-nil; e.g.

  (seconds-to-string 2511822 'expanded nil)
  "4 weeks 1 day"

p.s. There is a missing “is” in the last sentence of the docstring
for seconds-to-string.





^ permalink raw reply	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-12-07 19:17                       ` john muhl
@ 2024-12-08 19:52                         ` JD Smith
  2024-12-08 22:51                           ` john muhl
  0 siblings, 1 reply; 31+ messages in thread
From: JD Smith @ 2024-12-08 19:52 UTC (permalink / raw)
  To: john muhl; +Cc: Adam Porter, 71572, Eli Zaretskii, Jonas Bernoulli, Paul Eggert

[-- Attachment #1: Type: text/plain, Size: 1153 bytes --]


> On Dec 8, 2024, at 4:17 AM, john muhl <jm@pub.pink> wrote:
> 
> JD Smith <jdtsmith@gmail.com <mailto:jdtsmith@gmail.com>> writes:
> 
>>> On Dec 7, 2024, at 8:02 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>>> 
>>>> From: JD Smith <jdtsmith@gmail.com>
>>>> Date: Sat, 30 Nov 2024 13:58:52 -0500
>>>> Cc: Eli Zaretskii <eliz@gnu.org>,
>>>> Adam Porter <adam@alphapapa.net>,
>>>> jonas@bernoul.li,
>>>> Paul Eggert <eggert@cs.ucla.edu>
>>>> 
>>>> I was recently reminded of the need for a more capable seconds-to-string.  
>>>> 
>>>> Anyone have any additional comments on this proposed patch? If not,
>>>> I'd suggest someone with access
>>>> merges.
>>> 
>>> A few minor nits below, and then we can install:
>> 
>> Thanks.  Updated patch below.
> 
> Thanks for working on this. I gave a quick try and noticed some
> amounts aren’t pluralized how I expect; e.g.
> 
>  (seconds-to-string 36541462 'expanded nil 1)
>  ;; "1 year 1.9 month"
...
> According to the Chicago Manual of Style all fractional values are
> plural, even 1.0, 2.0 &c.

Thanks for this, a case I hadn't tested.  Corrected in the attached.



[-- Attachment #2.1: Type: text/html, Size: 6807 bytes --]

[-- Attachment #2.2: 0001-seconds-to-string-new-optional-arguments-for-readabl.patch --]
[-- Type: application/octet-stream, Size: 5860 bytes --]

From c1937c9969955cd040a6587cba9e4d91e79ec78c Mon Sep 17 00:00:00 2001
From: JD Smith <93749+jdtsmith@users.noreply.github.com>
Date: Thu, 11 Jul 2024 16:24:17 -0400
Subject: [PATCH] seconds-to-string: new optional arguments for readable
 strings

---
 doc/lispref/os.texi        |  6 +++
 etc/NEWS                   |  7 ++++
 lisp/calendar/time-date.el | 82 ++++++++++++++++++++++++++++++++++++--
 3 files changed, 91 insertions(+), 4 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 3ba3da459bf..ac9d5acdf3d 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -2155,6 +2155,12 @@ Time Calculations
 structure.  For instance, the 120th day in 2004 is April 29th.
 @end defun
 
+@defun seconds-to-string delay &optional readable abbrev precision
+Return a string describing a given @var{delay} (in seconds).  Optional
+arguments can be used to configure a human readable delay using various
+formats.  For example, a delay of 9861.5 seconds with @var{readable} set
+to the symbol @code{expanded} returns @samp{2 hours 44 minutes}.
+
 @node Timers
 @section Timers for Delayed Execution
 @cindex timers
diff --git a/etc/NEWS b/etc/NEWS
index f10f9ae4d65..1fd2a9404bb 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,13 @@ applies, and please also update docstrings as needed.
 \f
 * Changes in Emacs 31.1
 
+** Time & Date
+
++++
+*** 'seconds-to-string' includes new formatting options.
+Options are provided to produce human-readable delay strings in a
+variety of formats, for example "6 months 3 weeks" or "5m 52.5s".
+
 \f
 * Editing Changes in Emacs 31.1
 
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index eca80f1e8b6..5d53052a65c 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -409,11 +409,85 @@ seconds-to-string
         (list (* 3600 24 400) "d" (* 3600.0 24.0))
         (list nil "y" (* 365.25 24 3600)))
   "Formatting used by the function `seconds-to-string'.")
+
+(defvar seconds-to-string-readable
+  `(("Y" "year"   "years"   ,(round (* 60 60 24 365.2425)))
+    ("M" "month"  "months"  ,(round (* 60 60 24 30.436875)))
+    ("w" "week"   "weeks"   ,(* 60 60 24 7))
+    ("d" "day"    "days"    ,(* 60 60 24))
+    ("h" "hour"   "hours"   ,(* 60 60))
+    ("m" "minute" "minutes" 60)
+    ("s" "second" "seconds" 1))
+  "Formatting used by the function `seconds-to-string' with READABLE set.
+The format is an alist, with string keys ABBREV-UNIT, and elements like:
+
+  (ABBREV-UNIT UNIT UNIT-PLURAL SECS)
+
+where UNIT is a unit of time, ABBREV-UNIT is the abreviated form of
+UNIT, UNIT-PLURAL is the plural form of UNIT, and SECS is the number of
+seconds per UNIT.")
+
 ;;;###autoload
-(defun seconds-to-string (delay)
-  ;; FIXME: There's a similar (tho fancier) function in mastodon.el!
-  "Convert the time interval in seconds to a short string."
-  (cond ((> 0 delay) (concat "-" (seconds-to-string (- delay))))
+(defun seconds-to-string (delay &optional readable abbrev precision)
+  "Convert time interval DELAY (in seconds) to a string.
+By default, the returned string is formatted as a float in the smallest
+unit from the variable `seconds-to-string' that is longer than DELAY,
+and a precision of two.  If READABLE is non-nil, convert DELAY into a
+readable string, using the information provided in the variable
+`seconds-to-string-readable'.  If it is the symbol `expanded', use two
+units to describe DELAY, if appropriate.  E.g. \"1 hour 32 minutes\".
+If ABBREV is non-nil, abbreviate the readable units.  If PRECISION is a
+whole number, round the value associated with the smallest displayed
+unit to that many digits after the decimal.  If it is a non-negative
+float less than 1.0, round to that value."
+  (cond ((< delay 0)
+	 (concat "-" (seconds-to-string (- delay) readable precision)))
+        (readable
+         (let* ((stsa seconds-to-string-readable)
+		(expanded (eq readable 'expanded))
+		digits
+		(round-to (cond ((wholenump precision)
+				 (setq digits precision)
+				 (expt 10 (- precision)))
+				((and (floatp precision) (< precision 1.))
+				 (setq digits (- (floor (log precision 10))))
+				 precision)
+				(t (setq digits 0) 1)))
+		(dformat (if (> digits 0) (format "%%0.%df" digits)))
+		(padding (if abbrev "" " "))
+		here cnt cnt-pre here-pre cnt-val isfloatp)
+	   (if (= (round delay round-to) 0)
+	       (format "0%s" (if abbrev "s" " seconds"))
+	     (while (and (setq here (pop stsa)) stsa
+			 (< (/ delay (nth 3 here)) 1)))
+	     (or (and
+		  expanded stsa 	; smaller unit remains
+		  (progn
+		    (setq
+		     here-pre here here (car stsa)
+		     cnt-pre (floor (/ (float delay) (nth 3 here-pre)))
+		     cnt (round
+			  (/ (- (float delay) (* cnt-pre (nth 3 here-pre)))
+			     (nth 3 here))
+			  round-to))
+		    (if (> cnt 0) t (setq cnt cnt-pre here here-pre here-pre nil))))
+		 (setq cnt (round (/ (float delay) (nth 3 here)) round-to)))
+	     (setq cnt-val (* cnt round-to)
+                   isfloatp (and (> digits 0)
+			            (> (- cnt-val (floor cnt-val)) 0.)))
+	     (cl-labels
+		 ((unit (val here &optional plural)
+		    (cond (abbrev (car here))
+			  ((and (not plural) (<= (floor val) 1)) (nth 1 here))
+			  (t (nth 2 here)))))
+	       (concat
+		(when here-pre
+		  (concat (number-to-string cnt-pre) padding
+			  (unit (* cnt-pre round-to) here-pre) " "))
+		(if isfloatp (format dformat cnt-val)
+                  (number-to-string (floor cnt-val)))
+                padding
+                (unit cnt-val here isfloatp)))))) ; floats are always plural
         ((= 0 delay) "0s")
         (t (let ((sts seconds-to-string) here)
              (while (and (car (setq here (pop sts)))
-- 
2.45.2


[-- Attachment #2.3: Type: text/html, Size: 216 bytes --]

^ permalink raw reply related	[flat|nested] 31+ messages in thread

* bug#71572: [PATCH] seconds-to-string-approximate
  2024-12-08 19:52                         ` JD Smith
@ 2024-12-08 22:51                           ` john muhl
  0 siblings, 0 replies; 31+ messages in thread
From: john muhl @ 2024-12-08 22:51 UTC (permalink / raw)
  To: JD Smith; +Cc: Adam Porter, 71572, Eli Zaretskii, Jonas Bernoulli, Paul Eggert

JD Smith <jdtsmith@gmail.com> writes:

>  On Dec 8, 2024, at 4:17 AM, john muhl <jm@pub.pink> wrote:
>
>  JD Smith <jdtsmith@gmail.com> writes:
>
>  On Dec 7, 2024, at 8:02 AM, Eli Zaretskii <eliz@gnu.org> wrote:
>
>  From: JD Smith <jdtsmith@gmail.com>
>  Date: Sat, 30 Nov 2024 13:58:52 -0500
>  Cc: Eli Zaretskii <eliz@gnu.org>,
>  Adam Porter <adam@alphapapa.net>,
>  jonas@bernoul.li,
>  Paul Eggert <eggert@cs.ucla.edu>
>
>  I was recently reminded of the need for a more capable seconds-to-string.  
>
>  Anyone have any additional comments on this proposed patch? If not,
>  I'd suggest someone with access
>  merges.
>
>  A few minor nits below, and then we can install:
>
>  Thanks.  Updated patch below.
>
>  Thanks for working on this. I gave a quick try and noticed some
>  amounts aren’t pluralized how I expect; e.g.
>
>   (seconds-to-string 36541462 'expanded nil 1)
>   ;; "1 year 1.9 month"
>
> ...
>
>  According to the Chicago Manual of Style all fractional values are
>  plural, even 1.0, 2.0 &c.
>
> Thanks for this, a case I hadn't tested.  Corrected in the attached.

Looks good but the bigger interval is still always singular in the
expanded w/ precision case:

  (seconds-to-string 73082924 'expanded nil 2)
  ;; "2 year 3.79 months"
  ;;       ^ should be years

I got it working with:

  @@ -477,7 +477,7 @@ seconds-to-string
             (concat
          (when here-pre
            (concat (number-to-string cnt-pre) padding
  -             (unit (* cnt-pre round-to) here-pre) " "))
  +             (unit (* cnt-pre round-to) here-pre (> cnt-pre 1)) " "))
          (if isfloatp (format dformat cnt-val)
                     (number-to-string (floor cnt-val)))
                   padding

Thanks again.





^ permalink raw reply	[flat|nested] 31+ messages in thread

end of thread, other threads:[~2024-12-08 22:51 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-06-15 17:24 bug#71573: [PATCH] seconds-to-string-approximate JD Smith
2024-06-15 17:36 ` Eli Zaretskii
2024-06-17  6:20 ` bug#71573: Related functions from ts.el Adam Porter
2024-06-22 10:55   ` Stefan Kangas
2024-06-22 21:54     ` Adam Porter
2024-06-22  8:45 ` bug#71572: [PATCH] seconds-to-string-approximate Eli Zaretskii
2024-06-22 21:56   ` Adam Porter
2024-06-22 23:42   ` Paul Eggert
2024-06-23  2:16     ` JD Smith
2024-07-04  5:29       ` Eli Zaretskii
2024-07-04  6:04         ` Eli Zaretskii
2024-07-04  7:09         ` Paul Eggert
2024-07-06 19:29           ` JD Smith
2024-07-06 21:09             ` Paul Eggert
2024-07-11 21:01               ` JD Smith
2024-11-30 18:58                 ` JD Smith
2024-12-07 13:02                   ` Eli Zaretskii
2024-12-07 17:52                     ` JD Smith
2024-12-07 19:17                       ` john muhl
2024-12-08 19:52                         ` JD Smith
2024-12-08 22:51                           ` john muhl
2024-07-04 15:27         ` JD Smith
2024-07-04 15:59           ` Eli Zaretskii
2024-07-04 17:16             ` JD Smith
2024-07-04 18:06               ` Eli Zaretskii
2024-07-04 16:36           ` Ihor Radchenko
2024-07-04 17:23             ` JD Smith
2024-07-04 17:57               ` Ihor Radchenko
2024-06-23  5:13     ` Eli Zaretskii
2024-07-03 20:32       ` JD Smith
2024-07-04  5:29         ` Eli Zaretskii

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).