comparison lisp/calendar/cal-islam.el @ 13053:621d48117fde

Initial revision
author Edward M. Reingold <reingold@emr.cs.iit.edu>
date Thu, 21 Sep 1995 03:11:06 +0000
parents
children 83f275dcd93a
comparison
equal deleted inserted replaced
13052:71be832cf34a 13053:621d48117fde
1 ;;; cal-islamic.el --- calendar functions for the Islamic calendar.
2
3 ;; Copyright (C) 1995 Free Software Foundation, Inc.
4
5 ;; Author: Edward M. Reingold <reingold@cs.uiuc.edu>
6 ;; Keywords: calendar
7 ;; Human-Keywords: Islamic calendar, calendar, diary
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to
23 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
24
25 ;;; Commentary:
26
27 ;; This collection of functions implements the features of calendar.el and
28 ;; diary.el that deal with the Islamic calendar.
29
30 ;; Comments, corrections, and improvements should be sent to
31 ;; Edward M. Reingold Department of Computer Science
32 ;; (217) 333-6733 University of Illinois at Urbana-Champaign
33 ;; reingold@cs.uiuc.edu 1304 West Springfield Avenue
34 ;; Urbana, Illinois 61801
35
36 ;;; Code:
37
38 (require 'cal-julian)
39
40 (defvar calendar-islamic-month-name-array
41 ["Muharram" "Safar" "Rabi I" "Rabi II" "Jumada I" "Jumada II"
42 "Rajab" "Sha'ban" "Ramadan" "Shawwal" "Dhu al-Qada" "Dhu al-Hijjah"])
43
44 (defvar calendar-islamic-epoch (calendar-absolute-from-julian '(7 16 622))
45 "Absolute date of start of Islamic calendar = August 29, 284 A.D. (Julian).")
46
47 (defun islamic-calendar-leap-year-p (year)
48 "Returns t if YEAR is a leap year on the Islamic calendar."
49 (memq (% year 30)
50 (list 2 5 7 10 13 16 18 21 24 26 29)))
51
52 (defun islamic-calendar-last-day-of-month (month year)
53 "The last day in MONTH during YEAR on the Islamic calendar."
54 (cond
55 ((memq month (list 1 3 5 7 9 11)) 30)
56 ((memq month (list 2 4 6 8 10)) 29)
57 (t (if (islamic-calendar-leap-year-p year) 30 29))))
58
59 (defun islamic-calendar-day-number (date)
60 "Return the day number within the year of the Islamic date DATE."
61 (let* ((month (extract-calendar-month date))
62 (day (extract-calendar-day date)))
63 (+ (* 30 (/ month 2))
64 (* 29 (/ (1- month) 2))
65 day)))
66
67 (defun calendar-absolute-from-islamic (date)
68 "Absolute date of Islamic DATE.
69 The absolute date is the number of days elapsed since the (imaginary)
70 Gregorian date Sunday, December 31, 1 BC."
71 (let* ((month (extract-calendar-month date))
72 (day (extract-calendar-day date))
73 (year (extract-calendar-year date))
74 (y (% year 30))
75 (leap-years-in-cycle
76 (cond
77 ((< y 3) 0) ((< y 6) 1) ((< y 8) 2) ((< y 11) 3) ((< y 14) 4)
78 ((< y 17) 5) ((< y 19) 6) ((< y 22) 7) ((< y 25) 8) ((< y 27) 9)
79 (t 10))))
80 (+ (islamic-calendar-day-number date);; days so far this year
81 (* (1- year) 354) ;; days in all non-leap years
82 (* 11 (/ year 30)) ;; leap days in complete cycles
83 leap-years-in-cycle ;; leap days this cycle
84 (1- calendar-islamic-epoch)))) ;; days before start of calendar
85
86 (defun calendar-islamic-from-absolute (date)
87 "Compute the Islamic date (month day year) corresponding to absolute DATE.
88 The absolute date is the number of days elapsed since the (imaginary)
89 Gregorian date Sunday, December 31, 1 BC."
90 (if (< date calendar-islamic-epoch)
91 (list 0 0 0);; pre-Islamic date
92 (let* ((approx (/ (- date calendar-islamic-epoch)
93 355));; Approximation from below.
94 (year ;; Search forward from the approximation.
95 (+ approx
96 (calendar-sum y approx
97 (>= date (calendar-absolute-from-islamic
98 (list 1 1 (1+ y))))
99 1)))
100 (month ;; Search forward from Muharram.
101 (1+ (calendar-sum m 1
102 (> date
103 (calendar-absolute-from-islamic
104 (list m
105 (islamic-calendar-last-day-of-month
106 m year)
107 year)))
108 1)))
109 (day ;; Calculate the day by subtraction.
110 (- date
111 (1- (calendar-absolute-from-islamic (list month 1 year))))))
112 (list month day year))))
113
114 (defun calendar-islamic-date-string (&optional date)
115 "String of Islamic date before sunset of Gregorian DATE.
116 Returns the empty string if DATE is pre-Islamic.
117 Defaults to today's date if DATE is not given.
118 Driven by the variable `calendar-date-display-form'."
119 (let ((calendar-month-name-array calendar-islamic-month-name-array)
120 (islamic-date (calendar-islamic-from-absolute
121 (calendar-absolute-from-gregorian
122 (or date (calendar-current-date))))))
123 (if (< (extract-calendar-year islamic-date) 1)
124 ""
125 (calendar-date-string islamic-date nil t))))
126
127 (defun calendar-print-islamic-date ()
128 "Show the Islamic calendar equivalent of the date under the cursor."
129 (interactive)
130 (let ((i (calendar-islamic-date-string (calendar-cursor-to-date t))))
131 (if (string-equal i "")
132 (message "Date is pre-Islamic")
133 (message "Islamic date (until sunset): %s" i))))
134
135 (defun calendar-goto-islamic-date (date &optional noecho)
136 "Move cursor to Islamic DATE; echo Islamic date unless NOECHO is t."
137 (interactive
138 (let* ((today (calendar-current-date))
139 (year (calendar-read
140 "Islamic calendar year (>0): "
141 '(lambda (x) (> x 0))
142 (int-to-string
143 (extract-calendar-year
144 (calendar-islamic-from-absolute
145 (calendar-absolute-from-gregorian today))))))
146 (month-array calendar-islamic-month-name-array)
147 (completion-ignore-case t)
148 (month (cdr (assoc
149 (capitalize
150 (completing-read
151 "Islamic calendar month name: "
152 (mapcar 'list (append month-array nil))
153 nil t))
154 (calendar-make-alist month-array 1 'capitalize))))
155 (last (islamic-calendar-last-day-of-month month year))
156 (day (calendar-read
157 (format "Islamic calendar day (1-%d): " last)
158 '(lambda (x) (and (< 0 x) (<= x last))))))
159 (list (list month day year))))
160 (calendar-goto-date (calendar-gregorian-from-absolute
161 (calendar-absolute-from-islamic date)))
162 (or noecho (calendar-print-islamic-date)))
163
164 (defun diary-islamic-date ()
165 "Islamic calendar equivalent of date diary entry."
166 (let ((i (calendar-islamic-date-string (calendar-cursor-to-date t))))
167 (if (string-equal i "")
168 "Date is pre-Islamic"
169 (format "Islamic date (until sunset): %s" i))))
170
171 (defun holiday-islamic (month day string)
172 "Holiday on MONTH, DAY (Islamic) called STRING.
173 If MONTH, DAY (Islamic) is visible, the value returned is corresponding
174 Gregorian date in the form of the list (((month day year) STRING)). Returns
175 nil if it is not visible in the current calendar window."
176 (let* ((islamic-date (calendar-islamic-from-absolute
177 (calendar-absolute-from-gregorian
178 (list displayed-month 15 displayed-year))))
179 (m (extract-calendar-month islamic-date))
180 (y (extract-calendar-year islamic-date))
181 (date))
182 (if (< m 1)
183 nil;; Islamic calendar doesn't apply.
184 (increment-calendar-month m y (- 10 month))
185 (if (> m 7);; Islamic date might be visible
186 (let ((date (calendar-gregorian-from-absolute
187 (calendar-absolute-from-islamic (list month day y)))))
188 (if (calendar-date-is-visible-p date)
189 (list (list date string))))))))
190
191 (defun list-islamic-diary-entries ()
192 "Add any Islamic date entries from the diary file to `diary-entries-list'.
193 Islamic date diary entries must be prefaced by an `islamic-diary-entry-symbol'
194 \(normally an `I'). The same diary date forms govern the style of the Islamic
195 calendar entries, except that the Islamic month names must be spelled in full.
196 The Islamic months are numbered from 1 to 12 with Muharram being 1 and 12 being
197 Dhu al-Hijjah. If an Islamic date diary entry begins with a
198 `diary-nonmarking-symbol', the entry will appear in the diary listing, but will
199 not be marked in the calendar. This function is provided for use with the
200 `nongregorian-diary-listing-hook'."
201 (if (< 0 number)
202 (let ((buffer-read-only nil)
203 (diary-modified (buffer-modified-p))
204 (gdate original-date)
205 (mark (regexp-quote diary-nonmarking-symbol)))
206 (calendar-for-loop i from 1 to number do
207 (let* ((d diary-date-forms)
208 (idate (calendar-islamic-from-absolute
209 (calendar-absolute-from-gregorian gdate)))
210 (month (extract-calendar-month idate))
211 (day (extract-calendar-day idate))
212 (year (extract-calendar-year idate)))
213 (while d
214 (let*
215 ((date-form (if (equal (car (car d)) 'backup)
216 (cdr (car d))
217 (car d)))
218 (backup (equal (car (car d)) 'backup))
219 (dayname
220 (concat
221 (calendar-day-name gdate) "\\|"
222 (substring (calendar-day-name gdate) 0 3) ".?"))
223 (calendar-month-name-array
224 calendar-islamic-month-name-array)
225 (monthname
226 (concat
227 "\\*\\|"
228 (calendar-month-name month)))
229 (month (concat "\\*\\|0*" (int-to-string month)))
230 (day (concat "\\*\\|0*" (int-to-string day)))
231 (year
232 (concat
233 "\\*\\|0*" (int-to-string year)
234 (if abbreviated-calendar-year
235 (concat "\\|" (int-to-string (% year 100)))
236 "")))
237 (regexp
238 (concat
239 "\\(\\`\\|\^M\\|\n\\)" mark "?"
240 (regexp-quote islamic-diary-entry-symbol)
241 "\\("
242 (mapconcat 'eval date-form "\\)\\(")
243 "\\)"))
244 (case-fold-search t))
245 (goto-char (point-min))
246 (while (re-search-forward regexp nil t)
247 (if backup (re-search-backward "\\<" nil t))
248 (if (and (or (char-equal (preceding-char) ?\^M)
249 (char-equal (preceding-char) ?\n))
250 (not (looking-at " \\|\^I")))
251 ;; Diary entry that consists only of date.
252 (backward-char 1)
253 ;; Found a nonempty diary entry--make it visible and
254 ;; add it to the list.
255 (let ((entry-start (point))
256 (date-start))
257 (re-search-backward "\^M\\|\n\\|\\`")
258 (setq date-start (point))
259 (re-search-forward "\^M\\|\n" nil t 2)
260 (while (looking-at " \\|\^I")
261 (re-search-forward "\^M\\|\n" nil t))
262 (backward-char 1)
263 (subst-char-in-region date-start (point) ?\^M ?\n t)
264 (add-to-diary-list
265 gdate (buffer-substring entry-start (point)))))))
266 (setq d (cdr d))))
267 (setq gdate
268 (calendar-gregorian-from-absolute
269 (1+ (calendar-absolute-from-gregorian gdate)))))
270 (set-buffer-modified-p diary-modified))
271 (goto-char (point-min))))
272
273 (defun mark-islamic-diary-entries ()
274 "Mark days in the calendar window that have Islamic date diary entries.
275 Each entry in diary-file (or included files) visible in the calendar window
276 is marked. Islamic date entries are prefaced by a islamic-diary-entry-symbol
277 \(normally an `I'). The same diary-date-forms govern the style of the Islamic
278 calendar entries, except that the Islamic month names must be spelled in full.
279 The Islamic months are numbered from 1 to 12 with Muharram being 1 and 12 being
280 Dhu al-Hijjah. Islamic date diary entries that begin with a
281 diary-nonmarking-symbol will not be marked in the calendar. This function is
282 provided for use as part of the nongregorian-diary-marking-hook."
283 (let ((d diary-date-forms))
284 (while d
285 (let*
286 ((date-form (if (equal (car (car d)) 'backup)
287 (cdr (car d))
288 (car d)));; ignore 'backup directive
289 (dayname (diary-name-pattern calendar-day-name-array))
290 (monthname
291 (concat
292 (diary-name-pattern calendar-islamic-month-name-array t)
293 "\\|\\*"))
294 (month "[0-9]+\\|\\*")
295 (day "[0-9]+\\|\\*")
296 (year "[0-9]+\\|\\*")
297 (l (length date-form))
298 (d-name-pos (- l (length (memq 'dayname date-form))))
299 (d-name-pos (if (/= l d-name-pos) (+ 2 d-name-pos)))
300 (m-name-pos (- l (length (memq 'monthname date-form))))
301 (m-name-pos (if (/= l m-name-pos) (+ 2 m-name-pos)))
302 (d-pos (- l (length (memq 'day date-form))))
303 (d-pos (if (/= l d-pos) (+ 2 d-pos)))
304 (m-pos (- l (length (memq 'month date-form))))
305 (m-pos (if (/= l m-pos) (+ 2 m-pos)))
306 (y-pos (- l (length (memq 'year date-form))))
307 (y-pos (if (/= l y-pos) (+ 2 y-pos)))
308 (regexp
309 (concat
310 "\\(\\`\\|\^M\\|\n\\)"
311 (regexp-quote islamic-diary-entry-symbol)
312 "\\("
313 (mapconcat 'eval date-form "\\)\\(")
314 "\\)"))
315 (case-fold-search t))
316 (goto-char (point-min))
317 (while (re-search-forward regexp nil t)
318 (let* ((dd-name
319 (if d-name-pos
320 (buffer-substring
321 (match-beginning d-name-pos)
322 (match-end d-name-pos))))
323 (mm-name
324 (if m-name-pos
325 (buffer-substring
326 (match-beginning m-name-pos)
327 (match-end m-name-pos))))
328 (mm (string-to-int
329 (if m-pos
330 (buffer-substring
331 (match-beginning m-pos)
332 (match-end m-pos))
333 "")))
334 (dd (string-to-int
335 (if d-pos
336 (buffer-substring
337 (match-beginning d-pos)
338 (match-end d-pos))
339 "")))
340 (y-str (if y-pos
341 (buffer-substring
342 (match-beginning y-pos)
343 (match-end y-pos))))
344 (yy (if (not y-str)
345 0
346 (if (and (= (length y-str) 2)
347 abbreviated-calendar-year)
348 (let* ((current-y
349 (extract-calendar-year
350 (calendar-islamic-from-absolute
351 (calendar-absolute-from-gregorian
352 (calendar-current-date)))))
353 (y (+ (string-to-int y-str)
354 (* 100 (/ current-y 100)))))
355 (if (> (- y current-y) 50)
356 (- y 100)
357 (if (> (- current-y y) 50)
358 (+ y 100)
359 y)))
360 (string-to-int y-str)))))
361 (if dd-name
362 (mark-calendar-days-named
363 (cdr (assoc (capitalize (substring dd-name 0 3))
364 (calendar-make-alist
365 calendar-day-name-array
366 0
367 '(lambda (x) (substring x 0 3))))))
368 (if mm-name
369 (if (string-equal mm-name "*")
370 (setq mm 0)
371 (setq mm
372 (cdr (assoc
373 (capitalize mm-name)
374 (calendar-make-alist
375 calendar-islamic-month-name-array))))))
376 (mark-islamic-calendar-date-pattern mm dd yy)))))
377 (setq d (cdr d)))))
378
379 (defun mark-islamic-calendar-date-pattern (month day year)
380 "Mark dates in calendar window that conform to Islamic date MONTH/DAY/YEAR.
381 A value of 0 in any position is a wildcard."
382 (save-excursion
383 (set-buffer calendar-buffer)
384 (if (and (/= 0 month) (/= 0 day))
385 (if (/= 0 year)
386 ;; Fully specified Islamic date.
387 (let ((date (calendar-gregorian-from-absolute
388 (calendar-absolute-from-islamic
389 (list month day year)))))
390 (if (calendar-date-is-visible-p date)
391 (mark-visible-calendar-date date)))
392 ;; Month and day in any year--this taken from the holiday stuff.
393 (let* ((islamic-date (calendar-islamic-from-absolute
394 (calendar-absolute-from-gregorian
395 (list displayed-month 15 displayed-year))))
396 (m (extract-calendar-month islamic-date))
397 (y (extract-calendar-year islamic-date))
398 (date))
399 (if (< m 1)
400 nil;; Islamic calendar doesn't apply.
401 (increment-calendar-month m y (- 10 month))
402 (if (> m 7);; Islamic date might be visible
403 (let ((date (calendar-gregorian-from-absolute
404 (calendar-absolute-from-islamic
405 (list month day y)))))
406 (if (calendar-date-is-visible-p date)
407 (mark-visible-calendar-date date)))))))
408 ;; Not one of the simple cases--check all visible dates for match.
409 ;; Actually, the following code takes care of ALL of the cases, but
410 ;; it's much too slow to be used for the simple (common) cases.
411 (let ((m displayed-month)
412 (y displayed-year)
413 (first-date)
414 (last-date))
415 (increment-calendar-month m y -1)
416 (setq first-date
417 (calendar-absolute-from-gregorian
418 (list m 1 y)))
419 (increment-calendar-month m y 2)
420 (setq last-date
421 (calendar-absolute-from-gregorian
422 (list m (calendar-last-day-of-month m y) y)))
423 (calendar-for-loop date from first-date to last-date do
424 (let* ((i-date (calendar-islamic-from-absolute date))
425 (i-month (extract-calendar-month i-date))
426 (i-day (extract-calendar-day i-date))
427 (i-year (extract-calendar-year i-date)))
428 (and (or (zerop month)
429 (= month i-month))
430 (or (zerop day)
431 (= day i-day))
432 (or (zerop year)
433 (= year i-year))
434 (mark-visible-calendar-date
435 (calendar-gregorian-from-absolute date)))))))))
436
437 (defun insert-islamic-diary-entry (arg)
438 "Insert a diary entry.
439 For the Islamic date corresponding to the date indicated by point.
440 Prefix arg will make the entry nonmarking."
441 (interactive "P")
442 (let* ((calendar-month-name-array calendar-islamic-month-name-array))
443 (make-diary-entry
444 (concat
445 islamic-diary-entry-symbol
446 (calendar-date-string
447 (calendar-islamic-from-absolute
448 (calendar-absolute-from-gregorian
449 (calendar-cursor-to-date t)))
450 nil t))
451 arg)))
452
453 (defun insert-monthly-islamic-diary-entry (arg)
454 "Insert a monthly diary entry.
455 For the day of the Islamic month corresponding to the date indicated by point.
456 Prefix arg will make the entry nonmarking."
457 (interactive "P")
458 (let* ((calendar-date-display-form
459 (if european-calendar-style '(day " * ") '("* " day )))
460 (calendar-month-name-array calendar-islamic-month-name-array))
461 (make-diary-entry
462 (concat
463 islamic-diary-entry-symbol
464 (calendar-date-string
465 (calendar-islamic-from-absolute
466 (calendar-absolute-from-gregorian
467 (calendar-cursor-to-date t)))))
468 arg)))
469
470 (defun insert-yearly-islamic-diary-entry (arg)
471 "Insert an annual diary entry.
472 For the day of the Islamic year corresponding to the date indicated by point.
473 Prefix arg will make the entry nonmarking."
474 (interactive "P")
475 (let* ((calendar-date-display-form
476 (if european-calendar-style
477 '(day " " monthname)
478 '(monthname " " day)))
479 (calendar-month-name-array calendar-islamic-month-name-array))
480 (make-diary-entry
481 (concat
482 islamic-diary-entry-symbol
483 (calendar-date-string
484 (calendar-islamic-from-absolute
485 (calendar-absolute-from-gregorian
486 (calendar-cursor-to-date t)))))
487 arg)))
488
489 (provide 'cal-islamic)
490
491 ;;; cal-islamic.el ends here