From 2a8467a5c48a5b7296bce13ccaee01fb80b53704 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Mon, 18 Apr 2022 13:08:27 -0700 Subject: [PATCH 5/6] =?UTF-8?q?Support=20format-time-string=20%-N=20like?= =?UTF-8?q?=20=E2=80=98date=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit format-time-string now supports "%-N" to mean "do not pad past timestamp resolution". This is compatible with GNU 'date'. * src/timefns.c (hz_width, replace_percent_minus_N): New functions. (Fformat_time_string): Use them to support %-N. * test/src/timefns-tests.el (format-time-string-%-N): New test. (format-time-string-padding-minimal-deletes-unneeded-zeros): Adjust test to match new behavior. --- doc/lispref/os.texi | 3 +++ etc/NEWS | 5 ++++ src/timefns.c | 53 +++++++++++++++++++++++++++++++++++---- test/src/timefns-tests.el | 12 +++++++-- 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index bfcd51318e..30ece4aeff 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -1897,6 +1897,9 @@ Time Parsing @samp{_} pads with blanks, @samp{-} suppresses padding, @samp{^} upper-cases letters, and @samp{#} reverses the case of letters. +As a special case, @samp{%-N} suppresses padding past the resolution +of @var{time}. For example, if @var{time} is @code{(12345020 . 1000)}, +@samp{%-N} stands for @samp{020}. You can also specify the field width and type of padding for any of these @samp{%}-sequences. This works as in @code{printf}: you write diff --git a/etc/NEWS b/etc/NEWS index 38317fef03..d9635cce46 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1956,6 +1956,11 @@ For example, the macOS system clock resolution is 1 μs so (time-convert nil t) now acts like (time-convert nil 1000000). Formerly, time-convert used a clock resolution of 1 ns regardless. ++++ +** 'format-time-string' no longer pads %-N excessively. +Insteads, it pads to the resolution of the timestamp when the +timestamp resolution is greater than 1 ns. + +++ ** 'encode-time' now also accepts a 6-element list with just time and date. (encode-time (list SECOND MINUTE HOUR DAY MONTH YEAR)) is now short for diff --git a/src/timefns.c b/src/timefns.c index 0d0a8a5922..a6cc9c9f05 100644 --- a/src/timefns.c +++ b/src/timefns.c @@ -1430,6 +1430,42 @@ format_time_string (char const *format, ptrdiff_t formatlen, return result; } +/* Yield the number of decimal digits needed to output a time with the + clock frequency HZ (0 < HZ <= TIMESPEC_HZ / 10), without losing info. + But if HZ is 1, yield 1. 0 < result < LOG10_TIMESPEC_HZ. */ + +static int +hz_width (int hz) +{ + int i = 0; + for (int r = 1; r < hz; r *= 10) + i++; + return i | !i; +} + +/* Modify FORMAT, of length FORMATLEN and with FORMAT[FORMATLEN] == '\0', + in place, replacing each "%-N" with "%9N", "%6N", or whatever + number of digits is appropriate for min (HZ, TIMESPEC_HZ). + HZ is a positive integer. */ + +static void +replace_percent_minus_N (char *format, ptrdiff_t formatlen, Lisp_Object hz) +{ + for (char *f = format; f < format + formatlen; f++) + if (f[0] == '%') + { + if (f[1] == '-' && f[2] == 'N') + { + f[1] = (FIXNUMP (hz) && XFIXNUM (hz) <= TIMESPEC_HZ / 10 + ? '0' + hz_width (XFIXNUM (hz)) + : '9'); + f += 2; + } + else + f += f[1] == '%'; + } +} + DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0, doc: /* Use FORMAT-STRING to format the time value TIME. A time value that is omitted or nil stands for the current time, @@ -1484,7 +1520,7 @@ DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0, A %-sequence can contain optional flags, field width, and a modifier (in that order) after the `%'. The flags are: -`-' Do not pad the field. +`-' Do not pad the field. %-N means to not pad past TIME's resolution. `_' Pad with spaces. `0' Pad with zeros. `+' Pad with zeros and put `+' before nonnegative year numbers with >4 digits. @@ -1508,14 +1544,21 @@ DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0, usage: (format-time-string FORMAT-STRING &optional TIME ZONE) */) (Lisp_Object format_string, Lisp_Object timeval, Lisp_Object zone) { - struct timespec t = lisp_time_argument (timeval); + struct lisp_time lt = lisp_time_struct (timeval, 0); + struct timespec t = lisp_to_timespec (lt); + if (! timespec_valid_p (t)) + time_overflow (); struct tm tm; - CHECK_STRING (format_string); + /* Convert FORMAT_STRING to the locale's encoding, and modify + any %-N formats in the copy to be the system clock resolution. */ format_string = code_convert_string_norecord (format_string, Vlocale_coding_system, 1); - return format_time_string (SSDATA (format_string), SBYTES (format_string), - t, zone, &tm); + char *format = SSDATA (format_string); + ptrdiff_t formatlen = SBYTES (format_string); + replace_percent_minus_N (format, formatlen, lt.hz); + + return format_time_string (format, formatlen, t, zone, &tm); } DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0, diff --git a/test/src/timefns-tests.el b/test/src/timefns-tests.el index 08d06f27d9..0d79152f8a 100644 --- a/test/src/timefns-tests.el +++ b/test/src/timefns-tests.el @@ -128,7 +128,7 @@ format-time-string-with-bignum-on-32-bit (ert-deftest format-time-string-padding-minimal-deletes-unneeded-zeros () (let ((ref-time (encode-time '((123450 . 1000000) 0 0 15 2 2000 - - t)))) (should (equal (format-time-string "%-:::z" ref-time "FJT-12") "+12")) - (should (equal (format-time-string "%-N" ref-time t) "12345")) + (should (equal (format-time-string "%-N" ref-time t) "123450")) (should (equal (format-time-string "%-6N" ref-time t) "12345")) (should (equal (format-time-string "%-m" ref-time t) "2")))) ;not "02" @@ -137,7 +137,7 @@ format-time-string-padding-minimal-retains-needed-zeros (should (equal (format-time-string "%-z" ref-time "IST-5:30") "+530")) (should (equal (format-time-string "%-4z" ref-time "IST-5:30") "+530")) (should (equal (format-time-string "%4z" ref-time "IST-5:30") "+530")) - (should (equal (format-time-string "%-N" ref-time t) "00345")) + (should (equal (format-time-string "%-N" ref-time t) "003450")) (should (equal (format-time-string "%-3N" ref-time t) "003")) (should (equal (format-time-string "%3N" ref-time t) "003")) (should (equal (format-time-string "%-m" ref-time t) "10")) ;not "1" @@ -165,6 +165,14 @@ format-time-string-padding-zeros-adds-on-insignificant-side (should (equal (format-time-string "%9N" ref-time t) "123000000")) (should (equal (format-time-string "%6N" ref-time t) "123000")))) +(ert-deftest format-time-string-%-N () + (should (equal (format-time-string "%-N" '(0 . 10)) "0")) + (should (equal (format-time-string "%-N" '(0 . 11)) "00")) + (should (equal (format-time-string "%-N" '(0 . 100)) "00")) + (should (equal (format-time-string "%-N" '(0 . 101)) "000")) + (should (equal (format-time-string "%-N" '(0 . 100000000)) "00000000")) + (should (equal (format-time-string "%-N" '(0 . 100000001)) "000000000")) + (should (equal (format-time-string "%-N" '(0 . 1000000000)) "000000000"))) (ert-deftest time-equal-p-nil-nil () (should (time-equal-p nil nil))) -- 2.35.1