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