xref: /freebsd/usr.bin/calendar/sunpos.c (revision 22cf89c938886d14f5796fc49f9f020c23ea8eaf)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2009-2010 Edwin Groothuis <edwin@FreeBSD.org>.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  */
29 
30 #include <sys/cdefs.h>
31 /*
32  * This code is created to match the formulas available at:
33  * Formula and examples obtained from "How to Calculate alt/az: SAAO" at
34  * http://old.saao.ac.za/public-info/sun-moon-stars/sun-index/how-to-calculate-altaz/
35  */
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <limits.h>
40 #include <math.h>
41 #include <string.h>
42 #include <time.h>
43 #include "calendar.h"
44 
45 #define D2R(m)	((m) / 180 * M_PI)
46 #define R2D(m)	((m) * 180 / M_PI)
47 
48 #define	SIN(x)	(sin(D2R(x)))
49 #define	COS(x)	(cos(D2R(x)))
50 #define	TAN(x)	(tan(D2R(x)))
51 #define	ASIN(x)	(R2D(asin(x)))
52 #define	ATAN(x)	(R2D(atan(x)))
53 
54 #ifdef NOTDEF
55 static void
56 comp(char *s, double v, double c)
57 {
58 
59 	printf("%-*s %*g %*g %*g\n", 15, s, 15, v, 15, c, 15, v - c);
60 }
61 
62 int expY;
63 double expZJ = 30.5;
64 double expUTHM = 8.5;
65 double expD = 34743.854;
66 double expT = 0.9512349;
67 double expL = 324.885;
68 double expM = 42.029;
69 double expepsilon = 23.4396;
70 double explambda = 326.186;
71 double expalpha = 328.428;
72 double expDEC = -12.789;
73 double expeastlongitude = 17.10;
74 double explatitude = -22.57;
75 double expHA = -37.673;
76 double expALT = 49.822;
77 double expAZ = 67.49;
78 #endif
79 
80 static double
81 fixup(double *d)
82 {
83 
84 	if (*d < 0) {
85 		while (*d < 0)
86 			*d += 360;
87 	} else {
88 		while (*d > 360)
89 			*d -= 360;
90 	}
91 
92 	return (*d);
93 }
94 
95 static double ZJtable[] = {
96 	0, -0.5, 30.5, 58.5, 89.5, 119.5, 150.5, 180.5, 211.5, 242.5, 272.5, 303.5, 333.5 };
97 
98 static void
99 sunpos(int inYY, int inMM, int inDD, double UTCOFFSET, int inHOUR, int inMIN,
100     int inSEC, double eastlongitude, double latitude, double *L, double *DEC)
101 {
102 	int Y;
103 	double ZJ, D, T, M, epsilon, lambda, alpha, HA, UTHM;
104 
105 	ZJ = ZJtable[inMM];
106 	if (inMM <= 2 && isleap(inYY))
107 		ZJ -= 1.0;
108 
109 	UTHM = inHOUR + inMIN / FMINSPERHOUR + inSEC / FSECSPERHOUR - UTCOFFSET;
110 	Y = inYY - 1900;						/*  1 */
111 	D = floor(365.25 * Y) + ZJ + inDD + UTHM / FHOURSPERDAY;	/*  3 */
112 	T = D / 36525.0;						/*  4 */
113 	*L = 279.697 + 36000.769 * T;					/*  5 */
114 	fixup(L);
115 	M = 358.476 + 35999.050 * T;					/*  6 */
116 	fixup(&M);
117 	epsilon = 23.452 - 0.013 * T;					/*  7 */
118 	fixup(&epsilon);
119 
120 	lambda = *L + (1.919 - 0.005 * T) * SIN(M) + 0.020 * SIN(2 * M);/*  8 */
121 	fixup(&lambda);
122 	alpha = ATAN(TAN(lambda) * COS(epsilon));			/*  9 */
123 
124 	/* Alpha should be in the same quadrant as lamba */
125 	{
126 		int lssign = sin(D2R(lambda)) < 0 ? -1 : 1;
127 		int lcsign = cos(D2R(lambda)) < 0 ? -1 : 1;
128 		while (((sin(D2R(alpha)) < 0) ? -1 : 1) != lssign
129 		    || ((cos(D2R(alpha)) < 0) ? -1 : 1) != lcsign)
130 			alpha += 90.0;
131 	}
132 	fixup(&alpha);
133 
134 	*DEC = ASIN(SIN(lambda) * SIN(epsilon));			/* 10 */
135 	fixup(DEC);
136 	fixup(&eastlongitude);
137 	HA = *L - alpha + 180 + 15 * UTHM + eastlongitude;		/* 12 */
138 	fixup(&HA);
139 	fixup(&latitude);
140 #ifdef NOTDEF
141 	printf("%02d/%02d %02d:%02d:%02d l:%g d:%g h:%g\n",
142 	    inMM, inDD, inHOUR, inMIN, inSEC, latitude, *DEC, HA);
143 #endif
144 	return;
145 
146 	/*
147 	 * The following calculations are not used, so to save time
148 	 * they are not calculated.
149 	 */
150 #ifdef NOTDEF
151 	*ALT = ASIN(SIN(latitude) * SIN(*DEC) +
152 	    COS(latitude) * COS(*DEC) * COS(HA));			/* 13 */
153 	fixup(ALT);
154 	*AZ = ATAN(SIN(HA) /
155 	    (COS(HA) * SIN(latitude) - TAN(*DEC) * COS(latitude)));	/* 14 */
156 
157 	if (*ALT > 180)
158 		*ALT -= 360;
159 	if (*ALT < -180)
160 		*ALT += 360;
161 	printf("a:%g a:%g\n", *ALT, *AZ);
162 #endif
163 
164 #ifdef NOTDEF
165 	printf("Y:\t\t\t     %d\t\t     %d\t\t      %d\n", Y, expY, Y - expY);
166 	comp("ZJ", ZJ, expZJ);
167 	comp("UTHM", UTHM, expUTHM);
168 	comp("D", D, expD);
169 	comp("T", T, expT);
170 	comp("L", L, fixup(&expL));
171 	comp("M", M, fixup(&expM));
172 	comp("epsilon", epsilon, fixup(&expepsilon));
173 	comp("lambda", lambda, fixup(&explambda));
174 	comp("alpha", alpha, fixup(&expalpha));
175 	comp("DEC", DEC, fixup(&expDEC));
176 	comp("eastlongitude", eastlongitude, fixup(&expeastlongitude));
177 	comp("latitude", latitude, fixup(&explatitude));
178 	comp("HA", HA, fixup(&expHA));
179 	comp("ALT", ALT, fixup(&expALT));
180 	comp("AZ", AZ, fixup(&expAZ));
181 #endif
182 }
183 
184 
185 #define	SIGN(a)	(((a) > 180) ? -1 : 1)
186 #define ANGLE(a, b) (((a) < (b)) ? 1 : -1)
187 #define SHOUR(s) ((s) / 3600)
188 #define SMIN(s) (((s) % 3600) / 60)
189 #define SSEC(s) ((s) % 60)
190 #define HOUR(h) ((h) / 4)
191 #define MIN(h) (15 * ((h) % 4))
192 #define SEC(h)	0
193 #define	DEBUG1(y, m, d, hh, mm, pdec, dec) \
194 	printf("%4d-%02d-%02d %02d:%02d:00 - %7.7g -> %7.7g\n", \
195 	    y, m, d, hh, mm, pdec, dec)
196 #define	DEBUG2(y, m, d, hh, mm, pdec, dec, pang, ang) \
197 	printf("%4d-%02d-%02d %02d:%02d:00 - %7.7g -> %7.7g - %d -> %d\n", \
198 	    y, m, d, hh, mm, pdec, dec, pang, ang)
199 void
200 equinoxsolstice(int year, double UTCoffset, int *equinoxdays, int *solsticedays)
201 {
202 	double fe[2], fs[2];
203 
204 	fequinoxsolstice(year, UTCoffset, fe, fs);
205 	equinoxdays[0] = round(fe[0]);
206 	equinoxdays[1] = round(fe[1]);
207 	solsticedays[0] = round(fs[0]);
208 	solsticedays[1] = round(fs[1]);
209 }
210 
211 void
212 fequinoxsolstice(int year, double UTCoffset, double *equinoxdays, double *solsticedays)
213 {
214 	double dec, prevdec, L;
215 	int h, d, prevangle, angle;
216 	int found = 0;
217 
218 	double decleft, decright, decmiddle;
219 	int dial, s;
220 
221 	int *cumdays;
222 	cumdays = cumdaytab[isleap(year)];
223 
224 	/*
225 	 * Find the first equinox, somewhere in March:
226 	 * It happens when the returned value "dec" goes from
227 	 * [350 ... 360> -> [0 ... 10]
228 	 */
229 	for (d = 18; d < 31; d++) {
230 		/* printf("Comparing day %d to %d.\n", d, d+1); */
231 		sunpos(year, 3, d, UTCoffset, 0, 0, 0, 0.0, 0.0, &L, &decleft);
232 		sunpos(year, 3, d + 1, UTCoffset, 0, 0, 0, 0.0, 0.0,
233 		    &L, &decright);
234 		/* printf("Found %g and %g.\n", decleft, decright); */
235 		if (SIGN(decleft) == SIGN(decright))
236 			continue;
237 
238 		dial = SECSPERDAY;
239 		s = SECSPERDAY / 2;
240 		while (s > 0) {
241 			/* printf("Obtaining %d (%02d:%02d)\n",
242 			    dial, SHOUR(dial), SMIN(dial)); */
243 			sunpos(year, 3, d, UTCoffset,
244 			    SHOUR(dial), SMIN(dial), SSEC(dial),
245 			    0.0, 0.0, &L, &decmiddle);
246 			/* printf("Found %g\n", decmiddle); */
247 			if (SIGN(decleft) == SIGN(decmiddle)) {
248 				decleft = decmiddle;
249 				dial += s;
250 			} else {
251 				decright = decmiddle;
252 				dial -= s;
253 			}
254 			/*
255 			 printf("New boundaries: %g - %g\n", decleft, decright);
256 			*/
257 
258 			s /= 2;
259 		}
260 		equinoxdays[0] = 1 + cumdays[3] + d + (dial / FSECSPERDAY);
261 		break;
262 	}
263 
264 	/* Find the second equinox, somewhere in September:
265 	 * It happens when the returned value "dec" goes from
266 	 * [10 ... 0] -> <360 ... 350]
267 	 */
268 	for (d = 18; d < 31; d++) {
269 		/* printf("Comparing day %d to %d.\n", d, d+1); */
270 		sunpos(year, 9, d, UTCoffset, 0, 0, 0, 0.0, 0.0, &L, &decleft);
271 		sunpos(year, 9, d + 1, UTCoffset, 0, 0, 0, 0.0, 0.0,
272 		    &L, &decright);
273 		/* printf("Found %g and %g.\n", decleft, decright); */
274 		if (SIGN(decleft) == SIGN(decright))
275 			continue;
276 
277 		dial = SECSPERDAY;
278 		s = SECSPERDAY / 2;
279 		while (s > 0) {
280 			/* printf("Obtaining %d (%02d:%02d)\n",
281 			    dial, SHOUR(dial), SMIN(dial)); */
282 			sunpos(year, 9, d, UTCoffset,
283 			    SHOUR(dial), SMIN(dial), SSEC(dial),
284 			    0.0, 0.0, &L, &decmiddle);
285 			/* printf("Found %g\n", decmiddle); */
286 			if (SIGN(decleft) == SIGN(decmiddle)) {
287 				decleft = decmiddle;
288 				dial += s;
289 			} else {
290 				decright = decmiddle;
291 				dial -= s;
292 			}
293 			/*
294 			printf("New boundaries: %g - %g\n", decleft, decright);
295 			*/
296 
297 			s /= 2;
298 		}
299 		equinoxdays[1] = 1 + cumdays[9] + d + (dial / FSECSPERDAY);
300 		break;
301 	}
302 
303 	/*
304 	 * Find the first solstice, somewhere in June:
305 	 * It happens when the returned value "dec" peaks
306 	 * [40 ... 45] -> [45 ... 40]
307 	 */
308 	found = 0;
309 	prevdec = 0;
310 	prevangle = 1;
311 	for (d = 18; d < 31; d++) {
312 		for (h = 0; h < 4 * HOURSPERDAY; h++) {
313 			sunpos(year, 6, d, UTCoffset, HOUR(h), MIN(h), SEC(h),
314 			    0.0, 0.0, &L, &dec);
315 			angle = ANGLE(prevdec, dec);
316 			if (prevangle != angle) {
317 #ifdef NOTDEF
318 				DEBUG2(year, 6, d, HOUR(h), MIN(h),
319 				    prevdec, dec, prevangle, angle);
320 #endif
321 				solsticedays[0] = 1 + cumdays[6] + d +
322 				    ((h / 4.0) / 24.0);
323 				found = 1;
324 				break;
325 			}
326 			prevdec = dec;
327 			prevangle = angle;
328 		}
329 		if (found)
330 			break;
331 	}
332 
333 	/*
334 	 * Find the second solstice, somewhere in December:
335 	 * It happens when the returned value "dec" peaks
336 	 * [315 ... 310] -> [310 ... 315]
337 	 */
338 	found = 0;
339 	prevdec = 360;
340 	prevangle = -1;
341 	for (d = 18; d < 31; d++) {
342 		for (h = 0; h < 4 * HOURSPERDAY; h++) {
343 			sunpos(year, 12, d, UTCoffset, HOUR(h), MIN(h), SEC(h),
344 			    0.0, 0.0, &L, &dec);
345 			angle = ANGLE(prevdec, dec);
346 			if (prevangle != angle) {
347 #ifdef NOTDEF
348 				DEBUG2(year, 12, d, HOUR(h), MIN(h),
349 				    prevdec, dec, prevangle, angle);
350 #endif
351 				solsticedays[1] = 1 + cumdays[12] + d +
352 				    ((h / 4.0) / 24.0);
353 				found = 1;
354 				break;
355 			}
356 			prevdec = dec;
357 			prevangle = angle;
358 		}
359 		if (found)
360 			break;
361 	}
362 
363 	return;
364 }
365 
366 int
367 calculatesunlongitude30(int year, int degreeGMToffset, int *ichinesemonths)
368 {
369 	int m, d, h;
370 	double dec;
371 	double curL, prevL;
372 	int *pichinesemonths, *monthdays, *cumdays, i;
373 	int firstmonth330 = -1;
374 
375 	cumdays = cumdaytab[isleap(year)];
376 	monthdays = monthdaytab[isleap(year)];
377 	pichinesemonths = ichinesemonths;
378 
379 	h = 0;
380 	sunpos(year - 1, 12, 31,
381 	    -24 * (degreeGMToffset / 360.0),
382 	    HOUR(h), MIN(h), SEC(h), 0.0, 0.0, &prevL, &dec);
383 
384 	for (m = 1; m <= 12; m++) {
385 		for (d = 1; d <= monthdays[m]; d++) {
386 			for (h = 0; h < 4 * HOURSPERDAY; h++) {
387 				sunpos(year, m, d,
388 				    -24 * (degreeGMToffset / 360.0),
389 				    HOUR(h), MIN(h), SEC(h),
390 				    0.0, 0.0, &curL, &dec);
391 				if (curL < 180 && prevL > 180) {
392 					*pichinesemonths = cumdays[m] + d;
393 #ifdef DEBUG
394 printf("%04d-%02d-%02d %02d:%02d - %d %g\n",
395     year, m, d, HOUR(h), MIN(h), *pichinesemonths, curL);
396 #endif
397 					    pichinesemonths++;
398 				} else {
399 					for (i = 0; i <= 360; i += 30)
400 						if (curL > i && prevL < i) {
401 							*pichinesemonths =
402 							    cumdays[m] + d;
403 #ifdef DEBUG
404 printf("%04d-%02d-%02d %02d:%02d - %d %g\n",
405     year, m, d, HOUR(h), MIN(h), *pichinesemonths, curL);
406 #endif
407 							if (i == 330)
408 								firstmonth330 = *pichinesemonths;
409 							pichinesemonths++;
410 						}
411 				}
412 				prevL = curL;
413 			}
414 		}
415 	}
416 	*pichinesemonths = -1;
417 	return (firstmonth330);
418 }
419 
420 #ifdef NOTDEF
421 int
422 main(int argc, char **argv)
423 {
424 /*
425 	year      Mar        June       Sept       Dec
426 	     day   time  day   time  day time  day time
427 	2004  20   06:49  21   00:57  22  16:30 21  12:42
428 	2005  20   12:33  21   06:46  22  22:23 21  18:35
429 	2006  20   18:26  21   12:26  23  04:03 22  00:22
430 	2007  21   00:07  21   18:06  23  09:51 22  06:08
431 	2008  20   05:48  20   23:59  22  15:44 21  12:04
432 	2009  20   11:44  21   05:45  22  21:18 21  17:47
433 	2010  20   17:32  21   11:28  23  03:09 21  23:38
434 	2011  20   23:21  21   17:16  23  09:04 22  05:30
435 	2012  20   05:14  20   23:09  22  14:49 21  11:11
436 	2013  20   11:02  21   05:04  22  20:44 21  17:11
437 	2014  20   16:57  21   10:51  23  02:29 21  23:03
438 	2015  20   22:45  21   16:38  23  08:20 22  04:48
439 	2016  20   04:30  20   22:34  22  14:21 21  10:44
440 	2017  20   10:28  21   04:24  22  20:02 21  16:28
441 */
442 
443 	int eq[2], sol[2];
444 	equinoxsolstice(strtol(argv[1], NULL, 10), 0.0, eq, sol);
445 	printf("%d - %d - %d - %d\n", eq[0], sol[0], eq[1], sol[1]);
446 	return(0);
447 }
448 #endif
449