summaryrefslogtreecommitdiff
path: root/lisp/cal.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/cal.el')
-rw-r--r--lisp/cal.el242
1 files changed, 242 insertions, 0 deletions
diff --git a/lisp/cal.el b/lisp/cal.el
new file mode 100644
index 00000000000..2c39c4c5147
--- /dev/null
+++ b/lisp/cal.el
@@ -0,0 +1,242 @@
+;; Display a calendar inside GNU Emacs.
+;; Copyright (C) 1988 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs 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.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;;
+;; Comments, corrections, and improvements should be sent to
+;; Edward M. Reingold Department of Computer Science
+;; (217) 333-6733 University of Illinois at Urbana-Champaign
+;; reingold@a.cs.uiuc.edu 1304 West Springfield Avenue
+;; Urbana, Illinois 61801
+;;
+;; The author gratefully acknowledges the patient help of Richard Stallman
+;; in making this function into a reasonable piece of code!
+;;
+;; Modification for month-offset arguments suggested and implemented by
+;; Constantine Rasmussen Sun Microsystems, East Coast Division
+;; (617) 671-0404 2 Federal Street; Billerica, Ma. 01824
+;; ARPA: cdr@sun.com USENET: {cbosgd,decvax,hplabs,seismo}!sun!suneast!cdr
+;;
+;; Modification to mark current day with stars suggested by
+;; Franklin Davis Thinking Machines Corp
+;; (617) 876-1111 245 First Street, Cambridge, MA 02142
+;; fad@think.com
+
+(defvar calendar-hook nil
+ "List of functions called after the calendar buffer has been prepared with
+the calendar of the current month. This can be used, for example, to highlight
+today's date with asterisks--a function star-date is included for this purpose.
+The variable offset-calendar-hook is the list of functions called when the
+calendar function was called for a past or future month.")
+
+(defvar offset-calendar-hook nil
+ "List of functions called after the calendar buffer has been prepared with
+the calendar of a past or future month. The variable calendar-hook is the
+list of functions called when the calendar function was called for the
+current month.")
+
+(defun calendar (&optional month-offset)
+ "Display three-month calendar in another window.
+The three months appear side by side, with the current month in the middle
+surrounded by the previous and next months. The cursor is put on today's date.
+
+An optional prefix argument ARG causes the calendar displayed to be
+ARG months in the future if ARG is positive or in the past if ARG is
+negative; in this case the cursor goes on the first day of the month.
+
+The Gregorian calendar is assumed.
+
+After preparing the calendar window, the hooks calendar-hook are run
+when the calendar is for the current month--that is, the was no prefix
+argument. If the calendar is for a future or past month--that is, there
+was a prefix argument--the hooks offset-calendar-hook are run. Thus, for
+example, setting calendar-hooks to 'star-date will cause today's date to be
+replaced by asterisks to highlight it in the window."
+ (interactive "P")
+ (if month-offset (setq month-offset (prefix-numeric-value month-offset)))
+ (let ((today (make-marker)))
+ (save-excursion
+ (set-buffer (get-buffer-create "*Calendar*"))
+ (setq buffer-read-only t)
+ (let*
+ ((buffer-read-only nil)
+ ;; Get today's date and extract the day, month and year.
+ (date (current-time-string))
+ (garbage (string-match
+ " \\([A-Z][a-z][a-z]\\) *\\([0-9]*\\) .* \\([0-9]*\\)$"
+ date))
+ (day (or (and month-offset 1)
+ (string-to-int
+ (substring date (match-beginning 2) (match-end 2)))))
+ (month
+ (cdr (assoc
+ (substring date (match-beginning 1) (match-end 1))
+ '(("Jan" . 1) ("Feb" . 2) ("Mar" . 3) ("Apr" . 4)
+ ("May" . 5) ("Jun" . 6) ("Jul" . 7) ("Aug" . 8)
+ ("Sep" . 9) ("Oct" . 10) ("Nov" . 11) ("Dec" . 12)))))
+ (year (string-to-int
+ (substring date (match-beginning 3) (match-end 3)))))
+ (erase-buffer)
+ ;; If user requested a month in the future or the past,
+ ;; advance the variables MONTH and YEAR to describe that one.
+ (cond
+ (month-offset
+ (let ((year-month (+ (+ (* year 12) (- month 1)) month-offset)))
+ (setq month (+ (% year-month 12) 1))
+ (setq year (/ year-month 12)))))
+ ;; Generate previous month, starting at left margin.
+ (generate-month;; previous month
+ (if (= month 1) 12 (1- month))
+ (if (= month 1) (1- year) year)
+ 0)
+ ;; Generate this month, starting at column 24,
+ ;; and record where today's date appears, in the marker TODAY.
+ (goto-char (point-min))
+ (set-marker today (generate-month month year 24 day))
+ ;; Generate the following month, starting at column 48.
+ (goto-char (point-min))
+ (generate-month
+ (if (= month 12) 1 (1+ month))
+ (if (= month 12) (1+ year) year)
+ 48)))
+ ;; Display the buffer and put cursor on today's date.
+ ;; Do it in another window, but if this buffer is already visible,
+ ;; just select its window.
+ (pop-to-buffer "*Calendar*")
+ (goto-char (marker-position today))
+ ;; Make TODAY point nowhere so it won't slow down buffer editing until GC.
+ (set-marker today nil))
+ ;; Make the window just tall enough for its contents.
+ (let ((h (1- (window-height)))
+ (l (count-lines (point-min) (point-max))))
+ (or (= (+ (window-height (selected-window))
+ (window-height (minibuffer-window)))
+ (screen-height))
+ (<= h l)
+ (shrink-window (- h l))))
+ (if month-offset
+ (run-hooks 'offset-calendar-hook)
+ (run-hooks 'calendar-hook)))
+
+(defun leap-year-p (year)
+ "Returns true if YEAR is a Gregorian leap year, and false if not."
+ (or
+ (and (= (% year 4) 0)
+ (/= (% year 100) 0))
+ (= (% year 400) 0)))
+
+(defun day-number (month day year)
+ "Return day-number within year (origin-1) of the date MONTH DAY YEAR.
+For example, (day-number 1 1 1987) returns the value 1,
+while (day-number 12 31 1980) returns 366."
+;;
+;; an explanation of the calculation can be found in PascAlgorithms by
+;; Edward and Ruth Reingold, Scott-foresman/Little, Brown, 1988.
+;;
+ (let ((day-of-year (+ day (* 31 (1- month)))))
+ (if (> month 2)
+ (progn
+ (setq day-of-year (- day-of-year (/ (+ 23 (* 4 month)) 10)))
+ (if (leap-year-p year)
+ (setq day-of-year (1+ day-of-year)))))
+ day-of-year))
+
+(defun day-of-week (month day year)
+ "Returns the day-of-the-week index of MONTH DAY, YEAR.
+Value is 0 for Sunday, 1 for Monday, etc."
+;;
+;; Done by calculating the number of days elapsed since the (imaginary)
+;; Gregorian date Sunday, December 31, 1 BC and taking that number mod 7.
+;;
+ (%
+ (-
+ (+ (day-number month day year)
+ (* 365 (1- year))
+ (/ (1- year) 4))
+ (let ((correction (* (/ (1- year) 100) 3)))
+ (if (= (% correction 4) 0)
+ (/ correction 4)
+ (1+ (/ correction 4)))))
+ 7))
+
+(defun generate-month (month year indent &optional day)
+ "Produce a calendar for MONTH, YEAR on the Gregorian calendar, inserted
+in the buffer starting at the line on which point is currently located, but
+indented INDENT spaces. The position in the buffer of the optional
+parameter DAY is returned. The indentation is done from the first
+character on the line and does not disturb the first INDENT characters on
+the line."
+ (let* ((first-day-of-month (day-of-week month 1 year))
+ (first-saturday (- 7 first-day-of-month))
+ (last-of-month
+ (if (and (leap-year-p year) (= month 2))
+ 29
+ (aref [31 28 31 30 31 30 31 31 30 31 30 31] (1- month))))
+ (month-name
+ (aref ["January" "February" "March" "April" "May" "June"
+ "July" "August" "September" "October" "November" "December"]
+ (1- month))))
+ (insert-indented (format " %s %d" month-name year) indent t)
+ (insert-indented " S M Tu W Th F S" indent t)
+ (insert-indented "" indent);; move point to appropriate spot on line
+ (let ((i 0)) ;; add blank days before the first of the month
+ (while (<= (setq i (1+ i)) first-day-of-month)
+ (insert " ")))
+ (let ((i 0)
+ (day-marker)) ;; put in the days of the month
+ (while (<= (setq i (1+ i)) last-of-month)
+ (insert (format "%2d " i))
+ (and
+ day
+ (= i day) ;; save the location of the specified day
+ (setq day-marker (- (point) 2)))
+ (and (= (% i 7) (% first-saturday 7))
+ (/= i last-of-month)
+ (insert-indented "" 0 t) ;; force onto following line
+ (insert-indented "" indent))) ;; go to proper spot on line
+ day-marker)))
+
+(defun insert-indented (string indent &optional newline)
+ "Insert STRING at column INDENT.
+If the optional parameter NEWLINE is true, leave point at start of next
+line, inserting a newline if there was no next line; otherwise, leave point
+after the inserted text. Value is always `t'."
+ ;; Try to move to that column.
+ (move-to-column indent)
+ ;; If line is too short, indent out to that column.
+ (if (< (current-column) indent)
+ (indent-to indent))
+ (insert string)
+ ;; Advance to next line, if requested.
+ (if newline
+ (progn
+ (end-of-line)
+ (if (eobp)
+ (newline)
+ (forward-line 1))))
+ t)
+
+(defun star-date ()
+ "Replace today's date with asterisks in the calendar window.
+This function can be used with the calendar-hook run after the
+calendar window has been prepared."
+ (let ((buffer-read-only nil))
+ (forward-char 1)
+ (delete-backward-char 2)
+ (insert "**")
+ (backward-char 1)))
+