master 074cdea573e: timeclock: Optionally use 24-hour clock

Eli Zaretskii <[email protected]> Thu, 23 Jul 2026 02:15:24 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 074cdea573ed21c8b92d45e4aca5cd2b9e05bbaf
Author: Petteri Hintsanen <[email protected]>
Commit: Eli Zaretskii <[email protected]>

    timeclock: Optionally use 24-hour clock
    
    * lisp/calendar/timeclock.el (timeclock-use-24hr-format): New defcustom.
    (timeclock-time-format): New function.
    (timeclock-status-string, timeclock-when-to-leave-string)
    (timeclock-generate-report): Call timeclock-time-format to format
    displayed times.  (Bug#81440)
    
    * etc/NEWS: Document the change.
    
    * test/lisp/calendar/timeclock-tests.el: New file.
---
 etc/NEWS                              |  6 ++++++
 lisp/calendar/timeclock.el            | 30 ++++++++++++++++++----------
 test/lisp/calendar/timeclock-tests.el | 37 +++++++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 10 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 85d1af8d71c..10feed8e388 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -194,6 +194,12 @@ the currently selected item in the list view to the kill-ring.
 You can now visit such files in Rmail mode using ordinary file-visiting
 commands, such as 'C-x C-f'.
 
+** timeclock
+
+---
+*** New variable 'timeclock-use-24hr-format'.
+If this variable is set to non-nil, displayed times (clocked in/out
+since, time to leave) will use 24-hour clock instead of 12-hour clock.
 
 * New Modes and Packages in Emacs 32.1
 
diff --git a/lisp/calendar/timeclock.el b/lisp/calendar/timeclock.el
index acdf99f77ae..5ff938c3925 100644
--- a/lisp/calendar/timeclock.el
+++ b/lisp/calendar/timeclock.el
@@ -136,6 +136,11 @@ This variable only has effect if set with \\[customize]."
 	 (set symbol value))
   :type 'boolean)
 
+(defcustom timeclock-use-24hr-format nil
+  "If non-nil, use 24-hour clock when displaying times.
+Otherwise use 12-hour clock with AM/PM suffix."
+  :type 'boolean)
+
 (defvar timeclock-update-timer nil
   "The timer used to update `timeclock-mode-string'.")
 
@@ -411,11 +416,8 @@ worked today, ignoring the time worked on previous days."
          (status
 	  (format "Currently %s since %s (%s), %s %s, leave at %s"
 		  (if last-in "IN" "OUT")
-		  (if show-seconds
-		      (format-time-string "%-I:%M:%S %p"
-					  (nth 1 timeclock-last-event))
-		    (format-time-string "%-I:%M %p"
-					(nth 1 timeclock-last-event)))
+		  (format-time-string (timeclock-time-format show-seconds)
+				      (nth 1 timeclock-last-event))
 		  (or (nth 2 timeclock-last-event)
 		      (if last-in "**UNKNOWN**" "workday over"))
 		  (timeclock-seconds-to-string remainder show-seconds t)
@@ -539,10 +541,8 @@ relative only to the time worked today, and not to past time."
   ;; Should today-only be removed in favor of timeclock-relative? - gm
   (interactive)
   (let* ((then (timeclock-when-to-leave today-only))
-	 (string
-	  (if show-seconds
-	      (format-time-string "%-I:%M:%S %p" then)
-	    (format-time-string "%-I:%M %p" then))))
+         (string (format-time-string (timeclock-time-format show-seconds)
+                                     then)))
     (if (called-interactively-p 'interactive)
 	(message "%s" string)
       string)))
@@ -1171,7 +1171,9 @@ HTML-P is non-nil, HTML markup is added."
 		   (setq done t))
 	  (insert "OUT")))
       (unless done
-	(insert " since " (format-time-string "%Y/%m/%d %-I:%M %p" begin))
+	(insert " since " (format-time-string
+                           (concat "%Y/%m/%d " (timeclock-time-format))
+                           begin))
 	(if html-p
 	    (insert "<br>\n<b>")
 	  (insert "\n*"))
@@ -1322,6 +1324,14 @@ HTML-P is non-nil, HTML markup is added."
   (interactive)
   (find-file-other-window timeclock-file))
 
+(defun timeclock-time-format (&optional seconds)
+  "Return a time format string suitable for format-time-string.
+Use 24-hour clock if `timeclock-use-24hr-format' is non-nil, otherwise
+use 12-hour clock.  Include seconds field if SECONDS is non-nil."
+  (if timeclock-use-24hr-format
+      (if seconds "%-H:%M:%S" "%-H:%M")
+    (if seconds "%-I:%M:%S %p" "%-I:%M %p")))
+
 (provide 'timeclock)
 
 (run-hooks 'timeclock-load-hook)
diff --git a/test/lisp/calendar/timeclock-tests.el b/test/lisp/calendar/timeclock-tests.el
new file mode 100644
index 00000000000..f289f0d11ef
--- /dev/null
+++ b/test/lisp/calendar/timeclock-tests.el
@@ -0,0 +1,37 @@
+;;; timeclock-tests.el --- Test suite for timeclock.el  -*- lexical-binding:t -*-
+
+;; Copyright (C) 2026 Free Software Foundation, Inc.
+
+;; Author: Petteri Hintsanen <[email protected]>
+
+;; This file is part of GNU Emacs.
+
+;; 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
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'ert)
+(require 'timeclock)
+
+(ert-deftest test-timeclock-time-format ()
+  (setopt timeclock-use-24hr-format nil)
+  (should (equal (timeclock-time-format) "%-I:%M %p"))
+  (should (equal (timeclock-time-format t) "%-I:%M:%S %p"))
+  (setopt timeclock-use-24hr-format t)
+  (should (equal (timeclock-time-format) "%-H:%M"))
+  (should (equal (timeclock-time-format t) "%-H:%M:%S")))
+
+(provide 'timeclock-tests)