;; This file is part of GNU Emacs.
-;; Maintainer: Stephen Gildea <stepheng+emacs@gildea.com>
-;; Keywords: tools
+;; Author: Stephen Gildea <stepheng+emacs@gildea.com>
+;; Keywords: files, tools
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; A template in a file can be updated with a new time stamp when
;; you save the file. For example:
-;; static char *ts = "sdmain.c Time-stamp: <2020-04-18 14:10:21 gildea>";
+;; static char *ts = "sdmain.c Time-stamp: <2024-04-18 14:10:21 gildea>";
;; To use time-stamping, add this line to your init file:
;; (add-hook 'before-save-hook 'time-stamp)
;; Now any time-stamp templates in your files will be updated automatically.
-;; See the documentation for the functions `time-stamp'
-;; and `time-stamp-toggle-active' for details.
+;; See the documentation for the function `time-stamp' for details.
;;; Code:
(defgroup time-stamp nil
"Maintain last change time stamps in files edited by Emacs."
- :group 'data
+ :group 'files
:group 'extensions)
This is a string, used verbatim except for character sequences beginning
with %, 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
-%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)
+%: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
+%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)
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
-%h mail host name
+%% 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
+%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.
-For example, to get the format used by the `date' command,
+For example, to get a common format used by the `date' command,
use \"%3a %3b %2d %02H:%02M:%02S %Z %Y\".
The values of non-numeric formatted items depend on the locale
`time-stamp-start', or `time-stamp-end' in your init file, you
would be incompatible with other people's files.
-See also `time-stamp-count' and `time-stamp-inserts-lines'.
-
Examples:
-\"-10/\" (sets only `time-stamp-line-limit')
+;; time-stamp-pattern: \"-10/\"
+ (sets only `time-stamp-line-limit')
+
+// 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$\"
+ (sets `time-stamp-start', `time-stamp-format' and `time-stamp-end')
-\"-9/^Last modified: %%$\" (sets `time-stamp-line-limit',
-`time-stamp-start' and `time-stamp-end')
+%% time-stamp-pattern: \"newcommand{\\\\\\\\timestamp}{%%}\"
+ (sets `time-stamp-start'and `time-stamp-end')
-\"@set Time-stamp: %:B %1d, %Y$\" (sets `time-stamp-start',
-`time-stamp-format' and `time-stamp-end')
-\"newcommand{\\\\\\\\timestamp}{%%}\" (sets `time-stamp-start'
-and `time-stamp-end')")
+See also `time-stamp-count' and `time-stamp-inserts-lines'.")
;;;###autoload(put 'time-stamp-pattern 'safe-local-variable 'stringp)
;;;###autoload
(defun time-stamp ()
"Update any time stamp string(s) in the buffer.
-This function looks for a time stamp template and updates it with
-the current date, time, and/or other info.
+Look for a time stamp template and update it with the current date,
+time, and/or other info.
The template, which you manually create on one of the first 8 lines
of the file before running this function, by default can look like
Time-stamp: \" \"
This function writes the current time between the brackets or quotes,
by default formatted like this:
- Time-stamp: <2020-08-07 17:10:21 gildea>
+ Time-stamp: <2024-08-07 17:10:21 gildea>
Although you can run this function manually to update a time stamp
once, usually you want automatic time stamp updating.
this line to a local variables list near the end of the file:
eval: (add-hook \\='before-save-hook \\='time-stamp nil t)
-If the file has no time-stamp template, this function does nothing.
+If the file has no time stamp template, this function does nothing.
You can set `time-stamp-pattern' in a file's local variables list
to customize the information in the time stamp and where it is written.
(cond
((not time-stamp-active)
(if time-stamp-warn-inactive
- ;; don't signal an error in a write-file-hook
+ ;; don't signal an error in a hook
(progn
(message "Warning: time-stamp-active is off; did not time-stamp buffer.")
(sit-for 1))))
;; - The %_z format always outputs seconds, allowing all added padding
;; to be spaces. Without this rule, there would be no way to
;; request seconds that worked for both 2- and 3-digit hours.
+;; (We consider 3-digit hours not because such offsets are in use but
+;; instead to guide our design toward consistency and extensibility.)
;; - Conflicting options are rejected, lest users depend
;; on incidental behavior.
;;
colon-count
field-width
offset-secs)
- "Formats a time offset according to a %z variation.
+ "Format a time offset according to a %z variation.
With no flags, the output includes hours and minutes: +-HHMM
unless there is a non-zero seconds part, in which case the seconds
(should (equal (time-stamp-string "%#Z" ref-time1) utc-abbr)))))
(ert-deftest time-stamp-format-time-zone-offset ()
- "Tests time-stamp legacy format %z and spot tests of new offset format %5z."
+ "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
(should (equal (time-stamp-string "No percent" ref-time1) "No percent"))))
(ert-deftest time-stamp-format-multiple-conversions ()
- "Tests that multiple %-conversions are independent."
+ "Test that multiple %-conversions are independent."
(with-time-stamp-test-env
(let ((Mon (format-time-string "%a" ref-time1 t))
(MON (format-time-string "%^a" ref-time1 t))
;;;; Setup for tests of time offset formatting with %z
(defun formatz (format zone)
- "Uses FORMAT to format the offset of ZONE, returning the result.
+ "Use FORMAT to format the offset of ZONE, returning the result.
FORMAT must be time format \"%z\" or some variation thereof.
ZONE is as the ZONE argument of the `format-time-string' function.
This function is called by 99% of the `time-stamp' \"%z\" unit tests."
)))
(defun format-time-offset (format offset-secs)
- "Uses FORMAT to format the time zone represented by OFFSET-SECS.
+ "Use FORMAT to format the time zone represented by OFFSET-SECS.
FORMAT must be time format \"%z\" or some variation thereof.
This function is a wrapper around `time-stamp-formatz-from-parsed-options'
and is called by some low-level `time-stamp' \"%z\" unit tests."
trailing-string)))
(defun fz-make+zone (h &optional m s)
- "Creates a non-negative offset."
+ "Create a non-negative offset."
(declare (pure t))
(let ((m (or m 0))
(s (or s 0)))
(+ (* 3600 h) (* 60 m) s)))
(defun fz-make-zone (h &optional m s)
- "Creates a negative offset. The arguments are all non-negative."
+ "Create a negative offset. The arguments are all non-negative."
(declare (pure t))
(- (fz-make+zone h m s)))
(defmacro formatz-should-equal (zone expect)
- "Formats ZONE and compares it to EXPECT.
-Uses the free variables `form-string' and `pattern-mod'.
+ "Format ZONE and compares it to EXPECT.
+Use the free variables `form-string' and `pattern-mod'.
The functions in `pattern-mod' are composed left to right."
(declare (debug t))
`(let ((result ,expect))
;; for hours, minutes, and seconds.
(defun formatz-hours-exact-helper (form-string pattern-mod)
- "Tests format %z with whole hours."
+ "Test format %z with whole hours."
(formatz-should-equal (fz-make+zone 0) "+00") ;0 sign always +, both digits
(formatz-should-equal (fz-make+zone 10) "+10")
(formatz-should-equal (fz-make-zone 10) "-10")
)
(defun formatz-nonzero-minutes-helper (form-string pattern-mod)
- "Tests format %z with whole minutes."
+ "Test format %z with whole minutes."
(formatz-should-equal (fz-make+zone 0 30) "+00:30") ;has hours even though 0
(formatz-should-equal (fz-make-zone 0 30) "-00:30")
(formatz-should-equal (fz-make+zone 0 4) "+00:04")
)
(defun formatz-nonzero-seconds-helper (form-string pattern-mod)
- "Tests format %z with non-0 seconds."
+ "Test format %z with non-0 seconds."
;; non-0 seconds are always included
(formatz-should-equal (fz-make+zone 0 0 50) "+00:00:50")
(formatz-should-equal (fz-make-zone 0 0 50) "-00:00:50")
)
(defun formatz-hours-big-helper (form-string pattern-mod)
- "Tests format %z with hours that don't fit in two digits."
+ "Test format %z with hours that don't fit in two digits."
(formatz-should-equal (fz-make+zone 101) "+101:00")
(formatz-should-equal (fz-make+zone 123 10) "+123:10")
(formatz-should-equal (fz-make-zone 123 10) "-123:10")
)
(defun formatz-seconds-big-helper (form-string pattern-mod)
- "Tests format %z with hours greater than 99 and non-zero seconds."
+ "Test format %z with hours greater than 99 and non-zero seconds."
(formatz-should-equal (fz-make+zone 123 0 30) "+123:00:30")
(formatz-should-equal (fz-make-zone 123 0 30) "-123:00:30")
(formatz-should-equal (fz-make+zone 120 0 4) "+120:00:04")
;; use the above test cases for multiple formats.
(defun formatz-mod-del-colons (string)
- "Returns STRING with any colons removed."
+ "Return STRING with any colons removed."
(string-replace ":" "" string))
(defun formatz-mod-add-00 (string)
- "Returns STRING with \"00\" appended."
+ "Return STRING with \"00\" appended."
(concat string "00"))
(defun formatz-mod-add-colon00 (string)
- "Returns STRING with \":00\" appended."
+ "Return STRING with \":00\" appended."
(concat string ":00"))
(defun formatz-mod-pad-r10 (string)
- "Returns STRING padded on the right to 10 characters."
+ "Return STRING padded on the right to 10 characters."
(concat string (make-string (- 10 (length string)) ?\s)))
(defun formatz-mod-pad-r12 (string)
- "Returns STRING padded on the right to 12 characters."
+ "Return STRING padded on the right to 12 characters."
(concat string (make-string (- 12 (length string)) ?\s)))
;; Convenience macro for generating groups of test cases.
(defmacro formatz-generate-tests
(form-strings hour-mod mins-mod secs-mod big-mod secbig-mod)
- "Defines tests for time formats FORM-STRINGS.
+ "Define tests for time formats FORM-STRINGS.
FORM-STRINGS is a list of formats, each \"%z\" or some variation thereof.
Each of the remaining arguments is an unquoted list of the form
ert-test-list
(list
`(ert-deftest ,(intern (concat "formatz-" form-string "-hhmm")) ()
- ,(concat "Tests time-stamp format " form-string
+ ,(concat "Test time-stamp format " form-string
" with whole hours or minutes.")
(should (equal (formatz ,form-string (fz-make+zone 0))
,(car hour-mod)))
,(car mins-mod)))
(formatz-nonzero-minutes-helper ,form-string ',(cdr mins-mod)))
`(ert-deftest ,(intern (concat "formatz-" form-string "-seconds")) ()
- ,(concat "Tests time-stamp format " form-string
+ ,(concat "Test time-stamp format " form-string
" with offsets that have non-zero seconds.")
(should (equal (formatz ,form-string (fz-make+zone 0 0 30))
,(car secs-mod)))
(formatz-nonzero-seconds-helper ,form-string ',(cdr secs-mod)))
`(ert-deftest ,(intern (concat "formatz-" form-string "-threedigit")) ()
- ,(concat "Tests time-stamp format " form-string
+ ,(concat "Test time-stamp format " form-string
" with offsets that are 100 hours or greater.")
(should (equal (formatz ,form-string (fz-make+zone 100))
,(car big-mod)))
;; The legacy exception for %z in time-stamp will need to remain
;; through at least 2024 and Emacs 28.
(ert-deftest formatz-%z-spotcheck ()
- "Spot-checks internal implementation of time-stamp format %z."
+ "Spot-check internal implementation of time-stamp format %z."
(should (equal (format-time-offset "%z" (fz-make+zone 0)) "+0000"))
(should (equal (format-time-offset "%z" (fz-make+zone 0 30)) "+0030"))
(should (equal (format-time-offset "%z" (fz-make+zone 0 0 30)) "+000030"))
;;; Illegal %z formats
(ert-deftest formatz-illegal-options ()
- "Tests that illegal/nonsensical/ambiguous %z formats don't produce output."
+ "Test that illegal/nonsensical/ambiguous %z formats don't produce output."
;; multiple options
(should (equal "" (formatz "%_-z" 0)))
(should (equal "" (formatz "%-_z" 0)))