time-stamp: properly abbreviate instead of truncating names

* lisp/time-stamp (time-stamp-string-preprocess): Stop truncating month
and weekday name strings; it didn't internationalize well.
Some historical conversions, previously accepted quietly, now warn.
(time-stamp-format): Recommend the simpler formats implemented in 2019.
* test/lisp/time-stamp-tests.el: Update tests and comments to match.
Revert commit 83e4559664 (2022-07-01), which was working around the
former confusion between truncation and abbreviation.
This commit is contained in:
Stephen Gildea 2024-12-10 09:09:39 -08:00
parent ebd8feef14
commit 7665ec8df8
2 changed files with 255 additions and 182 deletions

View file

@ -41,53 +41,59 @@
:group 'extensions)
(defcustom time-stamp-format "%Y-%02m-%02d %02H:%02M:%02S %l"
(defcustom time-stamp-format "%Y-%m-%d %H:%M:%S %l"
"Format of the string inserted by \\[time-stamp].
This is a string, used verbatim except for character sequences beginning
with %, as follows.
The string is inserted verbatim except for character sequences beginning
with %, which are converted as follows:
%:A weekday name: `Monday' %#A gives uppercase: `MONDAY'
%3a abbreviated weekday: `Mon' %#a gives uppercase: `MON'
%:B month name: `January' %#B gives uppercase: `JANUARY'
%3b abbreviated month: `Jan' %#b gives uppercase: `JAN'
%02d day of month
%02H 24-hour clock hour
%02I 12-hour clock hour
%02m month number
%02M minute
%#p `am' or `pm' %P gives uppercase: `AM' or `PM'
%02S seconds
%A weekday name: `Monday' %a abbreviated weekday name: `Mon'
%B month name: `January' %b abbreviated month name: `Jan'
%d day of month
%H 24-hour clock hour %I 12-hour clock hour
%m month number
%M minute
%p `AM' or `PM'
%S seconds
%w day number of week, Sunday is 0
%02y 2-digit year %Y 4-digit year
%Z time zone name: `EST' %#Z gives lowercase: `est'
%5z time zone offset: `-0500' (since Emacs 27; see note below)
%Y 4-digit year %y 2-digit year
%Z time zone name: `EST'
%-z zone offset with hour: `-08' %:::z adds colons as needed: `+05:30'
%5z zone offset with mins: `-0800' %:z adds colon: `-08:00'
Non-date items:
%% a literal percent character: `%'
%f file name without directory %F absolute file name
%l login name %L full name of logged-in user
%q unqualified host name %Q fully-qualified host name
%% literal percent character: \"%\"
%f file name without directory %F absolute file name
%l login name %L full name of logged-in user
%q unqualified host name %Q fully-qualified host name
%h mail host name
Decimal digits between the % and the type character specify the
field width. Strings are truncated on the right.
A leading zero in the field width zero-fills a number.
A \"#\" after the % changes the case of letters. For example, on Mondays,
in the default locale, \"%#A\" converts to \"MONDAY\".
For example, to get a common format used by the `date' command,
use \"%3a %3b %2d %02H:%02M:%02S %Z %Y\".
Decimal digits before the type character specify the minimum field
width. A \"0\" before the field width adds insignificant zeroes
as appropriate, otherwise the padding is done with spaces.
If no padding is specified, a field that can be one or two digits is padded
with \"0\" to two digits if necessary. Follow the % with \"_\" to pad with a
space instead, or follow it with \"-\" to suppress this padding entirely.
Thus, on the 5th of the month, the day is converted as follows:
\"%d\" -> \"05\"
\"%_d\" -> \" 5\"
\"%-d\" -> \"5\"
For example, to get a common format used by the \"date\" command,
use \"%a %b %_d %H:%M:%S %Z %Y\".
The values of non-numeric formatted items depend on the locale
setting recorded in `system-time-locale' and `locale-coding-system'.
The examples here are for the default (`C') locale.
The examples here are for the default (\"C\") locale.
`time-stamp-time-zone' controls the time zone used.
The default padding of some formats has changed to be more compatible
with format-time-string. To be compatible with older versions of Emacs,
specify a padding width (as shown) or use the : modifier to request the
transitional behavior (again, as shown).
The behavior of `%5z' is new in Emacs 27. If your files might be
edited by older versions of Emacs also, do not use this format yet."
Some of the conversions recommended here work only in Emacs 27 or later.
If your files might be edited by older versions of Emacs also, you should
limit yourself to the formats recommended by that older version."
:type 'string
:version "27.1")
;;;###autoload(put 'time-stamp-format 'safe-local-variable 'stringp)
@ -273,11 +279,11 @@ Examples:
// time-stamp-pattern: \"-9/^Last modified: %%$\"
(sets `time-stamp-line-limit', `time-stamp-start' and `time-stamp-end')
@c time-stamp-pattern: \"@set Time-stamp: %:B %1d, %Y$\"
@c time-stamp-pattern: \"@set Time-stamp: %B %-d, %Y$\"
(sets `time-stamp-start', `time-stamp-format' and `time-stamp-end')
%% time-stamp-pattern: \"newcommand{\\\\\\\\timestamp}{%%}\"
(sets `time-stamp-start'and `time-stamp-end')
(sets `time-stamp-start' and `time-stamp-end')
See also `time-stamp-count' and `time-stamp-inserts-lines'.")
@ -483,7 +489,7 @@ normally the current time is used."
;;; At all times, all the formats recommended in the doc string
;;; of time-stamp-format will work not only in the current version of
;;; Emacs, but in all versions that have been released within the past
;;; two years.
;;; five years.
;;; The : modifier is a temporary conversion feature used to resolve
;;; ambiguous formats--formats that are changing (over time) incompatibly.
(defun time-stamp-string-preprocess (format &optional time)
@ -576,10 +582,22 @@ and all `time-stamp-format' compatibility."
(time-stamp--format "%#a" time)
(time-stamp--format "%a" time))))
((eq cur-char ?A)
(if (or change-case upcase (not (string-equal field-width
"")))
(time-stamp--format "%#A" time)
(time-stamp--format "%A" time)))
(if (and (>= (string-to-number field-width) 1)
(<= (string-to-number field-width) 3)
(not flag-minimize)
(not flag-pad-with-spaces))
(progn
(time-stamp-conv-warn "%3A" "%#a")
(time-stamp--format "%#a" time))
(if (or change-case upcase)
(time-stamp--format "%#A" time)
(if (or (> alt-form 0)
flag-minimize flag-pad-with-spaces
(string-equal field-width ""))
(time-stamp--format "%A" time)
(time-stamp-conv-warn (format "%%%sA" field-width)
(format "%%#%sA" field-width))
(time-stamp--format "%#A" time)))))
((eq cur-char ?b) ;month name
(if (> alt-form 0)
(if (string-equal field-width "")
@ -589,10 +607,22 @@ and all `time-stamp-format' compatibility."
(time-stamp--format "%#b" time)
(time-stamp--format "%b" time))))
((eq cur-char ?B)
(if (or change-case upcase (not (string-equal field-width
"")))
(time-stamp--format "%#B" time)
(time-stamp--format "%B" time)))
(if (and (>= (string-to-number field-width) 1)
(<= (string-to-number field-width) 3)
(not flag-minimize)
(not flag-pad-with-spaces))
(progn
(time-stamp-conv-warn "%3B" "%#b")
(time-stamp--format "%#b" time))
(if (or change-case upcase)
(time-stamp--format "%#B" time)
(if (or (> alt-form 0)
flag-minimize flag-pad-with-spaces
(string-equal field-width ""))
(time-stamp--format "%B" time)
(time-stamp-conv-warn (format "%%%sB" field-width)
(format "%%#%sB" field-width))
(time-stamp--format "%#B" time)))))
((eq cur-char ?d) ;day of month, 1-31
(time-stamp-do-number cur-char alt-form field-width time))
((eq cur-char ?H) ;hour, 0-23
@ -620,13 +650,15 @@ and all `time-stamp-format' compatibility."
((eq cur-char ?w) ;weekday number, Sunday is 0
(time-stamp--format "%w" time))
((eq cur-char ?y) ;year
(if (> alt-form 0)
(string-to-number (time-stamp--format "%Y" time))
(if (or (string-equal field-width "")
(<= (string-to-number field-width) 2))
(string-to-number (time-stamp--format "%y" time))
(time-stamp-conv-warn (format "%%%sy" field-width) "%Y")
(string-to-number (time-stamp--format "%Y" time)))))
(if (= alt-form 0)
(if (or (string-equal field-width "")
(<= (string-to-number field-width) 2))
(string-to-number (time-stamp--format "%y" time))
(time-stamp-conv-warn
(format "%%%sy" field-width) "%Y")
(string-to-number (time-stamp--format "%Y" time)))
(time-stamp-conv-warn "%:y" "%Y")
(string-to-number (time-stamp--format "%Y" time))))
((eq cur-char ?Y) ;4-digit year
(string-to-number (time-stamp--format "%Y" time)))
((eq cur-char ?z) ;time zone offset
@ -673,10 +705,13 @@ and all `time-stamp-format' compatibility."
(or buffer-file-name
time-stamp-no-file))
((eq cur-char ?s) ;system name, legacy
(time-stamp-conv-warn "%s" "%Q")
(system-name))
((eq cur-char ?u) ;user name, legacy
(time-stamp-conv-warn "%u" "%l")
(user-login-name))
((eq cur-char ?U) ;user full name, legacy
(time-stamp-conv-warn "%U" "%L")
(user-full-name))
((eq cur-char ?l) ;login name
(user-login-name))
@ -694,25 +729,14 @@ and all `time-stamp-format' compatibility."
))
(and (numberp field-result)
(= alt-form 0)
(string-equal field-width "")
(or (string-equal field-width "")
(string-equal field-width "0"))
;; no width provided; set width for default
(setq field-width "02"))
(let ((padded-result
(format (format "%%%s%c"
field-width
(if (numberp field-result) ?d ?s))
(or field-result ""))))
(let* ((initial-length (length padded-result))
(desired-length (if (string-equal field-width "")
initial-length
(string-to-number field-width))))
(if (> initial-length desired-length)
;; truncate strings on right
(if (and (stringp field-result)
(not (eq cur-char ?z))) ;offset does not truncate
(substring padded-result 0 desired-length)
padded-result) ;numbers don't truncate
padded-result)))))
(format (format "%%%s%c"
field-width
(if (numberp field-result) ?d ?s))
(or field-result ""))))
(t
(char-to-string cur-char)))))
(setq ind (1+ ind)))
@ -883,7 +907,7 @@ OFFSET-SECS is the time zone offset (in seconds east of UTC) to be
formatted according to the preceding parameters.
This is an internal function used by `time-stamp'."
;; The caller of this function must have already parsed the %z
;; Callers of this function need to have already parsed the %z
;; format string; this function accepts just the parts of the format.
;; `time-stamp-string-preprocess' is the full-fledged parser normally
;; used. The unit test (in time-stamp-tests.el) defines the simpler

View file

@ -45,7 +45,7 @@
,@body)))
(defmacro with-time-stamp-test-time (reference-time &rest body)
"Force any contained time-stamp call to use time REFERENCE-TIME."
"Force `time-stamp' to use time REFERENCE-TIME while evaluating BODY."
(declare (indent 1) (debug t))
`(cl-letf*
((orig-time-stamp-string-fn (symbol-function 'time-stamp-string))
@ -55,14 +55,14 @@
,@body))
(defmacro with-time-stamp-system-name (name &rest body)
"Force (system-name) to return NAME while evaluating BODY."
"Force `system-name' to return NAME while evaluating BODY."
(declare (indent 1) (debug t))
`(cl-letf (((symbol-function 'system-name)
(lambda () ,name)))
,@body))
(defmacro time-stamp-should-warn (form)
"Similar to `should' but verifies that a format warning is generated."
"Similar to `should' and also verify that FORM generates a format warning."
(declare (debug t))
`(let ((warning-count 0))
(cl-letf (((symbol-function 'time-stamp-conv-warn)
@ -269,70 +269,111 @@
(ert-deftest time-stamp-format-day-of-week ()
"Test time-stamp formats for named day of week."
(with-time-stamp-test-env
(let ((Mon (format-time-string "%a" ref-time1 t))
(MON (format-time-string "%^a" ref-time1 t))
(Monday (format-time-string "%A" ref-time1 t))
(MONDAY (format-time-string "%^A" ref-time1 t)))
;; implemented and documented since 1997
(should (equal (time-stamp-string "%3a" ref-time1) Mon))
(let* ((Mon (format-time-string "%a" ref-time1 t))
(MON (format-time-string "%^a" ref-time1 t))
(Monday (format-time-string "%A" ref-time1 t))
(MONDAY (format-time-string "%^A" ref-time1 t))
(p4-Mon (string-pad Mon 4 ?\s t))
(p4-MON (string-pad MON 4 ?\s t))
(p10-Monday (string-pad Monday 10 ?\s t))
(p10-MONDAY (string-pad MONDAY 10 ?\s t)))
;; implemented and recommended since 1997
(should (equal (time-stamp-string "%#A" ref-time1) MONDAY))
;; documented 1997-2019
(should (equal (time-stamp-string "%3A" ref-time1)
(substring MONDAY 0 3)))
(should (equal (time-stamp-string "%#10A" ref-time1) p10-MONDAY))
;; implemented since 1997, recommended 1997-2024
(should (equal (time-stamp-string "%3a" ref-time1) Mon))
;; recommended 1997-2019
(should (equal (time-stamp-string "%:a" ref-time1) Monday))
;; implemented since 2001, documented since 2019
;; recommended 1997-2019, warned since 2024, will change
(time-stamp-should-warn
(should (equal (time-stamp-string "%3A" ref-time1) MON)))
(time-stamp-should-warn
(should (equal (time-stamp-string "%10A" ref-time1) p10-MONDAY)))
;; implemented since 2001, recommended since 2019
(should (equal (time-stamp-string "%#a" ref-time1) MON))
(should (equal (time-stamp-string "%#3a" ref-time1) MON))
(should (equal (time-stamp-string "%#4a" ref-time1) p4-MON))
;; implemented since 2001, recommended 2019-2024
(should (equal (time-stamp-string "%:A" ref-time1) Monday))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; broken 2019-2024
(should (equal (time-stamp-string "%:10A" ref-time1) p10-Monday))
;; broken in 2019, changed in 2024
(should (equal (time-stamp-string "%-A" ref-time1) Monday))
(should (equal (time-stamp-string "%_A" ref-time1) Monday))
;; allowed but not recommended since 2019 (warned 1997-2019)
(should (equal (time-stamp-string "%^A" ref-time1) MONDAY))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%a" ref-time1) Mon))
(should (equal (time-stamp-string "%4a" ref-time1) p4-Mon))
(should (equal (time-stamp-string "%04a" ref-time1) p4-Mon))
(should (equal (time-stamp-string "%A" ref-time1) Monday))
;; warned 1997-2019, changed in 2019
(should (equal (time-stamp-string "%^a" ref-time1) MON))
(should (equal (time-stamp-string "%A" ref-time1) Monday)))))
(should (equal (time-stamp-string "%^4a" ref-time1) p4-MON)))))
(ert-deftest time-stamp-format-month-name ()
"Test time-stamp formats for month name."
(with-time-stamp-test-env
(let ((Jan (format-time-string "%b" ref-time1 t))
(JAN (format-time-string "%^b" ref-time1 t))
(January (format-time-string "%B" ref-time1 t))
(JANUARY (format-time-string "%^B" ref-time1 t)))
;; implemented and documented since 1997
(should (equal (time-stamp-string "%3b" ref-time1)
(substring January 0 3)))
(let* ((Jan (format-time-string "%b" ref-time1 t))
(JAN (format-time-string "%^b" ref-time1 t))
(January (format-time-string "%B" ref-time1 t))
(JANUARY (format-time-string "%^B" ref-time1 t))
(p4-Jan (string-pad Jan 4 ?\s t))
(p4-JAN (string-pad JAN 4 ?\s t))
(p10-January (string-pad January 10 ?\s t))
(p10-JANUARY (string-pad JANUARY 10 ?\s t)))
;; implemented and recommended since 1997
(should (equal (time-stamp-string "%#B" ref-time1) JANUARY))
;; documented 1997-2019
(should (equal (time-stamp-string "%3B" ref-time1)
(substring JANUARY 0 3)))
(should (equal (time-stamp-string "%#10B" ref-time1) p10-JANUARY))
;; implemented since 1997, recommended 1997-2024
(should (equal (time-stamp-string "%3b" ref-time1) Jan))
;; recommended 1997-2019
(should (equal (time-stamp-string "%:b" ref-time1) January))
;; implemented since 2001, documented since 2019
;; recommended 1997-2019, warned since 2024, will change
(time-stamp-should-warn
(should (equal (time-stamp-string "%3B" ref-time1) JAN)))
(time-stamp-should-warn
(should (equal (time-stamp-string "%10B" ref-time1) p10-JANUARY)))
;; implemented since 2001, recommended since 2019
(should (equal (time-stamp-string "%#b" ref-time1) JAN))
(should (equal (time-stamp-string "%#3b" ref-time1) JAN))
(should (equal (time-stamp-string "%#4b" ref-time1) p4-JAN))
;; implemented since 2001, recommended 2019-2024
(should (equal (time-stamp-string "%:B" ref-time1) January))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; broken 2019-2024
(should (equal (time-stamp-string "%:10B" ref-time1) p10-January))
;; broken in 2019, changed in 2024
(should (equal (time-stamp-string "%-B" ref-time1) January))
(should (equal (time-stamp-string "%_B" ref-time1) January))
;; allowed but not recommended since 2019 (warned 1997-2019)
(should (equal (time-stamp-string "%^B" ref-time1) JANUARY))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%b" ref-time1) Jan))
(should (equal (time-stamp-string "%4b" ref-time1) p4-Jan))
(should (equal (time-stamp-string "%04b" ref-time1) p4-Jan))
(should (equal (time-stamp-string "%B" ref-time1) January))
;; warned 1997-2019, changed in 2019
(should (equal (time-stamp-string "%^b" ref-time1) JAN))
(should (equal (time-stamp-string "%B" ref-time1) January)))))
(should (equal (time-stamp-string "%^4b" ref-time1) p4-JAN)))))
(ert-deftest time-stamp-format-day-of-month ()
"Test time-stamp formats for day of month."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended until 2024
(should (equal (time-stamp-string "%2d" ref-time1) " 2"))
(should (equal (time-stamp-string "%2d" ref-time2) "18"))
(should (equal (time-stamp-string "%02d" ref-time1) "02"))
(should (equal (time-stamp-string "%02d" ref-time2) "18"))
;; documented 1997-2019
;; recommended 1997-2019
(should (equal (time-stamp-string "%:d" ref-time1) "2"))
(should (equal (time-stamp-string "%:d" ref-time2) "18"))
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended 2019-2024
(should (equal (time-stamp-string "%1d" ref-time1) "2"))
(should (equal (time-stamp-string "%1d" ref-time2) "18"))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; warned 1997-2019, allowed 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%-d" ref-time1) "2"))
(should (equal (time-stamp-string "%-d" ref-time2) "18"))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%_d" ref-time1) " 2"))
(should (equal (time-stamp-string "%_d" ref-time2) "18"))
(should (equal (time-stamp-string "%d" ref-time1) "02"))
@ -341,26 +382,26 @@
(ert-deftest time-stamp-format-hours-24 ()
"Test time-stamp formats for hour on a 24-hour clock."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended until 2024
(should (equal (time-stamp-string "%2H" ref-time1) "15"))
(should (equal (time-stamp-string "%2H" ref-time2) "12"))
(should (equal (time-stamp-string "%2H" ref-time3) " 6"))
(should (equal (time-stamp-string "%02H" ref-time1) "15"))
(should (equal (time-stamp-string "%02H" ref-time2) "12"))
(should (equal (time-stamp-string "%02H" ref-time3) "06"))
;; documented 1997-2019
;; recommended 1997-2019
(should (equal (time-stamp-string "%:H" ref-time1) "15"))
(should (equal (time-stamp-string "%:H" ref-time2) "12"))
(should (equal (time-stamp-string "%:H" ref-time3) "6"))
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended 2019-2024
(should (equal (time-stamp-string "%1H" ref-time1) "15"))
(should (equal (time-stamp-string "%1H" ref-time2) "12"))
(should (equal (time-stamp-string "%1H" ref-time3) "6"))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; warned 1997-2019, allowed 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%-H" ref-time1) "15"))
(should (equal (time-stamp-string "%-H" ref-time2) "12"))
(should (equal (time-stamp-string "%-H" ref-time3) "6"))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%_H" ref-time1) "15"))
(should (equal (time-stamp-string "%_H" ref-time2) "12"))
(should (equal (time-stamp-string "%_H" ref-time3) " 6"))
@ -371,26 +412,26 @@
(ert-deftest time-stamp-format-hours-12 ()
"Test time-stamp formats for hour on a 12-hour clock."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended until 2024
(should (equal (time-stamp-string "%2I" ref-time1) " 3"))
(should (equal (time-stamp-string "%2I" ref-time2) "12"))
(should (equal (time-stamp-string "%2I" ref-time3) " 6"))
(should (equal (time-stamp-string "%02I" ref-time1) "03"))
(should (equal (time-stamp-string "%02I" ref-time2) "12"))
(should (equal (time-stamp-string "%02I" ref-time3) "06"))
;; documented 1997-2019
;; recommended 1997-2019
(should (equal (time-stamp-string "%:I" ref-time1) "3")) ;PM
(should (equal (time-stamp-string "%:I" ref-time2) "12")) ;PM
(should (equal (time-stamp-string "%:I" ref-time3) "6")) ;AM
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended since 2019
(should (equal (time-stamp-string "%1I" ref-time1) "3"))
(should (equal (time-stamp-string "%1I" ref-time2) "12"))
(should (equal (time-stamp-string "%1I" ref-time3) "6"))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; warned 1997-2019, allowed 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%-I" ref-time1) "3"))
(should (equal (time-stamp-string "%-I" ref-time2) "12"))
(should (equal (time-stamp-string "%-I" ref-time3) "6"))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%_I" ref-time1) " 3"))
(should (equal (time-stamp-string "%_I" ref-time2) "12"))
(should (equal (time-stamp-string "%_I" ref-time3) " 6"))
@ -401,21 +442,21 @@
(ert-deftest time-stamp-format-month-number ()
"Test time-stamp formats for month number."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended until 2024
(should (equal (time-stamp-string "%2m" ref-time1) " 1"))
(should (equal (time-stamp-string "%2m" ref-time2) "11"))
(should (equal (time-stamp-string "%02m" ref-time1) "01"))
(should (equal (time-stamp-string "%02m" ref-time2) "11"))
;; documented 1997-2019
;; recommended 1997-2019
(should (equal (time-stamp-string "%:m" ref-time1) "1"))
(should (equal (time-stamp-string "%:m" ref-time2) "11"))
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended since 2019
(should (equal (time-stamp-string "%1m" ref-time1) "1"))
(should (equal (time-stamp-string "%1m" ref-time2) "11"))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; warned 1997-2019, allowed 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%-m" ref-time1) "1"))
(should (equal (time-stamp-string "%-m" ref-time2) "11"))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%_m" ref-time1) " 1"))
(should (equal (time-stamp-string "%_m" ref-time2) "11"))
(should (equal (time-stamp-string "%m" ref-time1) "01"))
@ -424,21 +465,21 @@
(ert-deftest time-stamp-format-minute ()
"Test time-stamp formats for minute."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended until 2024
(should (equal (time-stamp-string "%2M" ref-time1) " 4"))
(should (equal (time-stamp-string "%2M" ref-time2) "14"))
(should (equal (time-stamp-string "%02M" ref-time1) "04"))
(should (equal (time-stamp-string "%02M" ref-time2) "14"))
;; documented 1997-2019
;; recommended 1997-2019
(should (equal (time-stamp-string "%:M" ref-time1) "4"))
(should (equal (time-stamp-string "%:M" ref-time2) "14"))
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended since 2019
(should (equal (time-stamp-string "%1M" ref-time1) "4"))
(should (equal (time-stamp-string "%1M" ref-time2) "14"))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; warned 1997-2019, allowed 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%-M" ref-time1) "4"))
(should (equal (time-stamp-string "%-M" ref-time2) "14"))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%_M" ref-time1) " 4"))
(should (equal (time-stamp-string "%_M" ref-time2) "14"))
(should (equal (time-stamp-string "%M" ref-time1) "04"))
@ -447,21 +488,21 @@
(ert-deftest time-stamp-format-second ()
"Test time-stamp formats for second."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended until 2024
(should (equal (time-stamp-string "%2S" ref-time1) " 5"))
(should (equal (time-stamp-string "%2S" ref-time2) "15"))
(should (equal (time-stamp-string "%02S" ref-time1) "05"))
(should (equal (time-stamp-string "%02S" ref-time2) "15"))
;; documented 1997-2019
;; recommended 1997-2019
(should (equal (time-stamp-string "%:S" ref-time1) "5"))
(should (equal (time-stamp-string "%:S" ref-time2) "15"))
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended since 2019
(should (equal (time-stamp-string "%1S" ref-time1) "5"))
(should (equal (time-stamp-string "%1S" ref-time2) "15"))
;; allowed but undocumented since 2019 (warned 1997-2019)
;; warned 1997-2019, allowed 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%-S" ref-time1) "5"))
(should (equal (time-stamp-string "%-S" ref-time2) "15"))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%_S" ref-time1) " 5"))
(should (equal (time-stamp-string "%_S" ref-time2) "15"))
(should (equal (time-stamp-string "%S" ref-time1) "05"))
@ -470,12 +511,14 @@
(ert-deftest time-stamp-format-year-2digit ()
"Test time-stamp formats for %y."
(with-time-stamp-test-env
;; implemented and documented since 1995
;; implemented since 1995, recommended 1995-2024
(should (equal (time-stamp-string "%02y" ref-time1) "06"))
(should (equal (time-stamp-string "%02y" ref-time2) "16"))
;; documented 1997-2019
(should (equal (time-stamp-string "%:y" ref-time1) "2006"))
(should (equal (time-stamp-string "%:y" ref-time2) "2016"))
;; recommended 1997-2019, warned since 2024
(time-stamp-should-warn
(should (equal (time-stamp-string "%:y" ref-time1) "2006")))
(time-stamp-should-warn
(should (equal (time-stamp-string "%:y" ref-time2) "2016")))
;; warned 1997-2019, changed in 2019
;; (We don't expect the %-y or %_y form to be useful,
;; but we test both so that we can confidently state that
@ -484,6 +527,7 @@
(should (equal (time-stamp-string "%-y" ref-time2) "16"))
(should (equal (time-stamp-string "%_y" ref-time1) " 6"))
(should (equal (time-stamp-string "%_y" ref-time2) "16"))
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%y" ref-time1) "06"))
(should (equal (time-stamp-string "%y" ref-time2) "16"))
;; implemented since 1995, warned since 2019, will change
@ -495,7 +539,7 @@
(ert-deftest time-stamp-format-year-4digit ()
"Test time-stamp format %Y."
(with-time-stamp-test-env
;; implemented since 1997, documented since 2019
;; implemented since 1997, recommended since 2019
(should (equal (time-stamp-string "%Y" ref-time1) "2006"))
;; numbers do not truncate
(should (equal (time-stamp-string "%2Y" ref-time1) "2006"))
@ -510,15 +554,16 @@
(Am (format-time-string "%p" ref-time3 t))
(PM (format-time-string "%^p" ref-time1 t))
(AM (format-time-string "%^p" ref-time3 t)))
;; implemented and documented since 1997
;; implemented and recommended since 1997
(should (equal (time-stamp-string "%#p" ref-time1) pm))
(should (equal (time-stamp-string "%#p" ref-time3) am))
;; implemented since 1997, recommended 1997-2024
(should (equal (time-stamp-string "%P" ref-time1) Pm))
(should (equal (time-stamp-string "%P" ref-time3) Am))
;; implemented since 1997
(should (equal (time-stamp-string "%^#p" ref-time1) pm))
(should (equal (time-stamp-string "%^#p" ref-time3) am))
;; warned 1997-2019, changed in 2019
;; warned 1997-2019, changed in 2019, recommended (with caveat) since 2024
(should (equal (time-stamp-string "%p" ref-time1) Pm))
(should (equal (time-stamp-string "%p" ref-time3) Am))
;; changed in 2024
@ -543,19 +588,22 @@
(with-time-stamp-test-env
(let ((UTC-abbr (format-time-string "%Z" ref-time1 t))
(utc-abbr (format-time-string "%#Z" ref-time1 t)))
;; implemented and documented since 1995
;; implemented and recommended since 1995
(should (equal (time-stamp-string "%Z" ref-time1) UTC-abbr))
;; implemented since 1997, documented since 2019
(should (equal (time-stamp-string "%#Z" ref-time1) utc-abbr)))))
;; implemented since 1997, recommended since 2019
(should (equal (time-stamp-string "%#Z" ref-time1) utc-abbr))
;; ^ accepted and ignored since 1995/1997, test for consistency with %p
(should (equal (time-stamp-string "%^Z" ref-time1) UTC-abbr))
(should (equal (time-stamp-string "%^#Z" ref-time1) utc-abbr)))))
(ert-deftest time-stamp-format-time-zone-offset ()
"Test time-stamp legacy format %z and spot-test new offset format %5z."
(with-time-stamp-test-env
(let ((utc-abbr (format-time-string "%#Z" ref-time1 t)))
;; documented 1995-2019, warned since 2019, will change
;; recommended 1995-2019, warned since 2019, will change
(time-stamp-should-warn
(equal (time-stamp-string "%z" ref-time1) utc-abbr)))
;; implemented and documented (with compat caveat) since 2019
;; implemented and recommended (with compat caveat) since 2019
(should (equal (time-stamp-string "%5z" ref-time1) "+0000"))
(let ((time-stamp-time-zone "PST8"))
(should (equal (time-stamp-string "%5z" ref-time1) "-0800")))
@ -563,20 +611,21 @@
(should (equal (time-stamp-string "%5z" ref-time1) "-1000")))
(let ((time-stamp-time-zone "CET-1"))
(should (equal (time-stamp-string "%5z" ref-time1) "+0100")))
;; implemented since 2019, verify that these don't warn
;; implemented since 2019, recommended (with compat caveat) since 2024
;; See also the "formatz" tests below, which since 2021 test more
;; variants with more offsets.
(should (equal (time-stamp-string "%-z" ref-time1) "+00"))
(should (equal (time-stamp-string "%:::z" ref-time1) "+00"))
(should (equal (time-stamp-string "%:z" ref-time1) "+00:00"))
;; implemented since 2019
(should (equal (time-stamp-string "%::z" ref-time1) "+00:00:00"))
(should (equal (time-stamp-string "%9::z" ref-time1) "+00:00:00"))
(should (equal (time-stamp-string "%:::z" ref-time1) "+00"))))
(should (equal (time-stamp-string "%9::z" ref-time1) "+00:00:00"))))
(ert-deftest time-stamp-format-non-date-conversions ()
"Test time-stamp formats for non-date items."
(with-time-stamp-test-env
(with-time-stamp-system-name "test-system-name.example.org"
;; implemented and documented since 1995
;; implemented and recommended since 1995
(should (equal (time-stamp-string "%%" ref-time1) "%")) ;% last char
(should (equal (time-stamp-string "%%P" ref-time1) "%P")) ;% not last char
(should (equal (time-stamp-string "%f" ref-time1) "time-stamped-file"))
@ -589,15 +638,18 @@
(let ((mail-host-address nil))
(should (equal (time-stamp-string "%h" ref-time1)
"test-system-name.example.org")))
;; documented 1995-2019
(should (equal (time-stamp-string "%s" ref-time1)
"test-system-name.example.org"))
(should (equal (time-stamp-string "%U" ref-time1) "100%d Tester"))
(should (equal (time-stamp-string "%u" ref-time1) "test-logname"))
;; implemented since 2001, documented since 2019
;; recommended 1997-2019, warned since 2024
(time-stamp-should-warn
(should (equal (time-stamp-string "%s" ref-time1)
"test-system-name.example.org")))
(time-stamp-should-warn
(should (equal (time-stamp-string "%U" ref-time1) "100%d Tester")))
(time-stamp-should-warn
(should (equal (time-stamp-string "%u" ref-time1) "test-logname")))
;; implemented since 2001, recommended since 2019
(should (equal (time-stamp-string "%L" ref-time1) "100%d Tester"))
(should (equal (time-stamp-string "%l" ref-time1) "test-logname"))
;; implemented since 2007, documented since 2019
;; implemented since 2007, recommended since 2019
(should (equal (time-stamp-string "%Q" ref-time1)
"test-system-name.example.org"))
(should (equal (time-stamp-string "%q" ref-time1) "test-system-name")))
@ -668,24 +720,20 @@
(ert-deftest time-stamp-format-string-width ()
"Test time-stamp string width modifiers."
(with-time-stamp-test-env
(let ((May (format-time-string "%b" ref-time3 t))
(SUN (format-time-string "%^a" ref-time3 t))
(NOV (format-time-string "%^b" ref-time2 t)))
;; strings truncate on the right or are blank-padded on the left
(should (equal (time-stamp-string "%0b" ref-time3) ""))
(should (equal (time-stamp-string "%1b" ref-time3) (substring May 0 1)))
(should (equal (time-stamp-string "%2b" ref-time3) (substring May 0 2)))
(should (equal (time-stamp-string "%3b" ref-time3) (substring May 0 3)))
(should (equal (time-stamp-string "%4b" ref-time3) (concat " " May)))
(should (equal (time-stamp-string "%0%" ref-time3) ""))
(should (equal (time-stamp-string "%1%" ref-time3) "%"))
(should (equal (time-stamp-string "%2%" ref-time3) " %"))
(should (equal (time-stamp-string "%9%" ref-time3) " %"))
(should (equal (time-stamp-string "%10%" ref-time3) " %"))
(should (equal (time-stamp-string "%#3a" ref-time3)
(substring SUN 0 3)))
(should (equal (time-stamp-string "%#3b" ref-time2)
(substring NOV 0 3))))))
(let ((UTC-abbr (format-time-string "%Z" ref-time1 t)))
(should (equal (time-stamp-string "%1%" ref-time3) "%"))
(should (equal (time-stamp-string "%2%" ref-time3) " %"))
(should (equal (time-stamp-string "%9%" ref-time3) " %"))
(should (equal (time-stamp-string "%10%" ref-time3) " %"))
(should (equal (time-stamp-string "%03d" ref-time3) "025"))
(should (equal (time-stamp-string "%3d" ref-time3) " 25"))
(should (equal (time-stamp-string "%_3d" ref-time3) " 25"))
;; since 2024
(should (equal (time-stamp-string "%0d" ref-time1) "02"))
(should (equal (time-stamp-string "%0d" ref-time2) "18"))
;; broken 2019-2024
(should (equal (time-stamp-string "%-Z" ref-time1) UTC-abbr))
(should (equal (time-stamp-string "%_Z" ref-time1) UTC-abbr)))))
;;; Tests of helper functions
@ -895,11 +943,11 @@ The functions in `pattern-mod' are composed left to right."
(defun formatz-mod-pad-r10 (string)
"Return STRING padded on the right to 10 characters."
(concat string (make-string (- 10 (length string)) ?\s)))
(string-pad string 10))
(defun formatz-mod-pad-r12 (string)
"Return STRING padded on the right to 12 characters."
(concat string (make-string (- 12 (length string)) ?\s)))
(string-pad string 12))
;; Convenience macro for generating groups of test cases.
@ -966,7 +1014,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
;;;; The actual test cases for %z
;;; %z formats without colons.
;;; Test %z formats without colons.
;; Option character "-" (minus) minimizes; it removes "00" minutes.
(formatz-generate-tests ("%-z" "%-3z")
@ -976,7 +1024,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00")
("+100:00:30"))
;; Tests that minus with padding pads with spaces.
;; Minus with padding pads with spaces.
(formatz-generate-tests ("%-12z")
("+00 " formatz-mod-pad-r12)
("+0030 " formatz-mod-del-colons formatz-mod-pad-r12)
@ -984,7 +1032,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00 " formatz-mod-pad-r12)
("+100:00:30 " formatz-mod-pad-r12))
;; Tests that 0 after other digits becomes padding of ten, not zero flag.
;; 0 after other digits becomes padding of ten, not zero flag.
(formatz-generate-tests ("%-10z")
("+00 " formatz-mod-pad-r10)
("+0030 " formatz-mod-del-colons formatz-mod-pad-r10)
@ -1017,7 +1065,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00")
("+100:00:30"))
;; Tests that padding adds spaces.
;; Padding adds spaces.
(formatz-generate-tests ("%12z")
("+0000 " formatz-mod-add-00 formatz-mod-pad-r12)
("+0030 " formatz-mod-del-colons formatz-mod-pad-r12)
@ -1049,7 +1097,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00:00 " formatz-mod-add-colon00 formatz-mod-pad-r12)
("+100:00:30 " formatz-mod-pad-r12))
;;; %z formats with colons
;;; Test %z formats with colons.
;; Three colons can output hours only,
;; like %-z, but uses colons with non-zero minutes and seconds.
@ -1061,14 +1109,15 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00")
("+100:00:30"))
;; Padding with three colons adds spaces
;; Padding with three colons adds spaces.
(formatz-generate-tests ("%12:::z")
("+00 " formatz-mod-pad-r12)
("+00:30 " formatz-mod-pad-r12)
("+00:00:30 " formatz-mod-pad-r12)
("+100:00 " formatz-mod-pad-r12)
("+100:00:30 " formatz-mod-pad-r12))
;; Tests that 0 after other digits becomes padding of ten, not zero flag.
;; 0 after other digits becomes padding of ten, not zero flag.
(formatz-generate-tests ("%10:::z")
("+00 " formatz-mod-pad-r10)
("+00:30 " formatz-mod-pad-r10)
@ -1084,7 +1133,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00")
("+100:00:30"))
;; Padding with one colon adds spaces
;; Padding with one colon adds spaces.
(formatz-generate-tests ("%12:z")
("+00:00 " formatz-mod-add-colon00 formatz-mod-pad-r12)
("+00:30 " formatz-mod-pad-r12)
@ -1117,7 +1166,7 @@ the other expected results for hours greater than 99 with non-zero seconds."
("+100:00:00 " formatz-mod-add-colon00 formatz-mod-pad-r12)
("+100:00:30 " formatz-mod-pad-r12))
;;; Illegal %z formats
;;; Test illegal %z formats.
(ert-deftest formatz-illegal-options ()
"Test that illegal/nonsensical/ambiguous %z formats don't produce output."