xref: /dragonfly/usr.bin/calendar/basics.c (revision d19ef5a2)
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 <err.h>
43*d19ef5a2SAaron LI #include <math.h>
44*d19ef5a2SAaron LI #include <stdio.h>
45*d19ef5a2SAaron LI 
46*d19ef5a2SAaron LI #include "basics.h"
47*d19ef5a2SAaron LI #include "gregorian.h"
48*d19ef5a2SAaron LI #include "utils.h"
49*d19ef5a2SAaron LI 
50*d19ef5a2SAaron LI /*
51*d19ef5a2SAaron LI  * Determine the day of week of the fixed date $rd.
52*d19ef5a2SAaron LI  * Ref: Sec.(1.12), Eq.(1.60)
53*d19ef5a2SAaron LI  */
54*d19ef5a2SAaron LI int
dayofweek_from_fixed(int rd)55*d19ef5a2SAaron LI dayofweek_from_fixed(int rd)
56*d19ef5a2SAaron LI {
57*d19ef5a2SAaron LI 	/* NOTE: R.D. 1 is Monday */
58*d19ef5a2SAaron LI 	return mod(rd, 7);
59*d19ef5a2SAaron LI }
60*d19ef5a2SAaron LI 
61*d19ef5a2SAaron LI /*
62*d19ef5a2SAaron LI  * Calculate the fixed date of the day-of-week $dow on or before
63*d19ef5a2SAaron LI  * the fixed date $rd.
64*d19ef5a2SAaron LI  * Ref: Sec.(1.12), Eq.(1.62)
65*d19ef5a2SAaron LI  */
66*d19ef5a2SAaron LI int
kday_onbefore(int dow,int rd)67*d19ef5a2SAaron LI kday_onbefore(int dow, int rd)
68*d19ef5a2SAaron LI {
69*d19ef5a2SAaron LI 	return rd - dayofweek_from_fixed(rd - dow);
70*d19ef5a2SAaron LI }
71*d19ef5a2SAaron LI 
72*d19ef5a2SAaron LI /*
73*d19ef5a2SAaron LI  * Calculate the fixed date of the day-of-week $dow after
74*d19ef5a2SAaron LI  * the fixed date $rd.
75*d19ef5a2SAaron LI  * Ref: Sec.(1.12), Eq.(1.68)
76*d19ef5a2SAaron LI  */
77*d19ef5a2SAaron LI int
kday_after(int dow,int rd)78*d19ef5a2SAaron LI kday_after(int dow, int rd)
79*d19ef5a2SAaron LI {
80*d19ef5a2SAaron LI 	return kday_onbefore(dow, rd + 7);
81*d19ef5a2SAaron LI }
82*d19ef5a2SAaron LI 
83*d19ef5a2SAaron LI /*
84*d19ef5a2SAaron LI  * Calculate the fixed date of the day-of-week $dow nearest to
85*d19ef5a2SAaron LI  * the fixed date $rd.
86*d19ef5a2SAaron LI  * Ref: Sec.(1.12), Eq.(1.66)
87*d19ef5a2SAaron LI  */
88*d19ef5a2SAaron LI int
kday_nearest(int dow,int rd)89*d19ef5a2SAaron LI kday_nearest(int dow, int rd)
90*d19ef5a2SAaron LI {
91*d19ef5a2SAaron LI 	return kday_onbefore(dow, rd + 3);
92*d19ef5a2SAaron LI }
93*d19ef5a2SAaron LI 
94*d19ef5a2SAaron LI /*
95*d19ef5a2SAaron LI  * Calculate the $n-th occurrence of a given day-of-week $dow counting
96*d19ef5a2SAaron LI  * from either after (if $n > 0) or before (if $n < 0) the given $date.
97*d19ef5a2SAaron LI  * Ref: Sec.(2.5), Eq.(2.33)
98*d19ef5a2SAaron LI  */
99*d19ef5a2SAaron LI int
nth_kday(int n,int dow,struct date * date)100*d19ef5a2SAaron LI nth_kday(int n, int dow, struct date *date)
101*d19ef5a2SAaron LI {
102*d19ef5a2SAaron LI 	if (n == 0)
103*d19ef5a2SAaron LI 		errx(1, "%s: invalid n = 0!", __func__);
104*d19ef5a2SAaron LI 
105*d19ef5a2SAaron LI 	int rd = fixed_from_gregorian(date);
106*d19ef5a2SAaron LI 	int kday;
107*d19ef5a2SAaron LI 	if (n > 0)
108*d19ef5a2SAaron LI 		kday = kday_onbefore(dow, rd - 1);  /* Sec.(1.12), Eq.(1.67) */
109*d19ef5a2SAaron LI 	else
110*d19ef5a2SAaron LI 		kday = kday_onbefore(dow, rd + 7);  /* Sec.(1.12), Eq.(1.68) */
111*d19ef5a2SAaron LI 	return 7 * n + kday;
112*d19ef5a2SAaron LI }
113*d19ef5a2SAaron LI 
114*d19ef5a2SAaron LI /*
115*d19ef5a2SAaron LI  * Calculate the ephemeris correction (fraction of day) required for
116*d19ef5a2SAaron LI  * converting between Universal Time and Dynamical Time at moment $t.
117*d19ef5a2SAaron LI  * Ref: Sec.(14.2), Eq.(14.15)
118*d19ef5a2SAaron LI  */
119*d19ef5a2SAaron LI double
ephemeris_correction(double t)120*d19ef5a2SAaron LI ephemeris_correction(double t)
121*d19ef5a2SAaron LI {
122*d19ef5a2SAaron LI 	int rd = (int)floor(t);
123*d19ef5a2SAaron LI 	int year = gregorian_year_from_fixed(rd);
124*d19ef5a2SAaron LI 	int y2000 = year - 2000;
125*d19ef5a2SAaron LI 	int y1700 = year - 1700;
126*d19ef5a2SAaron LI 	int y1600 = year - 1600;
127*d19ef5a2SAaron LI 	double y1820 = (year - 1820) / 100.0;
128*d19ef5a2SAaron LI 	double y1000 = (year - 1000) / 100.0;
129*d19ef5a2SAaron LI 	double y0    = year / 100.0;
130*d19ef5a2SAaron LI 
131*d19ef5a2SAaron LI 	double coef2006[] = { 62.92, 0.32217, 0.005589 };
132*d19ef5a2SAaron LI 	double coef1987[] = { 63.86, 0.3345, -0.060374, 0.0017275,
133*d19ef5a2SAaron LI 			      0.000651814, 0.00002373599 };
134*d19ef5a2SAaron LI 	double coef1900[] = { -0.00002, 0.000297, 0.025184, -0.181133,
135*d19ef5a2SAaron LI 			      0.553040, -0.861938, 0.677066, -0.212591 };
136*d19ef5a2SAaron LI 	double coef1800[] = { -0.000009, 0.003844, 0.083563, 0.865736,
137*d19ef5a2SAaron LI 			      4.867575, 15.845535, 31.332267, 38.291999,
138*d19ef5a2SAaron LI 			      28.316289, 11.636204, 2.043794 };
139*d19ef5a2SAaron LI 	double coef1700[] = { 8.118780842, -0.005092142,
140*d19ef5a2SAaron LI 			      0.003336121, -0.0000266484 };
141*d19ef5a2SAaron LI 	double coef1600[] = { 120.0, -0.9808, -0.01532, 0.000140272128 };
142*d19ef5a2SAaron LI 	double coef500[]  = { 1574.2, -556.01, 71.23472, 0.319781,
143*d19ef5a2SAaron LI 			      -0.8503463, -0.005050998, 0.0083572073 };
144*d19ef5a2SAaron LI 	double coef0[]    = { 10583.6, -1014.41, 33.78311, -5.952053,
145*d19ef5a2SAaron LI 			      -0.1798452, 0.022174192, 0.0090316521 };
146*d19ef5a2SAaron LI 
147*d19ef5a2SAaron LI 	double c_other = (-20.0 + 32.0 * y1820 * y1820) / 86400.0;
148*d19ef5a2SAaron LI 	struct date date1 = { 1900, 1, 1 };
149*d19ef5a2SAaron LI 	struct date date2 = { year, 7, 1 };
150*d19ef5a2SAaron LI 	double c = gregorian_date_difference(&date1, &date2) / 36525.0;
151*d19ef5a2SAaron LI 
152*d19ef5a2SAaron LI 	if (year > 2150) {
153*d19ef5a2SAaron LI 		return c_other;
154*d19ef5a2SAaron LI 	} else if (year >= 2051) {
155*d19ef5a2SAaron LI 		return c_other + 0.5628 * (2150 - year) / 86400.0;
156*d19ef5a2SAaron LI 	} else if (year >= 2006) {
157*d19ef5a2SAaron LI 		return poly(y2000, coef2006, nitems(coef2006)) / 86400.0;
158*d19ef5a2SAaron LI 	} else if (year >= 1987) {
159*d19ef5a2SAaron LI 		return poly(y2000, coef1987, nitems(coef1987)) / 86400.0;
160*d19ef5a2SAaron LI 	} else if (year >= 1900) {
161*d19ef5a2SAaron LI 		return poly(c, coef1900, nitems(coef1900));
162*d19ef5a2SAaron LI 	} else if (year >= 1800) {
163*d19ef5a2SAaron LI 		return poly(c, coef1800, nitems(coef1800));
164*d19ef5a2SAaron LI 	} else if (year >= 1700) {
165*d19ef5a2SAaron LI 		return poly(y1700, coef1700, nitems(coef1700)) / 86400.0;
166*d19ef5a2SAaron LI 	} else if (year >= 1600) {
167*d19ef5a2SAaron LI 		return poly(y1600, coef1600, nitems(coef1600)) / 86400.0;
168*d19ef5a2SAaron LI 	} else if (year >= 500) {
169*d19ef5a2SAaron LI 		return poly(y1000, coef500, nitems(coef500)) / 86400.0;
170*d19ef5a2SAaron LI 	} else if (year > -500) {
171*d19ef5a2SAaron LI 		return poly(y0, coef0, nitems(coef0)) / 86400.0;
172*d19ef5a2SAaron LI 	} else {
173*d19ef5a2SAaron LI 		return c_other;
174*d19ef5a2SAaron LI 	}
175*d19ef5a2SAaron LI }
176*d19ef5a2SAaron LI 
177*d19ef5a2SAaron LI /*
178*d19ef5a2SAaron LI  * Convert from Universal Time (UT) to Dynamical Time (DT).
179*d19ef5a2SAaron LI  * Ref: Sec.(14.2), Eq.(14.16)
180*d19ef5a2SAaron LI  */
181*d19ef5a2SAaron LI double
dynamical_from_universal(double t)182*d19ef5a2SAaron LI dynamical_from_universal(double t)
183*d19ef5a2SAaron LI {
184*d19ef5a2SAaron LI 	return t + ephemeris_correction(t);
185*d19ef5a2SAaron LI }
186*d19ef5a2SAaron LI 
187*d19ef5a2SAaron LI /*
188*d19ef5a2SAaron LI  * Convert from dynamical time to universal time.
189*d19ef5a2SAaron LI  * Ref: Sec.(14.2), Eq.(14.17)
190*d19ef5a2SAaron LI  */
191*d19ef5a2SAaron LI double
universal_from_dynamical(double t)192*d19ef5a2SAaron LI universal_from_dynamical(double t)
193*d19ef5a2SAaron LI {
194*d19ef5a2SAaron LI 	return t - ephemeris_correction(t);
195*d19ef5a2SAaron LI }
196*d19ef5a2SAaron LI 
197*d19ef5a2SAaron LI /*
198*d19ef5a2SAaron LI  * Calculate the number (and fraction) of uniform-length centuries
199*d19ef5a2SAaron LI  * (36525 days) since noon on January 1, 2000 (Gregorian) at moment $t.
200*d19ef5a2SAaron LI  * Ref: Sec.(14.2), Eq.(14.18)
201*d19ef5a2SAaron LI  */
202*d19ef5a2SAaron LI double
julian_centuries(double t)203*d19ef5a2SAaron LI julian_centuries(double t)
204*d19ef5a2SAaron LI {
205*d19ef5a2SAaron LI 	double dt = dynamical_from_universal(t);
206*d19ef5a2SAaron LI 	double j2000 = 0.5 + gregorian_new_year(2000);
207*d19ef5a2SAaron LI 	return (dt - j2000) / 36525.0;
208*d19ef5a2SAaron LI }
209*d19ef5a2SAaron LI 
210*d19ef5a2SAaron LI /*
211*d19ef5a2SAaron LI  * Calculate the mean sidereal time of day expressed as hour angle
212*d19ef5a2SAaron LI  * at moment $t.
213*d19ef5a2SAaron LI  * Ref: Sec.(14.3), Eq.(14.27)
214*d19ef5a2SAaron LI  */
215*d19ef5a2SAaron LI double
sidereal_from_moment(double t)216*d19ef5a2SAaron LI sidereal_from_moment(double t)
217*d19ef5a2SAaron LI {
218*d19ef5a2SAaron LI 	double j2000 = 0.5 + gregorian_new_year(2000);
219*d19ef5a2SAaron LI 	int century_days = 36525;
220*d19ef5a2SAaron LI 	double c = (t - j2000) / century_days;
221*d19ef5a2SAaron LI 	double coef[] = { 280.46061837, 360.98564736629 * century_days,
222*d19ef5a2SAaron LI 			  0.000387933, -1.0 / 38710000.0 };
223*d19ef5a2SAaron LI 	return mod_f(poly(c, coef, nitems(coef)), 360);
224*d19ef5a2SAaron LI }
225*d19ef5a2SAaron LI 
226*d19ef5a2SAaron LI /*
227*d19ef5a2SAaron LI  * Ref: Sec.(14.3), Eq.(14.20)
228*d19ef5a2SAaron LI  */
229*d19ef5a2SAaron LI double
equation_of_time(double t)230*d19ef5a2SAaron LI equation_of_time(double t)
231*d19ef5a2SAaron LI {
232*d19ef5a2SAaron LI 	double c = julian_centuries(t);
233*d19ef5a2SAaron LI 	double epsilon = obliquity(t);
234*d19ef5a2SAaron LI 	double y = pow(tan_deg(epsilon/2), 2);
235*d19ef5a2SAaron LI 
236*d19ef5a2SAaron LI 	double lambda = 280.46645 + 36000.76983 * c + 0.0003032 * c*c;
237*d19ef5a2SAaron LI 	double anomaly = (357.52910 + 35999.05030 * c -
238*d19ef5a2SAaron LI 			  0.0001559 * c*c - 0.00000048 * c*c*c);
239*d19ef5a2SAaron LI 	double eccentricity = (0.016708617 - 0.000042037 * c -
240*d19ef5a2SAaron LI 			       0.0000001236 * c*c);
241*d19ef5a2SAaron LI 
242*d19ef5a2SAaron LI 	double equation = (y * sin_deg(2*lambda) -
243*d19ef5a2SAaron LI 			   2 * eccentricity * sin_deg(anomaly) +
244*d19ef5a2SAaron LI 			   4 * eccentricity * y * sin_deg(anomaly) * cos_deg(2*lambda) -
245*d19ef5a2SAaron LI 			   1.25 * eccentricity*eccentricity * sin_deg(2*anomaly) -
246*d19ef5a2SAaron LI 			   0.5 * y*y * sin_deg(4*lambda)) / (2*M_PI);
247*d19ef5a2SAaron LI 
248*d19ef5a2SAaron LI 	double vmax = 0.5;  /* i.e., 12 hours */
249*d19ef5a2SAaron LI 	if (fabs(equation) < vmax)
250*d19ef5a2SAaron LI 		return equation;
251*d19ef5a2SAaron LI 	else
252*d19ef5a2SAaron LI 		return (equation > 0) ? vmax : -vmax;
253*d19ef5a2SAaron LI }
254*d19ef5a2SAaron LI 
255*d19ef5a2SAaron LI /*
256*d19ef5a2SAaron LI  * Calculate the sundial time from local time $t at location of
257*d19ef5a2SAaron LI  * longitude $longitude.
258*d19ef5a2SAaron LI  * Ref: Sec.(14.3), Eq.(14.21)
259*d19ef5a2SAaron LI  */
260*d19ef5a2SAaron LI double
apparent_from_local(double t,double longitude)261*d19ef5a2SAaron LI apparent_from_local(double t, double longitude)
262*d19ef5a2SAaron LI {
263*d19ef5a2SAaron LI 	double ut = t - longitude / 360.0;  /* local time -> universal time */
264*d19ef5a2SAaron LI 	return t + equation_of_time(ut);
265*d19ef5a2SAaron LI }
266*d19ef5a2SAaron LI 
267*d19ef5a2SAaron LI /*
268*d19ef5a2SAaron LI  * Calculate the local time from sundial time $t at location of
269*d19ef5a2SAaron LI  * longitude $longitude.
270*d19ef5a2SAaron LI  * Ref: Sec.(14.3), Eq.(14.22)
271*d19ef5a2SAaron LI  */
272*d19ef5a2SAaron LI double
local_from_apparent(double t,double longitude)273*d19ef5a2SAaron LI local_from_apparent(double t, double longitude)
274*d19ef5a2SAaron LI {
275*d19ef5a2SAaron LI 	double ut = t - longitude / 360.0;  /* local time -> universal time */
276*d19ef5a2SAaron LI 	return t - equation_of_time(ut);
277*d19ef5a2SAaron LI }
278*d19ef5a2SAaron LI 
279*d19ef5a2SAaron LI /*
280*d19ef5a2SAaron LI  * Calculate the obliquity of ecliptic at moment $t
281*d19ef5a2SAaron LI  * Ref: Sec.(14.4), Eq.(14.28)
282*d19ef5a2SAaron LI  */
283*d19ef5a2SAaron LI double
obliquity(double t)284*d19ef5a2SAaron LI obliquity(double t)
285*d19ef5a2SAaron LI {
286*d19ef5a2SAaron LI 	double c = julian_centuries(t);
287*d19ef5a2SAaron LI 	double coef[] = {
288*d19ef5a2SAaron LI 		0.0,
289*d19ef5a2SAaron LI 		angle2deg(0, 0, -46.8150),
290*d19ef5a2SAaron LI 		angle2deg(0, 0, -0.00059),
291*d19ef5a2SAaron LI 		angle2deg(0, 0, 0.001813),
292*d19ef5a2SAaron LI 	};
293*d19ef5a2SAaron LI 	double correction = poly(c, coef, nitems(coef));
294*d19ef5a2SAaron LI 	return angle2deg(23, 26, 21.448) + correction;
295*d19ef5a2SAaron LI }
296*d19ef5a2SAaron LI 
297*d19ef5a2SAaron LI /*
298*d19ef5a2SAaron LI  * Calculate the declination at moment $t of an object at latitude $beta
299*d19ef5a2SAaron LI  * and longitude $lambda.
300*d19ef5a2SAaron LI  * Ref: Sec.(14.4), Eq.(14.29)
301*d19ef5a2SAaron LI  */
302*d19ef5a2SAaron LI double
declination(double t,double beta,double lambda)303*d19ef5a2SAaron LI declination(double t, double beta, double lambda)
304*d19ef5a2SAaron LI {
305*d19ef5a2SAaron LI 	double epsilon = obliquity(t);
306*d19ef5a2SAaron LI 	return arcsin_deg(sin_deg(beta) * cos_deg(epsilon) +
307*d19ef5a2SAaron LI 			  cos_deg(beta) * sin_deg(epsilon) * sin_deg(lambda));
308*d19ef5a2SAaron LI }
309*d19ef5a2SAaron LI 
310*d19ef5a2SAaron LI /*
311*d19ef5a2SAaron LI  * Calculate the right ascension at moment $t of an object at latitude $beta
312*d19ef5a2SAaron LI  * and longitude $lambda.
313*d19ef5a2SAaron LI  * Ref: Sec.(14.4), Eq.(14.30)
314*d19ef5a2SAaron LI  */
315*d19ef5a2SAaron LI double
right_ascension(double t,double beta,double lambda)316*d19ef5a2SAaron LI right_ascension(double t, double beta, double lambda)
317*d19ef5a2SAaron LI {
318*d19ef5a2SAaron LI 	double epsilon = obliquity(t);
319*d19ef5a2SAaron LI 	double x = cos_deg(lambda);
320*d19ef5a2SAaron LI 	double y = (sin_deg(lambda) * cos_deg(epsilon) -
321*d19ef5a2SAaron LI 		    tan_deg(beta) * sin_deg(epsilon));
322*d19ef5a2SAaron LI 	return arctan_deg(y, x);
323*d19ef5a2SAaron LI }
324*d19ef5a2SAaron LI 
325*d19ef5a2SAaron LI /*
326*d19ef5a2SAaron LI  * Calculate the refraction angle (in degrees) at a location of elevation
327*d19ef5a2SAaron LI  * $elevation.
328*d19ef5a2SAaron LI  * Ref: Sec.(14.7), Eq.(14.75)
329*d19ef5a2SAaron LI  */
330*d19ef5a2SAaron LI double
refraction(double elevation)331*d19ef5a2SAaron LI refraction(double elevation)
332*d19ef5a2SAaron LI {
333*d19ef5a2SAaron LI 	double h = (elevation > 0) ? elevation : 0.0;
334*d19ef5a2SAaron LI 	double radius = 6.372e6;  /* Earth radius in meters */
335*d19ef5a2SAaron LI 	/* depression of visible horizon */
336*d19ef5a2SAaron LI 	double dip = arccos_deg(radius / (radius + h));
337*d19ef5a2SAaron LI 	/* depression contributed by an elevation of $h */
338*d19ef5a2SAaron LI 	double dip2 = (19.0/3600.0) * sqrt(h);
339*d19ef5a2SAaron LI 	/* average effect of refraction */
340*d19ef5a2SAaron LI 	double avg = 34.0 / 60.0;
341*d19ef5a2SAaron LI 
342*d19ef5a2SAaron LI 	return avg + dip + dip2;
343*d19ef5a2SAaron LI }
344*d19ef5a2SAaron LI 
345*d19ef5a2SAaron LI /*
346*d19ef5a2SAaron LI  * Determine the day of year of the fixed date $rd.
347*d19ef5a2SAaron LI  */
348*d19ef5a2SAaron LI int
dayofyear_from_fixed(int rd)349*d19ef5a2SAaron LI dayofyear_from_fixed(int rd)
350*d19ef5a2SAaron LI {
351*d19ef5a2SAaron LI 	int year = gregorian_year_from_fixed(rd);
352*d19ef5a2SAaron LI 	struct date date = { year - 1, 12, 31 };
353*d19ef5a2SAaron LI 
354*d19ef5a2SAaron LI 	return rd - fixed_from_gregorian(&date);
355*d19ef5a2SAaron LI }
356*d19ef5a2SAaron LI 
357*d19ef5a2SAaron LI /*
358*d19ef5a2SAaron LI  * Format the given time $t to 'HH:MM:SS' style.
359*d19ef5a2SAaron LI  */
360*d19ef5a2SAaron LI int
format_time(char * buf,size_t size,double t)361*d19ef5a2SAaron LI format_time(char *buf, size_t size, double t)
362*d19ef5a2SAaron LI {
363*d19ef5a2SAaron LI 	int hh, mm, ss, i;
364*d19ef5a2SAaron LI 
365*d19ef5a2SAaron LI 	t -= floor(t);
366*d19ef5a2SAaron LI 	i = (int)round(t * 24*60*60);
367*d19ef5a2SAaron LI 
368*d19ef5a2SAaron LI 	hh = i / (60*60);
369*d19ef5a2SAaron LI 	i %= 60*60;
370*d19ef5a2SAaron LI 	mm = i / 60;
371*d19ef5a2SAaron LI 	ss = i % 60;
372*d19ef5a2SAaron LI 
373*d19ef5a2SAaron LI 	return snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss);
374*d19ef5a2SAaron LI }
375*d19ef5a2SAaron LI 
376*d19ef5a2SAaron LI /*
377*d19ef5a2SAaron LI  * Format the given timezone (in fraction of days) $zone to '[+-]HH:MM' style.
378*d19ef5a2SAaron LI  */
379*d19ef5a2SAaron LI int
format_zone(char * buf,size_t size,double zone)380*d19ef5a2SAaron LI format_zone(char *buf, size_t size, double zone)
381*d19ef5a2SAaron LI {
382*d19ef5a2SAaron LI 	bool positive;
383*d19ef5a2SAaron LI 	int hh, mm, i;
384*d19ef5a2SAaron LI 
385*d19ef5a2SAaron LI 	positive = (zone >= 0);
386*d19ef5a2SAaron LI 	i = (int)round(fabs(zone) * 24*60);
387*d19ef5a2SAaron LI 	hh = i / 60;
388*d19ef5a2SAaron LI 	mm = i % 60;
389*d19ef5a2SAaron LI 
390*d19ef5a2SAaron LI 	return snprintf(buf, size, "%c%02d:%02d",
391*d19ef5a2SAaron LI 			(positive ? '+' : '-'), hh, mm);
392*d19ef5a2SAaron LI }
393*d19ef5a2SAaron LI 
394*d19ef5a2SAaron LI /*
395*d19ef5a2SAaron LI  * Format the location to style: 'dd°mm'ss" [NS], dd°mm'ss" [EW], mm.m m'
396*d19ef5a2SAaron LI  */
397*d19ef5a2SAaron LI int
format_location(char * buf,size_t size,const struct location * loc)398*d19ef5a2SAaron LI format_location(char *buf, size_t size, const struct location *loc)
399*d19ef5a2SAaron LI {
400*d19ef5a2SAaron LI 	int d1, d2, m1, m2, s1, s2, i;
401*d19ef5a2SAaron LI 	bool north, east;
402*d19ef5a2SAaron LI 
403*d19ef5a2SAaron LI 	north = (loc->latitude >= 0);
404*d19ef5a2SAaron LI 	i = (int)round(fabs(loc->latitude) * 60*60);
405*d19ef5a2SAaron LI 	d1 = i / (60*60);
406*d19ef5a2SAaron LI 	i %= 60*60;
407*d19ef5a2SAaron LI 	m1 = i / 60;
408*d19ef5a2SAaron LI 	s1 = i % 60;
409*d19ef5a2SAaron LI 
410*d19ef5a2SAaron LI 	east = (loc->longitude >= 0);
411*d19ef5a2SAaron LI 	i = (int)round(fabs(loc->longitude) * 60*60);
412*d19ef5a2SAaron LI 	d2 = i / (60*60);
413*d19ef5a2SAaron LI 	i %= 60*60;
414*d19ef5a2SAaron LI 	m2 = i / 60;
415*d19ef5a2SAaron LI 	s2 = i % 60;
416*d19ef5a2SAaron LI 
417*d19ef5a2SAaron LI 	return snprintf(buf, size, "%d°%d'%d\" %c, %d°%d'%d\" %c, %.1lf m",
418*d19ef5a2SAaron LI 			d1, m1, s1, (north ? 'N' : 'S'),
419*d19ef5a2SAaron LI 			d2, m2, s2, (east ? 'E' : 'W'),
420*d19ef5a2SAaron LI 			loc->elevation);
421*d19ef5a2SAaron LI }
422