1*d19ef5a2SAaron LI /*-
2*d19ef5a2SAaron LI * SPDX-License-Identifier: BSD-3-Clause
3*d19ef5a2SAaron LI *
4*d19ef5a2SAaron LI * Copyright (c) 2019-2020 The DragonFly Project. All rights reserved.
5*d19ef5a2SAaron LI *
6*d19ef5a2SAaron LI * This code is derived from software contributed to The DragonFly Project
7*d19ef5a2SAaron LI * by Aaron LI <aly@aaronly.me>
8*d19ef5a2SAaron LI *
9*d19ef5a2SAaron LI * Redistribution and use in source and binary forms, with or without
10*d19ef5a2SAaron LI * modification, are permitted provided that the following conditions
11*d19ef5a2SAaron LI * are met:
12*d19ef5a2SAaron LI *
13*d19ef5a2SAaron LI * 1. Redistributions of source code must retain the above copyright
14*d19ef5a2SAaron LI * notice, this list of conditions and the following disclaimer.
15*d19ef5a2SAaron LI * 2. Redistributions in binary form must reproduce the above copyright
16*d19ef5a2SAaron LI * notice, this list of conditions and the following disclaimer in
17*d19ef5a2SAaron LI * the documentation and/or other materials provided with the
18*d19ef5a2SAaron LI * distribution.
19*d19ef5a2SAaron LI * 3. Neither the name of The DragonFly Project nor the names of its
20*d19ef5a2SAaron LI * contributors may be used to endorse or promote products derived
21*d19ef5a2SAaron LI * from this software without specific, prior written permission.
22*d19ef5a2SAaron LI *
23*d19ef5a2SAaron LI * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24*d19ef5a2SAaron LI * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25*d19ef5a2SAaron LI * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26*d19ef5a2SAaron LI * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27*d19ef5a2SAaron LI * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28*d19ef5a2SAaron LI * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29*d19ef5a2SAaron LI * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30*d19ef5a2SAaron LI * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31*d19ef5a2SAaron LI * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32*d19ef5a2SAaron LI * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33*d19ef5a2SAaron LI * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34*d19ef5a2SAaron LI * SUCH DAMAGE.
35*d19ef5a2SAaron LI *
36*d19ef5a2SAaron LI * Reference:
37*d19ef5a2SAaron LI * Calendrical Calculations, The Ultimate Edition (4th Edition)
38*d19ef5a2SAaron LI * by Edward M. Reingold and Nachum Dershowitz
39*d19ef5a2SAaron LI * 2018, Cambridge University Press
40*d19ef5a2SAaron LI */
41*d19ef5a2SAaron LI
42*d19ef5a2SAaron LI #include <math.h>
43*d19ef5a2SAaron LI #include <stdbool.h>
44*d19ef5a2SAaron LI #include <stddef.h>
45*d19ef5a2SAaron LI #include <stdio.h>
46*d19ef5a2SAaron LI
47*d19ef5a2SAaron LI #include "basics.h"
48*d19ef5a2SAaron LI #include "gregorian.h"
49*d19ef5a2SAaron LI #include "sun.h"
50*d19ef5a2SAaron LI #include "utils.h"
51*d19ef5a2SAaron LI
52*d19ef5a2SAaron LI /*
53*d19ef5a2SAaron LI * Time for the "mean sun" traval from one mean vernal equinox to the next.
54*d19ef5a2SAaron LI * Ref: Sec.(14.4), Eq.(14.31)
55*d19ef5a2SAaron LI */
56*d19ef5a2SAaron LI const double mean_tropical_year = 365.242189;
57*d19ef5a2SAaron LI
58*d19ef5a2SAaron LI /*
59*d19ef5a2SAaron LI * Calculate the longitudinal nutation (in degrees) at moment $t.
60*d19ef5a2SAaron LI * Ref: Sec.(14.4), Eq.(14.34)
61*d19ef5a2SAaron LI */
62*d19ef5a2SAaron LI double
nutation(double t)63*d19ef5a2SAaron LI nutation(double t)
64*d19ef5a2SAaron LI {
65*d19ef5a2SAaron LI double c = julian_centuries(t);
66*d19ef5a2SAaron LI double coefsA[] = { 124.90, -1934.134, 0.002063 };
67*d19ef5a2SAaron LI double coefsB[] = { 201.11, 72001.5377, 0.00057 };
68*d19ef5a2SAaron LI double A = poly(c, coefsA, nitems(coefsA));
69*d19ef5a2SAaron LI double B = poly(c, coefsB, nitems(coefsB));
70*d19ef5a2SAaron LI return -0.004778 * sin_deg(A) - 0.0003667 * sin_deg(B);
71*d19ef5a2SAaron LI }
72*d19ef5a2SAaron LI
73*d19ef5a2SAaron LI /*
74*d19ef5a2SAaron LI * Calculate the aberration (in degrees) at moment $t.
75*d19ef5a2SAaron LI * Ref: Sec.(14.4), Eq.(14.35)
76*d19ef5a2SAaron LI */
77*d19ef5a2SAaron LI double
aberration(double t)78*d19ef5a2SAaron LI aberration(double t)
79*d19ef5a2SAaron LI {
80*d19ef5a2SAaron LI double c = julian_centuries(t);
81*d19ef5a2SAaron LI double A = 177.63 + 35999.01848 * c;
82*d19ef5a2SAaron LI return 0.0000974 * cos_deg(A) - 0.005575;
83*d19ef5a2SAaron LI }
84*d19ef5a2SAaron LI
85*d19ef5a2SAaron LI /*
86*d19ef5a2SAaron LI * Argument data used by 'solar_longitude()' for calculating the solar
87*d19ef5a2SAaron LI * longitude.
88*d19ef5a2SAaron LI * Ref: Sec.(14.4), Table(14.1)
89*d19ef5a2SAaron LI */
90*d19ef5a2SAaron LI static const struct solar_longitude_arg {
91*d19ef5a2SAaron LI int x;
92*d19ef5a2SAaron LI double y;
93*d19ef5a2SAaron LI double z;
94*d19ef5a2SAaron LI } solar_longitude_data[] = {
95*d19ef5a2SAaron LI { 403406, 270.54861, 0.9287892 },
96*d19ef5a2SAaron LI { 195207, 340.19128, 35999.1376958 },
97*d19ef5a2SAaron LI { 119433, 63.91854, 35999.4089666 },
98*d19ef5a2SAaron LI { 112392, 331.26220, 35998.7287385 },
99*d19ef5a2SAaron LI { 3891, 317.843 , 71998.20261 },
100*d19ef5a2SAaron LI { 2819, 86.631 , 71998.4403 },
101*d19ef5a2SAaron LI { 1721, 240.052 , 36000.35726 },
102*d19ef5a2SAaron LI { 660, 310.26 , 71997.4812 },
103*d19ef5a2SAaron LI { 350, 247.23 , 32964.4678 },
104*d19ef5a2SAaron LI { 334, 260.87 , -19.4410 },
105*d19ef5a2SAaron LI { 314, 297.82 , 445267.1117 },
106*d19ef5a2SAaron LI { 268, 343.14 , 45036.8840 },
107*d19ef5a2SAaron LI { 242, 166.79 , 3.1008 },
108*d19ef5a2SAaron LI { 234, 81.53 , 22518.4434 },
109*d19ef5a2SAaron LI { 158, 3.50 , -19.9739 },
110*d19ef5a2SAaron LI { 132, 132.75 , 65928.9345 },
111*d19ef5a2SAaron LI { 129, 182.95 , 9038.0293 },
112*d19ef5a2SAaron LI { 114, 162.03 , 3034.7684 },
113*d19ef5a2SAaron LI { 99, 29.8 , 33718.148 },
114*d19ef5a2SAaron LI { 93, 266.4 , 3034.448 },
115*d19ef5a2SAaron LI { 86, 249.2 , -2280.773 },
116*d19ef5a2SAaron LI { 78, 157.6 , 29929.992 },
117*d19ef5a2SAaron LI { 72, 257.8 , 31556.493 },
118*d19ef5a2SAaron LI { 68, 185.1 , 149.588 },
119*d19ef5a2SAaron LI { 64, 69.9 , 9037.750 },
120*d19ef5a2SAaron LI { 46, 8.0 , 107997.405 },
121*d19ef5a2SAaron LI { 38, 197.1 , -4444.176 },
122*d19ef5a2SAaron LI { 37, 250.4 , 151.771 },
123*d19ef5a2SAaron LI { 32, 65.3 , 67555.316 },
124*d19ef5a2SAaron LI { 29, 162.7 , 31556.080 },
125*d19ef5a2SAaron LI { 28, 341.5 , -4561.540 },
126*d19ef5a2SAaron LI { 27, 291.6 , 107996.706 },
127*d19ef5a2SAaron LI { 27, 98.5 , 1221.655 },
128*d19ef5a2SAaron LI { 25, 146.7 , 62894.167 },
129*d19ef5a2SAaron LI { 24, 110.0 , 31437.369 },
130*d19ef5a2SAaron LI { 21, 5.2 , 14578.298 },
131*d19ef5a2SAaron LI { 21, 342.6 , -31931.757 },
132*d19ef5a2SAaron LI { 20, 230.9 , 34777.243 },
133*d19ef5a2SAaron LI { 18, 256.1 , 1221.999 },
134*d19ef5a2SAaron LI { 17, 45.3 , 62894.511 },
135*d19ef5a2SAaron LI { 14, 242.9 , -4442.039 },
136*d19ef5a2SAaron LI { 13, 115.2 , 107997.909 },
137*d19ef5a2SAaron LI { 13, 151.8 , 119.066 },
138*d19ef5a2SAaron LI { 13, 285.3 , 16859.071 },
139*d19ef5a2SAaron LI { 12, 53.3 , -4.578 },
140*d19ef5a2SAaron LI { 10, 126.6 , 26895.292 },
141*d19ef5a2SAaron LI { 10, 205.7 , -39.127 },
142*d19ef5a2SAaron LI { 10, 85.9 , 12297.536 },
143*d19ef5a2SAaron LI { 10, 146.1 , 90073.778 },
144*d19ef5a2SAaron LI };
145*d19ef5a2SAaron LI
146*d19ef5a2SAaron LI /*
147*d19ef5a2SAaron LI * Calculate the longitude (in degrees) of Sun at moment $t.
148*d19ef5a2SAaron LI * Ref: Sec.(14.4), Eq.(14.33)
149*d19ef5a2SAaron LI */
150*d19ef5a2SAaron LI double
solar_longitude(double t)151*d19ef5a2SAaron LI solar_longitude(double t)
152*d19ef5a2SAaron LI {
153*d19ef5a2SAaron LI double c = julian_centuries(t);
154*d19ef5a2SAaron LI
155*d19ef5a2SAaron LI double sum = 0.0;
156*d19ef5a2SAaron LI const struct solar_longitude_arg *arg;
157*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(solar_longitude_data); i++) {
158*d19ef5a2SAaron LI arg = &solar_longitude_data[i];
159*d19ef5a2SAaron LI sum += arg->x * sin_deg(arg->y + arg->z * c);
160*d19ef5a2SAaron LI }
161*d19ef5a2SAaron LI double lambda = (282.7771834 + 36000.76953744 * c +
162*d19ef5a2SAaron LI 0.000005729577951308232 * sum);
163*d19ef5a2SAaron LI
164*d19ef5a2SAaron LI double ab = aberration(t);
165*d19ef5a2SAaron LI double nu = nutation(t);
166*d19ef5a2SAaron LI
167*d19ef5a2SAaron LI return mod_f(lambda + ab + nu, 360);
168*d19ef5a2SAaron LI }
169*d19ef5a2SAaron LI
170*d19ef5a2SAaron LI /*
171*d19ef5a2SAaron LI * Calculate the moment (in universal time) of the first time at or after
172*d19ef5a2SAaron LI * the given moment $t when the solar longitude will be $lambda degree.
173*d19ef5a2SAaron LI * Ref: Sec.(14.5), Eq.(14.36)
174*d19ef5a2SAaron LI */
175*d19ef5a2SAaron LI double
solar_longitude_atafter(double lambda,double t)176*d19ef5a2SAaron LI solar_longitude_atafter(double lambda, double t)
177*d19ef5a2SAaron LI {
178*d19ef5a2SAaron LI double rate = mean_tropical_year / 360.0;
179*d19ef5a2SAaron LI double lon = solar_longitude(t);
180*d19ef5a2SAaron LI double tau = t + rate * mod_f(lambda - lon, 360);
181*d19ef5a2SAaron LI
182*d19ef5a2SAaron LI /* estimate range (within 5 days) */
183*d19ef5a2SAaron LI double a = (t > tau - 5) ? t : tau - 5;
184*d19ef5a2SAaron LI double b = tau + 5;
185*d19ef5a2SAaron LI
186*d19ef5a2SAaron LI return invert_angular(solar_longitude, lambda, a, b);
187*d19ef5a2SAaron LI }
188*d19ef5a2SAaron LI
189*d19ef5a2SAaron LI /*
190*d19ef5a2SAaron LI * Calculate the approximate moment at or before the given moment $t when
191*d19ef5a2SAaron LI * the solar longitude just exceeded the given degree $lambda.
192*d19ef5a2SAaron LI * Ref: Sec.(14.5), Eq.(14.42)
193*d19ef5a2SAaron LI */
194*d19ef5a2SAaron LI double
estimate_prior_solar_longitude(double lambda,double t)195*d19ef5a2SAaron LI estimate_prior_solar_longitude(double lambda, double t)
196*d19ef5a2SAaron LI {
197*d19ef5a2SAaron LI double rate = mean_tropical_year / 360.0;
198*d19ef5a2SAaron LI
199*d19ef5a2SAaron LI /* first approximation */
200*d19ef5a2SAaron LI double lon = solar_longitude(t);
201*d19ef5a2SAaron LI double tau = t - rate * mod_f(lon - lambda, 360);
202*d19ef5a2SAaron LI
203*d19ef5a2SAaron LI /* refine the estimate to within a day */
204*d19ef5a2SAaron LI lon = solar_longitude(tau);
205*d19ef5a2SAaron LI double delta = mod3_f(lon - lambda, -180, 180);
206*d19ef5a2SAaron LI double t2 = tau - rate * delta;
207*d19ef5a2SAaron LI
208*d19ef5a2SAaron LI return (t < t2) ? t : t2;
209*d19ef5a2SAaron LI }
210*d19ef5a2SAaron LI
211*d19ef5a2SAaron LI /*
212*d19ef5a2SAaron LI * Calculate the geocentric altitude of Sun at moment $t and at location
213*d19ef5a2SAaron LI * ($latitude, $longitude), ignoring parallax and refraction.
214*d19ef5a2SAaron LI * Ref: Sec.(14.4), Eq.(14.41)
215*d19ef5a2SAaron LI */
216*d19ef5a2SAaron LI double
solar_altitude(double t,double latitude,double longitude)217*d19ef5a2SAaron LI solar_altitude(double t, double latitude, double longitude)
218*d19ef5a2SAaron LI {
219*d19ef5a2SAaron LI double lambda = solar_longitude(t);
220*d19ef5a2SAaron LI double alpha = right_ascension(t, 0, lambda);
221*d19ef5a2SAaron LI double delta = declination(t, 0, lambda);
222*d19ef5a2SAaron LI double theta = sidereal_from_moment(t);
223*d19ef5a2SAaron LI double H = mod_f(theta + longitude - alpha, 360);
224*d19ef5a2SAaron LI
225*d19ef5a2SAaron LI double v = (sin_deg(latitude) * sin_deg(delta) +
226*d19ef5a2SAaron LI cos_deg(latitude) * cos_deg(delta) * cos_deg(H));
227*d19ef5a2SAaron LI return mod3_f(arcsin_deg(v), -180, 180);
228*d19ef5a2SAaron LI }
229*d19ef5a2SAaron LI
230*d19ef5a2SAaron LI /*
231*d19ef5a2SAaron LI * Calculate the sine of angle between positions of Sun at local time $t
232*d19ef5a2SAaron LI * and when its depression angle is $alpha degrees at location ($latitude,
233*d19ef5a2SAaron LI * $longitude).
234*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.69)
235*d19ef5a2SAaron LI */
236*d19ef5a2SAaron LI static double
sine_offset(double t,double latitude,double longitude,double alpha)237*d19ef5a2SAaron LI sine_offset(double t, double latitude, double longitude, double alpha)
238*d19ef5a2SAaron LI {
239*d19ef5a2SAaron LI double ut = t - longitude / 360.0; /* local -> universal time */
240*d19ef5a2SAaron LI double lambda = solar_longitude(ut);
241*d19ef5a2SAaron LI double delta = declination(ut, 0, lambda);
242*d19ef5a2SAaron LI
243*d19ef5a2SAaron LI return (tan_deg(latitude) * tan_deg(delta) +
244*d19ef5a2SAaron LI sin_deg(alpha) / cos_deg(delta) / cos_deg(latitude));
245*d19ef5a2SAaron LI }
246*d19ef5a2SAaron LI
247*d19ef5a2SAaron LI /*
248*d19ef5a2SAaron LI * Approximate the moment in local time near the given moment $t when
249*d19ef5a2SAaron LI * the depression angle of Sun is $alpha (negative if above horizon) at
250*d19ef5a2SAaron LI * location ($latitude, $longitude). If $morning is true, then searching
251*d19ef5a2SAaron LI * for the morning event; otherwise for the evening event.
252*d19ef5a2SAaron LI * NOTE: Return an NaN if the depression angle cannot be reached.
253*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.68)
254*d19ef5a2SAaron LI */
255*d19ef5a2SAaron LI static double
approx_depression_moment(double t,double latitude,double longitude,double alpha,bool morning)256*d19ef5a2SAaron LI approx_depression_moment(double t, double latitude, double longitude,
257*d19ef5a2SAaron LI double alpha, bool morning)
258*d19ef5a2SAaron LI {
259*d19ef5a2SAaron LI double t2 = floor(t); /* midnight */
260*d19ef5a2SAaron LI if (alpha < 0)
261*d19ef5a2SAaron LI t2 += 0.5; /* midday */
262*d19ef5a2SAaron LI else if (morning == false)
263*d19ef5a2SAaron LI t2 += 1.0; /* next day */
264*d19ef5a2SAaron LI
265*d19ef5a2SAaron LI double try = sine_offset(t, latitude, longitude, alpha);
266*d19ef5a2SAaron LI double value = ((fabs(try) > 1) ?
267*d19ef5a2SAaron LI sine_offset(t2, latitude, longitude, alpha) :
268*d19ef5a2SAaron LI try);
269*d19ef5a2SAaron LI
270*d19ef5a2SAaron LI if (fabs(value) > 1) {
271*d19ef5a2SAaron LI return NAN;
272*d19ef5a2SAaron LI } else {
273*d19ef5a2SAaron LI double offset = mod3_f(arcsin_deg(value) / 360.0, -0.5, 0.5);
274*d19ef5a2SAaron LI double t3 = floor(t);
275*d19ef5a2SAaron LI if (morning)
276*d19ef5a2SAaron LI t3 += 6.0/24.0 - offset;
277*d19ef5a2SAaron LI else
278*d19ef5a2SAaron LI t3 += 18.0/24.0 + offset;
279*d19ef5a2SAaron LI return local_from_apparent(t3, longitude);
280*d19ef5a2SAaron LI }
281*d19ef5a2SAaron LI }
282*d19ef5a2SAaron LI
283*d19ef5a2SAaron LI /*
284*d19ef5a2SAaron LI * Calculate the moment in local time near the given moment $tapprox when
285*d19ef5a2SAaron LI * the depression angle of Sun is $alpha (negative if above horizon) at
286*d19ef5a2SAaron LI * location ($latitude, $longitude). If $morning is true, then searching
287*d19ef5a2SAaron LI * for the morning event; otherwise for the evening event.
288*d19ef5a2SAaron LI * NOTE: Return an NaN if the depression angle cannot be reached.
289*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.70)
290*d19ef5a2SAaron LI */
291*d19ef5a2SAaron LI static double
depression_moment(double tapprox,double latitude,double longitude,double alpha,bool morning)292*d19ef5a2SAaron LI depression_moment(double tapprox, double latitude, double longitude,
293*d19ef5a2SAaron LI double alpha, bool morning)
294*d19ef5a2SAaron LI {
295*d19ef5a2SAaron LI const double eps = 30.0 / 3600 / 24; /* accuracy of 30 seconds */
296*d19ef5a2SAaron LI double t = approx_depression_moment(tapprox, latitude, longitude,
297*d19ef5a2SAaron LI alpha, morning);
298*d19ef5a2SAaron LI if (isnan(t))
299*d19ef5a2SAaron LI return NAN;
300*d19ef5a2SAaron LI else if (fabs(t - tapprox) < eps)
301*d19ef5a2SAaron LI return t;
302*d19ef5a2SAaron LI else
303*d19ef5a2SAaron LI return depression_moment(t, latitude, longitude,
304*d19ef5a2SAaron LI alpha, morning);
305*d19ef5a2SAaron LI }
306*d19ef5a2SAaron LI
307*d19ef5a2SAaron LI /*
308*d19ef5a2SAaron LI * Calculate the moment of sunrise in standard time on fixed date $rd at
309*d19ef5a2SAaron LI * location $loc.
310*d19ef5a2SAaron LI * NOTE: Return an NaN if no sunrise.
311*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.72,14.76)
312*d19ef5a2SAaron LI */
313*d19ef5a2SAaron LI double
sunrise(int rd,const struct location * loc)314*d19ef5a2SAaron LI sunrise(int rd, const struct location *loc)
315*d19ef5a2SAaron LI {
316*d19ef5a2SAaron LI double sun_radius = 16.0 / 60.0; /* 16 arcminutes */
317*d19ef5a2SAaron LI double alpha = refraction(loc->elevation) + sun_radius;
318*d19ef5a2SAaron LI double tapprox = (double)rd + (6.0/24.0);
319*d19ef5a2SAaron LI double lt = depression_moment(tapprox, loc->latitude, loc->longitude,
320*d19ef5a2SAaron LI alpha, true);
321*d19ef5a2SAaron LI if (isnan(lt))
322*d19ef5a2SAaron LI return NAN;
323*d19ef5a2SAaron LI else
324*d19ef5a2SAaron LI return lt - loc->longitude / 360.0 + loc->zone;
325*d19ef5a2SAaron LI }
326*d19ef5a2SAaron LI
327*d19ef5a2SAaron LI /*
328*d19ef5a2SAaron LI * Calculate the moment of sunset in standard time on fixed date $rd at
329*d19ef5a2SAaron LI * location $loc.
330*d19ef5a2SAaron LI * NOTE: Return an NaN if no sunset.
331*d19ef5a2SAaron LI * Ref: Sec.(14.7), Eq.(14.74,14.77)
332*d19ef5a2SAaron LI */
333*d19ef5a2SAaron LI double
sunset(int rd,const struct location * loc)334*d19ef5a2SAaron LI sunset(int rd, const struct location *loc)
335*d19ef5a2SAaron LI {
336*d19ef5a2SAaron LI double sun_radius = 16.0 / 60.0; /* 16 arcminutes */
337*d19ef5a2SAaron LI double alpha = refraction(loc->elevation) + sun_radius;
338*d19ef5a2SAaron LI double tapprox = (double)rd + (18.0/24.0);
339*d19ef5a2SAaron LI double lt = depression_moment(tapprox, loc->latitude, loc->longitude,
340*d19ef5a2SAaron LI alpha, false);
341*d19ef5a2SAaron LI if (isnan(lt))
342*d19ef5a2SAaron LI return NAN;
343*d19ef5a2SAaron LI else
344*d19ef5a2SAaron LI return lt - loc->longitude / 360.0 + loc->zone;
345*d19ef5a2SAaron LI }
346*d19ef5a2SAaron LI
347*d19ef5a2SAaron LI /**************************************************************************/
348*d19ef5a2SAaron LI
349*d19ef5a2SAaron LI /* Equinoxes and solstices */
350*d19ef5a2SAaron LI static const struct solar_event {
351*d19ef5a2SAaron LI const char *name;
352*d19ef5a2SAaron LI int longitude; /* longitude of Sun */
353*d19ef5a2SAaron LI int month; /* month of the event */
354*d19ef5a2SAaron LI } SOLAR_EVENTS[] = {
355*d19ef5a2SAaron LI { "March Equinox", 0, 3 },
356*d19ef5a2SAaron LI { "June Solstice", 90, 6 },
357*d19ef5a2SAaron LI { "September Equinox", 180, 9 },
358*d19ef5a2SAaron LI { "December Solstice", 270, 12 },
359*d19ef5a2SAaron LI };
360*d19ef5a2SAaron LI
361*d19ef5a2SAaron LI /*
362*d19ef5a2SAaron LI * Print Sun information at the given moment $t (in standard time)
363*d19ef5a2SAaron LI * and events in the year.
364*d19ef5a2SAaron LI */
365*d19ef5a2SAaron LI void
show_sun_info(double t,const struct location * loc)366*d19ef5a2SAaron LI show_sun_info(double t, const struct location *loc)
367*d19ef5a2SAaron LI {
368*d19ef5a2SAaron LI char buf[64];
369*d19ef5a2SAaron LI int rd = (int)floor(t);
370*d19ef5a2SAaron LI
371*d19ef5a2SAaron LI /*
372*d19ef5a2SAaron LI * Sun position
373*d19ef5a2SAaron LI */
374*d19ef5a2SAaron LI double t_u = t - loc->zone; /* universal time */
375*d19ef5a2SAaron LI double lon = solar_longitude(t_u);
376*d19ef5a2SAaron LI double alt = solar_altitude(t_u, loc->latitude, loc->longitude);
377*d19ef5a2SAaron LI printf("Sun position: %.4lf° (longitude), %.4lf° (altitude)\n",
378*d19ef5a2SAaron LI lon, alt);
379*d19ef5a2SAaron LI
380*d19ef5a2SAaron LI /*
381*d19ef5a2SAaron LI * Sun rise and set
382*d19ef5a2SAaron LI */
383*d19ef5a2SAaron LI double moments[2] = { sunrise(rd, loc), sunset(rd, loc) };
384*d19ef5a2SAaron LI const char *names[2] = { "Sunrise", "Sunset" };
385*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(moments); i++) {
386*d19ef5a2SAaron LI if (isnan(moments[i]))
387*d19ef5a2SAaron LI snprintf(buf, sizeof(buf), "(null)");
388*d19ef5a2SAaron LI else
389*d19ef5a2SAaron LI format_time(buf, sizeof(buf), moments[i]);
390*d19ef5a2SAaron LI printf("%-7s: %s\n", names[i], buf);
391*d19ef5a2SAaron LI }
392*d19ef5a2SAaron LI
393*d19ef5a2SAaron LI /*
394*d19ef5a2SAaron LI * Equinoxes and solstices
395*d19ef5a2SAaron LI */
396*d19ef5a2SAaron LI const struct solar_event *event;
397*d19ef5a2SAaron LI int lambda, day_approx;
398*d19ef5a2SAaron LI int year = gregorian_year_from_fixed(rd);
399*d19ef5a2SAaron LI struct date date = { year, 1, 1 };
400*d19ef5a2SAaron LI
401*d19ef5a2SAaron LI printf("\nSolar events in year %d:\n", year);
402*d19ef5a2SAaron LI for (size_t i = 0; i < nitems(SOLAR_EVENTS); i++) {
403*d19ef5a2SAaron LI event = &SOLAR_EVENTS[i];
404*d19ef5a2SAaron LI lambda = event->longitude;
405*d19ef5a2SAaron LI date.month = event->month;
406*d19ef5a2SAaron LI date.day = 1;
407*d19ef5a2SAaron LI day_approx = fixed_from_gregorian(&date);
408*d19ef5a2SAaron LI t = solar_longitude_atafter(lambda, day_approx) + loc->zone;
409*d19ef5a2SAaron LI gregorian_from_fixed((int)floor(t), &date);
410*d19ef5a2SAaron LI format_time(buf, sizeof(buf), t);
411*d19ef5a2SAaron LI printf("%-17s: %3d°, %d-%02d-%02d %s\n",
412*d19ef5a2SAaron LI event->name, lambda,
413*d19ef5a2SAaron LI date.year, date.month, date.day, buf);
414*d19ef5a2SAaron LI }
415*d19ef5a2SAaron LI }
416