(zoneinfo-style-world-list, legacy-style-world-list): New defcustoms.
[bpt/emacs.git] / lisp / time.el
... / ...
CommitLineData
1;;; time.el --- display time, load and mail indicator in mode line of Emacs -*-coding: utf-8 -*-
2
3;; Copyright (C) 1985, 1986, 1987, 1993, 1994, 1996, 2000, 2001, 2002,
4;; 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5
6;; Maintainer: FSF
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 3, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23;; Boston, MA 02110-1301, USA.
24
25;;; Commentary:
26
27;; Facilities to display current time/date and a new-mail indicator
28;; in the Emacs mode line. The entry point is `display-time'.
29
30;; Display time world in a buffer, the entry point is
31;; `display-time-world'.
32
33;;; Code:
34
35(defgroup display-time nil
36 "Display time and load in mode line of Emacs."
37 :group 'mode-line
38 :group 'mail)
39
40
41(defcustom display-time-mail-file nil
42 "*File name of mail inbox file, for indicating existence of new mail.
43Non-nil and not a string means don't check for mail; nil means use
44default, which is system-dependent, and is the same as used by Rmail."
45 :type '(choice (const :tag "None" none)
46 (const :tag "Default" nil)
47 (file :format "%v"))
48 :group 'display-time)
49
50(defcustom display-time-mail-directory nil
51 "*Name of mail inbox directory, for indicating existence of new mail.
52Any nonempty regular file in the directory is regarded as newly arrived mail.
53If nil, do not check a directory for arriving mail."
54 :type '(choice (const :tag "None" nil)
55 (directory :format "%v"))
56 :group 'display-time)
57
58(defcustom display-time-mail-function nil
59 "*Function to call, for indicating existence of new mail.
60If nil, that means use the default method: check that the file
61specified by `display-time-mail-file' is nonempty or that the
62directory `display-time-mail-directory' contains nonempty files."
63 :type '(choice (const :tag "Default" nil)
64 (function))
65 :group 'display-time)
66
67(defcustom display-time-default-load-average 0
68 "*Which load average value will be shown in the mode line.
69Almost every system can provide values of load for past 1 minute, past 5 or
70past 15 minutes. The default is to display 1 minute load average."
71 :type '(choice (const :tag "1 minute load" 0)
72 (const :tag "5 minutes load" 1)
73 (const :tag "15 minutes load" 2)
74 (const :tag "None" nil))
75 :group 'display-time)
76
77(defvar display-time-load-average nil
78 "Load average currently being shown in mode line.")
79
80(defcustom display-time-load-average-threshold 0.1
81 "*Load-average values below this value won't be shown in the mode line."
82 :type 'number
83 :group 'display-time)
84
85;;;###autoload
86(defcustom display-time-day-and-date nil "\
87*Non-nil means \\[display-time] should display day and date as well as time."
88 :type 'boolean
89 :group 'display-time)
90
91(defvar display-time-timer nil)
92
93(defcustom display-time-interval 60
94 "*Seconds between updates of time in the mode line."
95 :type 'integer
96 :group 'display-time)
97
98(defcustom display-time-24hr-format nil
99 "*Non-nil indicates time should be displayed as hh:mm, 0 <= hh <= 23.
100A value of nil means 1 <= hh <= 12, and an AM/PM suffix is used."
101 :type 'boolean
102 :group 'display-time)
103
104(defvar display-time-string nil)
105
106(defcustom display-time-hook nil
107 "*List of functions to be called when the time is updated on the mode line."
108 :type 'hook
109 :group 'display-time)
110
111(defvar display-time-server-down-time nil
112 "Time when mail file's file system was recorded to be down.
113If that file system seems to be up, the value is nil.")
114
115(defcustom zoneinfo-style-world-list
116 '(("America/Los_Angeles" "Seattle")
117 ("America/New_York" "New York")
118 ("Europe/London" "London")
119 ("Europe/Paris" "Paris")
120 ("Asia/Calcutta" "Bangalore")
121 ("Asia/Tokyo" "Tokyo"))
122 "Alist of zoneinfo-style time zones and places for `display-time-world'.
123Each element has the form (TIMEZONE LABEL).
124TIMEZONE should be a string of the form AREA/LOCATION, where AREA is
125the name of a region -- a continent or ocean, and LOCATION is the name
126of a specific location, e.g., a city, within that region.
127LABEL is a string to display as the label of that TIMEZONE's time."
128 :group 'display-time
129 :type '(repeat (list string string))
130 :version "23.1")
131
132(defcustom legacy-style-world-list
133 '(("PST8PDT" "Seattle")
134 ("EST5EDT" "New York")
135 ("BST0BDT" "London")
136 ("CET-1CDT" "Paris")
137 ("IST-5:30IDT" "Bangalore")
138 ("JST-9JDT" "Tokyo"))
139 "Alist of traditional-style time zones and places for `display-time-world'.
140Each element has the form (TIMEZONE LABEL).
141TIMEZONE should be a string of the form:
142
143 std[+|-]offset[dst[offset][,date[/time],date[/time]]]
144
145See the documentation of the TZ environment variable on your system,
146for more details about the format of TIMEZONE.
147LABEL is a string to display as the label of that TIMEZONE's time."
148 :group 'display-time
149 :type '(repeat (list string string))
150 :version "23.1")
151
152(defcustom display-time-world-list
153 (if (memq 'system-type '(gnu/linux ms-dos))
154 zoneinfo-style-world-list
155 legacy-style-world-list)
156 "Alist of time zones and places for `display-time-world' to display.
157Each element has the form (TIMEZONE LABEL).
158TIMEZONE should be in the format supported by `set-time-zone-rule' on
159your system. See the documentation of `zoneinfo-style-world-list' and
160\`legacy-style-world-list' for two widely used formats.
161LABEL is a string to display as the label of that TIMEZONE's time."
162 :group 'display-time
163 :type '(repeat (list string string))
164 :version "23.1")
165
166(defcustom display-time-world-time-format "%A %d %B %R %Z"
167 "Format of the time displayed, see `format-time-string'."
168 :group 'display-time
169 :type 'string
170 :version "23.1")
171
172(defcustom display-time-world-buffer-name "*wclock*"
173 "Name of the wclock buffer."
174 :group 'display-time
175 :type 'string
176 :version "23.1")
177
178(defcustom display-time-world-timer-enable t
179 "If non-nil, a timer will update the world clock."
180 :group 'display-time
181 :type 'boolean
182 :version "23.1")
183
184(defcustom display-time-world-timer-second 60
185 "Interval in seconds for updating the world clock."
186 :group 'display-time
187 :type 'integer
188 :version "23.1")
189
190(defvar display-time-world-mode-map
191 (let ((map (make-sparse-keymap)))
192 (define-key map "q" 'kill-this-buffer)
193 map)
194 "Keymap of Display Time World mode")
195
196;;;###autoload
197(defun display-time ()
198 "Enable display of time, load level, and mail flag in mode lines.
199This display updates automatically every minute.
200If `display-time-day-and-date' is non-nil, the current day and date
201are displayed as well.
202This runs the normal hook `display-time-hook' after each update."
203 (interactive)
204 (display-time-mode 1))
205
206;; This business used to be simpler when all mode lines had the same
207;; face and the image could just be pbm. Now we try to rely on an xpm
208;; image with a transparent background. Otherwise, set the background
209;; for pbm.
210
211(defcustom display-time-mail-face nil
212 "Face to use for `display-time-mail-string'.
213If `display-time-use-mail-icon' is non-nil, the image's
214background color is the background of this face. Set this to
215make the mail indicator stand out on a color display."
216 :group 'mode-line-faces
217 :group 'display-time
218 :version "22.1"
219 :type '(choice (const :tag "None" nil) face))
220
221(defvar display-time-mail-icon
222 (find-image '((:type xpm :file "letter.xpm" :ascent center)
223 (:type pbm :file "letter.pbm" :ascent center)))
224 "Image specification to offer as the mail indicator on a graphic display.
225See `display-time-use-mail-icon' and `display-time-mail-face'.")
226
227;; Fixme: Default to icon on graphical display?
228(defcustom display-time-use-mail-icon nil
229 "Non-nil means use an icon as mail indicator on a graphic display.
230Otherwise use `display-time-mail-string'. The icon may consume less
231of the mode line. It is specified by `display-time-mail-icon'."
232 :group 'display-time
233 :type 'boolean)
234
235;; Fixme: maybe default to the character if we can display Unicode.
236(defcustom display-time-mail-string "Mail"
237 "String to use as the mail indicator in `display-time-string-forms'.
238This can use the Unicode letter character if you can display it."
239 :group 'display-time
240 :version "22.1"
241 :type '(choice (const "Mail")
242 ;; Use :tag here because the Lucid menu won't display
243 ;; multibyte text.
244 (const :tag "Unicode letter character" "✉")
245 string))
246
247(defcustom display-time-format nil
248 "*String specifying format for displaying the time in the mode line.
249See the function `format-time-string' for an explanation of
250how to write this string. If this is nil, the defaults
251depend on `display-time-day-and-date' and `display-time-24hr-format'."
252 :type '(choice (const :tag "Default" nil)
253 string)
254 :group 'display-time)
255
256(defcustom display-time-string-forms
257 '((if (and (not display-time-format) display-time-day-and-date)
258 (format-time-string "%a %b %e " now)
259 "")
260 (propertize
261 (format-time-string (or display-time-format
262 (if display-time-24hr-format "%H:%M" "%-I:%M%p"))
263 now)
264 'help-echo (format-time-string "%a %b %e, %Y" now))
265 load
266 (if mail
267 ;; Build the string every time to act on customization.
268 ;; :set-after doesn't help for `customize-option'. I think it
269 ;; should.
270 (concat
271 " "
272 (propertize
273 display-time-mail-string
274 'display `(when (and display-time-use-mail-icon
275 (display-graphic-p))
276 ,@display-time-mail-icon
277 ,@(if (and display-time-mail-face
278 (memq (plist-get (cdr display-time-mail-icon)
279 :type)
280 '(pbm xbm)))
281 (let ((bg (face-attribute display-time-mail-face
282 :background)))
283 (if (stringp bg)
284 (list :background bg)))))
285 'face display-time-mail-face
286 'help-echo "You have new mail; mouse-2: Read mail"
287 'mouse-face 'mode-line-highlight
288 'local-map (make-mode-line-mouse-map 'mouse-2
289 read-mail-command)))
290 ""))
291 "*List of expressions governing display of the time in the mode line.
292For most purposes, you can control the time format using `display-time-format'
293which is a more standard interface.
294
295This expression is a list of expressions that can involve the keywords
296`load', `day', `month', and `year', `12-hours', `24-hours', `minutes',
297`seconds', all numbers in string form, and `monthname', `dayname', `am-pm',
298and `time-zone' all alphabetic strings, and `mail' a true/nil value.
299
300For example, the form
301
302 '((substring year -2) \"/\" month \"/\" day
303 \" \" 24-hours \":\" minutes \":\" seconds
304 (if time-zone \" (\") time-zone (if time-zone \")\")
305 (if mail \" Mail\" \"\"))
306
307would give mode line times like `94/12/30 21:07:48 (UTC)'."
308 :type 'sexp
309 :group 'display-time)
310
311(defun display-time-event-handler ()
312 (display-time-update)
313 ;; Do redisplay right now, if no input pending.
314 (sit-for 0)
315 (let* ((current (current-time))
316 (timer display-time-timer)
317 ;; Compute the time when this timer will run again, next.
318 (next-time (timer-relative-time
319 (list (aref timer 1) (aref timer 2) (aref timer 3))
320 (* 5 (aref timer 4)) 0)))
321 ;; If the activation time is far in the past,
322 ;; skip executions until we reach a time in the future.
323 ;; This avoids a long pause if Emacs has been suspended for hours.
324 (or (> (nth 0 next-time) (nth 0 current))
325 (and (= (nth 0 next-time) (nth 0 current))
326 (> (nth 1 next-time) (nth 1 current)))
327 (and (= (nth 0 next-time) (nth 0 current))
328 (= (nth 1 next-time) (nth 1 current))
329 (> (nth 2 next-time) (nth 2 current)))
330 (progn
331 (timer-set-time timer (timer-next-integral-multiple-of-time
332 current display-time-interval)
333 display-time-interval)
334 (timer-activate timer)))))
335
336(defun display-time-next-load-average ()
337 (interactive)
338 (if (= 3 (setq display-time-load-average (1+ display-time-load-average)))
339 (setq display-time-load-average 0))
340 (display-time-update)
341 (sit-for 0))
342
343(defun display-time-mail-check-directory ()
344 (let ((mail-files (directory-files display-time-mail-directory t))
345 (size 0))
346 (while (and mail-files (= size 0))
347 ;; Count size of regular files only.
348 (setq size (+ size (or (and (file-regular-p (car mail-files))
349 (nth 7 (file-attributes (car mail-files))))
350 0)))
351 (setq mail-files (cdr mail-files)))
352 (if (> size 0)
353 size
354 nil)))
355
356(defun display-time-update ()
357 "Update the display-time info for the mode line.
358However, don't redisplay right now.
359
360This is used for things like Rmail `g' that want to force an
361update which can wait for the next redisplay."
362 (let* ((now (current-time))
363 (time (current-time-string now))
364 (load (if (null display-time-load-average)
365 ""
366 (condition-case ()
367 ;; Do not show values less than
368 ;; `display-time-load-average-threshold'.
369 (if (> (* display-time-load-average-threshold 100)
370 (nth display-time-load-average (load-average)))
371 ""
372 ;; The load average number is mysterious, so
373 ;; provide some help.
374 (let ((str (format " %03d"
375 (nth display-time-load-average
376 (load-average)))))
377 (propertize
378 (concat (substring str 0 -2) "." (substring str -2))
379 'local-map (make-mode-line-mouse-map
380 'mouse-2 'display-time-next-load-average)
381 'mouse-face 'mode-line-highlight
382 'help-echo (concat
383 "System load average for past "
384 (if (= 0 display-time-load-average)
385 "1 minute"
386 (if (= 1 display-time-load-average)
387 "5 minutes"
388 "15 minutes"))
389 "; mouse-2: next"))))
390 (error ""))))
391 (mail-spool-file (or display-time-mail-file
392 (getenv "MAIL")
393 (concat rmail-spool-directory
394 (user-login-name))))
395 (mail (or (and display-time-mail-function
396 (funcall display-time-mail-function))
397 (and display-time-mail-directory
398 (display-time-mail-check-directory))
399 (and (stringp mail-spool-file)
400 (or (null display-time-server-down-time)
401 ;; If have been down for 20 min, try again.
402 (> (- (nth 1 now) display-time-server-down-time)
403 1200)
404 (and (< (nth 1 now) display-time-server-down-time)
405 (> (- (nth 1 now)
406 display-time-server-down-time)
407 -64336)))
408 (let ((start-time (current-time)))
409 (prog1
410 (display-time-file-nonempty-p mail-spool-file)
411 (if (> (- (nth 1 (current-time))
412 (nth 1 start-time))
413 20)
414 ;; Record that mail file is not accessible.
415 (setq display-time-server-down-time
416 (nth 1 (current-time)))
417 ;; Record that mail file is accessible.
418 (setq display-time-server-down-time nil)))))))
419 (24-hours (substring time 11 13))
420 (hour (string-to-number 24-hours))
421 (12-hours (int-to-string (1+ (% (+ hour 11) 12))))
422 (am-pm (if (>= hour 12) "pm" "am"))
423 (minutes (substring time 14 16))
424 (seconds (substring time 17 19))
425 (time-zone (car (cdr (current-time-zone now))))
426 (day (substring time 8 10))
427 (year (substring time 20 24))
428 (monthname (substring time 4 7))
429 (month
430 (cdr
431 (assoc
432 monthname
433 '(("Jan" . "1") ("Feb" . "2") ("Mar" . "3") ("Apr" . "4")
434 ("May" . "5") ("Jun" . "6") ("Jul" . "7") ("Aug" . "8")
435 ("Sep" . "9") ("Oct" . "10") ("Nov" . "11") ("Dec" . "12")))))
436 (dayname (substring time 0 3)))
437 (setq display-time-string
438 (mapconcat 'eval display-time-string-forms ""))
439 ;; This is inside the let binding, but we are not going to document
440 ;; what variables are available.
441 (run-hooks 'display-time-hook))
442 (force-mode-line-update))
443
444(defun display-time-file-nonempty-p (file)
445 (and (file-exists-p file)
446 (< 0 (nth 7 (file-attributes (file-chase-links file))))))
447
448;;;###autoload
449(define-minor-mode display-time-mode
450 "Toggle display of time, load level, and mail flag in mode lines.
451With a numeric arg, enable this display if arg is positive.
452
453When this display is enabled, it updates automatically every minute.
454If `display-time-day-and-date' is non-nil, the current day and date
455are displayed as well.
456This runs the normal hook `display-time-hook' after each update."
457 :global t :group 'display-time
458 (and display-time-timer (cancel-timer display-time-timer))
459 (setq display-time-timer nil)
460 (setq display-time-string "")
461 (or global-mode-string (setq global-mode-string '("")))
462 (setq display-time-load-average display-time-default-load-average)
463 (if display-time-mode
464 (progn
465 (or (memq 'display-time-string global-mode-string)
466 (setq global-mode-string
467 (append global-mode-string '(display-time-string))))
468 ;; Set up the time timer.
469 (setq display-time-timer
470 (run-at-time t display-time-interval
471 'display-time-event-handler))
472 ;; Make the time appear right away.
473 (display-time-update)
474 ;; When you get new mail, clear "Mail" from the mode line.
475 (add-hook 'rmail-after-get-new-mail-hook
476 'display-time-event-handler))
477 (remove-hook 'rmail-after-get-new-mail-hook
478 'display-time-event-handler)))
479
480
481(defun display-time-world-mode ()
482 "Major mode for buffer that displays times in various time zones.
483See `display-time-world'."
484 (interactive)
485 (kill-all-local-variables)
486 (setq
487 major-mode 'display-time-world-mode
488 mode-name "World clock")
489 (use-local-map display-time-world-mode-map))
490
491(defun display-time-world-display (alist)
492 "Replace current buffer text with times in various zones, based on ALIST."
493 (let ((inhibit-read-only t)
494 (buffer-undo-list t))
495 (erase-buffer)
496 (let ((max-width 0)
497 (result ()))
498 (unwind-protect
499 (dolist (zone alist)
500 (let* ((label (cadr zone))
501 (width (string-width label)))
502 (set-time-zone-rule (car zone))
503 (setq result
504 (append result
505 (list
506 label width
507 (format-time-string display-time-world-time-format))))
508 (when (> width max-width)
509 (setq max-width width))))
510 (set-time-zone-rule nil))
511 (while result
512 (insert (pop result)
513 (make-string (1+ (- max-width (pop result))) ?\s)
514 (pop result) "\n")))
515 (delete-backward-char 1)))
516
517;;;###autoload
518(defun display-time-world ()
519 "Enable updating display of times in various time zones.
520`display-time-world-list' specifies the zones.
521To turn off the world time display, go to that window and type `q'."
522 (interactive)
523 (when (and display-time-world-timer-enable
524 (not (get-buffer display-time-world-buffer-name)))
525 (run-at-time t display-time-world-timer-second 'display-time-world-timer))
526 (with-current-buffer (get-buffer-create display-time-world-buffer-name)
527 (display-time-world-display display-time-world-list))
528 (pop-to-buffer display-time-world-buffer-name)
529 (fit-window-to-buffer)
530 (display-time-world-mode))
531
532(defun display-time-world-timer ()
533 (if (get-buffer display-time-world-buffer-name)
534 (with-current-buffer (get-buffer display-time-world-buffer-name)
535 (display-time-world-display display-time-world-list))
536 ;; cancel timer
537 (let ((list timer-list))
538 (while list
539 (let ((elt (pop list)))
540 (when (equal (symbol-name (aref elt 5)) "display-time-world-timer")
541 (cancel-timer elt)))))))
542
543(provide 'time)
544
545;;; arch-tag: b9c1623f-b5cb-48e4-b650-482a4d23c5a6
546;;; time.el ends here