Require loaddef file rather than loading it.
[emacs.git] / lisp / calendar / diary-lib.el
blob75cd12d32106b707e7acc3455f7e0751e4d311a5
1 ;;; diary-lib.el --- diary functions
3 ;; Copyright (C) 1989, 1990, 1992, 1993, 1994, 1995, 2001, 2002, 2003,
4 ;; 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
6 ;; Author: Edward M. Reingold <reingold@cs.uiuc.edu>
7 ;; Maintainer: Glenn Morris <rgm@gnu.org>
8 ;; Keywords: calendar
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 3, or (at your option)
15 ;; any later version.
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
27 ;;; Commentary:
29 ;; See calendar.el.
31 ;;; Code:
33 (require 'calendar)
34 (require 'diary-loaddefs)
36 (defcustom diary-include-string "#include"
37 "The string indicating inclusion of another file of diary entries.
38 See the documentation for the function `include-other-diary-files'."
39 :type 'string
40 :group 'diary)
42 (defcustom diary-list-include-blanks nil
43 "If nil, do not include days with no diary entry in the list of diary entries.
44 Such days will then not be shown in the fancy diary buffer, even if they
45 are holidays."
46 :type 'boolean
47 :group 'diary)
49 (defcustom diary-face 'diary
50 "Face name to use for diary entries."
51 :type 'face
52 :group 'diary)
53 (make-obsolete-variable 'diary-face "customize the face `diary' instead."
54 "23.1")
56 (defface diary-anniversary '((t :inherit font-lock-keyword-face))
57 "Face used for anniversaries in the fancy diary display."
58 :version "22.1"
59 :group 'diary)
61 (defface diary-time '((t :inherit font-lock-variable-name-face))
62 "Face used for times of day in the diary."
63 :version "22.1"
64 :group 'diary)
66 (defface diary-button '((((type pc) (class color))
67 (:foreground "lightblue")))
68 "Default face used for buttons."
69 :version "22.1"
70 :group 'diary)
71 ;; Backward-compatibility alias. FIXME make obsolete.
72 (put 'diary-button-face 'face-alias 'diary-button)
74 ;; Face markup of calendar and diary displays: Any entry line that
75 ;; ends with [foo:value] where foo is a face attribute (except :box
76 ;; :stipple) or with [face:blah] tags, will have these values applied
77 ;; to the calendar and fancy diary displays. These attributes "stack"
78 ;; on calendar displays. File-wide attributes can be defined as
79 ;; follows: the first line matching "^# [tag:value]" defines the value
80 ;; for that particular tag.
81 (defcustom diary-face-attrs
82 '((" *\\[foreground:\\([-a-z]+\\)\\]$" 1 :foreground string)
83 (" *\\[background:\\([-a-z]+\\)\\]$" 1 :background string)
84 (" *\\[width:\\([-a-z]+\\)\\]$" 1 :width symbol)
85 (" *\\[height:\\([.0-9]+\\)\\]$" 1 :height int)
86 (" *\\[weight:\\([-a-z]+\\)\\]$" 1 :weight symbol)
87 (" *\\[slant:\\([-a-z]+\\)\\]$" 1 :slant symbol)
88 (" *\\[underline:\\([-a-z]+\\)\\]$" 1 :underline stringtnil)
89 (" *\\[overline:\\([-a-z]+\\)\\]$" 1 :overline stringtnil)
90 (" *\\[strike-through:\\([-a-z]+\\)\\]$" 1 :strike-through stringtnil)
91 (" *\\[inverse-video:\\([-a-z]+\\)\\]$" 1 :inverse-video tnil)
92 (" *\\[face:\\([-0-9a-z]+\\)\\]$" 1 :face string)
93 (" *\\[font:\\([-a-z0-9]+\\)\\]$" 1 :font string)
94 ;; Unsupported.
95 ;;; (" *\\[box:\\([-a-z]+\\)\\]$" 1 :box)
96 ;;; (" *\\[stipple:\\([-a-z]+\\)\\]$" 1 :stipple)
98 "Alist of (REGEXP SUBEXP ATTRIBUTE TYPE) elements.
99 This is used by `diary-pull-attrs' to fontify certain diary
100 elements. REGEXP is a regular expression to for, and SUBEXP is
101 the numbered sub-expression to extract. `diary-glob-file-regexp-prefix'
102 is pre-pended to REGEXP for file-wide specifiers. ATTRIBUTE
103 specifies which face attribute (e.g. `:foreground') to modify, or
104 that this is a face (`:face') to apply. TYPE is the type of
105 attribute being applied. Available TYPES (see `diary-attrtype-convert')
106 are: `string', `symbol', `int', `tnil',`stringtnil.'"
107 :type '(repeat (list (string :tag "Regular expression")
108 (integer :tag "Sub-expression")
109 (symbol :tag "Attribute (e.g. :foreground)")
110 (choice (const string :tag "A string")
111 (const symbol :tag "A symbol")
112 (const int :tag "An integer")
113 (const tnil :tag "`t' or `nil'")
114 (const stringtnil
115 :tag "A string, `t', or `nil'"))))
116 :group 'diary)
118 (defcustom diary-glob-file-regexp-prefix "^\\#"
119 "Regular expression pre-pended to `diary-face-attrs' for file-wide specifiers."
120 :type 'regexp
121 :group 'diary)
123 (defcustom diary-file-name-prefix nil
124 "Non-nil means prefix each diary entry with the name of the file defining it."
125 :type 'boolean
126 :group 'diary)
128 (defcustom diary-file-name-prefix-function 'identity
129 "The function that will take a diary file name and return the desired prefix."
130 :type 'function
131 :group 'diary)
133 (defcustom sexp-diary-entry-symbol "%%"
134 "The string used to indicate a sexp diary entry in `diary-file'.
135 See the documentation for the function `list-sexp-diary-entries'."
136 :type 'string
137 :group 'diary)
139 (defcustom list-diary-entries-hook nil
140 "List of functions called after diary file is culled for relevant entries.
141 You might wish to add `include-other-diary-files', in which case
142 you will probably also want to add `mark-included-diary-files' to
143 `mark-diary-entries-hook'. For example, you could use
145 (add-hook 'list-diary-entries-hook 'include-other-diary-files)
146 (add-hook 'list-diary-entries-hook 'sort-diary-entries)
147 (add-hook 'diary-display-hook 'fancy-diary-display)
149 in your `.emacs' file to cause the fancy diary buffer to be displayed with
150 diary entries from various included files, each day's entries sorted into
151 lexicographic order."
152 :type 'hook
153 :options '(include-other-diary-files sort-diary-entries)
154 :group 'diary)
156 (defcustom mark-diary-entries-hook nil
157 "List of functions called after marking diary entries in the calendar.
158 You might wish to add `mark-included-diary-files', in which case
159 you will probably also want to add `include-other-diary-files' to
160 `list-diary-entries-hook'."
161 :type 'hook
162 :options '(mark-included-diary-files)
163 :group 'diary)
165 (defcustom nongregorian-diary-listing-hook nil
166 "List of functions called for listing diary file and included files.
167 As the files are processed for diary entries, these functions are used
168 to cull relevant entries. You can use any or all of
169 `list-hebrew-diary-entries', `diary-islamic-list-entries' and
170 `diary-bahai-list-entries'. The documentation for these functions
171 describes the style of such diary entries."
172 :type 'hook
173 :options '(list-hebrew-diary-entries
174 diary-islamic-list-entries
175 diary-bahai-list-entries)
176 :group 'diary)
178 (defcustom nongregorian-diary-marking-hook nil
179 "List of functions called for marking diary file and included files.
180 As the files are processed for diary entries, these functions are used
181 to cull relevant entries. You can use any or all of
182 `mark-hebrew-diary-entries', `diary-islamic-mark-entries' and
183 `bahai-mark-diary-entries'. The documentation for these functions
184 describes the style of such diary entries."
185 :type 'hook
186 :options '(mark-hebrew-diary-entries
187 diary-islamic-mark-entries
188 diary-bahai-mark-entries)
189 :group 'diary)
191 (defcustom print-diary-entries-hook 'lpr-buffer
192 "Run by `print-diary-entries' after preparing a temporary diary buffer.
193 The buffer shows only the diary entries currently visible in the
194 diary buffer. The default just does the printing. Other uses
195 might include, for example, rearranging the lines into order by
196 day and time, saving the buffer instead of deleting it, or
197 changing the function used to do the printing."
198 :type 'hook
199 :group 'diary)
201 (defcustom diary-unknown-time -9999
202 "Value returned by `diary-entry-time' when no time is found.
203 The default value -9999 causes entries with no recognizable time
204 to be placed before those with times; 9999 would place entries
205 with no recognizable time after those with times."
206 :type 'integer
207 :group 'diary
208 :version "20.3")
210 (defcustom diary-mail-addr
211 (or (bound-and-true-p user-mail-address) "")
212 "Email address that `diary-mail-entries' will send email to."
213 :group 'diary
214 :type 'string
215 :version "20.3")
217 (defcustom diary-mail-days 7
218 "Default number of days for `diary-mail-entries' to check."
219 :group 'diary
220 :type 'integer
221 :version "20.3")
223 (defcustom diary-remind-message
224 '("Reminder: Only "
225 (if (zerop (% days 7))
226 (format "%d week%s" (/ days 7) (if (= 7 days) "" "s"))
227 (format "%d day%s" days (if (= 1 days) "" "s")))
228 " until "
229 diary-entry)
230 "Pseudo-pattern giving form of reminder messages in the fancy diary display.
232 Used by the function `diary-remind', a pseudo-pattern is a list of
233 expressions that can involve the keywords `days' (a number), `date' (a list of
234 month, day, year), and `diary-entry' (a string)."
235 :type 'sexp
236 :group 'diary)
238 (defcustom abbreviated-calendar-year t
239 "Interpret a two-digit year DD in a diary entry as either 19DD or 20DD.
240 This applies to the Gregorian, Hebrew, Islamic, and Baha'i calendars.
241 When the current century is added to a two-digit year, if the result
242 is more than 50 years in the future, the previous century is assumed.
243 If the result is more than 50 years in the past, the next century is assumed.
244 If this variable is nil, years must be written in full."
245 :type 'boolean
246 :group 'diary)
248 (defcustom diary-outlook-formats
250 ;; When: 11 October 2001 12:00-14:00 (GMT) Greenwich Mean Time : Dublin, ...
251 ;; [Current UK format? The timezone is meaningless. Sometimes the
252 ;; Where is missing.]
253 ("When: \\([0-9]+ [[:alpha:]]+ [0-9]+\\) \
254 \\([^ ]+\\) [^\n]+
255 \[^\n]+
256 \\(?:Where: \\([^\n]+\\)\n+\\)?
257 \\*~\\*~\\*~\\*~\\*~\\*~\\*~\\*~\\*~\\*"
258 . "\\1\n \\2 %s, \\3")
259 ;; When: Tuesday, April 30, 2002 03:00 PM-03:30 PM (GMT) Greenwich Mean ...
260 ;; [Old UK format?]
261 ("^When: [[:alpha:]]+, \\([[:alpha:]]+\\) \\([0-9][0-9]*\\), \\([0-9]\\{4\\}\\) \
262 \\([^ ]+\\) [^\n]+
263 \[^\n]+
264 \\(?:Where: \\([^\n]+\\)\\)?\n+"
265 . "\\2 \\1 \\3\n \\4 %s, \\5")
267 ;; German format, apparently.
268 "^Zeit: [^ ]+, +\\([0-9]+\\)\. +\\([[:upper:]][[:lower:]][[:lower:]]\\)[^ ]* +\\([0-9]+\\) +\\([^ ]+\\).*$"
269 . "\\1 \\2 \\3\n \\4 %s"))
270 "Alist of regexps matching message text and replacement text.
272 The regexp must match the start of the message text containing an
273 appointment, but need not include a leading `^'. If it matches the
274 current message, a diary entry is made from the corresponding
275 template. If the template is a string, it should be suitable for
276 passing to `replace-match', and so will have occurrences of `\\D' to
277 substitute the match for the Dth subexpression. It must also contain
278 a single `%s' which will be replaced with the text of the message's
279 Subject field. Any other `%' characters must be doubled, so that the
280 template can be passed to `format'.
282 If the template is actually a function, it is called with the message
283 body text as argument, and may use `match-string' etc. to make a
284 template following the rules above."
285 :type '(alist :key-type (regexp :tag "Regexp matching time/place")
286 :value-type (choice
287 (string :tag "Template for entry")
288 (function :tag
289 "Unary function providing template")))
290 :version "22.1"
291 :group 'diary)
293 (defvar diary-header-line-flag)
294 (defvar diary-header-line-format)
296 (defun diary-set-header (symbol value)
297 "Set SYMBOL's value to VALUE, and redraw the diary header if necessary."
298 (let ((oldvalue (symbol-value symbol))
299 (dbuff (and diary-file
300 (find-buffer-visiting
301 (substitute-in-file-name diary-file)))))
302 (custom-set-default symbol value)
303 (and dbuff
304 (not (equal value oldvalue))
305 (with-current-buffer dbuff
306 (if (eq major-mode 'diary-mode)
307 (setq header-line-format (and diary-header-line-flag
308 diary-header-line-format)))))))
310 ;; This can be removed once the kill/yank treatment of invisible text
311 ;; (see etc/TODO) is fixed. -- gm
312 (defcustom diary-header-line-flag t
313 "Non-nil means `simple-diary-display' will show a header line.
314 The format of the header is specified by `diary-header-line-format'."
315 :group 'diary
316 :type 'boolean
317 :initialize 'custom-initialize-default
318 :set 'diary-set-header
319 :version "22.1")
321 (defvar diary-selective-display nil
322 "Internal diary variable; non-nil if some diary text is hidden.")
324 (defcustom diary-header-line-format
325 '(:eval (calendar-string-spread
326 (list (if diary-selective-display
327 "Some text is hidden - press \"s\" in calendar \
328 before edit/copy"
329 "Diary"))
330 ?\s (frame-width)))
331 "Format of the header line displayed by `simple-diary-display'.
332 Only used if `diary-header-line-flag' is non-nil."
333 :group 'diary
334 :type 'sexp
335 :initialize 'custom-initialize-default
336 :set 'diary-set-header
337 :version "22.1")
339 ;; The first version of this also checked for diary-selective-display
340 ;; in the non-fancy case. This was an attempt to distinguish between
341 ;; displaying the diary and just visiting the diary file. However,
342 ;; when using fancy diary, calling diary when there are no entries to
343 ;; display does not create the fancy buffer, nor does it set
344 ;; diary-selective-display in the diary buffer. This means some
345 ;; customizations will not take effect, eg:
346 ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-03/msg00466.html
347 ;; So the check for diary-selective-display was dropped. This means the
348 ;; diary will be displayed if one customizes a diary variable while
349 ;; just visiting the diary-file. This is i) unlikely, and ii) no great loss.
350 ;;;###cal-autoload
351 (defun diary-live-p ()
352 "Return non-nil if the diary is being displayed."
353 (or (get-buffer fancy-diary-buffer)
354 (and diary-file
355 (find-buffer-visiting (substitute-in-file-name diary-file)))))
357 ;;;###cal-autoload
358 (defun diary-set-maybe-redraw (symbol value)
359 "Set SYMBOL's value to VALUE, and redraw the diary if necessary.
360 Redraws the diary if it is being displayed (note this is not the same as
361 just visiting the `diary-file'), and SYMBOL's value is to be changed."
362 (let ((oldvalue (symbol-value symbol)))
363 (custom-set-default symbol value)
364 (and (not (equal value oldvalue))
365 (diary-live-p)
366 ;; Note this assumes diary was called without prefix arg.
367 (diary))))
369 (defcustom number-of-diary-entries 1
370 "Specifies how many days of diary entries are to be displayed initially.
371 This variable affects the diary display when the command \\[diary] is used,
372 or if the value of the variable `view-diary-entries-initially' is non-nil.
373 For example, if the default value 1 is used, then only the current day's diary
374 entries will be displayed. If the value 2 is used, then both the current
375 day's and the next day's entries will be displayed.
377 The value can also be a vector such as [0 2 2 2 2 4 1]; this value
378 says to display no diary entries on Sunday, the entries for
379 the current date and the day after on Monday through Thursday,
380 Friday through Monday's entries on Friday, and only Saturday's
381 entries on Saturday.
383 This variable does not affect the diary display with the `d' command
384 from the calendar; in that case, the prefix argument controls the
385 number of days of diary entries displayed."
386 :type '(choice (integer :tag "Entries")
387 (vector :value [0 0 0 0 0 0 0]
388 (integer :tag "Sunday")
389 (integer :tag "Monday")
390 (integer :tag "Tuesday")
391 (integer :tag "Wednesday")
392 (integer :tag "Thursday")
393 (integer :tag "Friday")
394 (integer :tag "Saturday")))
395 :initialize 'custom-initialize-default
396 :set 'diary-set-maybe-redraw
397 :group 'diary)
399 ;;; More user options in calendar.el.
402 (defun diary-check-diary-file ()
403 "Check that the file specified by `diary-file' exists and is readable.
404 If so, return the expanded file name, otherwise signal an error."
405 (let ((d-file (substitute-in-file-name diary-file)))
406 (if (and d-file (file-exists-p d-file))
407 (if (file-readable-p d-file)
408 d-file
409 (error "Diary file `%s' is not readable" diary-file))
410 (error "Diary file `%s' does not exist" diary-file))))
412 ;;;###autoload
413 (defun diary (&optional arg)
414 "Generate the diary window for ARG days starting with the current date.
415 If no argument is provided, the number of days of diary entries is governed
416 by the variable `number-of-diary-entries'. A value of ARG less than 1
417 does nothing. This function is suitable for execution in a `.emacs' file."
418 (interactive "P")
419 (diary-check-diary-file)
420 (diary-list-entries (calendar-current-date)
421 (if arg (prefix-numeric-value arg))))
423 ;;;###cal-autoload
424 (defun diary-view-entries (&optional arg)
425 "Prepare and display a buffer with diary entries.
426 Searches the file named in `diary-file' for entries that
427 match ARG days starting with the date indicated by the cursor position
428 in the displayed three-month calendar."
429 (interactive "p")
430 (diary-check-diary-file)
431 (diary-list-entries (calendar-cursor-to-date t) arg))
433 (define-obsolete-function-alias 'view-diary-entries 'diary-view-entries)
436 ;;;###cal-autoload
437 (defun view-other-diary-entries (arg dfile)
438 "Prepare and display buffer of diary entries from an alternative diary file.
439 Searches for entries that match ARG days, starting with the date indicated
440 by the cursor position in the displayed three-month calendar.
441 DFILE specifies the file to use as the diary file."
442 (interactive
443 (list (prefix-numeric-value current-prefix-arg)
444 (read-file-name "Enter diary file name: " default-directory nil t)))
445 (let ((diary-file dfile))
446 (diary-view-entries arg)))
448 (defvar diary-syntax-table
449 (let ((st (copy-syntax-table (standard-syntax-table))))
450 (modify-syntax-entry ?* "w" st)
451 (modify-syntax-entry ?: "w" st)
453 "The syntax table used when parsing dates in the diary file.
454 It is the standard syntax table used in Fundamental mode, but with the
455 syntax of `*' and `:' changed to be word constituents.")
457 (defun diary-attrtype-convert (attrvalue type)
458 "Convert string ATTRVALUE to TYPE appropriate for a face description.
459 Valid TYPEs are: string, symbol, int, stringtnil, tnil."
460 (cond ((eq type 'string) attrvalue)
461 ((eq type 'symbol) (intern-soft attrvalue))
462 ((eq type 'int) (string-to-number attrvalue))
463 ((eq type 'stringtnil)
464 (cond ((string-equal "t" attrvalue) t)
465 ((string-equal "nil" attrvalue) nil)
466 (t attrvalue)))
467 ((eq type 'tnil) (string-equal "t" attrvalue))))
469 (defun diary-pull-attrs (entry fileglobattrs)
470 "Search for matches for regexps from `diary-face-attrs'.
471 If ENTRY is nil, searches from the start of the current buffer, and
472 prepends all regexps with `diary-glob-file-regexp-prefix'.
473 If ENTRY is a string, search for matches in that string, and remove them.
474 Returns a list of ENTRY followed by (ATTRIBUTE VALUE) pairs.
475 When ENTRY is non-nil, FILEGLOBATTRS forms the start of the (ATTRIBUTE VALUE)
476 pairs."
477 (let (regexp regnum attrname attrname attrvalue type ret-attr)
478 (if (null entry)
479 (save-excursion
480 (dolist (attr diary-face-attrs)
481 ;; FIXME inefficient searching.
482 (goto-char (point-min))
483 (setq regexp (concat diary-glob-file-regexp-prefix (car attr))
484 regnum (cadr attr)
485 attrname (nth 2 attr)
486 type (nth 3 attr)
487 attrvalue (if (re-search-forward regexp nil t)
488 (match-string-no-properties regnum)))
489 (and attrvalue
490 (setq attrvalue (diary-attrtype-convert attrvalue type))
491 (setq ret-attr (append ret-attr
492 (list attrname attrvalue))))))
493 (setq ret-attr fileglobattrs)
494 (dolist (attr diary-face-attrs)
495 (setq regexp (car attr)
496 regnum (cadr attr)
497 attrname (nth 2 attr)
498 type (nth 3 attr)
499 attrvalue nil)
500 ;; If multiple matches, replace all, use the last (which may
501 ;; be the first instance in the line, if the regexp is
502 ;; anchored with $).
503 (while (string-match regexp entry)
504 (setq attrvalue (match-string-no-properties regnum entry)
505 entry (replace-match "" t t entry)))
506 (and attrvalue
507 (setq attrvalue (diary-attrtype-convert attrvalue type))
508 (setq ret-attr (append ret-attr (list attrname attrvalue))))))
509 (list entry ret-attr)))
513 (defvar diary-modify-entry-list-string-function nil
514 "Function applied to entry string before putting it into the entries list.
515 Can be used by programs integrating a diary list into other buffers (e.g.
516 org.el and planner.el) to modify the string or add properties to it.
517 The function takes a string argument and must return a string.")
519 (defvar diary-entries-list) ; bound in diary-list-entries
521 (defun add-to-diary-list (date string specifier &optional marker
522 globcolor literal)
523 "Add an entry to `diary-entries-list'.
524 Do nothing if DATE or STRING is nil. DATE is the (MONTH DAY
525 YEAR) for which the entry applies; STRING is the text of the
526 entry as it will appear in the diary (i.e. with any format
527 strings such as \"%d\" expanded); SPECIFIER is the date part of
528 the entry as it appears in the diary-file; LITERAL is the entry
529 as it appears in the diary-file (i.e. before expansion). If
530 LITERAL is nil, it is taken to be the same as STRING.
532 The entry is added to the list as (DATE STRING SPECIFIER LOCATOR
533 GLOBCOLOR), where LOCATOR has the form (MARKER FILENAME LITERAL),
534 FILENAME being the file containing the diary entry."
535 (when (and date string)
536 (if diary-file-name-prefix
537 (let ((prefix (funcall diary-file-name-prefix-function
538 (buffer-file-name))))
539 (or (string-equal prefix "")
540 (setq string (format "[%s] %s" prefix string)))))
541 (and diary-modify-entry-list-string-function
542 (setq string (funcall diary-modify-entry-list-string-function
543 string)))
544 (setq diary-entries-list
545 (append diary-entries-list
546 (list (list date string specifier
547 (list marker (buffer-file-name) literal)
548 globcolor))))))
550 (defun diary-list-entries-2 (date mark globattr list-only
551 &optional months symbol)
552 "Internal subroutine of `diary-list-entries'.
553 Find diary entries applying to DATE, by searching from point-min for
554 each element of `diary-date-forms'. MARK indicates an entry is non-marking.
555 GLOBATTR is the list of global file attributes. If LIST-ONLY is
556 non-nil, don't change the buffer, only return a list of entries.
557 Optional array MONTHS replaces `calendar-month-name-array', and
558 means months cannot be abbreviated. Optional string SYMBOL marks diary
559 entries of the desired type. Returns non-nil if any entries were found."
560 (let* ((month (extract-calendar-month date))
561 (day (extract-calendar-day date))
562 (year (extract-calendar-year date))
563 (dayname (format "%s\\|%s\\.?" (calendar-day-name date)
564 (calendar-day-name date 'abbrev)))
565 (calendar-month-name-array (or months calendar-month-name-array))
566 (monthname (format "\\*\\|%s%s" (calendar-month-name month)
567 (if months ""
568 (format "\\|%s\\.?"
569 (calendar-month-name month 'abbrev)))))
570 (month (format "\\*\\|0*%d" month))
571 (day (format "\\*\\|0*%d" day))
572 (year (format "\\*\\|0*%d%s" year
573 (if abbreviated-calendar-year
574 (format "\\|%02d" (% year 100))
575 "")))
576 (case-fold-search t)
577 entry-found)
578 (dolist (date-form diary-date-forms)
579 (let ((backup (when (eq (car date-form) 'backup)
580 (setq date-form (cdr date-form))
582 ;; date-form uses day etc as set above.
583 (regexp (format "^%s?%s\\(%s\\)" (regexp-quote mark)
584 (if symbol (regexp-quote symbol) "")
585 (mapconcat 'eval date-form "\\)\\(?:")))
586 entry-start date-start temp)
587 (goto-char (point-min))
588 (while (re-search-forward regexp nil t)
589 (if backup (re-search-backward "\\<" nil t))
590 ;; regexp moves us past the end of date, onto the next line.
591 ;; Trailing whitespace after date not allowed (see diary-file).
592 (if (and (bolp) (not (looking-at "[ \t]")))
593 ;; Diary entry that consists only of date.
594 (backward-char 1)
595 ;; Found a nonempty diary entry--make it
596 ;; visible and add it to the list.
597 (setq date-start (line-end-position 0))
598 ;; Actual entry starts on the next-line?
599 (if (looking-at "[ \t]*\n[ \t]") (forward-line 1))
600 (setq entry-found t
601 entry-start (point))
602 (forward-line 1)
603 (while (looking-at "[ \t]") ; continued entry
604 (forward-line 1))
605 (unless (and (eobp) (not (bolp)))
606 (backward-char 1))
607 (unless list-only
608 (remove-overlays date-start (point) 'invisible 'diary))
609 (setq temp (diary-pull-attrs
610 (buffer-substring-no-properties
611 entry-start (point)) globattr))
612 (add-to-diary-list
613 date (car temp)
614 (buffer-substring-no-properties (1+ date-start) (1- entry-start))
615 (copy-marker entry-start) (cadr temp))))))
616 entry-found))
618 (defvar original-date) ; from diary-list-entries
619 (defvar file-glob-attrs)
620 (defvar list-only)
621 (defvar number)
623 (defun diary-list-entries-1 (months symbol absfunc)
624 "List diary entries of a certain type.
625 MONTHS is an array of month names. SYMBOL marks diary entries of the type
626 in question. ABSFUNC is a function that converts absolute dates to dates
627 of the appropriate type."
628 (let ((gdate original-date))
629 (dotimes (idummy number)
630 (diary-list-entries-2
631 (funcall absfunc (calendar-absolute-from-gregorian gdate))
632 diary-nonmarking-symbol file-glob-attrs list-only months symbol)
633 (setq gdate
634 (calendar-gregorian-from-absolute
635 (1+ (calendar-absolute-from-gregorian gdate))))))
636 (goto-char (point-min)))
638 ;; FIXME non-greg and list hooks run same number of times?
639 (define-obsolete-function-alias 'list-diary-entries 'diary-list-entries)
640 (defun diary-list-entries (date number &optional list-only)
641 "Create and display a buffer containing the relevant lines in `diary-file'.
642 The arguments are DATE and NUMBER; the entries selected are those
643 for NUMBER days starting with date DATE. The other entries are hidden
644 using overlays. If NUMBER is less than 1, this function does nothing.
646 Returns a list of all relevant diary entries found, if any, in order by date.
647 The list entries have the form ((MONTH DAY YEAR) STRING SPECIFIER) where
648 \(MONTH DAY YEAR) is the date of the entry, STRING is the entry text, and
649 SPECIFIER is the applicability. If the variable `diary-list-include-blanks'
650 is non-nil, this list includes a dummy diary entry consisting of the empty
651 string for a date with no diary entries.
653 After the list is prepared, the following hooks are run:
655 `nongregorian-diary-listing-hook' can cull dates from the diary
656 and each included file, for example to process Islamic diary
657 entries. Applied to *each* file.
659 `list-diary-entries-hook' adds or manipulates diary entries from
660 external sources. Used, for example, to include diary entries
661 from other files or to sort the diary entries. Invoked *once*
662 only, before the display hook is run.
664 `diary-display-hook' does the actual display of information. If nil,
665 `simple-diary-display' is used. Use `add-hook' to use
666 `fancy-diary-display', if desired, or `ignore' for no display.
668 `diary-hook' is run last. This is used e.g. by `appt-check'.
670 Functions called by these hooks may use the variables ORIGINAL-DATE
671 and NUMBER, which are the arguments with which this function was called.
672 Note that hook functions should _not_ use DATE, but ORIGINAL-DATE.
673 \(Sexp diary entries may use DATE - see `list-sexp-diary-entries'.)
675 If LIST-ONLY is non-nil don't modify or display the buffer, only return a list."
676 (unless number
677 (setq number (if (vectorp number-of-diary-entries)
678 (aref number-of-diary-entries (calendar-day-of-week date))
679 number-of-diary-entries)))
680 (when (> number 0)
681 (let* ((original-date date) ; save for possible use in the hooks
682 (date-string (calendar-date-string date))
683 (d-file (substitute-in-file-name diary-file))
684 (diary-buffer (find-buffer-visiting d-file))
685 diary-entries-list file-glob-attrs)
686 (message "Preparing diary...")
687 (save-excursion
688 (if (not diary-buffer)
689 (set-buffer (find-file-noselect d-file t))
690 (set-buffer diary-buffer)
691 (or (verify-visited-file-modtime diary-buffer)
692 (revert-buffer t t)))
693 ;; Setup things like the header-line-format and invisibility-spec.
694 (if (eq major-mode default-major-mode)
695 (diary-mode)
696 ;; This kludge is to make customizations to
697 ;; diary-header-line-flag after diary has been displayed
698 ;; take effect. Unconditionally calling (diary-mode)
699 ;; clobbers file local variables.
700 ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-03/msg00363.html
701 ;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-04/msg00404.html
702 (if (eq major-mode 'diary-mode)
703 (setq header-line-format (and diary-header-line-flag
704 diary-header-line-format))))
705 ;; d-s-p is passed to the diary display function.
706 (let ((diary-saved-point (point)))
707 (save-excursion
708 (setq file-glob-attrs (cadr (diary-pull-attrs nil "")))
709 (with-syntax-table diary-syntax-table
710 (goto-char (point-min))
711 (unless list-only
712 (let ((ol (make-overlay (point-min) (point-max) nil t nil)))
713 (set (make-local-variable 'diary-selective-display) t)
714 (overlay-put ol 'invisible 'diary)
715 (overlay-put ol 'evaporate t)))
716 (dotimes (idummy number)
717 (let ((sexp-found (list-sexp-diary-entries date))
718 (entry-found (diary-list-entries-2
719 date diary-nonmarking-symbol
720 file-glob-attrs list-only)))
721 (if diary-list-include-blanks
722 (or sexp-found entry-found
723 (add-to-diary-list date "" "" "" "")))
724 (setq date
725 (calendar-gregorian-from-absolute
726 (1+ (calendar-absolute-from-gregorian date)))))))
727 (goto-char (point-min))
728 (run-hooks 'nongregorian-diary-listing-hook
729 'list-diary-entries-hook)
730 (unless list-only
731 (if diary-display-hook
732 (run-hooks 'diary-display-hook)
733 (simple-diary-display)))
734 (run-hooks 'diary-hook)
735 diary-entries-list))))))
737 (defun diary-unhide-everything ()
738 "Show all invisible text in the diary."
739 (kill-local-variable 'diary-selective-display)
740 (remove-overlays (point-min) (point-max) 'invisible 'diary)
741 (kill-local-variable 'mode-line-format))
743 (defvar original-date) ; bound in diary-list-entries
744 (defvar number)
746 (defun include-other-diary-files ()
747 "Include the diary entries from other diary files with those of `diary-file'.
748 This function is suitable for use with `list-diary-entries-hook';
749 it enables you to use shared diary files together with your own.
750 The files included are specified in the `diary-file' by lines of this form:
751 #include \"filename\"
752 This is recursive; that is, #include directives in diary files thus included
753 are obeyed. You can change the `#include' to some other string by
754 changing the variable `diary-include-string'."
755 (goto-char (point-min))
756 (while (re-search-forward
757 (format "^%s \"\\([^\"]*\\)\"" (regexp-quote diary-include-string))
758 nil t)
759 (let ((diary-file (substitute-in-file-name
760 (match-string-no-properties 1)))
761 (diary-list-include-blanks nil)
762 (list-diary-entries-hook 'include-other-diary-files)
763 (diary-display-hook 'ignore)
764 (diary-hook nil))
765 (if (file-exists-p diary-file)
766 (if (file-readable-p diary-file)
767 (unwind-protect
768 (setq diary-entries-list
769 (append diary-entries-list
770 (diary-list-entries original-date number)))
771 (with-current-buffer (find-buffer-visiting diary-file)
772 (diary-unhide-everything)))
773 (beep)
774 (message "Can't read included diary file %s" diary-file)
775 (sleep-for 2))
776 (beep)
777 (message "Can't find included diary file %s" diary-file)
778 (sleep-for 2))))
779 (goto-char (point-min)))
781 (defvar date-string) ; bound in diary-list-entries
783 (defun diary-display-no-entries ()
784 "Common subroutine of `simple-diary-display' and `fancy-diary-display'.
785 Handles the case where there are no diary entries.
786 Returns a cons (NOENTRIES . HOLIDAY-STRING)."
787 (let* ((holiday-list (if holidays-in-diary-buffer
788 (calendar-check-holidays original-date)))
789 (hol-string (format "%s%s%s"
790 date-string
791 (if holiday-list ": " "")
792 (mapconcat 'identity holiday-list "; ")))
793 (msg (format "No diary entries for %s" hol-string))
794 ;; Empty list, or single item with no text.
795 ;; FIXME multiple items with no text?
796 (noentries (or (not diary-entries-list)
797 (and (not (cdr diary-entries-list))
798 (string-equal "" (cadr
799 (car diary-entries-list)))))))
800 ;; Inconsistency: whether or not the holidays are displayed in a
801 ;; separate buffer depends on if there are diary entries.
802 (when noentries
803 (if (or (< (length msg) (frame-width))
804 (not holiday-list))
805 (message "%s" msg)
806 ;; holiday-list which is too wide for a message gets a buffer.
807 (calendar-in-read-only-buffer holiday-buffer
808 (calendar-set-mode-line (format "Holidays for %s" date-string))
809 (insert (mapconcat 'identity holiday-list "\n")))
810 (message "No diary entries for %s" date-string)))
811 (cons noentries hol-string)))
814 (defvar diary-saved-point) ; bound in diary-list-entries
816 (defun simple-diary-display ()
817 "Display the diary buffer if there are any relevant entries or holidays."
818 ;; If selected window is dedicated (to the calendar), need a new one
819 ;; to display the diary.
820 (let* ((pop-up-frames (or pop-up-frames
821 (window-dedicated-p (selected-window))))
822 (dbuff (find-buffer-visiting (substitute-in-file-name diary-file)))
823 (empty (diary-display-no-entries)))
824 ;; This may be too wide, but when simple diary is used there is
825 ;; nowhere else for the holidays to go. Also, it is documented in
826 ;; holidays-in-diary-buffer that the holidays go in the mode-line.
827 ;; FIXME however if there are no diary entries a separate buffer
828 ;; is displayed - this is inconsistent.
829 (with-current-buffer dbuff
830 (calendar-set-mode-line (format "Diary for %s" (cdr empty))))
831 (unless (car empty) ; no entries
832 (with-current-buffer dbuff
833 (let ((window (display-buffer (current-buffer))))
834 ;; d-s-p is passed from diary-list-entries.
835 (set-window-point window diary-saved-point)
836 (set-window-start window (point-min))))
837 (message "Preparing diary...done"))))
839 (define-button-type 'diary-entry
840 'action #'diary-goto-entry
841 'face 'diary-button)
843 (defun diary-goto-entry (button)
844 "Jump to the diary entry for the BUTTON at point."
845 (let* ((locator (button-get button 'locator))
846 (marker (car locator))
847 markbuf file)
848 ;; If marker pointing to diary location is valid, use that.
849 (if (and marker (setq markbuf (marker-buffer marker)))
850 (progn
851 (pop-to-buffer markbuf)
852 (goto-char (marker-position marker)))
853 ;; Marker is invalid (eg buffer has been killed).
854 (or (and (setq file (cadr locator))
855 (file-exists-p file)
856 (find-file-other-window file)
857 (progn
858 (when (eq major-mode default-major-mode) (diary-mode))
859 (goto-char (point-min))
860 (if (re-search-forward (format "%s.*\\(%s\\)"
861 (regexp-quote (nth 2 locator))
862 (regexp-quote (nth 3 locator)))
863 nil t)
864 (goto-char (match-beginning 1)))))
865 (message "Unable to locate this diary entry")))))
867 (defun fancy-diary-display ()
868 "Prepare a diary buffer with relevant entries in a fancy, noneditable form.
869 To use this function, add it to `diary-display-hook'."
870 ;; Turn off selective-display in the diary file's buffer.
871 (with-current-buffer
872 (find-buffer-visiting (substitute-in-file-name diary-file))
873 (diary-unhide-everything))
874 (unless (car (diary-display-no-entries)) ; no entries
875 ;; Prepare the fancy diary buffer.
876 (calendar-in-read-only-buffer fancy-diary-buffer
877 (calendar-set-mode-line "Diary Entries")
878 (let ((holiday-list-last-month 1)
879 (holiday-list-last-year 1)
880 (date (list 0 0 0))
881 holiday-list)
882 (dolist (entry diary-entries-list)
883 (unless (calendar-date-equal date (car entry))
884 (setq date (car entry))
885 (and holidays-in-diary-buffer
886 (calendar-date-compare
887 (list (list holiday-list-last-month
888 (calendar-last-day-of-month
889 holiday-list-last-month
890 holiday-list-last-year)
891 holiday-list-last-year))
892 (list date))
893 ;; We need to get the holidays for the next 3 months.
894 (setq holiday-list-last-month
895 (extract-calendar-month date)
896 holiday-list-last-year
897 (extract-calendar-year date))
898 (progn
899 (increment-calendar-month
900 holiday-list-last-month holiday-list-last-year 1)
902 (setq holiday-list
903 (let ((displayed-month holiday-list-last-month)
904 (displayed-year holiday-list-last-year))
905 (calendar-holiday-list)))
906 (increment-calendar-month
907 holiday-list-last-month holiday-list-last-year 1))
908 (let ((longest 0)
909 date-holiday-list cc)
910 ;; Make a list of all holidays for date.
911 (dolist (h holiday-list)
912 (if (calendar-date-equal date (car h))
913 (setq date-holiday-list (append date-holiday-list
914 (cdr h)))))
915 (insert (if (bobp) "" ?\n) (calendar-date-string date))
916 (if date-holiday-list (insert ": "))
917 (setq cc (current-column))
918 (insert (mapconcat (lambda (x)
919 (setq longest (max longest (length x)))
921 date-holiday-list
922 (concat "\n" (make-string cc ?\s))))
923 (insert ?\n (make-string (+ cc longest) ?=) ?\n)))
924 (let ((this-entry (cadr entry))
925 this-loc marks temp-face)
926 (unless (zerop (length this-entry))
927 (if (setq this-loc (nth 3 entry))
928 (insert-button (concat this-entry "\n")
929 ;; (MARKER FILENAME SPECIFIER LITERAL)
930 'locator (list (car this-loc)
931 (cadr this-loc)
932 (nth 2 entry)
933 (or (nth 2 this-loc)
934 (nth 1 entry)))
935 :type 'diary-entry)
936 (insert this-entry ?\n))
937 (and font-lock-mode
938 (setq marks (nth 4 entry))
939 (save-excursion
940 (setq temp-face (calendar-make-temp-face marks))
941 (search-backward this-entry)
942 (overlay-put
943 (make-overlay (match-beginning 0) (match-end 0))
944 'face temp-face)))))))
945 (fancy-diary-display-mode)
946 (calendar-set-mode-line date-string)
947 (message "Preparing diary...done"))))
949 ;; FIXME modernize?
950 (defun print-diary-entries ()
951 "Print a hard copy of the diary display.
953 If the simple diary display is being used, prepare a temp buffer with the
954 visible lines of the diary buffer, add a heading line composed from the mode
955 line, print the temp buffer, and destroy it.
957 If the fancy diary display is being used, just print the buffer.
959 The hooks given by the variable `print-diary-entries-hook' are called to do
960 the actual printing."
961 (interactive)
962 (let ((diary-buffer (get-buffer fancy-diary-buffer))
963 temp-buffer heading start end)
964 (if diary-buffer
965 (with-current-buffer diary-buffer
966 (run-hooks 'print-diary-entries-hook))
967 (or (setq diary-buffer
968 (find-buffer-visiting (substitute-in-file-name diary-file)))
969 (error "You don't have a diary buffer!"))
970 ;; Name affects printing?
971 (setq temp-buffer (get-buffer-create " *Printable Diary Entries*"))
972 (with-current-buffer diary-buffer
973 (setq heading
974 (if (not (stringp mode-line-format))
975 "All Diary Entries"
976 (string-match "^-*\\([^-].*[^-]\\)-*$" mode-line-format)
977 (match-string 1 mode-line-format))
978 start (point-min))
979 (while
980 (progn
981 (setq end (next-single-char-property-change start 'invisible))
982 (unless (get-char-property start 'invisible)
983 (with-current-buffer temp-buffer
984 (insert-buffer-substring diary-buffer start end)))
985 (setq start end)
986 (and end (< end (point-max))))))
987 (set-buffer temp-buffer)
988 (goto-char (point-min))
989 (insert heading "\n"
990 (make-string (length heading) ?=) "\n")
991 (run-hooks 'print-diary-entries-hook)
992 (kill-buffer temp-buffer))))
994 (define-obsolete-function-alias 'show-all-diary-entries 'diary-show-all-entries)
995 ;;;###cal-autoload
996 (defun diary-show-all-entries ()
997 "Show all of the diary entries in the diary file.
998 This function gets rid of the selective display of the diary file so that
999 all entries, not just some, are visible. If there is no diary buffer, one
1000 is created."
1001 (interactive)
1002 (let ((d-file (diary-check-diary-file))
1003 (pop-up-frames (or pop-up-frames
1004 (window-dedicated-p (selected-window)))))
1005 (with-current-buffer (or (find-buffer-visiting d-file)
1006 (find-file-noselect d-file t))
1007 (when (eq major-mode default-major-mode) (diary-mode))
1008 (diary-unhide-everything)
1009 (display-buffer (current-buffer)))))
1011 ;;;###autoload
1012 (defun diary-mail-entries (&optional ndays)
1013 "Send a mail message showing diary entries for next NDAYS days.
1014 If no prefix argument is given, NDAYS is set to `diary-mail-days'.
1015 Mail is sent to the address specified by `diary-mail-addr'.
1017 Here is an example of a script to call `diary-mail-entries',
1018 suitable for regular scheduling using cron (or at). Note that
1019 since `emacs -script' does not load your `.emacs' file, you
1020 should ensure that all relevant variables are set.
1022 #!/usr/bin/emacs -script
1023 ;; diary-rem.el - run the Emacs diary-reminder
1025 \(setq diary-mail-days 3
1026 diary-file \"/path/to/diary.file\"
1027 calendar-date-style 'european
1028 diary-mail-addr \"user@host.name\")
1030 \(diary-mail-entries)
1032 # diary-rem.el ends here
1034 (interactive "P")
1035 (if (string-equal diary-mail-addr "")
1036 (error "You must set `diary-mail-addr' to use this command")
1037 (let ((diary-display-hook 'fancy-diary-display))
1038 (diary-list-entries (calendar-current-date) (or ndays diary-mail-days)))
1039 (compose-mail diary-mail-addr
1040 (concat "Diary entries generated "
1041 (calendar-date-string (calendar-current-date))))
1042 (insert
1043 (if (get-buffer fancy-diary-buffer)
1044 (with-current-buffer fancy-diary-buffer (buffer-string))
1045 "No entries found"))
1046 (call-interactively (get mail-user-agent 'sendfunc))))
1048 (defun diary-name-pattern (string-array &optional abbrev-array paren)
1049 "Return a regexp matching the strings in the array STRING-ARRAY.
1050 If the optional argument ABBREV-ARRAY is present, then the function
1051 `calendar-abbrev-construct' is used to construct abbreviations from the
1052 two supplied arrays. The returned regexp will then also match these
1053 abbreviations, with or without final `.' characters. If the optional
1054 argument PAREN is non-nil, the regexp is surrounded by parentheses."
1055 (regexp-opt (append string-array
1056 (if abbrev-array
1057 (calendar-abbrev-construct abbrev-array
1058 string-array))
1059 (if abbrev-array
1060 (calendar-abbrev-construct abbrev-array
1061 string-array
1062 'period))
1063 nil)
1064 paren))
1066 (defvar marking-diary-entries nil
1067 "True during the marking of diary entries, nil otherwise.")
1069 (defvar marking-diary-entry nil
1070 "True during the marking of diary entries, if current entry is marking.")
1072 ;; file-glob-attrs bound in mark-diary-entries.
1073 (defun diary-mark-entries-1 (markfunc &optional months symbol absfunc)
1074 "Mark diary entries of a certain type.
1075 MARKFUNC is a function that marks entries of the appropriate type
1076 matching a given date pattern. MONTHS is an array of month names.
1077 SYMBOL marks diary entries of the type in question. ABSFUNC is a
1078 function that converts absolute dates to dates of the appropriate type. "
1079 (let ((dayname (diary-name-pattern calendar-day-name-array
1080 calendar-day-abbrev-array))
1081 (monthname (format "%s\\|\\*"
1082 (if months
1083 (diary-name-pattern months)
1084 (diary-name-pattern calendar-month-name-array
1085 calendar-month-abbrev-array))))
1086 (month "[0-9]+\\|\\*")
1087 (day "[0-9]+\\|\\*")
1088 (year "[0-9]+\\|\\*")
1089 (case-fold-search t)
1090 marks)
1091 (dolist (date-form diary-date-forms)
1092 (if (eq (car date-form) 'backup) ; ignore 'backup directive
1093 (setq date-form (cdr date-form)))
1094 (let* ((l (length date-form))
1095 (d-name-pos (- l (length (memq 'dayname date-form))))
1096 (d-name-pos (if (/= l d-name-pos) (1+ d-name-pos)))
1097 (m-name-pos (- l (length (memq 'monthname date-form))))
1098 (m-name-pos (if (/= l m-name-pos) (1+ m-name-pos)))
1099 (d-pos (- l (length (memq 'day date-form))))
1100 (d-pos (if (/= l d-pos) (1+ d-pos)))
1101 (m-pos (- l (length (memq 'month date-form))))
1102 (m-pos (if (/= l m-pos) (1+ m-pos)))
1103 (y-pos (- l (length (memq 'year date-form))))
1104 (y-pos (if (/= l y-pos) (1+ y-pos)))
1105 (regexp (format "^%s\\(%s\\)"
1106 (if symbol (regexp-quote symbol) "")
1107 (mapconcat 'eval date-form "\\)\\("))))
1108 (goto-char (point-min))
1109 (while (re-search-forward regexp nil t)
1110 (let* ((dd-name
1111 (if d-name-pos
1112 (match-string-no-properties d-name-pos)))
1113 (mm-name
1114 (if m-name-pos
1115 (match-string-no-properties m-name-pos)))
1116 (mm (string-to-number
1117 (if m-pos
1118 (match-string-no-properties m-pos)
1119 "")))
1120 (dd (string-to-number
1121 (if d-pos
1122 (match-string-no-properties d-pos)
1123 "")))
1124 (y-str (if y-pos
1125 (match-string-no-properties y-pos)))
1126 (yy (if (not y-str)
1128 (if (and (= (length y-str) 2)
1129 abbreviated-calendar-year)
1130 (let* ((current-y
1131 (extract-calendar-year
1132 (if absfunc
1133 (funcall
1134 absfunc
1135 (calendar-absolute-from-gregorian
1136 (calendar-current-date)))
1137 (calendar-current-date))))
1138 (y (+ (string-to-number y-str)
1139 ;; Current century, eg 2000.
1140 (* 100 (/ current-y 100))))
1141 (offset (- y current-y)))
1142 ;; Add 2-digit year to current century.
1143 ;; If more than 50 years in the future,
1144 ;; assume last century. If more than 50
1145 ;; years in the past, assume next century.
1146 (if (> offset 50)
1147 (- y 100)
1148 (if (< offset -50)
1149 (+ y 100)
1150 y)))
1151 (string-to-number y-str)))))
1152 (setq marks (cadr (diary-pull-attrs
1153 (buffer-substring-no-properties
1154 (point) (line-end-position))
1155 file-glob-attrs)))
1156 (if dd-name
1157 (mark-calendar-days-named
1158 (cdr (assoc-string dd-name
1159 (calendar-make-alist
1160 calendar-day-name-array
1161 0 nil calendar-day-abbrev-array) t)) marks)
1162 (if mm-name
1163 (setq mm
1164 (if (string-equal mm-name "*") 0
1165 (cdr (assoc-string
1166 mm-name
1167 (if months (calendar-make-alist months)
1168 (calendar-make-alist
1169 calendar-month-name-array
1170 1 nil calendar-month-abbrev-array)) t)))))
1171 (funcall markfunc mm dd yy marks))))))))
1173 ;;;###cal-autoload
1174 (defun mark-diary-entries (&optional redraw)
1175 "Mark days in the calendar window that have diary entries.
1176 Each entry in the diary file visible in the calendar window is
1177 marked. After the entries are marked, the hooks
1178 `nongregorian-diary-marking-hook' and `mark-diary-entries-hook'
1179 are run. If the optional argument REDRAW is non-nil (which is
1180 the case interactively, for example) then any existing diary
1181 marks are first removed. This is intended to deal with deleted
1182 diary entries."
1183 (interactive "p")
1184 ;; To remove any deleted diary entries. Do not redraw when:
1185 ;; i) processing #include diary files (else only get the marks from
1186 ;; the last #include file processed).
1187 ;; ii) called via calendar-redraw (since calendar has already been
1188 ;; erased).
1189 ;; Use of REDRAW handles both of these cases.
1190 (when (and redraw mark-diary-entries-in-calendar)
1191 (setq mark-diary-entries-in-calendar nil)
1192 (redraw-calendar))
1193 (let ((marking-diary-entries t)
1194 file-glob-attrs)
1195 (with-current-buffer (find-file-noselect (diary-check-diary-file) t)
1196 (save-excursion
1197 (when (eq major-mode default-major-mode) (diary-mode))
1198 (setq mark-diary-entries-in-calendar t)
1199 (message "Marking diary entries...")
1200 (setq file-glob-attrs (nth 1 (diary-pull-attrs nil '())))
1201 (with-syntax-table diary-syntax-table
1202 (diary-mark-entries-1 'mark-calendar-date-pattern)
1203 (mark-sexp-diary-entries)
1204 (run-hooks 'nongregorian-diary-marking-hook
1205 'mark-diary-entries-hook))
1206 (message "Marking diary entries...done")))))
1209 (defun diary-sexp-entry (sexp entry date)
1210 "Process a SEXP diary ENTRY for DATE."
1211 (let ((result (if calendar-debug-sexp
1212 (let ((stack-trace-on-error t))
1213 (eval (car (read-from-string sexp))))
1214 (condition-case nil
1215 (eval (car (read-from-string sexp)))
1216 (error
1217 (beep)
1218 (message "Bad sexp at line %d in %s: %s"
1219 (count-lines (point-min) (point))
1220 diary-file sexp)
1221 (sleep-for 2))))))
1222 (cond ((stringp result) result)
1223 ((and (consp result)
1224 (stringp (cdr result))) result)
1225 (result entry)
1226 (t nil))))
1228 (defvar displayed-year) ; bound in generate-calendar
1229 (defvar displayed-month)
1231 (defun mark-sexp-diary-entries ()
1232 "Mark days in the calendar window that have sexp diary entries.
1233 Each entry in the diary file (or included files) visible in the calendar window
1234 is marked. See the documentation for the function `list-sexp-diary-entries'."
1235 (let* ((sexp-mark (regexp-quote sexp-diary-entry-symbol))
1236 (s-entry (format "^\\(%s(\\)\\|\\(%s%s(diary-remind\\)" sexp-mark
1237 (regexp-quote diary-nonmarking-symbol)
1238 sexp-mark))
1239 (file-glob-attrs (nth 1 (diary-pull-attrs nil '())))
1240 m y first-date last-date date mark file-glob-attrs
1241 sexp-start sexp entry entry-start)
1242 (with-current-buffer calendar-buffer
1243 (setq m displayed-month
1244 y displayed-year))
1245 (increment-calendar-month m y -1)
1246 (setq first-date (calendar-absolute-from-gregorian (list m 1 y))
1247 date (1- first-date))
1248 (increment-calendar-month m y 2)
1249 (setq last-date
1250 (calendar-absolute-from-gregorian
1251 (list m (calendar-last-day-of-month m y) y)))
1252 (goto-char (point-min))
1253 (while (re-search-forward s-entry nil t)
1254 (setq marking-diary-entry (char-equal (preceding-char) ?\())
1255 (re-search-backward "(")
1256 (setq sexp-start (point))
1257 (forward-sexp)
1258 (setq sexp (buffer-substring-no-properties sexp-start (point)))
1259 (forward-char 1)
1260 (if (and (bolp) (not (looking-at "[ \t]")))
1261 ;; Diary entry consists only of the sexp.
1262 (progn
1263 (backward-char 1)
1264 (setq entry ""))
1265 (setq entry-start (point))
1266 ;; Find end of entry.
1267 (forward-line 1)
1268 (while (looking-at "[ \t]")
1269 (forward-line 1))
1270 (if (bolp) (backward-char 1))
1271 (setq entry (buffer-substring-no-properties entry-start (point))))
1272 (while (<= (setq date (1+ date)) last-date)
1273 (when (setq mark (diary-sexp-entry
1274 sexp entry
1275 (calendar-gregorian-from-absolute date)))
1276 (mark-visible-calendar-date
1277 (calendar-gregorian-from-absolute date)
1278 (or (cadr (diary-pull-attrs entry file-glob-attrs))
1279 (if (consp mark) (car mark)))))))))
1281 (defun mark-included-diary-files ()
1282 "Mark the diary entries from other diary files with those of the diary file.
1283 This function is suitable for use with `mark-diary-entries-hook'; it enables
1284 you to use shared diary files together with your own. The files included are
1285 specified in the `diary-file' by lines of this form:
1286 #include \"filename\"
1287 This is recursive; that is, #include directives in diary files thus included
1288 are obeyed. You can change the `#include' to some other string by
1289 changing the variable `diary-include-string'."
1290 (goto-char (point-min))
1291 (while (re-search-forward
1292 (format "^%s \"\\([^\"]*\\)\"" (regexp-quote diary-include-string))
1293 nil t)
1294 (let* ((diary-file (substitute-in-file-name
1295 (match-string-no-properties 1)))
1296 (mark-diary-entries-hook 'mark-included-diary-files)
1297 (dbuff (find-buffer-visiting diary-file)))
1298 (if (file-exists-p diary-file)
1299 (if (file-readable-p diary-file)
1300 (progn
1301 (mark-diary-entries)
1302 (unless dbuff
1303 (kill-buffer (find-buffer-visiting diary-file))))
1304 (beep)
1305 (message "Can't read included diary file %s" diary-file)
1306 (sleep-for 2))
1307 (beep)
1308 (message "Can't find included diary file %s" diary-file)
1309 (sleep-for 2))))
1310 (goto-char (point-min)))
1312 (defun mark-calendar-days-named (dayname &optional color)
1313 "Mark all dates in the calendar window that are day DAYNAME of the week.
1314 0 means all Sundays, 1 means all Mondays, and so on.
1315 Optional argument COLOR is passed to `mark-visible-calendar-date' as MARK."
1316 (with-current-buffer calendar-buffer
1317 (let ((prev-month displayed-month)
1318 (prev-year displayed-year)
1319 (succ-month displayed-month)
1320 (succ-year displayed-year)
1321 (last-day)
1322 (day))
1323 (increment-calendar-month succ-month succ-year 1)
1324 (increment-calendar-month prev-month prev-year -1)
1325 (setq day (calendar-absolute-from-gregorian
1326 (calendar-nth-named-day 1 dayname prev-month prev-year))
1327 last-day (calendar-absolute-from-gregorian
1328 (calendar-nth-named-day -1 dayname succ-month succ-year)))
1329 (while (<= day last-day)
1330 (mark-visible-calendar-date (calendar-gregorian-from-absolute day)
1331 color)
1332 (setq day (+ day 7))))))
1334 (defun mark-calendar-month (month year p-month p-day p-year &optional color)
1335 "Mark dates in the MONTH/YEAR that conform to pattern P-MONTH/P-DAY/P-YEAR.
1336 A value of 0 in any position of the pattern is a wildcard.
1337 Optional argument COLOR is passed to `mark-visible-calendar-date' as MARK."
1338 (if (or (and (= month p-month)
1339 (or (zerop p-year) (= year p-year)))
1340 (and (zerop p-month)
1341 (or (zerop p-year) (= year p-year))))
1342 (if (zerop p-day)
1343 (dotimes (i (calendar-last-day-of-month month year))
1344 (mark-visible-calendar-date (list month (1+ i) year) color))
1345 (mark-visible-calendar-date (list month p-day year) color))))
1347 (defun mark-calendar-date-pattern (month day year &optional color)
1348 "Mark all dates in the calendar window that conform to MONTH/DAY/YEAR.
1349 A value of 0 in any position is a wildcard. Optional argument COLOR is
1350 passed to `mark-visible-calendar-date' as MARK."
1351 (with-current-buffer calendar-buffer
1352 (let ((m displayed-month)
1353 (y displayed-year))
1354 (increment-calendar-month m y -1)
1355 (dotimes (idummy 3)
1356 (mark-calendar-month m y month day year color)
1357 (increment-calendar-month m y 1)))))
1360 ;; Bahai, Hebrew, Islamic.
1361 (defun calendar-mark-complex (month day year fromabs &optional color)
1362 "Mark dates in the calendar conforming to MONTH DAY YEAR of some system.
1363 The function FROMABS converts absolute dates to the appropriate date system.
1364 Optional argument COLOR is passed to `mark-visible-calendar-date' as MARK."
1365 ;; Not one of the simple cases--check all visible dates for match.
1366 ;; Actually, the following code takes care of ALL of the cases, but
1367 ;; it's much too slow to be used for the simple (common) cases.
1368 (let* ((m displayed-month)
1369 (y displayed-year)
1370 (first-date (progn
1371 (increment-calendar-month m y -1)
1372 (calendar-absolute-from-gregorian (list m 1 y))))
1373 (last-date (progn
1374 (increment-calendar-month m y 2)
1375 (calendar-absolute-from-gregorian
1376 (list m (calendar-last-day-of-month m y) y))))
1377 (date (1- first-date))
1378 local-date)
1379 (while (<= (setq date (1+ date)) last-date)
1380 (setq local-date (funcall fromabs date))
1381 (and (or (zerop month)
1382 (= month (extract-calendar-month local-date)))
1383 (or (zerop day)
1384 (= day (extract-calendar-day local-date)))
1385 (or (zerop year)
1386 (= year (extract-calendar-year local-date)))
1387 (mark-visible-calendar-date
1388 (calendar-gregorian-from-absolute date) color)))))
1390 ;; Bahai, Islamic.
1391 (defun calendar-mark-1 (month day year fromabs toabs &optional color)
1392 "Mark dates in the calendar conforming to MONTH DAY YEAR of some system.
1393 The function FROMABS converts absolute dates to the appropriate date system.
1394 The function TOABDS carries out the inverse operation. Optional argument
1395 COLOR is passed to `mark-visible-calendar-date' as MARK."
1396 (save-excursion
1397 (set-buffer calendar-buffer)
1398 (if (and (not (zerop month)) (not (zerop day)))
1399 (if (not (zerop year))
1400 ;; Fully specified date.
1401 (let ((date (calendar-gregorian-from-absolute
1402 (funcall toabs (list month day year)))))
1403 (if (calendar-date-is-visible-p date)
1404 (mark-visible-calendar-date date color)))
1405 ;; Month and day in any year--this taken from the holiday stuff.
1406 (let* ((i-date (funcall fromabs
1407 (calendar-absolute-from-gregorian
1408 (list displayed-month 15 displayed-year))))
1409 (m (extract-calendar-month i-date))
1410 (y (extract-calendar-year i-date))
1411 date)
1412 (unless (< m 1) ; calendar doesn't apply
1413 (increment-calendar-month m y (- 10 month))
1414 (and (> m 7) ; date might be visible
1415 (calendar-date-is-visible-p
1416 (setq date (calendar-gregorian-from-absolute
1417 (funcall toabs (list month day y)))))
1418 (mark-visible-calendar-date date color)))))
1419 (calendar-mark-complex month day year
1420 'calendar-bahai-from-absolute color))))
1423 (defun diary-entry-time (s)
1424 "Return time at the beginning of the string S as a military-style integer.
1425 For example, returns 1325 for 1:25pm.
1427 Returns `diary-unknown-time' (default value -9999) if no time is recognized.
1428 The recognized forms are XXXX, X:XX, or XX:XX (military time), and XXam,
1429 XXAM, XXpm, XXPM, XX:XXam, XX:XXAM XX:XXpm, or XX:XXPM. A period (.) can
1430 be used instead of a colon (:) to separate the hour and minute parts."
1431 (let (case-fold-search)
1432 (cond ((string-match ; military time
1433 "\\`[ \t\n]*\\([0-9]?[0-9]\\)[:.]?\\([0-9][0-9]\\)\\(\\>\\|[^ap]\\)"
1435 (+ (* 100 (string-to-number (match-string 1 s)))
1436 (string-to-number (match-string 2 s))))
1437 ((string-match ; hour only (XXam or XXpm)
1438 "\\`[ \t\n]*\\([0-9]?[0-9]\\)\\([ap]\\)m\\>" s)
1439 (+ (* 100 (% (string-to-number (match-string 1 s)) 12))
1440 (if (equal ?a (downcase (aref s (match-beginning 2))))
1441 0 1200)))
1442 ((string-match ; hour and minute (XX:XXam or XX:XXpm)
1443 "\\`[ \t\n]*\\([0-9]?[0-9]\\)[:.]\\([0-9][0-9]\\)\\([ap]\\)m\\>" s)
1444 (+ (* 100 (% (string-to-number (match-string 1 s)) 12))
1445 (string-to-number (match-string 2 s))
1446 (if (equal ?a (downcase (aref s (match-beginning 3))))
1447 0 1200)))
1448 (t diary-unknown-time)))) ; unrecognizable
1450 (defun diary-entry-compare (e1 e2)
1451 "Return t if E1 is earlier than E2."
1452 (or (calendar-date-compare e1 e2)
1453 (and (calendar-date-equal (car e1) (car e2))
1454 (let* ((ts1 (cadr e1)) (t1 (diary-entry-time ts1))
1455 (ts2 (cadr e2)) (t2 (diary-entry-time ts2)))
1456 (or (< t1 t2)
1457 (and (= t1 t2)
1458 (string-lessp ts1 ts2)))))))
1460 (defun sort-diary-entries ()
1461 "Sort the list of diary entries by time of day."
1462 (setq diary-entries-list (sort diary-entries-list 'diary-entry-compare)))
1465 (defun list-sexp-diary-entries (date)
1466 "Add sexp entries for DATE from the diary file to `diary-entries-list'.
1467 Also, make them visible in the diary. Returns t if any entries are found.
1469 Sexp diary entries must be prefaced by a `sexp-diary-entry-symbol'
1470 \(normally `%%'). The form of a sexp diary entry is
1472 %%(SEXP) ENTRY
1474 Both ENTRY and DATE are available when the SEXP is evaluated. If
1475 the SEXP returns nil, the diary entry does not apply. If it
1476 returns a non-nil value, ENTRY will be taken to apply to DATE; if
1477 the value is a string, that string will be the diary entry in the
1478 fancy diary display.
1480 For example, the following diary entry will apply to the 21st of
1481 the month if it is a weekday and the Friday before if the 21st is
1482 on a weekend:
1484 &%%(let ((dayname (calendar-day-of-week date))
1485 (day (extract-calendar-day date)))
1487 (and (= day 21) (memq dayname '(1 2 3 4 5)))
1488 (and (memq day '(19 20)) (= dayname 5)))
1489 ) UIUC pay checks deposited
1491 A number of built-in functions are available for this type of
1492 diary entry. In the following, the optional parameter MARK
1493 specifies a face or single-character string to use when
1494 highlighting the day in the calendar. For those functions that
1495 take MONTH, DAY, and YEAR as arguments, the order of the input
1496 parameters changes according to `calendar-date-style' (e.g. to
1497 DAY MONTH YEAR in the European style).
1499 %%(diary-date MONTH DAY YEAR &optional MARK) text
1500 Entry applies if date is MONTH, DAY, YEAR. DAY, MONTH, and YEAR can
1501 be a list of integers, `t' (meaning all values), or an integer.
1503 %%(diary-float MONTH DAYNAME N &optional DAY MARK) text
1504 Entry will appear on the Nth DAYNAME of MONTH (0 being Sunday,
1505 1 Monday, etc; if N is negative it counts backward from the end
1506 of the month. MONTH can be a list of months, a single month, or `t'
1507 to specify all months. Optional DAY means the Nth DAYNAME of MONTH
1508 on or after/before DAY. DAY defaults to 1 if N>0 and the last day of
1509 the month if N<0.
1511 %%(diary-block M1 D1 Y1 M2 D2 Y2 &optional MARK) text
1512 Entry will appear on dates between M1/D1/Y1 and M2/D2/Y2,
1513 inclusive.
1515 %%(diary-anniversary MONTH DAY YEAR &optional MARK) text
1516 Entry will appear on anniversary dates of MONTH DAY, YEAR.
1517 Text can contain `%d' or `%d%s'; `%d' will be replaced by the
1518 number of years since the MONTH DAY, YEAR, and `%s' by the
1519 ordinal ending of that number (i.e. `st', `nd', `rd' or `th',
1520 as appropriate). The anniversary of February 29 is
1521 considered to be March 1 in a non-leap year.
1523 %%(diary-cyclic N MONTH DAY YEAR &optional MARK) text
1524 Entry will appear every N days, starting MONTH DAY, YEAR.
1525 Text can contain `%d' or `%d%s'; `%d' will be replaced by the
1526 number of repetitions since the MONTH DAY, YEAR and `%s' by
1527 the ordinal ending of that number (i.e. `st', `nd', `rd' or
1528 `th', as appropriate).
1530 %%(diary-remind SEXP DAYS &optional MARKING) text
1531 Entry is a reminder for diary sexp SEXP. DAYS is either a
1532 single number or a list of numbers indicating the number(s)
1533 of days before the event that the warning(s) should occur. If
1534 the current date is (one of) DAYS before the event indicated
1535 by EXPR, then a suitable message (as specified by
1536 `diary-remind-message') appears. In addition to the
1537 reminders beforehand, the diary entry also appears on the
1538 date itself. If optional MARKING is non-nil then the
1539 *reminders* are marked on the calendar. Marking of reminders
1540 is independent of whether the entry *itself* is a marking or
1541 non-marking one.
1543 %%(diary-yahrzeit MONTH DAY YEAR) text
1544 Text is assumed to be the name of the person; the date is the
1545 date of death on the *civil* calendar. The diary entry will
1546 appear on the proper Hebrew-date anniversary and on the day
1547 before.
1549 All the remaining functions do not accept any text, and so only
1550 make sense with `fancy-diary-display'. Most produce output every day.
1552 `diary-day-of-year' - day of year and number of days remaining
1553 `diary-iso-date' - ISO commercial date
1554 `diary-astro-day-number' - astronomical (Julian) day number
1555 `diary-sunrise-sunset' - local times of sunrise and sunset
1557 These functions give the date in alternative calendrical systems:
1559 `diary-bahai-date', `diary-chinese-date', `diary-coptic-date',
1560 `diary-ethiopic-date', `diary-french-date', `diary-hebrew-date',
1561 `diary-islamic-date', `diary-julian-date', `diary-mayan-date',
1562 `diary-persian-date'
1564 Theses functions only produce output on certain dates:
1566 `diary-phases-of-moon' - phases of moon (on the appropriate days)
1567 `diary-omer' - Omer count, within 50 days after Passover
1568 `diary-parasha' - weekly parasha, every Saturday
1569 `diary-rosh-hodesh' - Rosh Hodesh, or the day or Saturday before
1570 `diary-sabbath-candles' - local time of candle lighting, on Fridays
1573 Marking these entries is *extremely* time consuming, so it is
1574 best if they are non-marking."
1575 (let ((s-entry (format "^%s?%s(" (regexp-quote diary-nonmarking-symbol)
1576 (regexp-quote sexp-diary-entry-symbol)))
1577 entry-found file-glob-attrs marks
1578 sexp-start sexp entry specifier entry-start line-start
1579 diary-entry temp literal)
1580 (goto-char (point-min))
1581 (save-excursion
1582 (setq file-glob-attrs (nth 1 (diary-pull-attrs nil '()))))
1583 (while (re-search-forward s-entry nil t)
1584 (backward-char 1)
1585 (setq sexp-start (point))
1586 (forward-sexp)
1587 (setq sexp (buffer-substring-no-properties sexp-start (point))
1588 line-start (line-end-position 0)
1589 specifier
1590 (buffer-substring-no-properties (1+ line-start) (point))
1591 entry-start (1+ line-start))
1592 (forward-char 1)
1593 (if (and (bolp) (not (looking-at "[ \t]")))
1594 ;; Diary entry consists only of the sexp.
1595 (progn
1596 (backward-char 1)
1597 (setq entry ""))
1598 (setq entry-start (point))
1599 (forward-line 1)
1600 (while (looking-at "[ \t]")
1601 (forward-line 1))
1602 (backward-char 1)
1603 (setq entry (buffer-substring-no-properties entry-start (point))))
1604 (setq diary-entry (diary-sexp-entry sexp entry date)
1605 literal entry ; before evaluation
1606 entry (if (consp diary-entry)
1607 (cdr diary-entry)
1608 diary-entry))
1609 (when diary-entry
1610 (remove-overlays line-start (point) 'invisible 'diary)
1611 (if (< 0 (length entry))
1612 (setq temp (diary-pull-attrs entry file-glob-attrs)
1613 entry (nth 0 temp)
1614 marks (nth 1 temp))))
1615 (add-to-diary-list date entry specifier
1616 (if entry-start (copy-marker entry-start))
1617 marks literal)
1618 (setq entry-found (or entry-found diary-entry)))
1619 entry-found))
1622 (defun diary-make-date (a b c)
1623 "Convert A B C into the internal calendar date form.
1624 The expected order of the inputs depends on `calendar-date-style',
1625 e.g. in the European case, A = day, B = month, C = year. Returns
1626 a list\(MONTH DAY YEAR), i.e. the American style, which is the
1627 form used internally by the calendar and diary."
1628 (cond ((eq calendar-date-style 'iso) ; YMD
1629 (list b c a))
1630 ((eq calendar-date-style 'european) ; DMY
1631 (list b a c))
1632 (t (list a b c))))
1635 ;;; Sexp diary functions.
1637 (defvar date)
1638 (defvar entry)
1640 ;; To be called from diary-sexp-entry, where DATE, ENTRY are bound.
1641 (defun diary-date (month day year &optional mark)
1642 "Specific date(s) diary entry.
1643 Entry applies if date is MONTH, DAY, YEAR. Each parameter can be
1644 a list of integers, `t' (meaning all values), or an integer. The
1645 order of the input parameters changes according to `calendar-date-style'
1646 \(e.g. to DAY MONTH YEAR in the European style).
1648 An optional parameter MARK specifies a face or single-character string to
1649 use when highlighting the day in the calendar."
1650 (let* ((ddate (diary-make-date month day year))
1651 (dd (extract-calendar-day ddate))
1652 (mm (extract-calendar-month ddate))
1653 (yy (extract-calendar-year ddate))
1654 (m (extract-calendar-month date))
1655 (y (extract-calendar-year date))
1656 (d (extract-calendar-day date)))
1657 (and
1658 (or (and (listp dd) (memq d dd))
1659 (equal d dd)
1660 (eq dd t))
1661 (or (and (listp mm) (memq m mm))
1662 (equal m mm)
1663 (eq mm t))
1664 (or (and (listp yy) (memq y yy))
1665 (equal y yy)
1666 (eq yy t))
1667 (cons mark entry))))
1669 ;; To be called from diary-sexp-entry, where DATE, ENTRY are bound.
1670 (defun diary-block (m1 d1 y1 m2 d2 y2 &optional mark)
1671 "Block diary entry.
1672 Entry applies if date is between, or on one of, two dates. The
1673 order of the input parameters changes according to
1674 `calendar-date-style' (e.g. to D1, M1, Y1, D2, M2, Y2 in the European style).
1676 An optional parameter MARK specifies a face or single-character string to
1677 use when highlighting the day in the calendar."
1678 (let ((date1 (calendar-absolute-from-gregorian
1679 (diary-make-date m1 d1 y1)))
1680 (date2 (calendar-absolute-from-gregorian
1681 (diary-make-date m2 d2 y2)))
1682 (d (calendar-absolute-from-gregorian date)))
1683 (and (<= date1 d) (<= d date2)
1684 (cons mark entry))))
1686 ;; To be called from diary-sexp-entry, where DATE, ENTRY are bound.
1687 (defun diary-float (month dayname n &optional day mark)
1688 "Floating diary entry--entry applies if date is the nth dayname of month.
1689 Parameters are MONTH, DAYNAME, N. MONTH can be a list of months, an integer,
1690 or `t' (meaning all months). If N is negative, count backward from the end
1691 of the month.
1693 An optional parameter DAY means the Nth DAYNAME on or after/before MONTH DAY.
1694 Optional MARK specifies a face or single-character string to use when
1695 highlighting the day in the calendar."
1696 ;; This is messy because the diary entry may apply, but the date on which it
1697 ;; is based can be in a different month/year. For example, asking for the
1698 ;; first Monday after December 30. For large values of |n| the problem is
1699 ;; more grotesque.
1700 (and (= dayname (calendar-day-of-week date))
1701 (let* ((m (extract-calendar-month date))
1702 (d (extract-calendar-day date))
1703 (y (extract-calendar-year date))
1704 ;; Last (n>0) or first (n<0) possible base date for entry.
1705 (limit
1706 (calendar-nth-named-absday (- n) dayname m y d))
1707 (last-abs (if (> n 0) limit (+ limit 6)))
1708 (first-abs (if (> n 0) (- limit 6) limit))
1709 (last (calendar-gregorian-from-absolute last-abs))
1710 (first (calendar-gregorian-from-absolute first-abs))
1711 ;; m1, d1 is first possible base date.
1712 (m1 (extract-calendar-month first))
1713 (d1 (extract-calendar-day first))
1714 (y1 (extract-calendar-year first))
1715 ;; m2, d2 is last possible base date.
1716 (m2 (extract-calendar-month last))
1717 (d2 (extract-calendar-day last))
1718 (y2 (extract-calendar-year last)))
1719 (if (or (and (= m1 m2) ; only possible base dates in one month
1720 (or (eq month t)
1721 (if (listp month)
1722 (memq m1 month)
1723 (= m1 month)))
1724 (let ((d (or day (if (> n 0)
1726 (calendar-last-day-of-month m1 y1)))))
1727 (and (<= d1 d) (<= d d2))))
1728 ;; Only possible base dates straddle two months.
1729 (and (or (< y1 y2)
1730 (and (= y1 y2) (< m1 m2)))
1732 ;; m1, d1 works as a base date.
1733 (and
1734 (or (eq month t)
1735 (if (listp month)
1736 (memq m1 month)
1737 (= m1 month)))
1738 (<= d1 (or day (if (> n 0)
1740 (calendar-last-day-of-month m1 y1)))))
1741 ;; m2, d2 works as a base date.
1742 (and (or (eq month t)
1743 (if (listp month)
1744 (memq m2 month)
1745 (= m2 month)))
1746 (<= (or day (if (> n 0)
1748 (calendar-last-day-of-month m2 y2)))
1749 d2)))))
1750 (cons mark entry)))))
1752 (defun diary-ordinal-suffix (n)
1753 "Ordinal suffix for N. (That is, `st', `nd', `rd', or `th', as appropriate.)"
1754 (if (or (memq (% n 100) '(11 12 13))
1755 (< 3 (% n 10)))
1756 "th"
1757 (aref ["th" "st" "nd" "rd"] (% n 10))))
1759 ;; To be called from diary-sexp-entry, where DATE, ENTRY are bound.
1760 (defun diary-anniversary (month day &optional year mark)
1761 "Anniversary diary entry.
1762 Entry applies if date is the anniversary of MONTH, DAY, YEAR.
1763 The order of the input parameters changes according to
1764 `calendar-date-style' (e.g. to DAY MONTH YEAR in the European style).
1766 The diary entry can contain `%d' or `%d%s'; the %d will be
1767 replaced by the number of years since the MONTH, DAY, YEAR, and the
1768 %s will be replaced by the ordinal ending of that number (that
1769 is, `st', `nd', `rd' or `th', as appropriate. The anniversary of
1770 February 29 is considered to be March 1 in non-leap years.
1772 An optional parameter MARK specifies a face or single-character
1773 string to use when highlighting the day in the calendar."
1774 (let* ((ddate (diary-make-date month day year))
1775 (dd (extract-calendar-day ddate))
1776 (mm (extract-calendar-month ddate))
1777 (yy (extract-calendar-year ddate))
1778 (y (extract-calendar-year date))
1779 (diff (if yy (- y yy) 100)))
1780 (and (= mm 2) (= dd 29) (not (calendar-leap-year-p y))
1781 (setq mm 3
1782 dd 1))
1783 (and (> diff 0) (calendar-date-equal (list mm dd y) date)
1784 (cons mark (format entry diff (diary-ordinal-suffix diff))))))
1786 ;; To be called from diary-sexp-entry, where DATE, ENTRY are bound.
1787 (defun diary-cyclic (n month day year &optional mark)
1788 "Cycle diary entry--entry applies every N days starting at MONTH, DAY, YEAR.
1789 The order of the input parameters changes according to
1790 `calendar-date-style' (e.g. to N DAY MONTH YEAR in the European
1791 style). ENTRY can contain `%d' or `%d%s'; the %d will be
1792 replaced by the number of repetitions since the MONTH DAY YEAR,
1793 and %s by the ordinal ending of that number (that is, `st', `nd',
1794 `rd' or `th', as appropriate.
1796 An optional parameter MARK specifies a face or single-character
1797 string to use when highlighting the day in the calendar."
1798 (let* ((diff (- (calendar-absolute-from-gregorian date)
1799 (calendar-absolute-from-gregorian
1800 (diary-make-date month day year))))
1801 (cycle (/ diff n)))
1802 (and (>= diff 0) (zerop (% diff n))
1803 (cons mark (format entry cycle (diary-ordinal-suffix cycle))))))
1805 (defun diary-day-of-year ()
1806 "Day of year and number of days remaining in the year of date diary entry."
1807 (calendar-day-of-year-string date))
1809 (defun diary-remind (sexp days &optional marking)
1810 "Provide a reminder of a diary entry.
1811 SEXP is a diary-sexp. DAYS is either a single number or a list of numbers
1812 indicating the number(s) of days before the event that the warning(s) should
1813 occur on. If the current date is (one of) DAYS before the event indicated by
1814 SEXP, then a suitable message (as specified by `diary-remind-message' is
1815 returned.
1817 In addition to the reminders beforehand, the diary entry also appears on the
1818 date itself.
1820 A `diary-nonmarking-symbol' at the beginning of the line of the `diary-remind'
1821 entry specifies that the diary entry (not the reminder) is non-marking.
1822 Marking of reminders is independent of whether the entry itself is a marking
1823 or nonmarking; if optional parameter MARKING is non-nil then the reminders are
1824 marked on the calendar."
1825 (let ((diary-entry (eval sexp))
1826 date)
1827 (cond
1828 ;; Diary entry applies on date.
1829 ((and diary-entry
1830 (or (not marking-diary-entries) marking-diary-entry))
1831 diary-entry)
1832 ;; Diary entry may apply to `days' before date.
1833 ((and (integerp days)
1834 (not diary-entry) ; diary entry does not apply to date
1835 (or (not marking-diary-entries) marking))
1836 (setq date (calendar-gregorian-from-absolute
1837 (+ (calendar-absolute-from-gregorian date) days)))
1838 (when (setq diary-entry (eval sexp)) ; re-evaluate with adjusted date
1839 ;; Discard any mark portion from diary-anniversary, etc.
1840 (if (consp diary-entry) (setq diary-entry (cdr diary-entry)))
1841 (mapconcat 'eval diary-remind-message "")))
1842 ;; Diary entry may apply to one of a list of days before date.
1843 ((and (listp days) days)
1844 (or (diary-remind sexp (car days) marking)
1845 (diary-remind sexp (cdr days) marking))))))
1848 ;;; Diary insertion functions.
1850 ;;;###cal-autoload
1851 (defun make-diary-entry (string &optional nonmarking file)
1852 "Insert a diary entry STRING which may be NONMARKING in FILE.
1853 If omitted, NONMARKING defaults to nil and FILE defaults to
1854 `diary-file'."
1855 (let ((pop-up-frames (or pop-up-frames
1856 (window-dedicated-p (selected-window)))))
1857 (find-file-other-window (substitute-in-file-name (or file diary-file))))
1858 (when (eq major-mode default-major-mode) (diary-mode))
1859 (widen)
1860 (diary-unhide-everything)
1861 (goto-char (point-max))
1862 (when (let ((case-fold-search t))
1863 (search-backward "Local Variables:"
1864 (max (- (point-max) 3000) (point-min))
1866 (beginning-of-line)
1867 (insert "\n")
1868 (forward-line -1))
1869 (insert
1870 (if (bolp) "" "\n")
1871 (if nonmarking diary-nonmarking-symbol "")
1872 string " "))
1874 ;;;###cal-autoload
1875 (defun insert-diary-entry (arg)
1876 "Insert a diary entry for the date indicated by point.
1877 Prefix argument ARG makes the entry nonmarking."
1878 (interactive "P")
1879 (make-diary-entry (calendar-date-string (calendar-cursor-to-date t) t t)
1880 arg))
1882 ;;;###cal-autoload
1883 (defun insert-weekly-diary-entry (arg)
1884 "Insert a weekly diary entry for the day of the week indicated by point.
1885 Prefix argument ARG makes the entry nonmarking."
1886 (interactive "P")
1887 (make-diary-entry (calendar-day-name (calendar-cursor-to-date t))
1888 arg))
1890 (defun diary-date-display-form (&optional type)
1891 "Return value for `calendar-date-display-form' using `calendar-date-style.'
1892 Optional symbol TYPE is either `monthly' or `yearly'."
1893 (cond ((eq type 'monthly) (cond ((eq calendar-date-style 'iso)
1894 '((format "*-*-%.2d"
1895 (string-to-number day))))
1896 ((eq calendar-date-style 'european)
1897 '(day " * "))
1898 (t '("* " day ))))
1899 ((eq type 'yearly) (cond ((eq calendar-date-style 'iso)
1900 '((format "*-%.2d-%.2d"
1901 (string-to-number month)
1902 (string-to-number day))))
1903 ((eq calendar-date-style 'european)
1904 '(day " " monthname))
1905 (t '(monthname " " day))))
1906 ;; Iso cannot contain "-", because this form used eg by
1907 ;; insert-anniversary-diary-entry.
1908 (t (cond ((eq calendar-date-style 'iso)
1909 '((format "%s %.2d %.2d" year
1910 (string-to-number month) (string-to-number day))))
1911 ((eq calendar-date-style 'european)
1912 '(day " " month " " year))
1913 (t '(month " " day " " year))))))
1915 (defun diary-insert-entry-1 (&optional type nomark months symbol absfunc)
1916 "Subroutine to insert a diary entry related to the date at point.
1917 TYPE is the type of entry (`monthly' or `yearly'). NOMARK
1918 non-nil means make the entry non-marking. Array MONTHS is used
1919 in place of `calendar-month-name-array'. String SYMBOL marks the
1920 type of diary entry. Function ABSFUNC converts absolute dates to
1921 dates of the appropriate type."
1922 (let ((calendar-date-display-form (if type
1923 (diary-date-display-form type)
1924 calendar-date-display-form))
1925 (calendar-month-name-array (or months calendar-month-name-array))
1926 (date (calendar-cursor-to-date t)))
1927 (make-diary-entry
1928 (format "%s%s" (or symbol "")
1929 (calendar-date-string
1930 (if absfunc
1931 (funcall absfunc (calendar-absolute-from-gregorian date))
1932 date)
1933 (not absfunc)
1934 (not type)))
1935 nomark)))
1937 ;;;###cal-autoload
1938 (defun insert-monthly-diary-entry (arg)
1939 "Insert a monthly diary entry for the day of the month indicated by point.
1940 Prefix argument ARG makes the entry nonmarking."
1941 (interactive "P")
1942 (diary-insert-entry-1 'monthly arg))
1944 ;;;###cal-autoload
1945 (defun insert-yearly-diary-entry (arg)
1946 "Insert an annual diary entry for the day of the year indicated by point.
1947 Prefix argument ARG makes the entry nonmarking."
1948 (interactive "P")
1949 (diary-insert-entry-1 'yearly arg))
1951 ;;;###cal-autoload
1952 (defun insert-anniversary-diary-entry (arg)
1953 "Insert an anniversary diary entry for the date given by point.
1954 Prefix argument ARG makes the entry nonmarking."
1955 (interactive "P")
1956 (let ((calendar-date-display-form (diary-date-display-form)))
1957 (make-diary-entry
1958 (format "%s(diary-anniversary %s)"
1959 sexp-diary-entry-symbol
1960 (calendar-date-string (calendar-cursor-to-date t) nil t))
1961 arg)))
1963 ;;;###cal-autoload
1964 (defun insert-block-diary-entry (arg)
1965 "Insert a block diary entry for the days between the point and marked date.
1966 Prefix argument ARG makes the entry nonmarking."
1967 (interactive "P")
1968 (let ((calendar-date-display-form (diary-date-display-form))
1969 (cursor (calendar-cursor-to-date t))
1970 (mark (or (car calendar-mark-ring)
1971 (error "No mark set in this buffer")))
1972 start end)
1973 (if (< (calendar-absolute-from-gregorian mark)
1974 (calendar-absolute-from-gregorian cursor))
1975 (setq start mark
1976 end cursor)
1977 (setq start cursor
1978 end mark))
1979 (make-diary-entry
1980 (format "%s(diary-block %s %s)"
1981 sexp-diary-entry-symbol
1982 (calendar-date-string start nil t)
1983 (calendar-date-string end nil t))
1984 arg)))
1986 ;;;###cal-autoload
1987 (defun insert-cyclic-diary-entry (arg)
1988 "Insert a cyclic diary entry starting at the date given by point.
1989 Prefix argument ARG makes the entry nonmarking."
1990 (interactive "P")
1991 (let ((calendar-date-display-form (diary-date-display-form)))
1992 (make-diary-entry
1993 (format "%s(diary-cyclic %d %s)"
1994 sexp-diary-entry-symbol
1995 (calendar-read "Repeat every how many days: "
1996 (lambda (x) (> x 0)))
1997 (calendar-date-string (calendar-cursor-to-date t) nil t))
1998 arg)))
2000 ;;; Diary mode.
2002 (defun diary-redraw-calendar ()
2003 "If `calendar-buffer' is live and diary entries are marked, redraw it."
2004 (and mark-diary-entries-in-calendar
2005 (save-excursion
2006 (redraw-calendar)))
2007 ;; Return value suitable for `write-contents-functions'.
2008 nil)
2010 (defvar diary-mode-map
2011 (let ((map (make-sparse-keymap)))
2012 (define-key map "\C-c\C-s" 'diary-show-all-entries)
2013 (define-key map "\C-c\C-q" 'quit-window)
2014 map)
2015 "Keymap for `diary-mode'.")
2017 (defun diary-font-lock-sexps (limit)
2018 "Recognize sexp diary entry up to LIMIT for font-locking."
2019 (if (re-search-forward
2020 (format "^%s?\\(%s\\)" (regexp-quote diary-nonmarking-symbol)
2021 (regexp-quote sexp-diary-entry-symbol))
2022 limit t)
2023 (condition-case nil
2024 (save-restriction
2025 (narrow-to-region (point-min) limit)
2026 (let ((start (point)))
2027 (forward-sexp 1)
2028 (store-match-data (list start (point)))
2030 (error t))))
2032 (defun diary-font-lock-date-forms (month-array &optional symbol abbrev-array)
2033 "Create font-lock patterns for `diary-date-forms' using MONTH-ARRAY.
2034 If given, optional SYMBOL must be a prefix to entries.
2035 If optional ABBREV-ARRAY is present, the abbreviations constructed
2036 from this array by the function `calendar-abbrev-construct' are
2037 matched (with or without a final `.'), in addition to the full month
2038 names."
2039 (let ((dayname (diary-name-pattern calendar-day-name-array
2040 calendar-day-abbrev-array t))
2041 (monthname (format "\\(%s\\|\\*\\)"
2042 (diary-name-pattern month-array abbrev-array)))
2043 (month "\\([0-9]+\\|\\*\\)")
2044 (day "\\([0-9]+\\|\\*\\)")
2045 (year "-?\\([0-9]+\\|\\*\\)"))
2046 (mapcar (lambda (x)
2047 (cons
2048 (concat "^" (regexp-quote diary-nonmarking-symbol) "?"
2049 (if symbol (regexp-quote symbol) "") "\\("
2050 (mapconcat 'eval
2051 ;; If backup, omit first item (backup)
2052 ;; and last item (not part of date).
2053 (if (equal (car x) 'backup)
2054 (nreverse (cdr (reverse (cdr x))))
2057 ;; With backup, last item is not part of date.
2058 (if (equal (car x) 'backup)
2059 (concat "\\)" (eval (car (reverse x))))
2060 "\\)"))
2061 '(1 diary-face)))
2062 diary-date-forms)))
2064 (defmacro diary-font-lock-keywords-1 (markfunc listfunc feature months symbol)
2065 "Subroutine of the function `diary-font-lock-keywords'.
2066 If MARKFUNC is a member of `nongregorian-diary-marking-hook', or
2067 LISTFUNC of `nongregorian-diary-listing-hook', then require FEATURE
2068 and return a font-lock pattern matching array of MONTHS and marking SYMBOL."
2069 `(when (or (memq ',markfunc nongregorian-diary-marking-hook)
2070 (memq ',listfunc nongregorian-diary-listing-hook))
2071 (require ',feature)
2072 (diary-font-lock-date-forms ,months ,symbol)))
2074 (defconst diary-time-regexp
2075 ;; Accepted formats: 10:00 10.00 10h00 10h 10am 10:00am 10.00am
2076 ;; Use of "." as a separator annoyingly matches numbers, eg "123.45".
2077 ;; Hence often prefix this with "\\(^\\|\\s-\\)."
2078 (concat "[0-9]?[0-9]\\([AaPp][mM]\\|\\("
2079 "[Hh]\\([0-9][0-9]\\)?\\|[:.][0-9][0-9]"
2080 "\\)\\([AaPp][Mm]\\)?\\)")
2081 "Regular expression matching a time of day.")
2083 (defvar calendar-hebrew-month-name-array-leap-year)
2084 (defvar calendar-islamic-month-name-array)
2085 (defvar calendar-bahai-month-name-array)
2087 ;;;###cal-autoload
2088 (defun diary-font-lock-keywords ()
2089 "Return a value for the variable `diary-font-lock-keywords'."
2090 (append
2091 (diary-font-lock-date-forms calendar-month-name-array
2092 nil calendar-month-abbrev-array)
2093 (diary-font-lock-keywords-1 mark-hebrew-diary-entries
2094 list-hebrew-diary-entries
2095 cal-hebrew
2096 calendar-hebrew-month-name-array-leap-year
2097 hebrew-diary-entry-symbol)
2098 (diary-font-lock-keywords-1 diary-islamic-mark-entries
2099 diary-islamic-list-entries
2100 cal-islam
2101 calendar-islamic-month-name-array
2102 islamic-diary-entry-symbol)
2103 (diary-font-lock-keywords-1 diary-bahai-mark-entries
2104 diary-bahai-list-entries
2105 cal-bahai
2106 calendar-bahai-month-name-array
2107 bahai-diary-entry-symbol)
2108 (list
2109 (cons
2110 (format "^%s.*$" (regexp-quote diary-include-string))
2111 'font-lock-keyword-face)
2112 (cons
2113 (format "^%s?\\(%s\\)" (regexp-quote diary-nonmarking-symbol)
2114 (regexp-quote sexp-diary-entry-symbol))
2115 '(1 font-lock-reference-face))
2116 (cons
2117 (format "^%s" (regexp-quote diary-nonmarking-symbol))
2118 'font-lock-reference-face)
2119 (cons
2120 (format "^%s?%s" (regexp-quote diary-nonmarking-symbol)
2121 (regexp-opt (mapcar 'regexp-quote
2122 (list hebrew-diary-entry-symbol
2123 islamic-diary-entry-symbol
2124 bahai-diary-entry-symbol))
2126 '(1 font-lock-reference-face))
2127 '(diary-font-lock-sexps . font-lock-keyword-face)
2128 `(,(format "\\(^\\|\\s-\\)%s\\(-%s\\)?" diary-time-regexp
2129 diary-time-regexp)
2130 . 'diary-time))))
2132 (defvar diary-font-lock-keywords (diary-font-lock-keywords)
2133 "Forms to highlight in `diary-mode'.")
2135 ;;;###autoload
2136 (define-derived-mode diary-mode fundamental-mode "Diary"
2137 "Major mode for editing the diary file."
2138 (set (make-local-variable 'font-lock-defaults)
2139 '(diary-font-lock-keywords t))
2140 (add-to-invisibility-spec '(diary . nil))
2141 (add-hook 'after-save-hook 'diary-redraw-calendar nil t)
2142 (if diary-header-line-flag
2143 (setq header-line-format diary-header-line-format)))
2146 ;;; Fancy Diary Mode.
2148 (defvar diary-fancy-date-pattern
2149 (concat
2150 (let ((dayname (diary-name-pattern calendar-day-name-array nil t))
2151 (monthname (diary-name-pattern calendar-month-name-array nil t))
2152 (day "[0-9]+")
2153 (month "[0-9]+")
2154 (year "-?[0-9]+"))
2155 (mapconcat 'eval calendar-date-display-form ""))
2156 ;; Optional ": holiday name" after the date.
2157 "\\(: .*\\)?")
2158 "Regular expression matching a date header in Fancy Diary.")
2160 (defvar fancy-diary-font-lock-keywords
2161 (list
2162 (list
2163 ;; Any number of " other holiday name" lines, followed by "==" line.
2164 (concat diary-fancy-date-pattern "\\(\n +.*\\)*\n=+$")
2165 '(0 (progn (put-text-property (match-beginning 0) (match-end 0)
2166 'font-lock-multiline t)
2167 diary-face)))
2168 '("^.*\\([aA]nniversary\\|[bB]irthday\\).*$" . 'diary-anniversary)
2169 '("^.*Yahrzeit.*$" . font-lock-reference-face)
2170 '("^\\(Erev \\)?Rosh Hodesh.*" . font-lock-function-name-face)
2171 '("^Day.*omer.*$" . font-lock-builtin-face)
2172 '("^Parashat.*$" . font-lock-comment-face)
2173 `(,(format "\\(^\\|\\s-\\)%s\\(-%s\\)?" diary-time-regexp
2174 diary-time-regexp) . 'diary-time))
2175 "Keywords to highlight in fancy diary display.")
2177 ;; If region looks like it might start or end in the middle of a
2178 ;; multiline pattern, extend the region to encompass the whole pattern.
2179 (defun diary-fancy-font-lock-fontify-region-function (beg end &optional verbose)
2180 "Function to use for `font-lock-fontify-region-function' in Fancy Diary.
2181 Needed to handle multiline keyword in `fancy-diary-font-lock-keywords'.
2182 Fontify the region between BEG and END, quietly unless VERBOSE is non-nil."
2183 (goto-char beg)
2184 (forward-line 0)
2185 (if (looking-at "=+$") (forward-line -1))
2186 (while (and (looking-at " +[^ ]")
2187 (zerop (forward-line -1))))
2188 ;; This check not essential.
2189 (if (looking-at diary-fancy-date-pattern)
2190 (setq beg (line-beginning-position)))
2191 (goto-char end)
2192 (forward-line 0)
2193 (while (and (looking-at " +[^ ]")
2194 (zerop (forward-line 1))))
2195 (if (looking-at "=+$")
2196 (setq end (line-beginning-position 2)))
2197 (font-lock-default-fontify-region beg end verbose))
2199 (define-derived-mode fancy-diary-display-mode fundamental-mode
2200 "Diary"
2201 "Major mode used while displaying diary entries using Fancy Display."
2202 (set (make-local-variable 'font-lock-defaults)
2203 '(fancy-diary-font-lock-keywords
2204 t nil nil nil
2205 (font-lock-fontify-region-function
2206 . diary-fancy-font-lock-fontify-region-function)))
2207 (local-set-key "q" 'quit-window))
2210 ;; Following code from Dave Love <fx@gnu.org>.
2211 ;; Import Outlook-format appointments from mail messages in Gnus or
2212 ;; Rmail using command `diary-from-outlook'. This, or the specialized
2213 ;; functions `diary-from-outlook-gnus' and `diary-from-outlook-rmail',
2214 ;; could be run from hooks to notice appointments automatically (in
2215 ;; which case they will prompt about adding to the diary). The
2216 ;; message formats recognized are customizable through
2217 ;; `diary-outlook-formats'.
2219 (defvar subject) ; bound in diary-from-outlook-gnus
2221 (defun diary-from-outlook-internal (&optional test-only)
2222 "Snarf a diary entry from a message assumed to be from MS Outlook.
2223 Assumes `body' is bound to a string comprising the body of the message and
2224 `subject' is bound to a string comprising its subject.
2225 Arg TEST-ONLY non-nil means return non-nil if and only if the
2226 message contains an appointment, don't make a diary entry."
2227 (catch 'finished
2228 (let (format-string)
2229 (dotimes (i (length diary-outlook-formats))
2230 (when (eq 0 (string-match (car (nth i diary-outlook-formats))
2231 body))
2232 (unless test-only
2233 (setq format-string (cdr (nth i diary-outlook-formats)))
2234 (save-excursion
2235 (save-window-excursion
2236 ;; Fixme: References to optional fields in the format
2237 ;; are treated literally, not replaced by the empty
2238 ;; string. I think this is an Emacs bug.
2239 (make-diary-entry
2240 (format (replace-match (if (functionp format-string)
2241 (funcall format-string body)
2242 format-string)
2243 t nil (match-string 0 body))
2244 subject))
2245 (save-buffer))))
2246 (throw 'finished t))))
2247 nil))
2249 (defvar gnus-article-mime-handles)
2250 (defvar gnus-article-buffer)
2252 (autoload 'gnus-fetch-field "gnus-util")
2253 (autoload 'gnus-narrow-to-body "gnus")
2254 (autoload 'mm-get-part "mm-decode")
2256 (defun diary-from-outlook-gnus (&optional noconfirm)
2257 "Maybe snarf diary entry from Outlook-generated message in Gnus.
2258 Unless the optional argument NOCONFIRM is non-nil (which is the case when
2259 this function is called interactively), then if an entry is found the
2260 user is asked to confirm its addition.
2261 Add this function to `gnus-article-prepare-hook' to notice appointments
2262 automatically."
2263 (interactive "p")
2264 (with-current-buffer gnus-article-buffer
2265 (let ((subject (gnus-fetch-field "subject"))
2266 (body (if gnus-article-mime-handles
2267 ;; We're multipart. Don't get confused by part
2268 ;; buttons &c. Assume info is in first part.
2269 (mm-get-part (nth 1 gnus-article-mime-handles))
2270 (save-restriction
2271 (gnus-narrow-to-body)
2272 (buffer-string)))))
2273 (when (diary-from-outlook-internal t)
2274 (when (or noconfirm (y-or-n-p "Snarf diary entry? "))
2275 (diary-from-outlook-internal)
2276 (message "Diary entry added"))))))
2278 (custom-add-option 'gnus-article-prepare-hook 'diary-from-outlook-gnus)
2280 (defvar rmail-buffer)
2282 (defun diary-from-outlook-rmail (&optional noconfirm)
2283 "Maybe snarf diary entry from Outlook-generated message in Rmail.
2284 Unless the optional argument NOCONFIRM is non-nil (which is the case when
2285 this function is called interactively), then if an entry is found the
2286 user is asked to confirm its addition."
2287 (interactive "p")
2288 (with-current-buffer rmail-buffer
2289 (let ((subject (mail-fetch-field "subject"))
2290 (body (buffer-substring (save-excursion
2291 (rfc822-goto-eoh)
2292 (point))
2293 (point-max))))
2294 (when (diary-from-outlook-internal t)
2295 (when (or noconfirm (y-or-n-p "Snarf diary entry? "))
2296 (diary-from-outlook-internal)
2297 (message "Diary entry added"))))))
2299 (defun diary-from-outlook (&optional noconfirm)
2300 "Maybe snarf diary entry from current Outlook-generated message.
2301 Currently knows about Gnus and Rmail modes. Unless the optional
2302 argument NOCONFIRM is non-nil (which is the case when this
2303 function is called interactively), then if an entry is found the
2304 user is asked to confirm its addition."
2305 (interactive "p")
2306 (let ((func (cond
2307 ((eq major-mode 'rmail-mode)
2308 #'diary-from-outlook-rmail)
2309 ((memq major-mode '(gnus-summary-mode gnus-article-mode))
2310 #'diary-from-outlook-gnus)
2311 (t (error "Don't know how to snarf in `%s'" major-mode)))))
2312 (funcall func noconfirm)))
2314 (provide 'diary-lib)
2316 ;; arch-tag: 22dd506e-2e33-410d-9ae1-095a0c1b2010
2317 ;;; diary-lib.el ends here