summaryrefslogtreecommitdiff
path: root/bits/bbdb-anniv.el
blob: 9e6205d6632fc7b4f7444d832181242d4841f837 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
;;; bbdb-anniv.el --- Get anniversaries from BBDB

;; Copyright (C) 1998 Ivar Rummelhoff

;; Author: Ivar Rummelhoff <ivarru@math.uio.no>
;; Maintainer: Ivar Rummelhoff <ivarru@math.uio.no>
;; Created: 11 March 1998
;; Time-stamp: <00/08/07 10:52:12 ivarru>
;; Keywords: calendar

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; If you have not received a copy of the GNU General Public License
;; along with this software, it can be obtained from the GNU Project's
;; World Wide Web server (http://www.gnu.org/copyleft/gpl.html), from
;; its FTP server (ftp://ftp.gnu.org/pub/gnu/GPL), by sending an electronic
;; mail to this program's maintainer or by writing to the Free Software
;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;;; Commentary:

;; (require 'bbdb-anniv)
;; (add-hook 'list-diary-entries-hook #'bbdb-include-anniversaries)
;;
;; will include BBDB-anniversaries when the diary is displayed
;; (fancy).  The anniversaries are stored in the field `anniversary'
;; in the format
;;
;;     [YYYY-MM-DD CLASS-OR-FORMAT-STRING]
;;     {\nYYYY-MM-DD CLASS-OR-FORMAT-STRING}*
;;
;; CLASS-OR-FORMAT-STRING is one of two things:
;;
;;  * an identifier for a class of anniversaries (eg. birthday or
;;    wedding) from `bbdb-anniversary-format-alist'.
;;  * the (format) string displayed in the diary.
;;
;; It defaults to the value of `bbdb-default-anniversary-format'
;; ("birthday" by default).
;;
;; The substitutions in the format string are (in order):
;;  * the name of the record containing this anniversary
;;  * the number of years
;;  * an ordinal suffix (st, nd, rd, th) for the year
;;
;; See the documentation of `bbdb-anniversary-format-alist' for
;; further options.
;;
;; Example (my own record):
;;
;;       1973-06-22 
;;       20??-??-?? wedding
;;       1998-03-12 %s created bbdb-anniv.el %d years ago
;;
;; If you use the hook `sort-diary-entries', you should make sure that
;; it is executed after `bbdb-include-anniversaries'.
;;

(require 'bbdb)
(require 'diary-lib)
(eval-when-compile (require 'cl))

;;;###autoload
(defgroup bbdb-utilities-anniversaries nil
  "Customizations for including diary anniversaries from BBDB."
  :link '(emacs-library-link :tag "Lisp File" "bbdb-anniv.el")
  :group 'bbdb-utilities)

;;;###autoload
(defcustom bbdb-anniversaries nil
  "Should BBDB anniversaries be included when the diary is displayed (fancy)?
You must modify via \\[customize] for this variable to have an effect."
  :set #'(lambda (symbol value)
	   (if value
	       (add-hook 'list-diary-entries-hook
			 #'bbdb-include-anniversaries)
	     (remove-hook 'list-diary-entries-hook
			  #'bbdb-include-anniversaries)))
  :type 'boolean
  :group 'bbdb-utilities-anniversaries
  :require 'bbdb-anniv)

(defcustom bbdb-default-anniversary-format "birthday"
  "Default anniversary class"
  :type  'string
  :group 'bbdb-utilities-anniversaries
  :require 'bbdb)

(defcustom bbdb-anniversary-format-alist 
  '( ("birthday" . "Birthday: %s (%d%s)")
     ("wedding"  . "%s's %d%s wedding anniversary") )
  "How different types of anniversaries should be formatted.
An alist of elements (STRING . FORMAT) where STRING is the name of an
anniversary class and format is either:
1) A format string with the following substitutions (in order):
    * the name of the record containing this anniversary
    * the number of years
    * an ordinal suffix (st, nd, rd, th) for the year

2) A function to be called with three arguments: NAME YEARS SUFFIX
   (string int string) returning a string for the diary or nil.

3) An emacs lisp form that should evaluate to a string (or nil) in the
   scope of variables NAME, YEARS and SUFFIX (among others)."
  :type 'sexp
  :group 'bbdb-utilities-anniversaries
  :require 'bbdb)

(defcustom bbdb-anniversary-field 'anniversary
  "Which BBDB field contains anniversaries."
  :type    'symbol
  :group   'bbdb-utilities-anniversaries
  :require 'bbdb)

(defcustom bbdb-extract-date-fun 'bbdb-anniv-extract-date
  "How to retrieve `month date year' from the anniversary field."
  :type 'function
  :group 'bbdb-utilities-anniversaries
  :require 'bbdb)

(defcustom bbdb-anniversary-reminder-days 0
  "Number of days warning you are given of an impending anniversary.
Modify this to give yourself a n-day warning of those important
anniversaries. This works in a naive fashion, extending (forwards) the
range of days for which diary entries are being listed. When set to 0, 
the behaviour is to only list anniversaries on the day."
  :type 'integer
  :group 'bbdb-utilities-anniversaries
  :require 'bbdb)

;; YYYY-MM-DD  =>  (month date year)
(defun bbdb-anniv-extract-date (time-str)
  (multiple-value-bind (y m d) (bbdb-split time-str "-")
    (list (string-to-number m)
	  (string-to-number d)
	  (string-to-number y))))

(defun bbdb-anniv-split (str)
  (let ((pos (string-match "[ \t]" str)))
    (if pos (list (substring str 0 pos)
		  (bbdb-string-trim (substring str pos)))
      (list str nil))))


(defvar number)
(defvar original-date)

;;;###autoload
(defun bbdb-include-anniversaries ()
  (let ((dates (loop repeat (+ number bbdb-anniversary-reminder-days)
		     for num from (calendar-absolute-from-gregorian
				   original-date)
		     for date = original-date
		     then (calendar-gregorian-from-absolute num)
		     ;; ((MM . DD) . YYYY)
		     collect (cons (cons (extract-calendar-month date)
					 (extract-calendar-day date))
				   (extract-calendar-year date))))
	annivs date years
	split class form)
    (dolist (rec (bbdb-records))
      (when (setq annivs (bbdb-record-getprop
			  rec bbdb-anniversary-field))
	(setq annivs (bbdb-split annivs "\n"))
	(while annivs
	  (setq split (bbdb-anniv-split (pop annivs)))
	  (multiple-value-bind (m d y)
	      (funcall bbdb-extract-date-fun (car split))

	    (when (and (or (setq date (assoc (cons m d) dates))
			   (and (= d 29)
				(= m 2)
				(setq date (assoc '(3 . 1) dates))
				(not (calendar-leap-year-p (cdr date)))))
		       (< 0 (setq years (-  (cdr date) y))))
	      (let* ((class (or (cadr split)
				bbdb-default-anniversary-format))
		     (form (or (cdr (assoc class
					   bbdb-anniversary-format-alist))
			       class))	; (as format string)
		     (name (bbdb-record-name rec))
		     (suffix (diary-ordinal-suffix years))
		     (text (cond
			    ((functionp form)
			     (funcall form name years suffix))
			    ((listp form) (eval form))
			    (t (format form name years suffix)))))
		(when text
		  (bbdb-anniv-add
		   (list (caar date) (cdar date) (cdr date)) ; MM DD YYYY
		   text))))))))))

(defun bbdb-anniv-add (a b)
  (add-to-diary-list a b ""))

(provide 'bbdb-anniv)

;;; bbdb-anniv.el ends here