aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Blandy1992-08-12 12:49:57 +0000
committerJim Blandy1992-08-12 12:49:57 +0000
commit9f34a2a0c82e7323e825471b10b54fa60ea8859f (patch)
treef2962b8c65ccfcb9d4d9a66cda7bec3f5f4a1684
parent98a91e8eaa616b6d26f23bc55d3861a93de5aff0 (diff)
downloademacs-9f34a2a0c82e7323e825471b10b54fa60ea8859f.tar.gz
emacs-9f34a2a0c82e7323e825471b10b54fa60ea8859f.zip
Initial revision
-rw-r--r--lisp/calendar/solar.el443
1 files changed, 443 insertions, 0 deletions
diff --git a/lisp/calendar/solar.el b/lisp/calendar/solar.el
new file mode 100644
index 00000000000..6a6956ad81d
--- /dev/null
+++ b/lisp/calendar/solar.el
@@ -0,0 +1,443 @@
1;;; solar.el --- calendar functions for solar events.
2
3;; Copyright (C) 1992 Free Software Foundation, Inc.
4
5;; Author: Edward M. Reingold <reingold@cs.uiuc.edu>
6;; Keywords: sunrise, sunset, equinox, solstice, calendar, diary, holidays
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is distributed in the hope that it will be useful,
11;; but WITHOUT ANY WARRANTY. No author or distributor
12;; accepts responsibility to anyone for the consequences of using it
13;; or for whether it serves any particular purpose or works at all,
14;; unless he says so in writing. Refer to the GNU Emacs General Public
15;; License for full details.
16
17;; Everyone is granted permission to copy, modify and redistribute
18;; GNU Emacs, but only under the conditions described in the
19;; GNU Emacs General Public License. A copy of this license is
20;; supposed to have been given to you along with GNU Emacs so you
21;; can know your rights and responsibilities. It should be in a
22;; file named COPYING. Among other things, the copyright notice
23;; and this notice must be preserved on all copies.
24
25;;; Commentary:
26
27;; This collection of functions implements the features of calendar.el and
28;; diary.el that deal with sunrise/sunset and eqinoxes/solstices.
29
30;; Based on the ``Almanac for Computers 1984,'' prepared by the Nautical
31;; Almanac Office, United States Naval Observatory, Washington, 1984 and
32;; on ``Astronomical Formulae for Calculators,'' 3rd ed., by Jean Meeus,
33;; Willmann-Bell, Inc., 1985.
34;;
35;; WARNINGS:
36;; 1. SUNRISE/SUNSET calculations will be accurate only to +/- 2 minutes.
37;; Locations should be between +/- 65 degrees of latitude.
38;; Dates should be in the latter half of the 20th century.
39;;
40;; 2. Equinox/solstice times will be accurate only to +/- 15 minutes.
41
42;; The author would be delighted to have an astronomically more sophisticated
43;; person rewrite the code for the solar calculations in this file!
44
45;; Comments, corrections, and improvements should be sent to
46;; Edward M. Reingold Department of Computer Science
47;; (217) 333-6733 University of Illinois at Urbana-Champaign
48;; reingold@cs.uiuc.edu 1304 West Springfield Avenue
49;; Urbana, Illinois 61801
50
51;;; Code:
52
53(if (fboundp 'atan)
54 (require 'lisp-float-type)
55 (error "Solar calculations impossible since floating point is unavailable."))
56
57(require 'calendar)
58
59(defun solar-setup ()
60 "Prompt user for latitude, longitude, and time zone."
61 (beep)
62 (if (not calendar-longitude)
63 (setq calendar-longitude
64 (solar-get-number
65 "Enter longitude (decimal fraction; + east, - west): ")))
66 (if (not calendar-latitude)
67 (setq calendar-latitude
68 (solar-get-number
69 "Enter latitude (decimal fraction; + north, - south): ")))
70 (if (not calendar-time-zone)
71 (setq calendar-time-zone
72 (solar-get-number
73 "Enter difference from Universal Time (in minutes): "))))
74
75(defun solar-get-number (prompt)
76 "Return a number from the minibuffer, prompting with PROMPT.
77Returns nil if nothing was entered."
78 (let ((x (read-string prompt "")))
79 (if (not (string-equal x ""))
80 (string-to-int x))))
81
82(defun solar-sin-degrees (x)
83 (sin (degrees-to-radians x)))
84
85(defun solar-cosine-degrees (x)
86 (cos (degrees-to-radians x)))
87
88(defun solar-tangent-degrees (x)
89 (tan (degrees-to-radians x)))
90
91(defun solar-xy-to-quadrant (x y)
92 "Determines the quadrant of the point X, Y."
93 (if (> x 0)
94 (if (> y 0) 1 4)
95 (if (> y 0) 2 3)))
96
97(defun solar-degrees-to-quadrant (angle)
98 "Determines the quadrant of ANGLE."
99 (1+ (truncate (/ (solar-mod angle 360.0) 90.0))))
100
101(defun solar-arctan (x quad)
102 "Arctangent of X in quadrant QUAD."
103 (let ((deg (radians-to-degrees (atan x))))
104 (cond ((equal quad 2) (+ deg 180))
105 ((equal quad 3) (+ deg 180))
106 ((equal quad 4) (+ deg 360))
107 (t deg))))
108
109(defun solar-arccos (x)
110 (let ((y (sqrt (- 1 (* x x)))))
111 (solar-arctan (/ y x) (solar-xy-to-quadrant x y))))
112
113(defun solar-arcsin (y)
114 (let ((x (sqrt (- 1 (* y y)))))
115 (solar-arctan (/ y x) (solar-xy-to-quadrant x y))))
116
117(defun solar-mod (x y)
118 "Returns X mod Y; value is *always* non-negative."
119 (let ((v (% x y)))
120 (if (> 0 v)
121 (+ v y)
122 v)))
123
124(defconst solar-earth-inclination 23.441884
125 "Inclination of earth's equator to its solar orbit in degrees.")
126
127(defconst solar-cos-inclination (solar-cosine-degrees solar-earth-inclination)
128 "Cosine of earth's inclination.")
129
130(defconst solar-sin-inclination (solar-sin-degrees solar-earth-inclination)
131 "Sine of earth's inclination.")
132
133(defconst solar-earth-orbit-eccentricity 0.016718
134 "Eccentricity of orbit of the earth around the sun.")
135
136(defmacro solar-degrees-to-hours (deg)
137 (list '/ deg 15))
138
139(defmacro solar-hours-to-days (hour)
140 (list '/ hour 24))
141
142(defun solar-longitude-of-sun (day)
143 "Longitude of the sun at DAY in the year."
144 (let ((mean-anomaly (- (* 0.9856 day) 3.289)))
145 (solar-mod (+ mean-anomaly
146 (* 1.916 (solar-sin-degrees mean-anomaly))
147 (* 0.020 (solar-sin-degrees (* 2 mean-anomaly)))
148 282.634)
149 360)))
150
151(defun solar-right-ascension (longitude)
152 "Right ascension of the sun, given its LONGITUDE."
153 (solar-degrees-to-hours
154 (solar-arctan
155 (* solar-cos-inclination (solar-tangent-degrees longitude))
156 (solar-degrees-to-quadrant longitude))))
157
158(defun solar-declination (longitude)
159 "Declination of the sun, given its LONGITUDE."
160 (solar-arcsin
161 (* solar-sin-inclination
162 (solar-sin-degrees longitude))))
163
164(defun solar-sunrise (date)
165 "Calculates the *standard* time of sunrise for Gregorian DATE for location
166given by `calendar-latitude' and `calendar-longitude'. Returns a decimal fraction
167of hours. Returns nil if the sun does not rise at that location on that day."
168 (let* ((day-of-year (calendar-day-number date))
169 (approx-sunrise
170 (+ day-of-year
171 (solar-hours-to-days
172 (- 6 (solar-degrees-to-hours calendar-longitude)))))
173 (solar-longitude-of-sun-at-sunrise
174 (solar-longitude-of-sun approx-sunrise))
175 (solar-right-ascension-at-sunrise
176 (solar-right-ascension solar-longitude-of-sun-at-sunrise))
177 (solar-declination-at-sunrise
178 (solar-declination solar-longitude-of-sun-at-sunrise))
179 (cos-local-sunrise
180 (/ (- (solar-cosine-degrees (+ 90 (/ 50.0 60.0)))
181 (* (solar-sin-degrees solar-declination-at-sunrise)
182 (solar-sin-degrees calendar-latitude)))
183 (* (solar-cosine-degrees solar-declination-at-sunrise)
184 (solar-cosine-degrees calendar-latitude)))))
185 (if (<= (abs cos-local-sunrise) 1);; otherwise, no sunrise that day
186 (let* ((local-sunrise (solar-degrees-to-hours
187 (- 360 (solar-arccos cos-local-sunrise))))
188 (local-mean-sunrise
189 (solar-mod (- (+ local-sunrise solar-right-ascension-at-sunrise)
190 (+ (* 0.065710 approx-sunrise)
191 6.622))
192 24)))
193 (+ (- local-mean-sunrise (solar-degrees-to-hours calendar-longitude))
194 (/ calendar-time-zone 60.0))))))
195
196(defun solar-sunset (date)
197 "Calculates the *standard* time of sunset for Gregorian DATE for location
198given by `calendar-latitude' and `calendar-longitude'. Returns a decimal fractions
199of hours. Returns nil if the sun does not set at that location on that day."
200 (let* ((day-of-year (calendar-day-number date))
201 (approx-sunset
202 (+ day-of-year
203 (solar-hours-to-days
204 (- 18 (solar-degrees-to-hours calendar-longitude)))))
205 (solar-longitude-of-sun-at-sunset
206 (solar-longitude-of-sun approx-sunset))
207 (solar-right-ascension-at-sunset
208 (solar-right-ascension solar-longitude-of-sun-at-sunset))
209 (solar-declination-at-sunset
210 (solar-declination solar-longitude-of-sun-at-sunset))
211 (cos-local-sunset
212 (/ (- (solar-cosine-degrees (+ 90 (/ 50.0 60.0)))
213 (* (solar-sin-degrees solar-declination-at-sunset)
214 (solar-sin-degrees calendar-latitude)))
215 (* (solar-cosine-degrees solar-declination-at-sunset)
216 (solar-cosine-degrees calendar-latitude)))))
217 (if (<= (abs cos-local-sunset) 1);; otherwise, no sunset that day
218 (let* ((local-sunset (solar-degrees-to-hours
219 (solar-arccos cos-local-sunset)))
220 (local-mean-sunset
221 (solar-mod (- (+ local-sunset solar-right-ascension-at-sunset)
222 (+ (* 0.065710 approx-sunset) 6.622))
223 24)))
224 (+ (- local-mean-sunset (solar-degrees-to-hours calendar-longitude))
225 (/ calendar-time-zone 60.0))))))
226
227(defun solar-time-string (time date)
228 "Printable form for decimal fraction standard TIME on DATE.
229Format used is given by `calendar-time-display-form'. Converted to daylight
230savings time according to `calendar-daylight-savings-starts' and
231`calendar-daylight-savings-ends', if those variables are not nil."
232 (let* ((year (extract-calendar-year date))
233 (abs-date (calendar-absolute-from-gregorian date))
234 (dst (and calendar-daylight-savings-starts
235 calendar-daylight-savings-ends
236 (<= (calendar-absolute-from-gregorian
237 (eval calendar-daylight-savings-starts))
238 abs-date)
239 (< abs-date
240 (calendar-absolute-from-gregorian
241 (eval calendar-daylight-savings-ends)))))
242 (time (if dst (1+ time) time))
243 (time-zone (if dst
244 calendar-daylight-time-zone-name
245 calendar-standard-time-zone-name))
246 (24-hours (truncate time))
247 (12-hours (format "%d" (if (> 24-hours 12)
248 (- 24-hours 12)
249 (if (= 24-hours 0) 12 24-hours))))
250 (am-pm (if (>= 24-hours 12) "pm" "am"))
251 (minutes (format "%02d" (round (* 60 (- time 24-hours)))))
252 (24-hours (format "%02d" 24-hours)))
253 (mapconcat 'eval calendar-time-display-form "")))
254
255(defun solar-sunrise-sunset (date)
256 "String giving local times of sunrise and sunset on Gregorian DATE."
257 (let ((rise (solar-sunrise date))
258 (set (solar-sunset date)))
259 (format "%s, %s at %s"
260 (if rise
261 (concat "Sunrise " (solar-time-string rise date))
262 "No sunrise")
263 (if set
264 (concat "sunset " (solar-time-string set date))
265 "no sunset")
266 (eval calendar-location-name))))
267
268(defun solar-apparent-longitude-of-sun (date)
269 "Apparent longitude of the sun on Gregorian DATE."
270 (let* ((time (/ (- (calendar-absolute-from-gregorian date)
271 (calendar-absolute-from-gregorian '(1 0.5 1900)))
272 36525))
273 (l (+ 279.69668
274 (* 36000.76892 time)
275 (* 0.0003025 time time)))
276 (m (+ 358.47583
277 (* 35999.04975 time)
278 (* -0.000150 time time)
279 (* -0.0000033 time time time)))
280 (c (+ (* (+ 1.919460
281 (* -0.004789 time)
282 (* -0.000014 time time))
283 (solar-sin-degrees m))
284 (* (+ 0.020094
285 (* -0.000100 time))
286 (solar-sin-degrees (* 2 m)))
287 (* 0.000293
288 (solar-sin-degrees (* 3 m)))))
289 (L (+ l c))
290 (omega (+ 259.18
291 (* -1934.142 time)))
292 (app (+ L
293 -0.00569
294 (* -0.00479
295 (solar-sin-degrees omega)))))
296 app))
297
298(defun solar-ephemeris-correction (year)
299 "Difference in minutes between Ephemeris time an Universal time in YEAR.
300Value is only an approximation."
301 (let ((T (/ (- year 1900) 100.0)))
302 (+ 0.41 (* 1.2053 T) (* 0.4992 T T))))
303
304(defun solar-equinoxes/solstices (k year)
305 "Date of equinox/solstice K for YEAR. K=0, spring equinox; K=1, summer
306solstice; K=2, fall equinox; K=3, winter solstice. Accurate to within
307several minutes."
308 (let ((date (list (+ 3 (* k 3)) 21 year))
309 (correction 1000))
310 (while (> correction 0.00001)
311 (setq app (solar-mod (solar-apparent-longitude-of-sun date) 360.0))
312 (setq correction (* 58 (solar-sin-degrees (- (* k 90) app))))
313 (setq date (list (extract-calendar-month date)
314 (+ (extract-calendar-day date) correction)
315 year)))
316 (list (extract-calendar-month date)
317 (+ (extract-calendar-day date) (/ calendar-time-zone 60.0 24.0)
318 (- (/ (solar-ephemeris-correction year) 60.0 24.0)))
319 year)))
320
321;;;###autoload
322(defun sunrise-sunset (&optional arg)
323 "Local time of sunrise and sunset for today. Accurate to +/- 2 minutes.
324If called with an optional prefix argument, prompts for date.
325
326If called with an optional double prefix argument, prompts for longitude,
327latitude, time zone, and date.
328
329This function is suitable for execution in a .emacs file."
330 (interactive "p")
331 (if (< arg 16)
332 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
333 (solar-setup)))
334 (let* ((calendar-longitude
335 (if (< arg 16)
336 calendar-longitude
337 (solar-get-number
338 "Enter longitude (decimal fraction; + east, - west): ")))
339 (calendar-latitude
340 (if (< arg 16)
341 calendar-latitude
342 (solar-get-number
343 "Enter latitude (decimal fraction; + north, - south): ")))
344 (calendar-time-zone
345 (if (< arg 16)
346 calendar-time-zone
347 (solar-get-number
348 "Enter difference from Universal Time (in minutes): ")))
349 (calendar-location-name
350 (let ((float-output-format "%.1f"))
351 (format "%s%s, %s%s"
352 (abs calendar-latitude)
353 (if (> calendar-latitude 0) "N" "S")
354 (abs calendar-longitude)
355 (if (> calendar-longitude 0) "E" "W"))))
356 (calendar-standard-time-zone-name
357 (cond ((= calendar-time-zone 0) "UT")
358 ((< calendar-time-zone 0) (format "UT%dmin" calendar-time-zone))
359 (t (format "UT+%dmin" calendar-time-zone))))
360 (calendar-daylight-savings-starts nil)
361 (calendar-daylight-savings-ends nil)
362 (date (if (< arg 4)
363 (calendar-current-date)
364 (calendar-read-date)))
365 (date-string (calendar-date-string date t))
366 (time-string (solar-sunrise-sunset date))
367 (msg (format "%s: %s" date-string time-string))
368 (one-window (one-window-p t)))
369 (if (<= (length msg) (screen-width))
370 (message msg)
371 (with-output-to-temp-buffer "*temp*"
372 (princ (concat date-string "\n" time-string)))
373 (message (substitute-command-keys
374 (if one-window
375 (if pop-up-windows
376 "Type \\[delete-other-windows] to remove temp window."
377 "Type \\[switch-to-buffer] RET to remove temp window.")
378 "Type \\[switch-to-buffer-other-window] RET to restore old contents of temp window."))))))
379
380(defun calendar-sunrise-sunset ()
381 "Local time of sunrise and sunset for date under cursor.
382Accurate to +/- 2 minutes."
383 (interactive)
384 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
385 (solar-setup))
386 (message
387 (solar-sunrise-sunset
388 (or (calendar-cursor-to-date)
389 (error "Cursor is not on a date!")))))
390
391(defun diary-sunrise-sunset ()
392 "Local time of sunrise and sunset as a diary entry.
393Accurate to +/- 2 minutes."
394 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
395 (solar-setup))
396 (solar-sunrise-sunset date))
397
398(defun diary-sabbath-candles ()
399 "Local time of candle lighting diary entry--applies if date is a Friday.
400No diary entry if there is no sunset on that date."
401 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
402 (solar-setup))
403 (if (= (% (calendar-absolute-from-gregorian date) 7) 5);; Friday
404 (let* ((sunset (solar-sunset date))
405 (light (if sunset (- sunset (/ 18.0 60.0)))))
406 (if light (format "%s Sabbath candle lighting"
407 (solar-time-string light date))))))
408
409(defun calendar-holiday-function-solar-equinoxes-solstices ()
410 "Date and time of equinoxes and solstices, if visible in the calendar window.
411Requires floating point."
412 (let* ((m displayed-month)
413 (y displayed-year))
414 (increment-calendar-month m y (cond ((= 1 (% m 3)) -1)
415 ((= 2 (% m 3)) 1)
416 (t 0)))
417 (let* ((calendar-standard-time-zone-name
418 (if calendar-time-zone calendar-standard-time-zone-name "UT"))
419 (calendar-daylight-savings-starts
420 (if calendar-time-zone calendar-daylight-savings-starts))
421 (calendar-daylight-savings-ends
422 (if calendar-time-zone calendar-daylight-savings-ends))
423 (calendar-time-zone (if calendar-time-zone calendar-time-zone 0))
424 (k (1- (/ m 3)))
425 (date (solar-equinoxes/solstices k y))
426 (day (extract-calendar-day date))
427 (time (* 24 (- day (truncate day))))
428 ;; Time zone/DST can't move the date out of range,
429 ;; so let solar-time-string do the conversion.
430 (date (list (extract-calendar-month date)
431 (truncate day)
432 (extract-calendar-year date))))
433 (list (list date
434 (format "%s %s"
435 (cond ((= k 0) "Vernal Equinox")
436 ((= k 1) "Summer Solstice")
437 ((= k 2) "Fall Equinox")
438 ((= k 3) "Winter Solstice"))
439 (solar-time-string time date)))))))
440
441(provide 'solar)
442
443;;; solar.el ends here