]> code.delx.au - gnu-emacs/blob - lisp/calendar/solar.el
(appt-display-format): Default value must be
[gnu-emacs] / lisp / calendar / solar.el
1 ;;; solar.el --- calendar functions for solar events
2
3 ;; Copyright (C) 1992, 1993, 1995, 1997, 2001, 2002, 2003, 2004, 2005,
4 ;; 2006 Free Software Foundation, Inc.
5
6 ;; Author: Edward M. Reingold <reingold@cs.uiuc.edu>
7 ;; Denis B. Roegel <Denis.Roegel@loria.fr>
8 ;; Maintainer: Glenn Morris <rgm@gnu.org>
9 ;; Keywords: calendar
10 ;; Human-Keywords: sunrise, sunset, equinox, solstice, calendar, diary,
11 ;; holidays
12
13 ;; This file is part of GNU Emacs.
14
15 ;; GNU Emacs is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License as published by
17 ;; the Free Software Foundation; either version 2, or (at your option)
18 ;; any later version.
19
20 ;; GNU Emacs is distributed in the hope that it will be useful,
21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ;; GNU General Public License for more details.
24
25 ;; You should have received a copy of the GNU General Public License
26 ;; along with GNU Emacs; see the file COPYING. If not, write to the
27 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 ;; Boston, MA 02110-1301, USA.
29
30 ;;; Commentary:
31
32 ;; This collection of functions implements the features of calendar.el,
33 ;; diary.el, and holiday.el that deal with times of day, sunrise/sunset, and
34 ;; equinoxes/solstices.
35
36 ;; Based on the ``Almanac for Computers 1984,'' prepared by the Nautical
37 ;; Almanac Office, United States Naval Observatory, Washington, 1984, on
38 ;; ``Astronomical Formulae for Calculators,'' 3rd ed., by Jean Meeus,
39 ;; Willmann-Bell, Inc., 1985, on ``Astronomical Algorithms'' by Jean Meeus,
40 ;; Willmann-Bell, Inc., 1991, and on ``Planetary Programs and Tables from
41 ;; -4000 to +2800'' by Pierre Bretagnon and Jean-Louis Simon, Willmann-Bell,
42 ;; Inc., 1986.
43
44 ;;
45 ;; Accuracy:
46 ;; 1. Sunrise/sunset times will be accurate to the minute for years
47 ;; 1951--2050. For other years the times will be within +/- 2 minutes.
48 ;;
49 ;; 2. Equinox/solstice times will be accurate to the minute for years
50 ;; 1951--2050. For other years the times will be within +/- 1 minute.
51
52 ;; Technical details of all the calendrical calculations can be found in
53 ;; ``Calendrical Calculations: The Millennium Edition'' by Edward M. Reingold
54 ;; and Nachum Dershowitz, Cambridge University Press (2001).
55
56 ;; Comments, corrections, and improvements should be sent to
57 ;; Edward M. Reingold Department of Computer Science
58 ;; (217) 333-6733 University of Illinois at Urbana-Champaign
59 ;; reingold@cs.uiuc.edu 1304 West Springfield Avenue
60 ;; Urbana, Illinois 61801
61
62 ;;; Code:
63
64 (defvar date)
65 (defvar displayed-month)
66 (defvar displayed-year)
67
68 (if (fboundp 'atan)
69 (require 'lisp-float-type)
70 (error "Solar/lunar calculations impossible since floating point is unavailable"))
71
72 (require 'cal-dst)
73 (require 'cal-julian)
74
75 ;;;###autoload
76 (defcustom calendar-time-display-form
77 '(12-hours ":" minutes am-pm
78 (if time-zone " (") time-zone (if time-zone ")"))
79 "*The pseudo-pattern that governs the way a time of day is formatted.
80
81 A pseudo-pattern is a list of expressions that can involve the keywords
82 `12-hours', `24-hours', and `minutes', all numbers in string form,
83 and `am-pm' and `time-zone', both alphabetic strings.
84
85 For example, the form
86
87 '(24-hours \":\" minutes
88 (if time-zone \" (\") time-zone (if time-zone \")\"))
89
90 would give military-style times like `21:07 (UTC)'."
91 :type 'sexp
92 :group 'calendar)
93
94 ;;;###autoload
95 (defcustom calendar-latitude nil
96 "*Latitude of `calendar-location-name' in degrees.
97
98 The value can be either a decimal fraction (one place of accuracy is
99 sufficient), + north, - south, such as 40.7 for New York City, or the value
100 can be a vector [degrees minutes north/south] such as [40 50 north] for New
101 York City.
102
103 This variable should be set in `site-start'.el."
104 :type '(choice (const nil)
105 (number :tag "Exact")
106 (vector :value [0 0 north]
107 (integer :tag "Degrees")
108 (integer :tag "Minutes")
109 (choice :tag "Position"
110 (const north)
111 (const south))))
112 :group 'calendar)
113
114 ;;;###autoload
115 (defcustom calendar-longitude nil
116 "*Longitude of `calendar-location-name' in degrees.
117
118 The value can be either a decimal fraction (one place of accuracy is
119 sufficient), + east, - west, such as -73.9 for New York City, or the value
120 can be a vector [degrees minutes east/west] such as [73 55 west] for New
121 York City.
122
123 This variable should be set in `site-start'.el."
124 :type '(choice (const nil)
125 (number :tag "Exact")
126 (vector :value [0 0 west]
127 (integer :tag "Degrees")
128 (integer :tag "Minutes")
129 (choice :tag "Position"
130 (const east)
131 (const west))))
132 :group 'calendar)
133
134 (defsubst calendar-latitude ()
135 "Convert calendar-latitude to a signed decimal fraction, if needed."
136 (if (numberp calendar-latitude)
137 calendar-latitude
138 (let ((lat (+ (aref calendar-latitude 0)
139 (/ (aref calendar-latitude 1) 60.0))))
140 (if (equal (aref calendar-latitude 2) 'north)
141 lat
142 (- lat)))))
143
144 (defsubst calendar-longitude ()
145 "Convert calendar-longitude to a signed decimal fraction, if needed."
146 (if (numberp calendar-longitude)
147 calendar-longitude
148 (let ((long (+ (aref calendar-longitude 0)
149 (/ (aref calendar-longitude 1) 60.0))))
150 (if (equal (aref calendar-longitude 2) 'east)
151 long
152 (- long)))))
153
154 ;;;###autoload
155 (defcustom calendar-location-name
156 '(let ((float-output-format "%.1f"))
157 (format "%s%s, %s%s"
158 (if (numberp calendar-latitude)
159 (abs calendar-latitude)
160 (+ (aref calendar-latitude 0)
161 (/ (aref calendar-latitude 1) 60.0)))
162 (if (numberp calendar-latitude)
163 (if (> calendar-latitude 0) "N" "S")
164 (if (equal (aref calendar-latitude 2) 'north) "N" "S"))
165 (if (numberp calendar-longitude)
166 (abs calendar-longitude)
167 (+ (aref calendar-longitude 0)
168 (/ (aref calendar-longitude 1) 60.0)))
169 (if (numberp calendar-longitude)
170 (if (> calendar-longitude 0) "E" "W")
171 (if (equal (aref calendar-longitude 2) 'east) "E" "W"))))
172 "*Expression evaluating to name of `calendar-longitude', `calendar-latitude'.
173 For example, \"New York City\". Default value is just the latitude, longitude
174 pair.
175
176 This variable should be set in `site-start'.el."
177 :type 'sexp
178 :group 'calendar)
179
180 (defcustom solar-error 0.5
181 "*Tolerance (in minutes) for sunrise/sunset calculations.
182
183 A larger value makes the calculations for sunrise/sunset faster, but less
184 accurate. The default is half a minute (30 seconds), so that sunrise/sunset
185 times will be correct to the minute.
186
187 It is useless to set the value smaller than 4*delta, where delta is the
188 accuracy in the longitude of the sun (given by the function
189 `solar-ecliptic-coordinates') in degrees since (delta/360) x (86400/60) = 4 x
190 delta. At present, delta = 0.01 degrees, so the value of the variable
191 `solar-error' should be at least 0.04 minutes (about 2.5 seconds)."
192 :type 'number
193 :group 'calendar)
194
195 (defvar solar-n-hemi-seasons
196 '("Vernal Equinox" "Summer Solstice" "Autumnal Equinox" "Winter Solstice")
197 "List of season changes for the northern hemisphere.")
198
199 (defvar solar-s-hemi-seasons
200 '("Autumnal Equinox" "Winter Solstice" "Vernal Equinox" "Summer Solstice")
201 "List of season changes for the southern hemisphere.")
202
203 (defvar solar-sidereal-time-greenwich-midnight
204 nil
205 "Sidereal time at Greenwich at midnight (universal time).")
206
207 (defvar solar-northern-spring-or-summer-season nil
208 "Non-nil if northern spring or summer and nil otherwise.
209 Needed for polar areas, in order to know whether the day lasts 0 or 24 hours.")
210
211 (defun solar-setup ()
212 "Prompt user for latitude, longitude, and time zone."
213 (beep)
214 (if (not calendar-longitude)
215 (setq calendar-longitude
216 (solar-get-number
217 "Enter longitude (decimal fraction; + east, - west): ")))
218 (if (not calendar-latitude)
219 (setq calendar-latitude
220 (solar-get-number
221 "Enter latitude (decimal fraction; + north, - south): ")))
222 (if (not calendar-time-zone)
223 (setq calendar-time-zone
224 (solar-get-number
225 "Enter difference from Coordinated Universal Time (in minutes): "))))
226
227 (defun solar-get-number (prompt)
228 "Return a number from the minibuffer, prompting with PROMPT.
229 Returns nil if nothing was entered."
230 (let ((x (read-string prompt "")))
231 (if (not (string-equal x ""))
232 (string-to-number x))))
233
234 ;; The condition-case stuff is needed to catch bogus arithmetic
235 ;; exceptions that occur on some machines (like Sparcs)
236 (defun solar-sin-degrees (x)
237 (condition-case nil
238 (sin (degrees-to-radians (mod x 360.0)))
239 (solar-sin-degrees x)))
240 (defun solar-cosine-degrees (x)
241 (condition-case nil
242 (cos (degrees-to-radians (mod x 360.0)))
243 (solar-cosine-degrees x)))
244 (defun solar-tangent-degrees (x)
245 (condition-case nil
246 (tan (degrees-to-radians (mod x 360.0)))
247 (solar-tangent-degrees x)))
248
249 (defun solar-xy-to-quadrant (x y)
250 "Determines the quadrant of the point X, Y."
251 (if (> x 0)
252 (if (> y 0) 1 4)
253 (if (> y 0) 2 3)))
254
255 (defun solar-degrees-to-quadrant (angle)
256 "Determines the quadrant of ANGLE."
257 (1+ (floor (mod angle 360) 90)))
258
259 (defun solar-arctan (x quad)
260 "Arctangent of X in quadrant QUAD."
261 (let ((deg (radians-to-degrees (atan x))))
262 (cond ((equal quad 2) (+ deg 180))
263 ((equal quad 3) (+ deg 180))
264 ((equal quad 4) (+ deg 360))
265 (t deg))))
266
267 (defun solar-atn2 (x y)
268 "Arctan of point X, Y."
269 (if (= x 0)
270 (if (> y 0) 90 270)
271 (solar-arctan (/ y x) (solar-xy-to-quadrant x y))))
272
273 (defun solar-arccos (x)
274 "Arcos of X."
275 (let ((y (sqrt (- 1 (* x x)))))
276 (solar-atn2 x y)))
277
278 (defun solar-arcsin (y)
279 "Arcsin of Y."
280 (let ((x (sqrt (- 1 (* y y)))))
281 (solar-atn2 x y)
282 ))
283
284 (defsubst solar-degrees-to-hours (degrees)
285 "Convert DEGREES to hours."
286 (/ degrees 15.0))
287
288 (defsubst solar-hours-to-days (hour)
289 "Convert HOUR to decimal fraction of a day."
290 (/ hour 24.0))
291
292 (defun solar-right-ascension (longitude obliquity)
293 "Right ascension of the sun, in hours, given LONGITUDE and OBLIQUITY.
294 Both arguments are in degrees."
295 (solar-degrees-to-hours
296 (solar-arctan
297 (* (solar-cosine-degrees obliquity) (solar-tangent-degrees longitude))
298 (solar-degrees-to-quadrant longitude))))
299
300 (defun solar-declination (longitude obliquity)
301 "Declination of the sun, in degrees, given LONGITUDE and OBLIQUITY.
302 Both arguments are in degrees."
303 (solar-arcsin
304 (* (solar-sin-degrees obliquity)
305 (solar-sin-degrees longitude))))
306
307 (defun solar-sunrise-and-sunset (time latitude longitude height)
308 "Sunrise, sunset and length of day.
309 Parameters are the midday TIME and the LATITUDE, LONGITUDE of the location.
310
311 TIME is a pair with the first component being the number of Julian centuries
312 elapsed at 0 Universal Time, and the second component being the universal
313 time. For instance, the pair corresponding to November 28, 1995 at 16 UT is
314 \(-0.040945 16), -0.040945 being the number of julian centuries elapsed between
315 Jan 1, 2000 at 12 UT and November 28, 1995 at 0 UT.
316
317 HEIGHT is the angle the center of the sun has over the horizon for the contact
318 we are trying to find. For sunrise and sunset, it is usually -0.61 degrees,
319 accounting for the edge of the sun being on the horizon.
320
321 Coordinates are included because this function is called with latitude=1
322 degrees to find out if polar regions have 24 hours of sun or only night."
323 (let* ((rise-time (solar-moment -1 latitude longitude time height))
324 (set-time (solar-moment 1 latitude longitude time height))
325 (day-length))
326 (if (not (and rise-time set-time))
327 (if (or (and (> latitude 0)
328 solar-northern-spring-or-summer-season)
329 (and (< latitude 0)
330 (not solar-northern-spring-or-summer-season)))
331 (setq day-length 24)
332 (setq day-length 0))
333 (setq day-length (- set-time rise-time)))
334 (list (if rise-time (+ rise-time (/ calendar-time-zone 60.0)) nil)
335 (if set-time (+ set-time (/ calendar-time-zone 60.0)) nil)
336 day-length)))
337
338 (defun solar-moment (direction latitude longitude time height)
339 "Sunrise/sunset at location.
340 Sunrise if DIRECTION =-1 or sunset if =1 at LATITUDE, LONGITUDE, with midday
341 being TIME.
342
343 TIME is a pair with the first component being the number of Julian centuries
344 elapsed at 0 Universal Time, and the second component being the universal
345 time. For instance, the pair corresponding to November 28, 1995 at 16 UT is
346 \(-0.040945 16), -0.040945 being the number of julian centuries elapsed between
347 Jan 1, 2000 at 12 UT and November 28, 1995 at 0 UT.
348
349 HEIGHT is the angle the center of the sun has over the horizon for the contact
350 we are trying to find. For sunrise and sunset, it is usually -0.61 degrees,
351 accounting for the edge of the sun being on the horizon.
352
353 Uses binary search."
354 (let* ((ut (car (cdr time)))
355 (possible t) ; we assume that rise or set are possible
356 (utmin (+ ut (* direction 12.0)))
357 (utmax ut) ; the time searched is between utmin and utmax
358 ; utmin and utmax are in hours
359 (utmoment-old 0.0) ; rise or set approximation
360 (utmoment 1.0) ; rise or set approximation
361 (hut 0) ; sun height at utmoment
362 (t0 (car time))
363 (hmin (car (cdr
364 (solar-horizontal-coordinates (list t0 utmin)
365 latitude longitude t))))
366 (hmax (car (cdr
367 (solar-horizontal-coordinates (list t0 utmax)
368 latitude longitude t)))))
369 ; -0.61 degrees is the height of the middle of the sun, when it rises
370 ; or sets.
371 (if (< hmin height)
372 (if (> hmax height)
373 (while ;(< i 20) ; we perform a simple dichotomy
374 ; (> (abs (- hut height)) epsilon)
375 (>= (abs (- utmoment utmoment-old))
376 (/ solar-error 60))
377 (setq utmoment-old utmoment)
378 (setq utmoment (/ (+ utmin utmax) 2))
379 (setq hut (car (cdr
380 (solar-horizontal-coordinates
381 (list t0 utmoment) latitude longitude t))))
382 (if (< hut height) (setq utmin utmoment))
383 (if (> hut height) (setq utmax utmoment))
384 )
385 (setq possible nil)) ; the sun never rises
386 (setq possible nil)) ; the sun never sets
387 (if (not possible) nil utmoment)))
388
389 (defun solar-time-string (time time-zone)
390 "Printable form for decimal fraction TIME in TIME-ZONE.
391 Format used is given by `calendar-time-display-form'."
392 (let* ((time (round (* 60 time)))
393 (24-hours (/ time 60))
394 (minutes (format "%02d" (% time 60)))
395 (12-hours (format "%d" (1+ (% (+ 24-hours 11) 12))))
396 (am-pm (if (>= 24-hours 12) "pm" "am"))
397 (24-hours (format "%02d" 24-hours)))
398 (mapconcat 'eval calendar-time-display-form "")))
399
400
401 (defun solar-daylight (time)
402 "Printable form for time expressed in hours."
403 (format "%d:%02d"
404 (floor time)
405 (floor (* 60 (- time (floor time))))))
406
407 (defun solar-exact-local-noon (date)
408 "Date and Universal Time of local noon at *local date* date.
409
410 The date may be different from the one asked for, but it will be the right
411 local date. The second component of date should be an integer."
412 (let* ((nd date)
413 (ut (- 12.0 (/ (calendar-longitude) 15)))
414 (te (solar-time-equation date ut)))
415 (setq ut (- ut te))
416 (if (>= ut 24)
417 (progn
418 (setq nd (list (car date) (+ 1 (car (cdr date)))
419 (car (cdr (cdr date)))))
420 (setq ut (- ut 24))))
421 (if (< ut 0)
422 (progn
423 (setq nd (list (car date) (- (car (cdr date)) 1)
424 (car (cdr (cdr date)))))
425 (setq ut (+ ut 24))))
426 (setq nd (calendar-gregorian-from-absolute
427 (calendar-absolute-from-gregorian nd)))
428 ; date standardization
429 (list nd ut)))
430
431 (defun solar-sunrise-sunset (date)
432 "List of *local* times of sunrise, sunset, and daylight on Gregorian DATE.
433
434 Corresponding value is nil if there is no sunrise/sunset."
435 (let* (; first, get the exact moment of local noon.
436 (exact-local-noon (solar-exact-local-noon date))
437 ; get the time from the 2000 epoch.
438 (t0 (solar-julian-ut-centuries (car exact-local-noon)))
439 ; store the sidereal time at Greenwich at midnight of UT time.
440 ; find if summer or winter slightly above the equator
441 (equator-rise-set
442 (progn (setq solar-sidereal-time-greenwich-midnight
443 (solar-sidereal-time t0))
444 (solar-sunrise-and-sunset
445 (list t0 (car (cdr exact-local-noon)))
446 1.0
447 (calendar-longitude) 0)))
448 ; store the spring/summer information,
449 ; compute sunrise and sunset (two first components of rise-set).
450 ; length of day is the third component (it is only the difference
451 ; between sunset and sunrise when there is a sunset and a sunrise)
452 (rise-set
453 (progn
454 (setq solar-northern-spring-or-summer-season
455 (if (> (car (cdr (cdr equator-rise-set))) 12) t nil))
456 (solar-sunrise-and-sunset
457 (list t0 (car (cdr exact-local-noon)))
458 (calendar-latitude)
459 (calendar-longitude) -0.61)))
460 (rise (car rise-set))
461 (adj-rise (if rise (dst-adjust-time date rise) nil))
462 (set (car (cdr rise-set)))
463 (adj-set (if set (dst-adjust-time date set) nil))
464 (length (car (cdr (cdr rise-set)))) )
465 (list
466 (and rise (calendar-date-equal date (car adj-rise)) (cdr adj-rise))
467 (and set (calendar-date-equal date (car adj-set)) (cdr adj-set))
468 (solar-daylight length))))
469
470 (defun solar-sunrise-sunset-string (date)
471 "String of *local* times of sunrise, sunset, and daylight on Gregorian DATE."
472 (let ((l (solar-sunrise-sunset date)))
473 (format
474 "%s, %s at %s (%s hours daylight)"
475 (if (car l)
476 (concat "Sunrise " (apply 'solar-time-string (car l)))
477 "No sunrise")
478 (if (car (cdr l))
479 (concat "sunset " (apply 'solar-time-string (car (cdr l))))
480 "no sunset")
481 (eval calendar-location-name)
482 (car (cdr (cdr l))))))
483
484 (defun solar-julian-ut-centuries (date)
485 "Number of Julian centuries elapsed since 1 Jan, 2000 at noon U.T. for Gregorian DATE."
486 (/ (- (calendar-absolute-from-gregorian date)
487 (calendar-absolute-from-gregorian '(1 1.5 2000)))
488 36525.0))
489
490 (defun solar-ephemeris-time(time)
491 "Ephemeris Time at moment TIME.
492
493 TIME is a pair with the first component being the number of Julian centuries
494 elapsed at 0 Universal Time, and the second component being the universal
495 time. For instance, the pair corresponding to November 28, 1995 at 16 UT is
496 \(-0.040945 16), -0.040945 being the number of julian centuries elapsed between
497 Jan 1, 2000 at 12 UT and November 28, 1995 at 0 UT.
498
499 Result is in julian centuries of ephemeris time."
500 (let* ((t0 (car time))
501 (ut (car (cdr time)))
502 (t1 (+ t0 (/ (/ ut 24.0) 36525)))
503 (y (+ 2000 (* 100 t1)))
504 (dt (* 86400 (solar-ephemeris-correction (floor y)))))
505 (+ t1 (/ (/ dt 86400) 36525))))
506
507 (defun solar-date-next-longitude (d l)
508 "First moment on or after Julian day number D when sun's longitude is a
509 multiple of L degrees at calendar-location-name with that location's
510 local time (including any daylight savings rules).
511
512 L must be an integer divisor of 360.
513
514 Result is in local time expressed astronomical (Julian) day numbers.
515
516 The values of calendar-daylight-savings-starts,
517 calendar-daylight-savings-starts-time, calendar-daylight-savings-ends,
518 calendar-daylight-savings-ends-time, calendar-daylight-time-offset, and
519 calendar-time-zone are used to interpret local time."
520 (let* ((long)
521 (start d)
522 (start-long (solar-longitude d))
523 (next (mod (* l (1+ (floor (/ start-long l)))) 360))
524 (end (+ d (* (/ l 360.0) 400)))
525 (end-long (solar-longitude end)))
526 (while ;; bisection search for nearest minute
527 (< 0.00001 (- end start))
528 ;; start <= d < end
529 ;; start-long <= next < end-long when next != 0
530 ;; when next = 0, we look for the discontinuity (start-long is near 360
531 ;; and end-long is small (less than l).
532 (setq d (/ (+ start end) 2.0))
533 (setq long (solar-longitude d))
534 (if (or (and (/= next 0) (< long next))
535 (and (= next 0) (< l long)))
536 (progn
537 (setq start d)
538 (setq start-long long))
539 (setq end d)
540 (setq end-long long)))
541 (/ (+ start end) 2.0)))
542
543 (defun solar-horizontal-coordinates
544 (time latitude longitude for-sunrise-sunset)
545 "Azimuth and height of the sun at TIME, LATITUDE, and LONGITUDE.
546
547 TIME is a pair with the first component being the number of Julian centuries
548 elapsed at 0 Universal Time, and the second component being the universal
549 time. For instance, the pair corresponding to November 28, 1995 at 16 UT is
550 \(-0.040945 16), -0.040945 being the number of julian centuries elapsed between
551 Jan 1, 2000 at 12 UT and November 28, 1995 at 0 UT.
552
553 The azimuth is given in degrees as well as the height (between -180 and 180)."
554 (let* ((ut (car (cdr time)))
555 (ec (solar-equatorial-coordinates time for-sunrise-sunset))
556 (st (+ solar-sidereal-time-greenwich-midnight
557 (* ut 1.00273790935)))
558 (ah (- (* st 15) (* 15 (car ec)) (* -1 (calendar-longitude))))
559 ; hour angle (in degrees)
560 (de (car (cdr ec)))
561 (azimuth (solar-atn2 (- (* (solar-cosine-degrees ah)
562 (solar-sin-degrees latitude))
563 (* (solar-tangent-degrees de)
564 (solar-cosine-degrees latitude)))
565 (solar-sin-degrees ah)))
566 (height (solar-arcsin
567 (+ (* (solar-sin-degrees latitude) (solar-sin-degrees de))
568 (* (solar-cosine-degrees latitude)
569 (solar-cosine-degrees de)
570 (solar-cosine-degrees ah))))))
571 (if (> height 180) (setq height (- height 360)))
572 (list azimuth height)))
573
574 (defun solar-equatorial-coordinates (time for-sunrise-sunset)
575 "Right ascension (in hours) and declination (in degrees) of the sun at TIME.
576
577 TIME is a pair with the first component being the number of Julian centuries
578 elapsed at 0 Universal Time, and the second component being the universal
579 time. For instance, the pair corresponding to November 28, 1995 at 16 UT is
580 \(-0.040945 16), -0.040945 being the number of julian centuries elapsed between
581 Jan 1, 2000 at 12 UT and November 28, 1995 at 0 UT."
582 (let* ((tm (solar-ephemeris-time time))
583 (ec (solar-ecliptic-coordinates tm for-sunrise-sunset)))
584 (list (solar-right-ascension (car ec) (car (cdr ec)))
585 (solar-declination (car ec) (car (cdr ec))))))
586
587 (defun solar-ecliptic-coordinates (time for-sunrise-sunset)
588 "Apparent longitude of the sun, ecliptic inclination, (both in degrees)
589 equation of time (in hours) and nutation in longitude (in seconds)
590 at moment `time', expressed in julian centuries of Ephemeris Time
591 since January 1st, 2000, at 12 ET."
592 (let* ((l (+ 280.46645
593 (* 36000.76983 time)
594 (* 0.0003032 time time))) ; sun mean longitude
595 (ml (+ 218.3165
596 (* 481267.8813 time))) ; moon mean longitude
597 (m (+ 357.52910
598 (* 35999.05030 time)
599 (* -0.0001559 time time)
600 (* -0.00000048 time time time))) ; sun mean anomaly
601 (i (+ 23.43929111 (* -0.013004167 time)
602 (* -0.00000016389 time time)
603 (* 0.0000005036 time time time))); mean inclination
604 (c (+ (* (+ 1.914600
605 (* -0.004817 time)
606 (* -0.000014 time time))
607 (solar-sin-degrees m))
608 (* (+ 0.019993 (* -0.000101 time))
609 (solar-sin-degrees (* 2 m)))
610 (* 0.000290
611 (solar-sin-degrees (* 3 m))))) ; center equation
612 (L (+ l c)) ; total longitude
613 (omega (+ 125.04
614 (* -1934.136 time))) ; longitude of moon's ascending node
615 ; on the ecliptic
616 (nut (if (not for-sunrise-sunset)
617 (+ (* -17.20 (solar-sin-degrees omega))
618 (* -1.32 (solar-sin-degrees (* 2 l)))
619 (* -0.23 (solar-sin-degrees (* 2 ml)))
620 (* 0.21 (solar-sin-degrees (* 2 omega))))
621 nil))
622 ; nut = nutation in longitude, measured in seconds of angle.
623 (ecc (if (not for-sunrise-sunset)
624 (+ 0.016708617
625 (* -0.000042037 time)
626 (* -0.0000001236 time time)) ; eccentricity of earth's orbit
627 nil))
628 (app (+ L
629 -0.00569
630 (* -0.00478
631 (solar-sin-degrees omega)))) ; apparent longitude of sun
632 (y (if (not for-sunrise-sunset)
633 (* (solar-tangent-degrees (/ i 2))
634 (solar-tangent-degrees (/ i 2)))
635 nil))
636 (time-eq (if (not for-sunrise-sunset)
637 (/ (* 12 (+ (* y (solar-sin-degrees (* 2 l)))
638 (* -2 ecc (solar-sin-degrees m))
639 (* 4 ecc y (solar-sin-degrees m)
640 (solar-cosine-degrees (* 2 l)))
641 (* -0.5 y y (solar-sin-degrees (* 4 l)))
642 (* -1.25 ecc ecc (solar-sin-degrees (* 2 m)))))
643 3.1415926535)
644 nil)))
645 ; equation of time, in hours
646 (list app i time-eq nut)))
647
648 (defconst solar-data-list
649 '((403406 4.721964 1.621043)
650 (195207 5.937458 62830.348067)
651 (119433 1.115589 62830.821524)
652 (112392 5.781616 62829.634302)
653 (3891 5.5474 125660.5691)
654 (2819 1.5120 125660.984)
655 (1721 4.1897 62832.4766)
656 (0 1.163 0.813)
657 (660 5.415 125659.31)
658 (350 4.315 57533.85)
659 (334 4.553 -33.931)
660 (314 5.198 777137.715)
661 (268 5.989 78604.191)
662 (242 2.911 5.412)
663 (234 1.423 39302.098)
664 (158 0.061 -34.861)
665 (132 2.317 115067.698)
666 (129 3.193 15774.337)
667 (114 2.828 5296.670)
668 (99 0.52 58849.27)
669 (93 4.65 5296.11)
670 (86 4.35 -3980.70)
671 (78 2.75 52237.69)
672 (72 4.50 55076.47)
673 (68 3.23 261.08)
674 (64 1.22 15773.85)
675 (46 0.14 188491.03)
676 (38 3.44 -7756.55)
677 (37 4.37 264.89)
678 (32 1.14 117906.27)
679 (29 2.84 55075.75)
680 (28 5.96 -7961.39)
681 (27 5.09 188489.81)
682 (27 1.72 2132.19)
683 (25 2.56 109771.03)
684 (24 1.92 54868.56)
685 (21 0.09 25443.93)
686 (21 5.98 -55731.43)
687 (20 4.03 60697.74)
688 (18 4.47 2132.79)
689 (17 0.79 109771.63)
690 (14 4.24 -7752.82)
691 (13 2.01 188491.91)
692 (13 2.65 207.81)
693 (13 4.98 29424.63)
694 (12 0.93 -7.99)
695 (10 2.21 46941.14)
696 (10 3.59 -68.29)
697 (10 1.50 21463.25)
698 (10 2.55 157208.40)))
699
700 (defun solar-longitude (d)
701 "Longitude of sun on astronomical (Julian) day number D.
702 Accurary is about 0.0006 degree (about 365.25*24*60*0.0006/360 = 1 minutes).
703
704 The values of calendar-daylight-savings-starts,
705 calendar-daylight-savings-starts-time, calendar-daylight-savings-ends,
706 calendar-daylight-savings-ends-time, calendar-daylight-time-offset, and
707 calendar-time-zone are used to interpret local time."
708 (let* ((a-d (calendar-absolute-from-astro d))
709 ;; get Universal Time
710 (date (calendar-astro-from-absolute
711 (- a-d
712 (if (dst-in-effect a-d)
713 (/ calendar-daylight-time-offset 24.0 60.0) 0)
714 (/ calendar-time-zone 60.0 24.0))))
715 ;; get Ephemeris Time
716 (date (+ date (solar-ephemeris-correction
717 (extract-calendar-year
718 (calendar-gregorian-from-absolute
719 (floor
720 (calendar-absolute-from-astro
721 date)))))))
722 (U (/ (- date 2451545) 3652500))
723 (longitude
724 (+ 4.9353929
725 (* 62833.1961680 U)
726 (* 0.0000001
727 (apply '+
728 (mapcar '(lambda (x)
729 (* (car x)
730 (sin (mod
731 (+ (car (cdr x))
732 (* (car (cdr (cdr x))) U))
733 (* 2 pi)))))
734 solar-data-list)))))
735 (aberration
736 (* 0.0000001 (- (* 17 (cos (+ 3.10 (* 62830.14 U)))) 973)))
737 (A1 (mod (+ 2.18 (* U (+ -3375.70 (* 0.36 U)))) (* 2 pi)))
738 (A2 (mod (+ 3.51 (* U (+ 125666.39 (* 0.10 U)))) (* 2 pi)))
739 (nutation (* -0.0000001 (+ (* 834 (sin A1)) (* 64 (sin A2))))))
740 (mod (radians-to-degrees (+ longitude aberration nutation)) 360.0)))
741
742 (defun solar-ephemeris-correction (year)
743 "Ephemeris time minus Universal Time during Gregorian year.
744 Result is in days.
745
746 For the years 1800-1987, the maximum error is 1.9 seconds.
747 For the other years, the maximum error is about 30 seconds."
748 (cond ((and (<= 1988 year) (< year 2020))
749 (/ (+ year -2000 67.0) 60.0 60.0 24.0))
750 ((and (<= 1900 year) (< year 1988))
751 (let* ((theta (/ (- (calendar-astro-from-absolute
752 (calendar-absolute-from-gregorian
753 (list 7 1 year)))
754 (calendar-astro-from-absolute
755 (calendar-absolute-from-gregorian
756 '(1 1 1900))))
757 36525.0))
758 (theta2 (* theta theta))
759 (theta3 (* theta2 theta))
760 (theta4 (* theta2 theta2))
761 (theta5 (* theta3 theta2)))
762 (+ -0.00002
763 (* 0.000297 theta)
764 (* 0.025184 theta2)
765 (* -0.181133 theta3)
766 (* 0.553040 theta4)
767 (* -0.861938 theta5)
768 (* 0.677066 theta3 theta3)
769 (* -0.212591 theta4 theta3))))
770 ((and (<= 1800 year) (< year 1900))
771 (let* ((theta (/ (- (calendar-astro-from-absolute
772 (calendar-absolute-from-gregorian
773 (list 7 1 year)))
774 (calendar-astro-from-absolute
775 (calendar-absolute-from-gregorian
776 '(1 1 1900))))
777 36525.0))
778 (theta2 (* theta theta))
779 (theta3 (* theta2 theta))
780 (theta4 (* theta2 theta2))
781 (theta5 (* theta3 theta2)))
782 (+ -0.000009
783 (* 0.003844 theta)
784 (* 0.083563 theta2)
785 (* 0.865736 theta3)
786 (* 4.867575 theta4)
787 (* 15.845535 theta5)
788 (* 31.332267 theta3 theta3)
789 (* 38.291999 theta4 theta3)
790 (* 28.316289 theta4 theta4)
791 (* 11.636204 theta4 theta5)
792 (* 2.043794 theta5 theta5))))
793 ((and (<= 1620 year) (< year 1800))
794 (let ((x (/ (- year 1600) 10.0)))
795 (/ (+ (* 2.19167 x x) (* -40.675 x) 196.58333) 60.0 60.0 24.0)))
796 (t (let* ((tmp (- (calendar-astro-from-absolute
797 (calendar-absolute-from-gregorian
798 (list 1 1 year)))
799 2382148))
800 (second (- (/ (* tmp tmp) 41048480.0) 15)))
801 (/ second 60.0 60.0 24.0)))))
802
803 (defun solar-sidereal-time (t0)
804 "Sidereal time (in hours) in Greenwich.
805
806 At T0=Julian centuries of universal time.
807 T0 must correspond to 0 hours UT."
808 (let* ((mean-sid-time (+ 6.6973746
809 (* 2400.051337 t0)
810 (* 0.0000258622 t0 t0)
811 (* -0.0000000017222 t0 t0 t0)))
812 (et (solar-ephemeris-time (list t0 0.0)))
813 (nut-i (solar-ecliptic-coordinates et nil))
814 (nut (car (cdr (cdr (cdr nut-i))))) ; nutation
815 (i (car (cdr nut-i)))) ; inclination
816 (mod (+ (mod (+ mean-sid-time
817 (/ (/ (* nut (solar-cosine-degrees i)) 15) 3600)) 24.0)
818 24.0)
819 24.0)))
820
821 (defun solar-time-equation (date ut)
822 "Equation of time expressed in hours at Gregorian DATE at Universal time UT."
823 (let* ((et (solar-date-to-et date ut))
824 (ec (solar-ecliptic-coordinates et nil)))
825 (car (cdr (cdr ec)))))
826
827 (defun solar-date-to-et (date ut)
828 "Ephemeris Time at Gregorian DATE at Universal Time UT (in hours).
829 Expressed in julian centuries of Ephemeris Time."
830 (let ((t0 (solar-julian-ut-centuries date)))
831 (solar-ephemeris-time (list t0 ut))))
832
833 ;;;###autoload
834 (defun sunrise-sunset (&optional arg)
835 "Local time of sunrise and sunset for today. Accurate to a few seconds.
836 If called with an optional prefix argument, prompt for date.
837
838 If called with an optional double prefix argument, prompt for longitude,
839 latitude, time zone, and date, and always use standard time.
840
841 This function is suitable for execution in a .emacs file."
842 (interactive "p")
843 (or arg (setq arg 1))
844 (if (and (< arg 16)
845 (not (and calendar-latitude calendar-longitude calendar-time-zone)))
846 (solar-setup))
847 (let* ((calendar-longitude
848 (if (< arg 16) calendar-longitude
849 (solar-get-number
850 "Enter longitude (decimal fraction; + east, - west): ")))
851 (calendar-latitude
852 (if (< arg 16) calendar-latitude
853 (solar-get-number
854 "Enter latitude (decimal fraction; + north, - south): ")))
855 (calendar-time-zone
856 (if (< arg 16) calendar-time-zone
857 (solar-get-number
858 "Enter difference from Coordinated Universal Time (in minutes): ")))
859 (calendar-location-name
860 (if (< arg 16) calendar-location-name
861 (let ((float-output-format "%.1f"))
862 (format "%s%s, %s%s"
863 (if (numberp calendar-latitude)
864 (abs calendar-latitude)
865 (+ (aref calendar-latitude 0)
866 (/ (aref calendar-latitude 1) 60.0)))
867 (if (numberp calendar-latitude)
868 (if (> calendar-latitude 0) "N" "S")
869 (if (equal (aref calendar-latitude 2) 'north) "N" "S"))
870 (if (numberp calendar-longitude)
871 (abs calendar-longitude)
872 (+ (aref calendar-longitude 0)
873 (/ (aref calendar-longitude 1) 60.0)))
874 (if (numberp calendar-longitude)
875 (if (> calendar-longitude 0) "E" "W")
876 (if (equal (aref calendar-longitude 2) 'east)
877 "E" "W"))))))
878 (calendar-standard-time-zone-name
879 (if (< arg 16) calendar-standard-time-zone-name
880 (cond ((= calendar-time-zone 0) "UTC")
881 ((< calendar-time-zone 0)
882 (format "UTC%dmin" calendar-time-zone))
883 (t (format "UTC+%dmin" calendar-time-zone)))))
884 (calendar-daylight-savings-starts
885 (if (< arg 16) calendar-daylight-savings-starts))
886 (calendar-daylight-savings-ends
887 (if (< arg 16) calendar-daylight-savings-ends))
888 (date (if (< arg 4) (calendar-current-date) (calendar-read-date)))
889 (date-string (calendar-date-string date t))
890 (time-string (solar-sunrise-sunset-string date))
891 (msg (format "%s: %s" date-string time-string))
892 (one-window (one-window-p t)))
893 (if (<= (length msg) (frame-width))
894 (message "%s" msg)
895 (with-output-to-temp-buffer "*temp*"
896 (princ (concat date-string "\n" time-string)))
897 (message "%s"
898 (substitute-command-keys
899 (if one-window
900 (if pop-up-windows
901 "Type \\[delete-other-windows] to remove temp window."
902 "Type \\[switch-to-buffer] RET to remove temp window.")
903 "Type \\[switch-to-buffer-other-window] RET to restore old contents of temp window."))))))
904
905 (defun calendar-sunrise-sunset ()
906 "Local time of sunrise and sunset for date under cursor.
907 Accurate to a few seconds."
908 (interactive)
909 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
910 (solar-setup))
911 (let ((date (calendar-cursor-to-date t)))
912 (message "%s: %s"
913 (calendar-date-string date t t)
914 (solar-sunrise-sunset-string date))))
915
916 (defun diary-sunrise-sunset ()
917 "Local time of sunrise and sunset as a diary entry.
918 Accurate to a few seconds."
919 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
920 (solar-setup))
921 (solar-sunrise-sunset-string date))
922
923 (defcustom diary-sabbath-candles-minutes 18
924 "*Number of minutes before sunset for sabbath candle lighting."
925 :group 'diary
926 :type 'integer
927 :version "21.1")
928
929 (defun diary-sabbath-candles (&optional mark)
930 "Local time of candle lighting diary entry--applies if date is a Friday.
931 No diary entry if there is no sunset on that date.
932
933 An optional parameter MARK specifies a face or single-character string to
934 use when highlighting the day in the calendar."
935 (if (not (and calendar-latitude calendar-longitude calendar-time-zone))
936 (solar-setup))
937 (if (= (% (calendar-absolute-from-gregorian date) 7) 5);; Friday
938 (let* ((sunset (car (cdr (solar-sunrise-sunset date))))
939 (light (if sunset
940 (cons (- (car sunset)
941 (/ diary-sabbath-candles-minutes 60.0))
942 (cdr sunset)))))
943 (if sunset
944 (cons mark
945 (format "%s Sabbath candle lighting"
946 (apply 'solar-time-string light)))))))
947
948 ; from Meeus, 1991, page 167
949 (defconst solar-seasons-data
950 '((485 324.96 1934.136)
951 (203 337.23 32964.467)
952 (199 342.08 20.186)
953 (182 27.85 445267.112)
954 (156 73.14 45036.886)
955 (136 171.52 22518.443)
956 (77 222.54 65928.934)
957 (74 296.72 3034.906)
958 (70 243.58 9037.513)
959 (58 119.81 33718.147)
960 (52 297.17 150.678)
961 (50 21.02 2281.226)
962 (45 247.54 29929.562)
963 (44 325.15 31555.956)
964 (29 60.93 4443.417)
965 (18 155.12 67555.328)
966 (17 288.79 4562.452)
967 (16 198.04 62894.029)
968 (14 199.76 31436.921)
969 (12 95.39 14577.848)
970 (12 287.11 31931.756)
971 (12 320.81 34777.259)
972 (9 227.73 1222.114)
973 (8 15.45 16859.074)))
974
975 (defun solar-equinoxes/solstices (k year)
976 "Date of equinox/solstice K for YEAR.
977 K=0, spring equinox; K=1, summer solstice; K=2, fall equinox;
978 K=3, winter solstice.
979 RESULT is a gregorian local date.
980
981 Accurate to less than a minute between 1951 and 2050."
982 (let* ((JDE0 (solar-mean-equinoxes/solstices k year))
983 (T (/ (- JDE0 2451545.0) 36525))
984 (W (- (* 35999.373 T) 2.47))
985 (Delta-lambda (+ 1 (* 0.0334 (solar-cosine-degrees W))
986 (* 0.0007 (solar-cosine-degrees (* 2 W)))))
987 (S (apply '+ (mapcar '(lambda(x)
988 (* (car x) (solar-cosine-degrees
989 (+ (* (car (cdr (cdr x))) T)
990 (car (cdr x))))))
991 solar-seasons-data)))
992 (JDE (+ JDE0 (/ (* 0.00001 S) Delta-lambda)))
993 (correction (+ 102.3 (* 123.5 T) (* 32.5 T T)))
994 ; ephemeris time correction
995 (JD (- JDE (/ correction 86400)))
996 (date (calendar-gregorian-from-absolute (floor (- JD 1721424.5))))
997 (time (- (- JD 0.5) (floor (- JD 0.5))))
998 )
999 (list (car date) (+ (car (cdr date)) time
1000 (/ (/ calendar-time-zone 60.0) 24.0))
1001 (car (cdr (cdr date))))))
1002
1003 ; from Meeus, 1991, page 166
1004 (defun solar-mean-equinoxes/solstices (k year)
1005 "Julian day of mean equinox/solstice K for YEAR.
1006 K=0, spring equinox; K=1, summer solstice; K=2, fall equinox; K=3, winter
1007 solstice. These formulas are only to be used between 1000 BC and 3000 AD."
1008 (let ((y (/ year 1000.0))
1009 (z (/ (- year 2000) 1000.0)))
1010 (if (< year 1000) ; actually between -1000 and 1000
1011 (cond ((equal k 0) (+ 1721139.29189
1012 (* 365242.13740 y)
1013 (* 0.06134 y y)
1014 (* 0.00111 y y y)
1015 (* -0.00071 y y y y)))
1016 ((equal k 1) (+ 1721233.25401
1017 (* 365241.72562 y)
1018 (* -0.05323 y y)
1019 (* 0.00907 y y y)
1020 (* 0.00025 y y y y)))
1021 ((equal k 2) (+ 1721325.70455
1022 (* 365242.49558 y)
1023 (* -0.11677 y y)
1024 (* -0.00297 y y y)
1025 (* 0.00074 y y y y)))
1026 ((equal k 3) (+ 1721414.39987
1027 (* 365242.88257 y)
1028 (* -0.00769 y y)
1029 (* -0.00933 y y y)
1030 (* -0.00006 y y y y))))
1031 ; actually between 1000 and 3000
1032 (cond ((equal k 0) (+ 2451623.80984
1033 (* 365242.37404 z)
1034 (* 0.05169 z z)
1035 (* -0.00411 z z z)
1036 (* -0.00057 z z z z)))
1037 ((equal k 1) (+ 2451716.56767
1038 (* 365241.62603 z)
1039 (* 0.00325 z z)
1040 (* 0.00888 z z z)
1041 (* -0.00030 z z z z)))
1042 ((equal k 2) (+ 2451810.21715
1043 (* 365242.01767 z)
1044 (* -0.11575 z z)
1045 (* 0.00337 z z z)
1046 (* 0.00078 z z z z)))
1047 ((equal k 3) (+ 2451900.05952
1048 (* 365242.74049 z)
1049 (* -0.06223 z z)
1050 (* -0.00823 z z z)
1051 (* 0.00032 z z z z)))))))
1052
1053 ;;;###autoload
1054 (defun solar-equinoxes-solstices ()
1055 "*local* date and time of equinoxes and solstices, if visible in the calendar window.
1056 Requires floating point."
1057 (let ((m displayed-month)
1058 (y displayed-year))
1059 (increment-calendar-month m y (cond ((= 1 (% m 3)) -1)
1060 ((= 2 (% m 3)) 1)
1061 (t 0)))
1062 (let* ((calendar-standard-time-zone-name
1063 (if calendar-time-zone calendar-standard-time-zone-name "UTC"))
1064 (calendar-daylight-savings-starts
1065 (if calendar-time-zone calendar-daylight-savings-starts))
1066 (calendar-daylight-savings-ends
1067 (if calendar-time-zone calendar-daylight-savings-ends))
1068 (calendar-time-zone (if calendar-time-zone calendar-time-zone 0))
1069 (k (1- (/ m 3)))
1070 (d0 (solar-equinoxes/solstices k y))
1071 (d1 (list (car d0) (floor (car (cdr d0))) (car (cdr (cdr d0)))))
1072 (h0 (* 24 (- (car (cdr d0)) (floor (car (cdr d0))))))
1073 (adj (dst-adjust-time d1 h0))
1074 (d (list (car (car adj))
1075 (+ (car (cdr (car adj)) )
1076 (/ (car (cdr adj)) 24.0))
1077 (car (cdr (cdr (car adj))))))
1078 ; The following is nearly as accurate, but not quite:
1079 ;(d0 (solar-date-next-longitude
1080 ; (calendar-astro-from-absolute
1081 ; (calendar-absolute-from-gregorian
1082 ; (list (+ 3 (* k 3)) 15 y)))
1083 ; 90))
1084 ;(abs-day (calendar-absolute-from-astro d)))
1085 (abs-day (calendar-absolute-from-gregorian d)))
1086 (list
1087 (list (calendar-gregorian-from-absolute (floor abs-day))
1088 (format "%s %s"
1089 (nth k (if (and calendar-latitude
1090 (< (calendar-latitude) 0))
1091 solar-s-hemi-seasons
1092 solar-n-hemi-seasons))
1093 (solar-time-string
1094 (* 24 (- abs-day (floor abs-day)))
1095 (if (dst-in-effect abs-day)
1096 calendar-daylight-time-zone-name
1097 calendar-standard-time-zone-name))))))))
1098
1099
1100 (provide 'solar)
1101
1102 ;;; arch-tag: bc0ff693-df58-4666-bde4-2a7837ccb8fe
1103 ;;; solar.el ends here