]> code.delx.au - gnu-emacs/blob - lisp/calendar/diary-lib.el
2004-05-08 John Wiegley <johnw@newartisans.com>
[gnu-emacs] / lisp / calendar / diary-lib.el
1 ;;; diary-lib.el --- diary functions
2
3 ;; Copyright (C) 1989, 1990, 1992, 1993, 1994, 1995, 2003, 2004
4 ;; Free Software Foundation, Inc.
5
6 ;; Author: Edward M. Reingold <reingold@cs.uiuc.edu>
7 ;; Keywords: calendar
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25
26 ;;; Commentary:
27
28 ;; This collection of functions implements the diary features as described
29 ;; in calendar.el.
30
31 ;; Comments, corrections, and improvements should be sent to
32 ;; Edward M. Reingold Department of Computer Science
33 ;; (217) 333-6733 University of Illinois at Urbana-Champaign
34 ;; reingold@cs.uiuc.edu 1304 West Springfield Avenue
35 ;; Urbana, Illinois 61801
36
37 ;;; Code:
38
39 (require 'calendar)
40
41 (defun diary-check-diary-file ()
42 "Check that the file specified by `diary-file' exists and is readable.
43 If so, return the expanded file name, otherwise signal an error."
44 (let ((d-file (substitute-in-file-name diary-file)))
45 (if (and d-file (file-exists-p d-file))
46 (if (file-readable-p d-file)
47 d-file
48 (error "Diary file `%s' is not readable" diary-file))
49 (error "Diary file `%s' does not exist" diary-file))))
50
51 ;;;###autoload
52 (defun diary (&optional arg)
53 "Generate the diary window for ARG days starting with the current date.
54 If no argument is provided, the number of days of diary entries is governed
55 by the variable `number-of-diary-entries'. A value of ARG less than 1
56 does nothing. This function is suitable for execution in a `.emacs' file."
57 (interactive "P")
58 (diary-check-diary-file)
59 (let ((date (calendar-current-date)))
60 (list-diary-entries
61 date
62 (cond (arg (prefix-numeric-value arg))
63 ((vectorp number-of-diary-entries)
64 (aref number-of-diary-entries (calendar-day-of-week date)))
65 (t number-of-diary-entries)))))
66
67 (defun view-diary-entries (arg)
68 "Prepare and display a buffer with diary entries.
69 Searches the file named in `diary-file' for entries that
70 match ARG days starting with the date indicated by the cursor position
71 in the displayed three-month calendar."
72 (interactive "p")
73 (diary-check-diary-file)
74 (list-diary-entries (calendar-cursor-to-date t) arg))
75
76 (defun view-other-diary-entries (arg d-file)
77 "Prepare and display buffer of diary entries from an alternative diary file.
78 Searches for entries that match ARG days, starting with the date indicated
79 by the cursor position in the displayed three-month calendar.
80 D-FILE specifies the file to use as the diary file."
81 (interactive
82 (list (if arg (prefix-numeric-value arg) 1)
83 (read-file-name "Enter diary file name: " default-directory nil t)))
84 (let ((diary-file d-file))
85 (view-diary-entries arg)))
86
87 (autoload 'check-calendar-holidays "holidays"
88 "Check the list of holidays for any that occur on DATE.
89 The value returned is a list of strings of relevant holiday descriptions.
90 The holidays are those in the list `calendar-holidays'.")
91
92 (autoload 'calendar-holiday-list "holidays"
93 "Form the list of holidays that occur on dates in the calendar window.
94 The holidays are those in the list `calendar-holidays'.")
95
96 (autoload 'diary-french-date "cal-french"
97 "French calendar equivalent of date diary entry.")
98
99 (autoload 'diary-mayan-date "cal-mayan"
100 "Mayan calendar equivalent of date diary entry.")
101
102 (autoload 'diary-iso-date "cal-iso"
103 "ISO calendar equivalent of date diary entry.")
104
105 (autoload 'diary-julian-date "cal-julian"
106 "Julian calendar equivalent of date diary entry.")
107
108 (autoload 'diary-astro-day-number "cal-julian"
109 "Astronomical (Julian) day number diary entry.")
110
111 (autoload 'diary-chinese-date "cal-china"
112 "Chinese calendar equivalent of date diary entry.")
113
114 (autoload 'diary-islamic-date "cal-islam"
115 "Islamic calendar equivalent of date diary entry.")
116
117 (autoload 'list-islamic-diary-entries "cal-islam"
118 "Add any Islamic date entries from the diary file to `diary-entries-list'.")
119
120 (autoload 'mark-islamic-diary-entries "cal-islam"
121 "Mark days in the calendar window that have Islamic date diary entries.")
122
123 (autoload 'mark-islamic-calendar-date-pattern "cal-islam"
124 "Mark dates in calendar window that conform to Islamic date MONTH/DAY/YEAR.")
125
126 (autoload 'diary-bahai-date "cal-bahai"
127 "Baha'i calendar equivalent of date diary entry."
128 t)
129
130 (autoload 'list-bahai-diary-entries "cal-bahai"
131 "Add any Baha'i date entries from the diary file to `diary-entries-list'."
132 t)
133
134 (autoload 'mark-bahai-diary-entries "cal-bahai"
135 "Mark days in the calendar window that have Baha'i date diary entries."
136 t)
137
138 (autoload 'mark-bahai-calendar-date-pattern "cal-bahai"
139 "Mark dates in calendar window that conform to Baha'i date MONTH/DAY/YEAR."
140 t)
141
142 (autoload 'diary-hebrew-date "cal-hebrew"
143 "Hebrew calendar equivalent of date diary entry.")
144
145 (autoload 'diary-omer "cal-hebrew"
146 "Omer count diary entry.")
147
148 (autoload 'diary-yahrzeit "cal-hebrew"
149 "Yahrzeit diary entry--entry applies if date is yahrzeit or the day before.")
150
151 (autoload 'diary-parasha "cal-hebrew"
152 "Parasha diary entry--entry applies if date is a Saturday.")
153
154 (autoload 'diary-rosh-hodesh "cal-hebrew"
155 "Rosh Hodesh diary entry.")
156
157 (autoload 'list-hebrew-diary-entries "cal-hebrew"
158 "Add any Hebrew date entries from the diary file to `diary-entries-list'.")
159
160 (autoload 'mark-hebrew-diary-entries "cal-hebrew"
161 "Mark days in the calendar window that have Hebrew date diary entries.")
162
163 (autoload 'mark-hebrew-calendar-date-pattern "cal-hebrew"
164 "Mark dates in calendar window that conform to Hebrew date MONTH/DAY/YEAR.")
165
166 (autoload 'diary-coptic-date "cal-coptic"
167 "Coptic calendar equivalent of date diary entry.")
168
169 (autoload 'diary-ethiopic-date "cal-coptic"
170 "Ethiopic calendar equivalent of date diary entry.")
171
172 (autoload 'diary-persian-date "cal-persia"
173 "Persian calendar equivalent of date diary entry.")
174
175 (autoload 'diary-phases-of-moon "lunar" "Moon phases diary entry.")
176
177 (autoload 'diary-sunrise-sunset "solar"
178 "Local time of sunrise and sunset as a diary entry.")
179
180 (autoload 'diary-sabbath-candles "solar"
181 "Local time of candle lighting diary entry--applies if date is a Friday.
182 No diary entry if there is no sunset on that date.")
183
184 (defvar diary-syntax-table (copy-syntax-table (standard-syntax-table))
185 "The syntax table used when parsing dates in the diary file.
186 It is the standard syntax table used in Fundamental mode, but with the
187 syntax of `*' and `:' changed to be word constituents.")
188
189 (modify-syntax-entry ?* "w" diary-syntax-table)
190 (modify-syntax-entry ?: "w" diary-syntax-table)
191
192 (defvar diary-entries-list)
193 (defvar displayed-year)
194 (defvar displayed-month)
195 (defvar entry)
196 (defvar date)
197 (defvar number)
198 (defvar date-string)
199 (defvar original-date)
200
201 (defun diary-attrtype-convert (attrvalue type)
202 "Convert string ATTRVALUE to TYPE appropriate for a face description.
203 Valid TYPEs are: string, symbol, int, stringtnil, tnil."
204 (let (ret)
205 (setq ret (cond ((eq type 'string) attrvalue)
206 ((eq type 'symbol) (read attrvalue))
207 ((eq type 'int) (string-to-int attrvalue))
208 ((eq type 'stringtnil)
209 (cond ((string= "t" attrvalue) t)
210 ((string= "nil" attrvalue) nil)
211 (t attrvalue)))
212 ((eq type 'tnil)
213 (cond ((string= "t" attrvalue) t)
214 ((string= "nil" attrvalue) nil)))))
215 ; (message "(%s)[%s]=[%s]" (print type) attrvalue ret)
216 ret))
217
218
219 (defun diary-pull-attrs (entry fileglobattrs)
220 "Pull the face-related attributes off the entry, merge with the
221 fileglobattrs, and return the (possibly modified) entry and face
222 data in a list of attrname attrvalue values.
223 The entry will be modified to drop all tags that are used for face matching.
224 If entry is nil, then the fileglobattrs are being searched for,
225 the fileglobattrs variable is ignored, and
226 diary-glob-file-regexp-prefix is prepended to the regexps before each
227 search."
228 (save-excursion
229 (let (regexp regnum attrname attr-list attrname attrvalue type
230 ret-attr attr)
231 (if (null entry)
232 (progn
233 (setq ret-attr '()
234 attr-list diary-face-attrs)
235 (while attr-list
236 (goto-char (point-min))
237 (setq attr (car attr-list)
238 regexp (nth 0 attr)
239 regnum (nth 1 attr)
240 attrname (nth 2 attr)
241 type (nth 3 attr)
242 regexp (concat diary-glob-file-regexp-prefix regexp))
243 (setq attrvalue nil)
244 (if (re-search-forward regexp (point-max) t)
245 (setq attrvalue (buffer-substring-no-properties
246 (match-beginning regnum)
247 (match-end regnum))))
248 (if (and attrvalue
249 (setq attrvalue (diary-attrtype-convert attrvalue type)))
250 (setq ret-attr (append ret-attr (list attrname attrvalue))))
251 (setq attr-list (cdr attr-list)))
252 (setq fileglobattrs ret-attr))
253 (progn
254 (setq ret-attr fileglobattrs
255 attr-list diary-face-attrs)
256 (while attr-list
257 (goto-char (point-min))
258 (setq attr (car attr-list)
259 regexp (nth 0 attr)
260 regnum (nth 1 attr)
261 attrname (nth 2 attr)
262 type (nth 3 attr))
263 (setq attrvalue nil)
264 (if (string-match regexp entry)
265 (progn
266 (setq attrvalue (substring-no-properties entry
267 (match-beginning regnum)
268 (match-end regnum)))
269 (setq entry (replace-match "" t t entry))))
270 (if (and attrvalue
271 (setq attrvalue (diary-attrtype-convert attrvalue type)))
272 (setq ret-attr (append ret-attr (list attrname attrvalue))))
273 (setq attr-list (cdr attr-list)))))
274 (list entry ret-attr))))
275
276
277 ;; This can be removed once the kill/yank treatment of invisible text
278 ;; (see etc/TODO) is fixed. -- gm
279 (defcustom diary-header-line-flag t
280 "*If non-nil, `simple-diary-display' will show a header line.
281 The format of the header is specified by `diary-header-line-format'."
282 :group 'diary
283 :type 'boolean
284 :version "21.4")
285
286 (defcustom diary-header-line-format
287 '(:eval (calendar-string-spread
288 (list (if selective-display
289 "Selective display active - press \"s\" in calendar \
290 before edit/copy"
291 "Diary"))
292 ?\ (frame-width)))
293 "*Format of the header line displayed by `simple-diary-display'.
294 Only used if `diary-header-line-flag' is non-nil."
295 :group 'diary
296 :type 'sexp
297 :version "21.4")
298
299 (defun list-diary-entries (date number)
300 "Create and display a buffer containing the relevant lines in diary-file.
301 The arguments are DATE and NUMBER; the entries selected are those
302 for NUMBER days starting with date DATE. The other entries are hidden
303 using selective display. If NUMBER is less than 1, this function does nothing.
304
305 Returns a list of all relevant diary entries found, if any, in order by date.
306 The list entries have the form ((month day year) string specifier) where
307 \(month day year) is the date of the entry, string is the entry text, and
308 specifier is the applicability. If the variable `diary-list-include-blanks'
309 is t, this list includes a dummy diary entry consisting of the empty string)
310 for a date with no diary entries.
311
312 After the list is prepared, the hooks `nongregorian-diary-listing-hook',
313 `list-diary-entries-hook', `diary-display-hook', and `diary-hook' are run.
314 These hooks have the following distinct roles:
315
316 `nongregorian-diary-listing-hook' can cull dates from the diary
317 and each included file. Usually used for Hebrew or Islamic
318 diary entries in files. Applied to *each* file.
319
320 `list-diary-entries-hook' adds or manipulates diary entries from
321 external sources. Used, for example, to include diary entries
322 from other files or to sort the diary entries. Invoked *once* only,
323 before the display hook is run.
324
325 `diary-display-hook' does the actual display of information. If this is
326 nil, simple-diary-display will be used. Use add-hook to set this to
327 fancy-diary-display, if desired. If you want no diary display, use
328 add-hook to set this to ignore.
329
330 `diary-hook' is run last. This can be used for an appointment
331 notification function."
332
333 (when (> number 0)
334 (let ((original-date date);; save for possible use in the hooks
335 old-diary-syntax-table
336 diary-entries-list
337 file-glob-attrs
338 (date-string (calendar-date-string date))
339 (d-file (substitute-in-file-name diary-file)))
340 (message "Preparing diary...")
341 (save-excursion
342 (let ((diary-buffer (find-buffer-visiting d-file)))
343 (if (not diary-buffer)
344 (set-buffer (find-file-noselect d-file t))
345 (set-buffer diary-buffer)
346 (or (verify-visited-file-modtime diary-buffer)
347 (revert-buffer t t))))
348 (setq file-glob-attrs (nth 1 (diary-pull-attrs nil "")))
349 (setq selective-display t)
350 (setq selective-display-ellipses nil)
351 (if diary-header-line-flag
352 (setq header-line-format diary-header-line-format))
353 (setq old-diary-syntax-table (syntax-table))
354 (set-syntax-table diary-syntax-table)
355 (unwind-protect
356 (let ((buffer-read-only nil)
357 (diary-modified (buffer-modified-p))
358 (mark (regexp-quote diary-nonmarking-symbol)))
359 ;; First and last characters must be ^M or \n for
360 ;; selective display to work properly
361 (goto-char (1- (point-max)))
362 (if (not (looking-at "\^M\\|\n"))
363 (progn
364 (goto-char (point-max))
365 (insert "\^M")))
366 (goto-char (point-min))
367 (if (not (looking-at "\^M\\|\n"))
368 (insert "\^M"))
369 (subst-char-in-region (point-min) (point-max) ?\n ?\^M t)
370 (calendar-for-loop i from 1 to number do
371 (let ((d diary-date-forms)
372 (month (extract-calendar-month date))
373 (day (extract-calendar-day date))
374 (year (extract-calendar-year date))
375 (entry-found (list-sexp-diary-entries date)))
376 (while d
377 (let*
378 ((date-form (if (equal (car (car d)) 'backup)
379 (cdr (car d))
380 (car d)))
381 (backup (equal (car (car d)) 'backup))
382 (dayname
383 (format "%s\\|%s\\.?"
384 (calendar-day-name date)
385 (calendar-day-name date 'abbrev)))
386 (monthname
387 (format "\\*\\|%s\\|%s\\.?"
388 (calendar-month-name month)
389 (calendar-month-name month 'abbrev)))
390 (month (concat "\\*\\|0*" (int-to-string month)))
391 (day (concat "\\*\\|0*" (int-to-string day)))
392 (year
393 (concat
394 "\\*\\|0*" (int-to-string year)
395 (if abbreviated-calendar-year
396 (concat "\\|" (format "%02d" (% year 100)))
397 "")))
398 (regexp
399 (concat
400 "\\(\\`\\|\^M\\|\n\\)" mark "?\\("
401 (mapconcat 'eval date-form "\\)\\(")
402 "\\)"))
403 (case-fold-search t))
404 (goto-char (point-min))
405 (while (re-search-forward regexp nil t)
406 (if backup (re-search-backward "\\<" nil t))
407 (if (and (or (char-equal (preceding-char) ?\^M)
408 (char-equal (preceding-char) ?\n))
409 (not (looking-at " \\|\^I")))
410 ;; Diary entry that consists only of date.
411 (backward-char 1)
412 ;; Found a nonempty diary entry--make it visible and
413 ;; add it to the list.
414 (setq entry-found t)
415 (let ((entry-start (point))
416 date-start temp)
417 (re-search-backward "\^M\\|\n\\|\\`")
418 (setq date-start (point))
419 (re-search-forward "\^M\\|\n" nil t 2)
420 (while (looking-at " \\|\^I")
421 (re-search-forward "\^M\\|\n" nil t))
422 (backward-char 1)
423 (subst-char-in-region date-start
424 (point) ?\^M ?\n t)
425 (setq entry (buffer-substring entry-start (point))
426 temp (diary-pull-attrs entry file-glob-attrs)
427 entry (nth 0 temp))
428 (add-to-diary-list
429 date
430 entry
431 (buffer-substring
432 (1+ date-start) (1- entry-start))
433 (copy-marker entry-start) (nth 1 temp))))))
434 (setq d (cdr d)))
435 (or entry-found
436 (not diary-list-include-blanks)
437 (setq diary-entries-list
438 (append diary-entries-list
439 (list (list date "" "" "" "")))))
440 (setq date
441 (calendar-gregorian-from-absolute
442 (1+ (calendar-absolute-from-gregorian date))))
443 (setq entry-found nil)))
444 (set-buffer-modified-p diary-modified))
445 (set-syntax-table old-diary-syntax-table))
446 (goto-char (point-min))
447 (run-hooks 'nongregorian-diary-listing-hook
448 'list-diary-entries-hook)
449 (if diary-display-hook
450 (run-hooks 'diary-display-hook)
451 (simple-diary-display))
452 (run-hooks 'diary-hook)
453 diary-entries-list))))
454
455 (defun include-other-diary-files ()
456 "Include the diary entries from other diary files with those of diary-file.
457 This function is suitable for use in `list-diary-entries-hook';
458 it enables you to use shared diary files together with your own.
459 The files included are specified in the diaryfile by lines of this form:
460 #include \"filename\"
461 This is recursive; that is, #include directives in diary files thus included
462 are obeyed. You can change the `#include' to some other string by
463 changing the variable `diary-include-string'."
464 (goto-char (point-min))
465 (while (re-search-forward
466 (concat
467 "\\(\\`\\|\^M\\|\n\\)"
468 (regexp-quote diary-include-string)
469 " \"\\([^\"]*\\)\"")
470 nil t)
471 (let* ((diary-file (substitute-in-file-name
472 (buffer-substring-no-properties
473 (match-beginning 2) (match-end 2))))
474 (diary-list-include-blanks nil)
475 (list-diary-entries-hook 'include-other-diary-files)
476 (diary-display-hook 'ignore)
477 (diary-hook nil)
478 (d-buffer (find-buffer-visiting diary-file))
479 (diary-modified (if d-buffer
480 (save-excursion
481 (set-buffer d-buffer)
482 (buffer-modified-p)))))
483 (if (file-exists-p diary-file)
484 (if (file-readable-p diary-file)
485 (unwind-protect
486 (setq diary-entries-list
487 (append diary-entries-list
488 (list-diary-entries original-date number)))
489 (save-excursion
490 (set-buffer (find-buffer-visiting diary-file))
491 (let ((inhibit-read-only t))
492 (subst-char-in-region (point-min) (point-max) ?\^M ?\n t))
493 (setq selective-display nil)
494 (set-buffer-modified-p diary-modified)))
495 (beep)
496 (message "Can't read included diary file %s" diary-file)
497 (sleep-for 2))
498 (beep)
499 (message "Can't find included diary file %s" diary-file)
500 (sleep-for 2))))
501 (goto-char (point-min)))
502
503 (defun simple-diary-display ()
504 "Display the diary buffer if there are any relevant entries or holidays."
505 (let* ((holiday-list (if holidays-in-diary-buffer
506 (check-calendar-holidays original-date)))
507 (hol-string (format "%s%s%s"
508 date-string
509 (if holiday-list ": " "")
510 (mapconcat 'identity holiday-list "; ")))
511 (msg (format "No diary entries for %s" hol-string))
512 ;; If selected window is dedicated (to the calendar),
513 ;; need a new one to display the diary.
514 (pop-up-frames (window-dedicated-p (selected-window))))
515 (calendar-set-mode-line (format "Diary for %s" hol-string))
516 (if (or (not diary-entries-list)
517 (and (not (cdr diary-entries-list))
518 (string-equal (car (cdr (car diary-entries-list))) "")))
519 (if (< (length msg) (frame-width))
520 (message "%s" msg)
521 (set-buffer (get-buffer-create holiday-buffer))
522 (setq buffer-read-only nil)
523 (calendar-set-mode-line date-string)
524 (erase-buffer)
525 (insert (mapconcat 'identity holiday-list "\n"))
526 (goto-char (point-min))
527 (set-buffer-modified-p nil)
528 (setq buffer-read-only t)
529 (display-buffer holiday-buffer)
530 (message "No diary entries for %s" date-string))
531 (display-buffer (find-buffer-visiting
532 (substitute-in-file-name diary-file)))
533 (message "Preparing diary...done"))))
534
535 (defface diary-button-face '((((type pc) (class color))
536 (:foreground "lightblue")))
537 "Default face used for buttons."
538 :version "21.4"
539 :group 'diary)
540
541 (define-button-type 'diary-entry
542 'action #'diary-goto-entry
543 'face #'diary-button-face)
544
545 (defun diary-goto-entry (button)
546 (let ((marker (button-get button 'marker)))
547 (when marker
548 (pop-to-buffer (marker-buffer marker))
549 (goto-char (marker-position marker)))))
550
551 (defun fancy-diary-display ()
552 "Prepare a diary buffer with relevant entries in a fancy, noneditable form.
553 This function is provided for optional use as the `diary-display-hook'."
554 (save-excursion;; Turn off selective-display in the diary file's buffer.
555 (set-buffer (find-buffer-visiting (substitute-in-file-name diary-file)))
556 (let ((diary-modified (buffer-modified-p)))
557 (subst-char-in-region (point-min) (point-max) ?\^M ?\n t)
558 (setq selective-display nil)
559 (kill-local-variable 'mode-line-format)
560 (set-buffer-modified-p diary-modified)))
561 (if (or (not diary-entries-list)
562 (and (not (cdr diary-entries-list))
563 (string-equal (car (cdr (car diary-entries-list))) "")))
564 (let* ((holiday-list (if holidays-in-diary-buffer
565 (check-calendar-holidays original-date)))
566 (msg (format "No diary entries for %s %s"
567 (concat date-string (if holiday-list ":" ""))
568 (mapconcat 'identity holiday-list "; "))))
569 (if (<= (length msg) (frame-width))
570 (message "%s" msg)
571 (set-buffer (get-buffer-create holiday-buffer))
572 (setq buffer-read-only nil)
573 (erase-buffer)
574 (insert (mapconcat 'identity holiday-list "\n"))
575 (goto-char (point-min))
576 (set-buffer-modified-p nil)
577 (setq buffer-read-only t)
578 (display-buffer holiday-buffer)
579 (message "No diary entries for %s" date-string)))
580 (save-excursion;; Prepare the fancy diary buffer.
581 (set-buffer (make-fancy-diary-buffer))
582 (setq buffer-read-only nil)
583 (let ((entry-list diary-entries-list)
584 (holiday-list)
585 (holiday-list-last-month 1)
586 (holiday-list-last-year 1)
587 (date (list 0 0 0)))
588 (while entry-list
589 (if (not (calendar-date-equal date (car (car entry-list))))
590 (progn
591 (setq date (car (car entry-list)))
592 (and holidays-in-diary-buffer
593 (calendar-date-compare
594 (list (list holiday-list-last-month
595 (calendar-last-day-of-month
596 holiday-list-last-month
597 holiday-list-last-year)
598 holiday-list-last-year))
599 (list date))
600 ;; We need to get the holidays for the next 3 months.
601 (setq holiday-list-last-month
602 (extract-calendar-month date))
603 (setq holiday-list-last-year
604 (extract-calendar-year date))
605 (progn
606 (increment-calendar-month
607 holiday-list-last-month holiday-list-last-year 1)
608 t)
609 (setq holiday-list
610 (let ((displayed-month holiday-list-last-month)
611 (displayed-year holiday-list-last-year))
612 (calendar-holiday-list)))
613 (increment-calendar-month
614 holiday-list-last-month holiday-list-last-year 1))
615 (let* ((date-string (calendar-date-string date))
616 (date-holiday-list
617 (let ((h holiday-list)
618 (d))
619 ;; Make a list of all holidays for date.
620 (while h
621 (if (calendar-date-equal date (car (car h)))
622 (setq d (append d (cdr (car h)))))
623 (setq h (cdr h)))
624 d)))
625 (insert (if (= (point) (point-min)) "" ?\n) date-string)
626 (if date-holiday-list (insert ": "))
627 (let* ((l (current-column))
628 (longest 0))
629 (insert (mapconcat (lambda (x)
630 (if (< longest (length x))
631 (setq longest (length x)))
632 x)
633 date-holiday-list
634 (concat "\n" (make-string l ? ))))
635 (insert ?\n (make-string (+ l longest) ?=) ?\n)))))
636
637 (setq entry (car (cdr (car entry-list))))
638 (if (< 0 (length entry))
639 (progn
640 (if (nth 3 (car entry-list))
641 (insert-button (concat entry "\n")
642 'marker (nth 3 (car entry-list))
643 :type 'diary-entry)
644 (insert entry ?\n))
645 (save-excursion
646 (let* ((marks (nth 4 (car entry-list)))
647 (temp-face (make-symbol
648 (apply
649 'concat "temp-face-"
650 (mapcar '(lambda (sym)
651 (if (stringp sym)
652 sym
653 (symbol-name sym)))
654 marks))))
655 (faceinfo marks))
656 (make-face temp-face)
657 ;; Remove :face info from the marks,
658 ;; copy the face info into temp-face
659 (while (setq faceinfo (memq :face faceinfo))
660 (copy-face (read (nth 1 faceinfo)) temp-face)
661 (setcar faceinfo nil)
662 (setcar (cdr faceinfo) nil))
663 (setq marks (delq nil marks))
664 ;; Apply the font aspects
665 (apply 'set-face-attribute temp-face nil marks)
666 (search-backward entry)
667 (overlay-put
668 (make-overlay (match-beginning 0) (match-end 0))
669 'face temp-face)))))
670 (setq entry-list (cdr entry-list))))
671 (set-buffer-modified-p nil)
672 (goto-char (point-min))
673 (setq buffer-read-only t)
674 (display-buffer fancy-diary-buffer)
675 (fancy-diary-display-mode)
676 (calendar-set-mode-line date-string)
677 (message "Preparing diary...done"))))
678
679 (defun make-fancy-diary-buffer ()
680 "Create and return the initial fancy diary buffer."
681 (save-excursion
682 (set-buffer (get-buffer-create fancy-diary-buffer))
683 (setq buffer-read-only nil)
684 (calendar-set-mode-line "Diary Entries")
685 (erase-buffer)
686 (set-buffer-modified-p nil)
687 (setq buffer-read-only t)
688 (get-buffer fancy-diary-buffer)))
689
690 (defun print-diary-entries ()
691 "Print a hard copy of the diary display.
692
693 If the simple diary display is being used, prepare a temp buffer with the
694 visible lines of the diary buffer, add a heading line composed from the mode
695 line, print the temp buffer, and destroy it.
696
697 If the fancy diary display is being used, just print the buffer.
698
699 The hooks given by the variable `print-diary-entries-hook' are called to do
700 the actual printing."
701 (interactive)
702 (if (bufferp (get-buffer fancy-diary-buffer))
703 (save-excursion
704 (set-buffer (get-buffer fancy-diary-buffer))
705 (run-hooks 'print-diary-entries-hook))
706 (let ((diary-buffer
707 (find-buffer-visiting (substitute-in-file-name diary-file))))
708 (if diary-buffer
709 (let ((temp-buffer (get-buffer-create "*Printable Diary Entries*"))
710 (heading))
711 (save-excursion
712 (set-buffer diary-buffer)
713 (setq heading
714 (if (not (stringp mode-line-format))
715 "All Diary Entries"
716 (string-match "^-*\\([^-].*[^-]\\)-*$" mode-line-format)
717 (substring mode-line-format
718 (match-beginning 1) (match-end 1))))
719 (copy-to-buffer temp-buffer (point-min) (point-max))
720 (set-buffer temp-buffer)
721 (while (re-search-forward "\^M.*$" nil t)
722 (replace-match ""))
723 (goto-char (point-min))
724 (insert heading "\n"
725 (make-string (length heading) ?=) "\n")
726 (run-hooks 'print-diary-entries-hook)
727 (kill-buffer temp-buffer)))
728 (error "You don't have a diary buffer!")))))
729
730 (defun show-all-diary-entries ()
731 "Show all of the diary entries in the diary file.
732 This function gets rid of the selective display of the diary file so that
733 all entries, not just some, are visible. If there is no diary buffer, one
734 is created."
735 (interactive)
736 (let ((d-file (diary-check-diary-file))
737 (pop-up-frames (window-dedicated-p (selected-window))))
738 (save-excursion
739 (set-buffer (or (find-buffer-visiting d-file)
740 (find-file-noselect d-file t)))
741 (let ((buffer-read-only nil)
742 (diary-modified (buffer-modified-p)))
743 (subst-char-in-region (point-min) (point-max) ?\^M ?\n t)
744 (setq selective-display nil
745 mode-line-format default-mode-line-format)
746 (display-buffer (current-buffer))
747 (set-buffer-modified-p diary-modified)))))
748
749 (defcustom diary-mail-addr
750 (if (boundp 'user-mail-address) user-mail-address "")
751 "*Email address that `diary-mail-entries' will send email to."
752 :group 'diary
753 :type 'string
754 :version "20.3")
755
756 (defcustom diary-mail-days 7
757 "*Default number of days for `diary-mail-entries' to check."
758 :group 'diary
759 :type 'integer
760 :version "20.3")
761
762 ;;;###autoload
763 (defun diary-mail-entries (&optional ndays)
764 "Send a mail message showing diary entries for next NDAYS days.
765 If no prefix argument is given, NDAYS is set to `diary-mail-days'.
766 Mail is sent to the address specified by `diary-mail-addr'.
767
768 You can call `diary-mail-entries' every night using an at/cron job.
769 For example, this script will run the program at 2am daily. Since
770 `emacs -batch' does not load your `.emacs' file, you must ensure that
771 all relevant variables are set, as done here.
772
773 #!/bin/sh
774 # diary-rem.sh -- repeatedly run the Emacs diary-reminder
775 emacs -batch \\
776 -eval \"(setq diary-mail-days 3 \\
777 diary-file \\\"/path/to/diary.file\\\" \\
778 european-calendar-style t \\
779 diary-mail-addr \\\"user@host.name\\\" )\" \\
780 -l diary-lib -f diary-mail-entries
781 at -f diary-rem.sh 0200 tomorrow
782
783 You may have to tweak the syntax of the `at' command to suit your
784 system. Alternatively, you can specify a cron entry:
785 0 1 * * * diary-rem.sh
786 to run it every morning at 1am."
787 (interactive "P")
788 (if (string-equal diary-mail-addr "")
789 (error "You must set `diary-mail-addr' to use this command")
790 (let ((diary-display-hook 'fancy-diary-display))
791 (list-diary-entries (calendar-current-date) (or ndays diary-mail-days)))
792 (compose-mail diary-mail-addr
793 (concat "Diary entries generated "
794 (calendar-date-string (calendar-current-date))))
795 (insert
796 (if (get-buffer fancy-diary-buffer)
797 (save-excursion
798 (set-buffer fancy-diary-buffer)
799 (buffer-substring (point-min) (point-max)))
800 "No entries found"))
801 (call-interactively (get mail-user-agent 'sendfunc))))
802
803 (defun diary-name-pattern (string-array &optional abbrev-array paren)
804 "Return a regexp matching the strings in the array STRING-ARRAY.
805 If the optional argument ABBREV-ARRAY is present, then the function
806 `calendar-abbrev-construct' is used to construct abbreviations from the
807 two supplied arrays. The returned regexp will then also match these
808 abbreviations, with or without final `.' characters. If the optional
809 argument PAREN is non-nil, the regexp is surrounded by parentheses."
810 (regexp-opt (append string-array
811 (if abbrev-array
812 (calendar-abbrev-construct abbrev-array
813 string-array))
814 (if abbrev-array
815 (calendar-abbrev-construct abbrev-array
816 string-array
817 'period))
818 nil)
819 paren))
820
821 (defvar marking-diary-entries nil
822 "True during the marking of diary entries, nil otherwise.")
823
824 (defvar marking-diary-entry nil
825 "True during the marking of diary entries, if current entry is marking.")
826
827 (defun mark-diary-entries ()
828 "Mark days in the calendar window that have diary entries.
829 Each entry in the diary file visible in the calendar window is marked.
830 After the entries are marked, the hooks `nongregorian-diary-marking-hook' and
831 `mark-diary-entries-hook' are run."
832 (interactive)
833 (setq mark-diary-entries-in-calendar t)
834 (let ((marking-diary-entries t)
835 file-glob-attrs marks)
836 (save-excursion
837 (set-buffer (find-file-noselect (diary-check-diary-file) t))
838 (message "Marking diary entries...")
839 (setq file-glob-attrs (nth 1 (diary-pull-attrs nil '())))
840 (let ((d diary-date-forms)
841 (old-diary-syntax-table (syntax-table))
842 temp)
843 (set-syntax-table diary-syntax-table)
844 (while d
845 (let* ((date-form (if (equal (car (car d)) 'backup)
846 (cdr (car d))
847 (car d)));; ignore 'backup directive
848 (dayname
849 (diary-name-pattern calendar-day-name-array
850 calendar-day-abbrev-array))
851 (monthname
852 (format "%s\\|\\*"
853 (diary-name-pattern calendar-month-name-array
854 calendar-month-abbrev-array)))
855 (month "[0-9]+\\|\\*")
856 (day "[0-9]+\\|\\*")
857 (year "[0-9]+\\|\\*")
858 (l (length date-form))
859 (d-name-pos (- l (length (memq 'dayname date-form))))
860 (d-name-pos (if (/= l d-name-pos) (+ 2 d-name-pos)))
861 (m-name-pos (- l (length (memq 'monthname date-form))))
862 (m-name-pos (if (/= l m-name-pos) (+ 2 m-name-pos)))
863 (d-pos (- l (length (memq 'day date-form))))
864 (d-pos (if (/= l d-pos) (+ 2 d-pos)))
865 (m-pos (- l (length (memq 'month date-form))))
866 (m-pos (if (/= l m-pos) (+ 2 m-pos)))
867 (y-pos (- l (length (memq 'year date-form))))
868 (y-pos (if (/= l y-pos) (+ 2 y-pos)))
869 (regexp
870 (concat
871 "\\(\\`\\|\^M\\|\n\\)\\("
872 (mapconcat 'eval date-form "\\)\\(")
873 "\\)"))
874 (case-fold-search t))
875 (goto-char (point-min))
876 (while (re-search-forward regexp nil t)
877 (let* ((dd-name
878 (if d-name-pos
879 (buffer-substring-no-properties
880 (match-beginning d-name-pos)
881 (match-end d-name-pos))))
882 (mm-name
883 (if m-name-pos
884 (buffer-substring-no-properties
885 (match-beginning m-name-pos)
886 (match-end m-name-pos))))
887 (mm (string-to-int
888 (if m-pos
889 (buffer-substring-no-properties
890 (match-beginning m-pos)
891 (match-end m-pos))
892 "")))
893 (dd (string-to-int
894 (if d-pos
895 (buffer-substring-no-properties
896 (match-beginning d-pos)
897 (match-end d-pos))
898 "")))
899 (y-str (if y-pos
900 (buffer-substring-no-properties
901 (match-beginning y-pos)
902 (match-end y-pos))))
903 (yy (if (not y-str)
904 0
905 (if (and (= (length y-str) 2)
906 abbreviated-calendar-year)
907 (let* ((current-y
908 (extract-calendar-year
909 (calendar-current-date)))
910 (y (+ (string-to-int y-str)
911 (* 100
912 (/ current-y 100)))))
913 (if (> (- y current-y) 50)
914 (- y 100)
915 (if (> (- current-y y) 50)
916 (+ y 100)
917 y)))
918 (string-to-int y-str))))
919 (save-excursion
920 (setq entry (buffer-substring-no-properties
921 (point) (line-end-position))
922 temp (diary-pull-attrs entry file-glob-attrs)
923 entry (nth 0 temp)
924 marks (nth 1 temp))))
925 (if dd-name
926 (mark-calendar-days-named
927 (cdr (assoc-string
928 dd-name
929 (calendar-make-alist
930 calendar-day-name-array
931 0 nil calendar-day-abbrev-array) t)) marks)
932 (if mm-name
933 (setq mm
934 (if (string-equal mm-name "*") 0
935 (cdr (assoc-string
936 mm-name
937 (calendar-make-alist
938 calendar-month-name-array
939 1 nil calendar-month-abbrev-array) t)))))
940 (mark-calendar-date-pattern mm dd yy marks))))
941 (setq d (cdr d))))
942 (mark-sexp-diary-entries)
943 (run-hooks 'nongregorian-diary-marking-hook
944 'mark-diary-entries-hook)
945 (set-syntax-table old-diary-syntax-table)
946 (message "Marking diary entries...done")))))
947
948 (defun mark-sexp-diary-entries ()
949 "Mark days in the calendar window that have sexp diary entries.
950 Each entry in the diary file (or included files) visible in the calendar window
951 is marked. See the documentation for the function `list-sexp-diary-entries'."
952 (let* ((sexp-mark (regexp-quote sexp-diary-entry-symbol))
953 (s-entry (concat "\\(\\`\\|\^M\\|\n\\)\\("
954 sexp-mark "(\\)\\|\\("
955 (regexp-quote diary-nonmarking-symbol)
956 sexp-mark "(diary-remind\\)"))
957 (file-glob-attrs (nth 1 (diary-pull-attrs nil '())))
958 m y first-date last-date mark file-glob-attrs)
959 (save-excursion
960 (set-buffer calendar-buffer)
961 (setq m displayed-month)
962 (setq y displayed-year))
963 (increment-calendar-month m y -1)
964 (setq first-date
965 (calendar-absolute-from-gregorian (list m 1 y)))
966 (increment-calendar-month m y 2)
967 (setq last-date
968 (calendar-absolute-from-gregorian
969 (list m (calendar-last-day-of-month m y) y)))
970 (goto-char (point-min))
971 (while (re-search-forward s-entry nil t)
972 (setq marking-diary-entry (char-equal (preceding-char) ?\())
973 (re-search-backward "(")
974 (let ((sexp-start (point))
975 sexp entry entry-start line-start marks)
976 (forward-sexp)
977 (setq sexp (buffer-substring-no-properties sexp-start (point)))
978 (save-excursion
979 (re-search-backward "\^M\\|\n\\|\\`")
980 (setq line-start (point)))
981 (forward-char 1)
982 (if (and (or (char-equal (preceding-char) ?\^M)
983 (char-equal (preceding-char) ?\n))
984 (not (looking-at " \\|\^I")))
985 (progn;; Diary entry consists only of the sexp
986 (backward-char 1)
987 (setq entry ""))
988 (setq entry-start (point))
989 ;; Find end of entry
990 (re-search-forward "\^M\\|\n" nil t)
991 (while (looking-at " \\|\^I")
992 (or (re-search-forward "\^M\\|\n" nil t)
993 (re-search-forward "$" nil t)))
994 (if (or (char-equal (preceding-char) ?\^M)
995 (char-equal (preceding-char) ?\n))
996 (backward-char 1))
997 (setq entry (buffer-substring-no-properties entry-start (point)))
998 (while (string-match "[\^M]" entry)
999 (aset entry (match-beginning 0) ?\n )))
1000 (calendar-for-loop date from first-date to last-date do
1001 (if (setq mark (diary-sexp-entry sexp entry
1002 (calendar-gregorian-from-absolute date)))
1003 (progn
1004 (setq marks (diary-pull-attrs entry file-glob-attrs)
1005 marks (nth 1 (diary-pull-attrs entry file-glob-attrs)))
1006 (mark-visible-calendar-date
1007 (calendar-gregorian-from-absolute date)
1008 (if (< 0 (length marks))
1009 marks
1010 (if (consp mark)
1011 (car mark)))))))))))
1012
1013 (defun mark-included-diary-files ()
1014 "Mark the diary entries from other diary files with those of the diary file.
1015 This function is suitable for use as the `mark-diary-entries-hook'; it enables
1016 you to use shared diary files together with your own. The files included are
1017 specified in the diary-file by lines of this form:
1018 #include \"filename\"
1019 This is recursive; that is, #include directives in diary files thus included
1020 are obeyed. You can change the `#include' to some other string by
1021 changing the variable `diary-include-string'."
1022 (goto-char (point-min))
1023 (while (re-search-forward
1024 (concat
1025 "\\(\\`\\|\^M\\|\n\\)"
1026 (regexp-quote diary-include-string)
1027 " \"\\([^\"]*\\)\"")
1028 nil t)
1029 (let ((diary-file (substitute-in-file-name
1030 (buffer-substring-no-properties
1031 (match-beginning 2) (match-end 2))))
1032 (mark-diary-entries-hook 'mark-included-diary-files))
1033 (if (file-exists-p diary-file)
1034 (if (file-readable-p diary-file)
1035 (progn
1036 (mark-diary-entries)
1037 (kill-buffer (find-buffer-visiting diary-file)))
1038 (beep)
1039 (message "Can't read included diary file %s" diary-file)
1040 (sleep-for 2))
1041 (beep)
1042 (message "Can't find included diary file %s" diary-file)
1043 (sleep-for 2))))
1044 (goto-char (point-min)))
1045
1046 (defun mark-calendar-days-named (dayname &optional color)
1047 "Mark all dates in the calendar window that are day DAYNAME of the week.
1048 0 means all Sundays, 1 means all Mondays, and so on."
1049 (save-excursion
1050 (set-buffer calendar-buffer)
1051 (let ((prev-month displayed-month)
1052 (prev-year displayed-year)
1053 (succ-month displayed-month)
1054 (succ-year displayed-year)
1055 (last-day)
1056 (day))
1057 (increment-calendar-month succ-month succ-year 1)
1058 (increment-calendar-month prev-month prev-year -1)
1059 (setq day (calendar-absolute-from-gregorian
1060 (calendar-nth-named-day 1 dayname prev-month prev-year)))
1061 (setq last-day (calendar-absolute-from-gregorian
1062 (calendar-nth-named-day -1 dayname succ-month succ-year)))
1063 (while (<= day last-day)
1064 (mark-visible-calendar-date (calendar-gregorian-from-absolute day) color)
1065 (setq day (+ day 7))))))
1066
1067 (defun mark-calendar-date-pattern (month day year &optional color)
1068 "Mark all dates in the calendar window that conform to MONTH/DAY/YEAR.
1069 A value of 0 in any position is a wildcard."
1070 (save-excursion
1071 (set-buffer calendar-buffer)
1072 (let ((m displayed-month)
1073 (y displayed-year))
1074 (increment-calendar-month m y -1)
1075 (calendar-for-loop i from 0 to 2 do
1076 (mark-calendar-month m y month day year color)
1077 (increment-calendar-month m y 1)))))
1078
1079 (defun mark-calendar-month (month year p-month p-day p-year &optional color)
1080 "Mark dates in the MONTH/YEAR that conform to pattern P-MONTH/P_DAY/P-YEAR.
1081 A value of 0 in any position of the pattern is a wildcard."
1082 (if (or (and (= month p-month)
1083 (or (= p-year 0) (= year p-year)))
1084 (and (= p-month 0)
1085 (or (= p-year 0) (= year p-year))))
1086 (if (= p-day 0)
1087 (calendar-for-loop
1088 i from 1 to (calendar-last-day-of-month month year) do
1089 (mark-visible-calendar-date (list month i year) color))
1090 (mark-visible-calendar-date (list month p-day year) color))))
1091
1092 (defun sort-diary-entries ()
1093 "Sort the list of diary entries by time of day."
1094 (setq diary-entries-list (sort diary-entries-list 'diary-entry-compare)))
1095
1096 (defun diary-entry-compare (e1 e2)
1097 "Returns t if E1 is earlier than E2."
1098 (or (calendar-date-compare e1 e2)
1099 (and (calendar-date-equal (car e1) (car e2))
1100 (let* ((ts1 (cadr e1)) (t1 (diary-entry-time ts1))
1101 (ts2 (cadr e2)) (t2 (diary-entry-time ts2)))
1102 (or (< t1 t2)
1103 (and (= t1 t2)
1104 (string-lessp ts1 ts2)))))))
1105
1106 (defcustom diary-unknown-time
1107 -9999
1108 "*Value returned by diary-entry-time when no time is found.
1109 The default value -9999 causes entries with no recognizable time to be placed
1110 before those with times; 9999 would place entries with no recognizable time
1111 after those with times."
1112 :type 'integer
1113 :group 'diary
1114 :version "20.3")
1115
1116 (defun diary-entry-time (s)
1117 "Return time at the beginning of the string S as a military-style integer.
1118 For example, returns 1325 for 1:25pm.
1119
1120 Returns `diary-unknown-time' (default value -9999) if no time is recognized.
1121 The recognized forms are XXXX, X:XX, or XX:XX (military time), and XXam,
1122 XXAM, XXpm, XXPM, XX:XXam, XX:XXAM XX:XXpm, or XX:XXPM. A period (.) can
1123 be used instead of a colon (:) to separate the hour and minute parts."
1124 (let ((case-fold-search nil))
1125 (cond ((string-match ; Military time
1126 "\\`[ \t\n\\^M]*\\([0-9]?[0-9]\\)[:.]?\\([0-9][0-9]\\)\\(\\>\\|[^ap]\\)"
1127 s)
1128 (+ (* 100 (string-to-int
1129 (substring s (match-beginning 1) (match-end 1))))
1130 (string-to-int (substring s (match-beginning 2) (match-end 2)))))
1131 ((string-match ; Hour only XXam or XXpm
1132 "\\`[ \t\n\\^M]*\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" s)
1133 (+ (* 100 (% (string-to-int
1134 (substring s (match-beginning 1) (match-end 1)))
1135 12))
1136 (if (equal ?a (downcase (aref s (match-beginning 2))))
1137 0 1200)))
1138 ((string-match ; Hour and minute XX:XXam or XX:XXpm
1139 "\\`[ \t\n\\^M]*\\([0-9]?[0-9]\\)[:.]\\([0-9][0-9]\\)\\([ap]\\)m\\>" s)
1140 (+ (* 100 (% (string-to-int
1141 (substring s (match-beginning 1) (match-end 1)))
1142 12))
1143 (string-to-int (substring s (match-beginning 2) (match-end 2)))
1144 (if (equal ?a (downcase (aref s (match-beginning 3))))
1145 0 1200)))
1146 (t diary-unknown-time)))) ; Unrecognizable
1147
1148 ;; Unrecognizable
1149
1150 (defun list-sexp-diary-entries (date)
1151 "Add sexp entries for DATE from the diary file to `diary-entries-list'.
1152 Also, Make them visible in the diary file. Returns t if any entries were
1153 found.
1154
1155 Sexp diary entries must be prefaced by a `sexp-diary-entry-symbol' (normally
1156 `%%'). The form of a sexp diary entry is
1157
1158 %%(SEXP) ENTRY
1159
1160 Both ENTRY and DATE are globally available when the SEXP is evaluated. If the
1161 SEXP yields the value nil, the diary entry does not apply. If it yields a
1162 non-nil value, ENTRY will be taken to apply to DATE; if the non-nil value is a
1163 string, that string will be the diary entry in the fancy diary display.
1164
1165 For example, the following diary entry will apply to the 21st of the month
1166 if it is a weekday and the Friday before if the 21st is on a weekend:
1167
1168 &%%(let ((dayname (calendar-day-of-week date))
1169 (day (extract-calendar-day date)))
1170 (or
1171 (and (= day 21) (memq dayname '(1 2 3 4 5)))
1172 (and (memq day '(19 20)) (= dayname 5)))
1173 ) UIUC pay checks deposited
1174
1175 A number of built-in functions are available for this type of diary entry:
1176
1177 %%(diary-date MONTH DAY YEAR &optional MARK) text
1178 Entry applies if date is MONTH, DAY, YEAR if
1179 `european-calendar-style' is nil, and DAY, MONTH, YEAR if
1180 `european-calendar-style' is t. DAY, MONTH, and YEAR
1181 can be lists of integers, the constant t, or an integer.
1182 The constant t means all values. An optional parameter
1183 MARK specifies a face or single-character string to use
1184 when highlighting the day in the calendar.
1185
1186 %%(diary-float MONTH DAYNAME N &optional DAY MARK) text
1187 Entry will appear on the Nth DAYNAME of MONTH.
1188 (DAYNAME=0 means Sunday, 1 means Monday, and so on;
1189 if N is negative it counts backward from the end of
1190 the month. MONTH can be a list of months, a single
1191 month, or t to specify all months. Optional DAY means
1192 Nth DAYNAME of MONTH on or after/before DAY. DAY defaults
1193 to 1 if N>0 and the last day of the month if N<0. An
1194 optional parameter MARK specifies a face or single-character
1195 string to use when highlighting the day in the calendar.
1196
1197 %%(diary-block M1 D1 Y1 M2 D2 Y2 &optional MARK) text
1198 Entry will appear on dates between M1/D1/Y1 and M2/D2/Y2,
1199 inclusive. (If `european-calendar-style' is t, the
1200 order of the parameters should be changed to D1, M1, Y1,
1201 D2, M2, Y2.) An optional parameter MARK specifies a face
1202 or single-character string to use when highlighting the
1203 day in the calendar.
1204
1205 %%(diary-anniversary MONTH DAY YEAR &optional MARK) text
1206 Entry will appear on anniversary dates of MONTH DAY, YEAR.
1207 (If `european-calendar-style' is t, the order of the
1208 parameters should be changed to DAY, MONTH, YEAR.) Text
1209 can contain %d or %d%s; %d will be replaced by the number
1210 of years since the MONTH DAY, YEAR and %s will be replaced
1211 by the ordinal ending of that number (that is, `st', `nd',
1212 `rd' or `th', as appropriate. The anniversary of February
1213 29 is considered to be March 1 in a non-leap year. An
1214 optional parameter MARK specifies a face or single-character
1215 string to use when highlighting the day in the calendar.
1216
1217 %%(diary-cyclic N MONTH DAY YEAR &optional MARK) text
1218 Entry will appear every N days, starting MONTH DAY, YEAR.
1219 (If `european-calendar-style' is t, the order of the
1220 parameters should be changed to N, DAY, MONTH, YEAR.) Text
1221 can contain %d or %d%s; %d will be replaced by the number
1222 of repetitions since the MONTH DAY, YEAR and %s will
1223 be replaced by the ordinal ending of that number (that is,
1224 `st', `nd', `rd' or `th', as appropriate. An optional
1225 parameter MARK specifies a face or single-character string
1226 to use when highlighting the day in the calendar.
1227
1228 %%(diary-remind SEXP DAYS &optional MARKING) text
1229 Entry is a reminder for diary sexp SEXP. DAYS is either a
1230 single number or a list of numbers indicating the number(s)
1231 of days before the event that the warning(s) should occur.
1232 If the current date is (one of) DAYS before the event
1233 indicated by EXPR, then a suitable message (as specified
1234 by `diary-remind-message') appears. In addition to the
1235 reminders beforehand, the diary entry also appears on
1236 the date itself. If optional MARKING is non-nil then the
1237 *reminders* are marked on the calendar. Marking of
1238 reminders is independent of whether the entry *itself* is
1239 a marking or nonmarking one.
1240
1241 %%(diary-day-of-year)
1242 Diary entries giving the day of the year and the number of
1243 days remaining in the year will be made every day. Note
1244 that since there is no text, it makes sense only if the
1245 fancy diary display is used.
1246
1247 %%(diary-iso-date)
1248 Diary entries giving the corresponding ISO commercial date
1249 will be made every day. Note that since there is no text,
1250 it makes sense only if the fancy diary display is used.
1251
1252 %%(diary-french-date)
1253 Diary entries giving the corresponding French Revolutionary
1254 date will be made every day. Note that since there is no
1255 text, it makes sense only if the fancy diary display is used.
1256
1257 %%(diary-islamic-date)
1258 Diary entries giving the corresponding Islamic date will be
1259 made every day. Note that since there is no text, it
1260 makes sense only if the fancy diary display is used.
1261
1262 %%(diary-hebrew-date)
1263 Diary entries giving the corresponding Hebrew date will be
1264 made every day. Note that since there is no text, it
1265 makes sense only if the fancy diary display is used.
1266
1267 %%(diary-astro-day-number) Diary entries giving the corresponding
1268 astronomical (Julian) day number will be made every day.
1269 Note that since there is no text, it makes sense only if the
1270 fancy diary display is used.
1271
1272 %%(diary-julian-date) Diary entries giving the corresponding
1273 Julian date will be made every day. Note that since
1274 there is no text, it makes sense only if the fancy diary
1275 display is used.
1276
1277 %%(diary-sunrise-sunset)
1278 Diary entries giving the local times of sunrise and sunset
1279 will be made every day. Note that since there is no text,
1280 it makes sense only if the fancy diary display is used.
1281 Floating point required.
1282
1283 %%(diary-phases-of-moon)
1284 Diary entries giving the times of the phases of the moon
1285 will be when appropriate. Note that since there is no text,
1286 it makes sense only if the fancy diary display is used.
1287 Floating point required.
1288
1289 %%(diary-yahrzeit MONTH DAY YEAR) text
1290 Text is assumed to be the name of the person; the date is
1291 the date of death on the *civil* calendar. The diary entry
1292 will appear on the proper Hebrew-date anniversary and on the
1293 day before. (If `european-calendar-style' is t, the order
1294 of the parameters should be changed to DAY, MONTH, YEAR.)
1295
1296 %%(diary-rosh-hodesh)
1297 Diary entries will be made on the dates of Rosh Hodesh on
1298 the Hebrew calendar. Note that since there is no text, it
1299 makes sense only if the fancy diary display is used.
1300
1301 %%(diary-parasha)
1302 Diary entries giving the weekly parasha will be made on
1303 every Saturday. Note that since there is no text, it
1304 makes sense only if the fancy diary display is used.
1305
1306 %%(diary-omer)
1307 Diary entries giving the omer count will be made every day
1308 from Passover to Shavuot. Note that since there is no text,
1309 it makes sense only if the fancy diary display is used.
1310
1311 Marking these entries is *extremely* time consuming, so these entries are
1312 best if they are nonmarking."
1313 (let ((s-entry (concat "\\(\\`\\|\^M\\|\n\\)"
1314 (regexp-quote diary-nonmarking-symbol)
1315 "?"
1316 (regexp-quote sexp-diary-entry-symbol)
1317 "("))
1318 entry-found file-glob-attrs marks)
1319 (goto-char (point-min))
1320 (save-excursion
1321 (setq file-glob-attrs (nth 1 (diary-pull-attrs nil '()))))
1322 (while (re-search-forward s-entry nil t)
1323 (backward-char 1)
1324 (let ((sexp-start (point))
1325 sexp entry specifier entry-start line-start)
1326 (forward-sexp)
1327 (setq sexp (buffer-substring-no-properties sexp-start (point)))
1328 (save-excursion
1329 (re-search-backward "\^M\\|\n\\|\\`")
1330 (setq line-start (point)))
1331 (setq specifier
1332 (buffer-substring-no-properties (1+ line-start) (point))
1333 entry-start (1+ line-start))
1334 (forward-char 1)
1335 (if (and (or (char-equal (preceding-char) ?\^M)
1336 (char-equal (preceding-char) ?\n))
1337 (not (looking-at " \\|\^I")))
1338 (progn;; Diary entry consists only of the sexp
1339 (backward-char 1)
1340 (setq entry ""))
1341 (setq entry-start (point))
1342 (re-search-forward "\^M\\|\n" nil t)
1343 (while (looking-at " \\|\^I")
1344 (re-search-forward "\^M\\|\n" nil t))
1345 (backward-char 1)
1346 (setq entry (buffer-substring-no-properties entry-start (point)))
1347 (while (string-match "[\^M]" entry)
1348 (aset entry (match-beginning 0) ?\n )))
1349 (let ((diary-entry (diary-sexp-entry sexp entry date))
1350 temp)
1351 (setq entry (if (consp diary-entry)
1352 (cdr diary-entry)
1353 diary-entry))
1354 (if diary-entry
1355 (progn
1356 (subst-char-in-region line-start (point) ?\^M ?\n t)
1357 (if (< 0 (length entry))
1358 (setq temp (diary-pull-attrs entry file-glob-attrs)
1359 entry (nth 0 temp)
1360 marks (nth 1 temp)))))
1361 (add-to-diary-list date
1362 entry
1363 specifier
1364 (if entry-start (copy-marker entry-start)
1365 nil)
1366 marks)
1367 (setq entry-found (or entry-found diary-entry)))))
1368 entry-found))
1369
1370 (defun diary-sexp-entry (sexp entry date)
1371 "Process a SEXP diary ENTRY for DATE."
1372 (let ((result (if calendar-debug-sexp
1373 (let ((stack-trace-on-error t))
1374 (eval (car (read-from-string sexp))))
1375 (condition-case nil
1376 (eval (car (read-from-string sexp)))
1377 (error
1378 (beep)
1379 (message "Bad sexp at line %d in %s: %s"
1380 (save-excursion
1381 (save-restriction
1382 (narrow-to-region 1 (point))
1383 (goto-char (point-min))
1384 (let ((lines 1))
1385 (while (re-search-forward "\n\\|\^M" nil t)
1386 (setq lines (1+ lines)))
1387 lines)))
1388 diary-file sexp)
1389 (sleep-for 2))))))
1390 (cond ((stringp result) result)
1391 ((and (consp result)
1392 (stringp (cdr result))) result)
1393 (result entry)
1394 (t nil))))
1395
1396 (defun diary-date (month day year &optional mark)
1397 "Specific date(s) diary entry.
1398 Entry applies if date is MONTH, DAY, YEAR if `european-calendar-style' is nil,
1399 and DAY, MONTH, YEAR if `european-calendar-style' is t. DAY, MONTH, and YEAR
1400 can be lists of integers, the constant t, or an integer. The constant t means
1401 all values.
1402
1403 An optional parameter MARK specifies a face or single-character string to
1404 use when highlighting the day in the calendar."
1405 (let ((dd (if european-calendar-style
1406 month
1407 day))
1408 (mm (if european-calendar-style
1409 day
1410 month))
1411 (m (extract-calendar-month date))
1412 (y (extract-calendar-year date))
1413 (d (extract-calendar-day date)))
1414 (if (and
1415 (or (and (listp dd) (memq d dd))
1416 (equal d dd)
1417 (eq dd t))
1418 (or (and (listp mm) (memq m mm))
1419 (equal m mm)
1420 (eq mm t))
1421 (or (and (listp year) (memq y year))
1422 (equal y year)
1423 (eq year t)))
1424 (cons mark entry))))
1425
1426 (defun diary-block (m1 d1 y1 m2 d2 y2 &optional mark)
1427 "Block diary entry.
1428 Entry applies if date is between, or on one of, two dates.
1429 The order of the parameters is
1430 M1, D1, Y1, M2, D2, Y2 if `european-calendar-style' is nil, and
1431 D1, M1, Y1, D2, M2, Y2 if `european-calendar-style' is t.
1432
1433 An optional parameter MARK specifies a face or single-character string to
1434 use when highlighting the day in the calendar."
1435
1436 (let ((date1 (calendar-absolute-from-gregorian
1437 (if european-calendar-style
1438 (list d1 m1 y1)
1439 (list m1 d1 y1))))
1440 (date2 (calendar-absolute-from-gregorian
1441 (if european-calendar-style
1442 (list d2 m2 y2)
1443 (list m2 d2 y2))))
1444 (d (calendar-absolute-from-gregorian date)))
1445 (if (and (<= date1 d) (<= d date2))
1446 (cons mark entry))))
1447
1448 (defun diary-float (month dayname n &optional day mark)
1449 "Floating diary entry--entry applies if date is the nth dayname of month.
1450 Parameters are MONTH, DAYNAME, N. MONTH can be a list of months, the constant
1451 t, or an integer. The constant t means all months. If N is negative, count
1452 backward from the end of the month.
1453
1454 An optional parameter DAY means the Nth DAYNAME on or after/before MONTH DAY.
1455 Optional MARK specifies a face or single-character string to use when
1456 highlighting the day in the calendar."
1457 ;; This is messy because the diary entry may apply, but the date on which it
1458 ;; is based can be in a different month/year. For example, asking for the
1459 ;; first Monday after December 30. For large values of |n| the problem is
1460 ;; more grotesque.
1461 (and (= dayname (calendar-day-of-week date))
1462 (let* ((m (extract-calendar-month date))
1463 (d (extract-calendar-day date))
1464 (y (extract-calendar-year date))
1465 (limit; last (n>0) or first (n<0) possible base date for entry
1466 (calendar-nth-named-absday (- n) dayname m y d))
1467 (last-abs (if (> n 0) limit (+ limit 6)))
1468 (first-abs (if (> n 0) (- limit 6) limit))
1469 (last (calendar-gregorian-from-absolute last-abs))
1470 (first (calendar-gregorian-from-absolute first-abs))
1471 ; m1, d1 is first possible base date
1472 (m1 (extract-calendar-month first))
1473 (d1 (extract-calendar-day first))
1474 (y1 (extract-calendar-year first))
1475 ; m2, d2 is last possible base date
1476 (m2 (extract-calendar-month last))
1477 (d2 (extract-calendar-day last))
1478 (y2 (extract-calendar-year last)))
1479 (if (or (and (= m1 m2) ; only possible base dates in one month
1480 (or (eq month t)
1481 (if (listp month)
1482 (memq m1 month)
1483 (= m1 month)))
1484 (let ((d (or day (if (> n 0)
1485 1
1486 (calendar-last-day-of-month m1 y1)))))
1487 (and (<= d1 d) (<= d d2))))
1488 ;; only possible base dates straddle two months
1489 (and (or (< y1 y2)
1490 (and (= y1 y2) (< m1 m2)))
1491 (or
1492 ;; m1, d1 works as a base date
1493 (and
1494 (or (eq month t)
1495 (if (listp month)
1496 (memq m1 month)
1497 (= m1 month)))
1498 (<= d1 (or day (if (> n 0)
1499 1
1500 (calendar-last-day-of-month m1 y1)))))
1501 ;; m2, d2 works as a base date
1502 (and (or (eq month t)
1503 (if (listp month)
1504 (memq m2 month)
1505 (= m2 month)))
1506 (<= (or day (if (> n 0)
1507 1
1508 (calendar-last-day-of-month m2 y2)))
1509 d2)))))
1510 (cons mark entry)))))
1511
1512
1513 (defun diary-anniversary (month day year &optional mark)
1514 "Anniversary diary entry.
1515 Entry applies if date is the anniversary of MONTH, DAY, YEAR if
1516 `european-calendar-style' is nil, and DAY, MONTH, YEAR if
1517 `european-calendar-style' is t. Diary entry can contain `%d' or `%d%s'; the
1518 %d will be replaced by the number of years since the MONTH DAY, YEAR and the
1519 %s will be replaced by the ordinal ending of that number (that is, `st', `nd',
1520 `rd' or `th', as appropriate. The anniversary of February 29 is considered
1521 to be March 1 in non-leap years.
1522
1523 An optional parameter MARK specifies a face or single-character string to
1524 use when highlighting the day in the calendar."
1525 (let* ((d (if european-calendar-style
1526 month
1527 day))
1528 (m (if european-calendar-style
1529 day
1530 month))
1531 (y (extract-calendar-year date))
1532 (diff (- y year)))
1533 (if (and (= m 2) (= d 29) (not (calendar-leap-year-p y)))
1534 (setq m 3
1535 d 1))
1536 (if (and (> diff 0) (calendar-date-equal (list m d y) date))
1537 (cons mark (format entry diff (diary-ordinal-suffix diff))))))
1538
1539 (defun diary-cyclic (n month day year &optional mark)
1540 "Cycle diary entry--entry applies every N days starting at MONTH, DAY, YEAR.
1541 If `european-calendar-style' is t, parameters are N, DAY, MONTH, YEAR.
1542 ENTRY can contain `%d' or `%d%s'; the %d will be replaced by the number of
1543 repetitions since the MONTH DAY, YEAR and %s will be replaced by the
1544 ordinal ending of that number (that is, `st', `nd', `rd' or `th', as
1545 appropriate.
1546
1547 An optional parameter MARK specifies a face or single-character string to
1548 use when highlighting the day in the calendar."
1549 (let* ((d (if european-calendar-style
1550 month
1551 day))
1552 (m (if european-calendar-style
1553 day
1554 month))
1555 (diff (- (calendar-absolute-from-gregorian date)
1556 (calendar-absolute-from-gregorian
1557 (list m d year))))
1558 (cycle (/ diff n)))
1559 (if (and (>= diff 0) (zerop (% diff n)))
1560 (cons mark (format entry cycle (diary-ordinal-suffix cycle))))))
1561
1562 (defun diary-ordinal-suffix (n)
1563 "Ordinal suffix for N. (That is, `st', `nd', `rd', or `th', as appropriate.)"
1564 (if (or (memq (% n 100) '(11 12 13))
1565 (< 3 (% n 10)))
1566 "th"
1567 (aref ["th" "st" "nd" "rd"] (% n 10))))
1568
1569 (defun diary-day-of-year ()
1570 "Day of year and number of days remaining in the year of date diary entry."
1571 (calendar-day-of-year-string date))
1572
1573 (defcustom diary-remind-message
1574 '("Reminder: Only "
1575 (if (= 0 (% days 7))
1576 (concat (int-to-string (/ days 7)) (if (= 7 days) " week" " weeks"))
1577 (concat (int-to-string days) (if (= 1 days) " day" " days")))
1578 " until "
1579 diary-entry)
1580 "*Pseudo-pattern giving form of reminder messages in the fancy diary
1581 display.
1582
1583 Used by the function `diary-remind', a pseudo-pattern is a list of
1584 expressions that can involve the keywords `days' (a number), `date' (a list of
1585 month, day, year), and `diary-entry' (a string)."
1586 :type 'sexp
1587 :group 'diary)
1588
1589 (defun diary-remind (sexp days &optional marking)
1590 "Provide a reminder of a diary entry.
1591 SEXP is a diary-sexp. DAYS is either a single number or a list of numbers
1592 indicating the number(s) of days before the event that the warning(s) should
1593 occur on. If the current date is (one of) DAYS before the event indicated by
1594 SEXP, then a suitable message (as specified by `diary-remind-message' is
1595 returned.
1596
1597 In addition to the reminders beforehand, the diary entry also appears on the
1598 date itself.
1599
1600 A `diary-nonmarking-symbol' at the beginning of the line of the diary-remind
1601 entry specifies that the diary entry (not the reminder) is non-marking.
1602 Marking of reminders is independent of whether the entry itself is a marking
1603 or nonmarking; if optional parameter MARKING is non-nil then the reminders are
1604 marked on the calendar."
1605 (let ((diary-entry (eval sexp)))
1606 (cond
1607 ;; Diary entry applies on date
1608 ((and diary-entry
1609 (or (not marking-diary-entries) marking-diary-entry))
1610 diary-entry)
1611 ;; Diary entry may apply to `days' before date
1612 ((and (integerp days)
1613 (not diary-entry); Diary entry does not apply to date
1614 (or (not marking-diary-entries) marking))
1615 (let ((date (calendar-gregorian-from-absolute
1616 (+ (calendar-absolute-from-gregorian date) days))))
1617 (if (setq diary-entry (eval sexp))
1618 (mapconcat 'eval diary-remind-message ""))))
1619 ;; Diary entry may apply to one of a list of days before date
1620 ((and (listp days) days)
1621 (or (diary-remind sexp (car days) marking)
1622 (diary-remind sexp (cdr days) marking))))))
1623
1624 (defun add-to-diary-list (date string specifier marker &optional globcolor)
1625 "Add the entry (DATE STRING SPECIFIER MARKER GLOBCOLOR) to `diary-entries-list'.
1626 Do nothing if DATE or STRING is nil."
1627 (when (and date string)
1628 (if diary-file-name-prefix
1629 (let ((prefix (funcall diary-file-name-prefix-function
1630 (buffer-file-name))))
1631 (or (string= prefix "")
1632 (setq string (format "[%s] %s" prefix string)))))
1633 (setq diary-entries-list
1634 (append diary-entries-list
1635 (list (list date string specifier marker globcolor))))))
1636
1637 (defun make-diary-entry (string &optional nonmarking file)
1638 "Insert a diary entry STRING which may be NONMARKING in FILE.
1639 If omitted, NONMARKING defaults to nil and FILE defaults to `diary-file'."
1640 (let ((pop-up-frames (window-dedicated-p (selected-window))))
1641 (find-file-other-window (substitute-in-file-name (or file diary-file))))
1642 (widen)
1643 (goto-char (point-max))
1644 (when (let ((case-fold-search t))
1645 (search-backward "Local Variables:"
1646 (max (- (point-max) 3000) (point-min))
1647 t))
1648 (beginning-of-line)
1649 (insert "\n")
1650 (previous-line 1))
1651 (insert
1652 (if (bolp) "" "\n")
1653 (if nonmarking diary-nonmarking-symbol "")
1654 string " "))
1655
1656 (defun insert-diary-entry (arg)
1657 "Insert a diary entry for the date indicated by point.
1658 Prefix arg will make the entry nonmarking."
1659 (interactive "P")
1660 (make-diary-entry (calendar-date-string (calendar-cursor-to-date t) t t)
1661 arg))
1662
1663 (defun insert-weekly-diary-entry (arg)
1664 "Insert a weekly diary entry for the day of the week indicated by point.
1665 Prefix arg will make the entry nonmarking."
1666 (interactive "P")
1667 (make-diary-entry (calendar-day-name (calendar-cursor-to-date t))
1668 arg))
1669
1670 (defun insert-monthly-diary-entry (arg)
1671 "Insert a monthly diary entry for the day of the month indicated by point.
1672 Prefix arg will make the entry nonmarking."
1673 (interactive "P")
1674 (let ((calendar-date-display-form
1675 (if european-calendar-style
1676 '(day " * ")
1677 '("* " day))))
1678 (make-diary-entry (calendar-date-string (calendar-cursor-to-date t) t)
1679 arg)))
1680
1681 (defun insert-yearly-diary-entry (arg)
1682 "Insert an annual diary entry for the day of the year indicated by point.
1683 Prefix arg will make the entry nonmarking."
1684 (interactive "P")
1685 (let ((calendar-date-display-form
1686 (if european-calendar-style
1687 '(day " " monthname)
1688 '(monthname " " day))))
1689 (make-diary-entry (calendar-date-string (calendar-cursor-to-date t) t)
1690 arg)))
1691
1692 (defun insert-anniversary-diary-entry (arg)
1693 "Insert an anniversary diary entry for the date given by point.
1694 Prefix arg will make the entry nonmarking."
1695 (interactive "P")
1696 (let ((calendar-date-display-form
1697 (if european-calendar-style
1698 '(day " " month " " year)
1699 '(month " " day " " year))))
1700 (make-diary-entry
1701 (format "%s(diary-anniversary %s)"
1702 sexp-diary-entry-symbol
1703 (calendar-date-string (calendar-cursor-to-date t) nil t))
1704 arg)))
1705
1706 (defun insert-block-diary-entry (arg)
1707 "Insert a block diary entry for the days between the point and marked date.
1708 Prefix arg will make the entry nonmarking."
1709 (interactive "P")
1710 (let ((calendar-date-display-form
1711 (if european-calendar-style
1712 '(day " " month " " year)
1713 '(month " " day " " year)))
1714 (cursor (calendar-cursor-to-date t))
1715 (mark (or (car calendar-mark-ring)
1716 (error "No mark set in this buffer")))
1717 start end)
1718 (if (< (calendar-absolute-from-gregorian mark)
1719 (calendar-absolute-from-gregorian cursor))
1720 (setq start mark
1721 end cursor)
1722 (setq start cursor
1723 end mark))
1724 (make-diary-entry
1725 (format "%s(diary-block %s %s)"
1726 sexp-diary-entry-symbol
1727 (calendar-date-string start nil t)
1728 (calendar-date-string end nil t))
1729 arg)))
1730
1731 (defun insert-cyclic-diary-entry (arg)
1732 "Insert a cyclic diary entry starting at the date given by point.
1733 Prefix arg will make the entry nonmarking."
1734 (interactive "P")
1735 (let ((calendar-date-display-form
1736 (if european-calendar-style
1737 '(day " " month " " year)
1738 '(month " " day " " year))))
1739 (make-diary-entry
1740 (format "%s(diary-cyclic %d %s)"
1741 sexp-diary-entry-symbol
1742 (calendar-read "Repeat every how many days: "
1743 (lambda (x) (> x 0)))
1744 (calendar-date-string (calendar-cursor-to-date t) nil t))
1745 arg)))
1746
1747 ;;;###autoload
1748 (define-derived-mode diary-mode fundamental-mode
1749 "Diary"
1750 "Major mode for editing the diary file."
1751 (set (make-local-variable 'font-lock-defaults)
1752 '(diary-font-lock-keywords t)))
1753
1754 (define-derived-mode fancy-diary-display-mode fundamental-mode
1755 "Diary"
1756 "Major mode used while displaying diary entries using Fancy Display."
1757 (set (make-local-variable 'font-lock-defaults)
1758 '(fancy-diary-font-lock-keywords t))
1759 (define-key (current-local-map) "q" 'quit-window))
1760
1761
1762 (defvar fancy-diary-font-lock-keywords
1763 (list
1764 (cons
1765 (concat
1766 (let ((dayname (diary-name-pattern calendar-day-name-array nil t))
1767 (monthname (diary-name-pattern calendar-month-name-array nil t))
1768 (day "[0-9]+")
1769 (month "[0-9]+")
1770 (year "-?[0-9]+"))
1771 (mapconcat 'eval calendar-date-display-form ""))
1772 "\\(\\(: .*\\)\\|\\(\n +.*\\)\\)*\n=+$")
1773 'diary-face)
1774 '("^.*anniversary.*$" . font-lock-keyword-face)
1775 '("^.*birthday.*$" . font-lock-keyword-face)
1776 '("^.*Yahrzeit.*$" . font-lock-reference-face)
1777 '("^\\(Erev \\)?Rosh Hodesh.*" . font-lock-function-name-face)
1778 '("^Day.*omer.*$" . font-lock-builtin-face)
1779 '("^Parashat.*$" . font-lock-comment-face)
1780 '("^[ \t]*[0-9]?[0-9]\\([:.]?[0-9][0-9]\\)?\\(am\\|pm\\|AM\\|PM\\)?\\(-[0-9]?[0-9]\\([:.]?[0-9][0-9]\\)?\\(am\\|pm\\|AM\\|PM\\)?\\)?"
1781 . font-lock-variable-name-face))
1782 "Keywords to highlight in fancy diary display")
1783
1784
1785 (defun font-lock-diary-sexps (limit)
1786 "Recognize sexp diary entry for font-locking."
1787 (if (re-search-forward
1788 (concat "^" (regexp-quote diary-nonmarking-symbol)
1789 "?\\(" (regexp-quote sexp-diary-entry-symbol) "\\)")
1790 limit t)
1791 (condition-case nil
1792 (save-restriction
1793 (narrow-to-region (point-min) limit)
1794 (let ((start (point)))
1795 (forward-sexp 1)
1796 (store-match-data (list start (point)))
1797 t))
1798 (error t))))
1799
1800 (defun font-lock-diary-date-forms (month-array &optional symbol abbrev-array)
1801 "Create font-lock patterns for `diary-date-forms' using MONTH-ARRAY.
1802 If given, optional SYMBOL must be a prefix to entries.
1803 If optional ABBREV-ARRAY is present, the abbreviations constructed
1804 from this array by the function `calendar-abbrev-construct' are
1805 matched (with or without a final `.'), in addition to the full month
1806 names."
1807 (let ((dayname (diary-name-pattern calendar-day-name-array
1808 calendar-day-abbrev-array t))
1809 (monthname (format "\\(%s\\|\\*\\)"
1810 (diary-name-pattern month-array abbrev-array)))
1811 (month "\\([0-9]+\\|\\*\\)")
1812 (day "\\([0-9]+\\|\\*\\)")
1813 (year "-?\\([0-9]+\\|\\*\\)"))
1814 (mapcar '(lambda (x)
1815 (cons
1816 (concat "^" (regexp-quote diary-nonmarking-symbol) "?"
1817 (if symbol (regexp-quote symbol) "") "\\("
1818 (mapconcat 'eval
1819 ;; If backup, omit first item (backup)
1820 ;; and last item (not part of date)
1821 (if (equal (car x) 'backup)
1822 (reverse (cdr (reverse (cdr x))))
1823 x)
1824 "")
1825 ;; With backup, last item is not part of date
1826 (if (equal (car x) 'backup)
1827 (concat "\\)" (eval (car (reverse x))))
1828 "\\)"))
1829 '(1 diary-face)))
1830 diary-date-forms)))
1831
1832 (eval-when-compile (require 'cal-hebrew)
1833 (require 'cal-islam))
1834
1835 (defvar diary-font-lock-keywords
1836 (append
1837 (font-lock-diary-date-forms calendar-month-name-array
1838 nil calendar-month-abbrev-array)
1839 (when (or (memq 'mark-hebrew-diary-entries
1840 nongregorian-diary-marking-hook)
1841 (memq 'list-hebrew-diary-entries
1842 nongregorian-diary-listing-hook))
1843 (require 'cal-hebrew)
1844 (font-lock-diary-date-forms
1845 calendar-hebrew-month-name-array-leap-year
1846 hebrew-diary-entry-symbol))
1847 (when (or (memq 'mark-islamic-diary-entries
1848 nongregorian-diary-marking-hook)
1849 (memq 'list-islamic-diary-entries
1850 nongregorian-diary-listing-hook))
1851 (require 'cal-islam)
1852 (font-lock-diary-date-forms
1853 calendar-islamic-month-name-array
1854 islamic-diary-entry-symbol))
1855 (list
1856 (cons
1857 (concat "^" (regexp-quote diary-include-string) ".*$")
1858 'font-lock-keyword-face)
1859 (cons
1860 (concat "^" (regexp-quote diary-nonmarking-symbol)
1861 "?\\(" (regexp-quote sexp-diary-entry-symbol) "\\)")
1862 '(1 font-lock-reference-face))
1863 (cons
1864 (concat "^" (regexp-quote diary-nonmarking-symbol))
1865 'font-lock-reference-face)
1866 (cons
1867 (concat "^" (regexp-quote diary-nonmarking-symbol)
1868 "?\\(" (regexp-quote hebrew-diary-entry-symbol) "\\)")
1869 '(1 font-lock-reference-face))
1870 (cons
1871 (concat "^" (regexp-quote diary-nonmarking-symbol)
1872 "?\\(" (regexp-quote islamic-diary-entry-symbol) "\\)")
1873 '(1 font-lock-reference-face))
1874 '(font-lock-diary-sexps . font-lock-keyword-face)
1875 '("[0-9]?[0-9]\\([:.]?[0-9][0-9]\\)?\\(am\\|pm\\|AM\\|PM\\)\\(-[0-9]?[0-9]\\([:.]?[0-9][0-9]\\)?\\(am\\|pm\\|AM\\|PM\\)\\)?"
1876 . font-lock-function-name-face)))
1877 "Forms to highlight in diary-mode")
1878
1879
1880 ;; Following code from Dave Love <fx@gnu.org>.
1881 ;; Import Outlook-format appointments from mail messages in Gnus or
1882 ;; Rmail using command `diary-from-outlook'. This, or the specialized
1883 ;; functions `diary-from-outlook-gnus' and `diary-from-outlook-rmail',
1884 ;; could be run from hooks to notice appointments automatically (in
1885 ;; which case they will prompt about adding to the diary). The
1886 ;; message formats recognized are customizable through
1887 ;; `diary-outlook-formats'.
1888
1889 (defcustom diary-outlook-formats
1890 '(
1891 ;; When: 11 October 2001 12:00-14:00 (GMT) Greenwich Mean Time : Dublin, ...
1892 ;; [Current UK format? The timezone is meaningless. Sometimes the
1893 ;; Where is missing.]
1894 ("When: \\([0-9]+ [[:alpha:]]+ [0-9]+\\) \
1895 \\([^ ]+\\) [^\n]+
1896 \[^\n]+
1897 \\(?:Where: \\([^\n]+\\)\n+\\)?
1898 \\*~\\*~\\*~\\*~\\*~\\*~\\*~\\*~\\*~\\*"
1899 . "\\1\n \\2 %s, \\3")
1900 ;; When: Tuesday, April 30, 2002 03:00 PM-03:30 PM (GMT) Greenwich Mean ...
1901 ;; [Old UK format?]
1902 ("^When: [[:alpha:]]+, \\([[:alpha:]]+\\) \\([0-9][0-9]*\\), \\([0-9]\\{4\\}\\) \
1903 \\([^ ]+\\) [^\n]+
1904 \[^\n]+
1905 \\(?:Where: \\([^\n]+\\)\\)?\n+"
1906 . "\\2 \\1 \\3\n \\4 %s, \\5")
1907 (
1908 ;; German format, apparently.
1909 "^Zeit: [^ ]+, +\\([0-9]+\\)\. +\\([[:upper:]][[:lower:]][[:lower:]]\\)[^ ]* +\\([0-9]+\\) +\\([^ ]+\\).*$"
1910 . "\\1 \\2 \\3\n \\4 %s"))
1911 "Alist of regexps matching message text and replacement text.
1912
1913 The regexp must match the start of the message text containing an
1914 appointment, but need not include a leading `^'. If it matches the
1915 current message, a diary entry is made from the corresponding
1916 template. If the template is a string, it should be suitable for
1917 passing to `replace-match', and so will have occurrences of `\\D' to
1918 substitute the match for the Dth subexpression. It must also contain
1919 a single `%s' which will be replaced with the text of the message's
1920 Subject field. Any other `%' characters must be doubled, so that the
1921 template can be passed to `format'.
1922
1923 If the template is actually a function, it is called with the message
1924 body text as argument, and may use `match-string' etc. to make a
1925 template following the rules above."
1926 :type '(alist :key-type (regexp :tag "Regexp matching time/place")
1927 :value-type (choice
1928 (string :tag "Template for entry")
1929 (function :tag "Unary function providing template")))
1930 :version "21.4"
1931 :group 'diary)
1932
1933
1934 ;; Dynamically bound.
1935 (defvar body)
1936 (defvar subject)
1937
1938 (defun diary-from-outlook-internal (&optional test-only)
1939 "Snarf a diary entry from a message assumed to be from MS Outlook.
1940 Assumes `body' is bound to a string comprising the body of the message and
1941 `subject' is bound to a string comprising its subject.
1942 Arg TEST-ONLY non-nil means return non-nil if and only if the
1943 message contains an appointment, don't make a diary entry."
1944 (catch 'finished
1945 (let (format-string)
1946 (dotimes (i (length diary-outlook-formats))
1947 (when (eq 0 (string-match (car (nth i diary-outlook-formats))
1948 body))
1949 (unless test-only
1950 (setq format-string (cdr (nth i diary-outlook-formats)))
1951 (save-excursion
1952 (save-window-excursion
1953 ;; Fixme: References to optional fields in the format
1954 ;; are treated literally, not replaced by the empty
1955 ;; string. I think this is an Emacs bug.
1956 (make-diary-entry
1957 (format (replace-match (if (functionp format-string)
1958 (funcall format-string body)
1959 format-string)
1960 t nil (match-string 0 body))
1961 subject))
1962 (save-buffer))))
1963 (throw 'finished t))))
1964 nil))
1965
1966 (defun diary-from-outlook ()
1967 "Maybe snarf diary entry from current Outlook-generated message.
1968 Currently knows about Gnus and Rmail modes."
1969 (interactive)
1970 (let ((func (cond
1971 ((eq major-mode 'rmail-mode)
1972 #'diary-from-outlook-rmail)
1973 ((memq major-mode '(gnus-summary-mode gnus-article-mode))
1974 #'diary-from-outlook-gnus)
1975 (t (error "Don't know how to snarf in `%s'" major-mode)))))
1976 (if (interactive-p)
1977 (call-interactively func)
1978 (funcall func))))
1979
1980
1981 (defvar gnus-article-mime-handles)
1982 (defvar gnus-article-buffer)
1983
1984 (autoload 'gnus-fetch-field "gnus-util")
1985 (autoload 'gnus-narrow-to-body "gnus")
1986 (autoload 'mm-get-part "mm-decode")
1987
1988 (defun diary-from-outlook-gnus ()
1989 "Maybe snarf diary entry from Outlook-generated message in Gnus.
1990 Add this to `gnus-article-prepare-hook' to notice appointments
1991 automatically."
1992 (interactive)
1993 (with-current-buffer gnus-article-buffer
1994 (let ((subject (gnus-fetch-field "subject"))
1995 (body (if gnus-article-mime-handles
1996 ;; We're multipart. Don't get confused by part
1997 ;; buttons &c. Assume info is in first part.
1998 (mm-get-part (nth 1 gnus-article-mime-handles))
1999 (save-restriction
2000 (gnus-narrow-to-body)
2001 (buffer-string)))))
2002 (when (diary-from-outlook-internal t)
2003 (when (or (interactive-p)
2004 (y-or-n-p "Snarf diary entry? "))
2005 (diary-from-outlook-internal)
2006 (message "Diary entry added"))))))
2007
2008 (custom-add-option 'gnus-article-prepare-hook 'diary-from-outlook-gnus)
2009
2010
2011 (defvar rmail-buffer)
2012
2013 (defun diary-from-outlook-rmail ()
2014 "Maybe snarf diary entry from Outlook-generated message in Rmail."
2015 (interactive)
2016 (with-current-buffer rmail-buffer
2017 (let ((subject (mail-fetch-field "subject"))
2018 (body (buffer-substring (save-excursion
2019 (rfc822-goto-eoh)
2020 (point))
2021 (point-max))))
2022 (when (diary-from-outlook-internal t)
2023 (when (or (interactive-p)
2024 (y-or-n-p "Snarf diary entry? "))
2025 (diary-from-outlook-internal)
2026 (message "Diary entry added"))))))
2027
2028
2029 (provide 'diary-lib)
2030
2031 ;;; arch-tag: 22dd506e-2e33-410d-9ae1-095a0c1b2010
2032 ;;; diary-lib.el ends here