xref: /dflybsd-src/usr.bin/calendar/sun.c (revision d19ef5a274debcb71f2e8cd8dce8b954dc73944b)
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