2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.astro.internal.calc;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.util.Calendar;
18 import java.util.GregorianCalendar;
19 import java.util.TimeZone;
21 import org.junit.jupiter.api.BeforeEach;
22 import org.junit.jupiter.api.Disabled;
23 import org.junit.jupiter.api.Test;
24 import org.openhab.binding.astro.internal.model.Sun;
25 import org.openhab.binding.astro.internal.model.SunPhaseName;
28 * Specific unit tests to check if {@link SunCalc} generates correct data for
29 * Amsterdam city on 27 February 2019. In particular the following cases are
32 * <li>checks if generated data are the same (with some accuracy) as produced by
33 * haevens-above.com</li>
34 * <li>checks if the generated {@link Sun#getAllRanges()} are consistent with
38 * @author Witold Markowski - Initial contribution
39 * @see <a href="https://github.com/openhab/openhab-addons/issues/5006">[astro]
40 * Sun Phase returns UNDEF</a>
41 * @see <a href="https://www.heavens-above.com/sun.aspx">Heavens Above Sun</a>
43 public class SunCalcTest {
45 private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("Europe/Amsterdam");
46 private static final Calendar FEB_27_2019 = SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 1, 0, TIME_ZONE);
47 private static final double AMSTERDAM_LATITUDE = 52.367607;
48 private static final double AMSTERDAM_LONGITUDE = 4.8978293;
49 private static final double AMSTERDAM_ALTITUDE = 0.0;
50 private static final int ACCURACY_IN_MILLIS = 3 * 60 * 1000;
52 private SunCalc sunCalc;
56 sunCalc = new SunCalc();
60 public void testGetSunInfoForOldDate() {
61 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
63 assertNotNull(sun.getNight());
65 assertNotNull(sun.getAstroDawn());
66 assertNotNull(sun.getNauticDawn());
67 assertNotNull(sun.getCivilDawn());
69 assertNotNull(sun.getRise());
71 assertNotNull(sun.getDaylight());
72 assertNotNull(sun.getNoon());
73 assertNotNull(sun.getSet());
75 assertNotNull(sun.getCivilDusk());
76 assertNotNull(sun.getNauticDusk());
77 assertNotNull(sun.getAstroDusk());
78 assertNotNull(sun.getNight());
80 assertNotNull(sun.getMorningNight());
81 assertNotNull(sun.getEveningNight());
83 // for an old date the phase should also be calculated
84 assertNotNull(sun.getPhase().getName());
88 public void testGetSunInfoForAstronomicalDawnAccuracy() {
89 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
91 // expected result from haevens-above.com is 27 Feb 2019 05:39 till 06:18
92 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 5, 39, TIME_ZONE).getTimeInMillis(),
93 sun.getAstroDawn().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
94 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 18, TIME_ZONE).getTimeInMillis(),
95 sun.getAstroDawn().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
99 public void testGetSunInfoForNauticDawnAccuracy() {
100 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
102 // expected result from haevens-above.com is 27 Feb 2019 06:18 till 06:58
103 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 18, TIME_ZONE).getTimeInMillis(),
104 sun.getNauticDawn().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
105 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 58, TIME_ZONE).getTimeInMillis(),
106 sun.getNauticDawn().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
110 public void testGetSunInfoForCivilDawnAccuracy() {
111 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
113 // expected result from haevens-above.com is 27 Feb 2019 06:58 till 07:32
114 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 6, 58, TIME_ZONE).getTimeInMillis(),
115 sun.getCivilDawn().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
116 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 7, 32, TIME_ZONE).getTimeInMillis(),
117 sun.getCivilDawn().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
121 public void testGetSunInfoForRiseAccuracy() {
122 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
124 // expected result from haevens-above.com is 27 Feb 2019 07:32
125 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 7, 32, TIME_ZONE).getTimeInMillis(),
126 sun.getRise().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
130 public void testGetSunInfoForSunNoonAccuracy() {
131 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
133 // expected result from haevens-above.com is 27 Feb 2019 12:54
134 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 12, 54, TIME_ZONE).getTimeInMillis(),
135 sun.getNoon().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
139 public void testGetSunInfoForSetAccuracy() {
140 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
142 // expected result from haevens-above.com is 27 Feb 2019 18:15
143 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 15, TIME_ZONE).getTimeInMillis(),
144 sun.getSet().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
148 public void testGetSunInfoForCivilDuskAccuracy() {
149 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
151 // expected result from haevens-above.com is 27 Feb 2019 18:15 till 18:50
152 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 15, TIME_ZONE).getTimeInMillis(),
153 sun.getCivilDusk().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
154 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 50, TIME_ZONE).getTimeInMillis(),
155 sun.getCivilDusk().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
159 public void testGetSunInfoForNauticDuskAccuracy() {
160 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
162 // expected result from haevens-above.com is 27 Feb 2019 18:50 till 19:29
163 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 18, 50, TIME_ZONE).getTimeInMillis(),
164 sun.getNauticDusk().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
165 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 19, 29, TIME_ZONE).getTimeInMillis(),
166 sun.getNauticDusk().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
170 public void testGetSunInfoForAstronomicalDuskAccuracy() {
171 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
173 // expected result from haevens-above.com is 27 Feb 2019 19:29 till 20:09
174 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 19, 29, TIME_ZONE).getTimeInMillis(),
175 sun.getAstroDusk().getStart().getTimeInMillis(), ACCURACY_IN_MILLIS);
176 assertEquals(SunCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 20, 9, TIME_ZONE).getTimeInMillis(),
177 sun.getAstroDusk().getEnd().getTimeInMillis(), ACCURACY_IN_MILLIS);
182 public void testRangesForCoherenceBetweenNightEndAndAstroDawnStart() {
183 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
185 assertEquals(sun.getAllRanges().get(SunPhaseName.NIGHT).getEnd(),
186 sun.getAllRanges().get(SunPhaseName.ASTRO_DAWN).getStart());
190 public void testRangesForCoherenceBetweenMorningNightEndAndAstroDawnStart() {
191 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
193 assertEquals(sun.getAllRanges().get(SunPhaseName.MORNING_NIGHT).getEnd(),
194 sun.getAllRanges().get(SunPhaseName.ASTRO_DAWN).getStart());
198 public void testRangesForCoherenceBetweenAstroDownEndAndNauticDawnStart() {
199 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
201 assertEquals(sun.getAllRanges().get(SunPhaseName.ASTRO_DAWN).getEnd(),
202 sun.getAllRanges().get(SunPhaseName.NAUTIC_DAWN).getStart());
206 public void testRangesForCoherenceBetweenNauticDawnEndAndCivilDawnStart() {
207 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
209 assertEquals(sun.getAllRanges().get(SunPhaseName.NAUTIC_DAWN).getEnd(),
210 sun.getAllRanges().get(SunPhaseName.CIVIL_DAWN).getStart());
214 public void testRangesForCoherenceBetweenCivilDawnEndAndSunRiseStart() {
215 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
217 assertEquals(sun.getAllRanges().get(SunPhaseName.CIVIL_DAWN).getEnd(),
218 sun.getAllRanges().get(SunPhaseName.SUN_RISE).getStart());
222 public void testRangesForCoherenceBetweenSunRiseEndAndDaylightStart() {
223 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
225 assertEquals(sun.getAllRanges().get(SunPhaseName.SUN_RISE).getEnd(),
226 sun.getAllRanges().get(SunPhaseName.DAYLIGHT).getStart());
230 public void testRangesForCoherenceBetweenDaylightEndAndSunSetStart() {
231 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
233 assertEquals(sun.getAllRanges().get(SunPhaseName.DAYLIGHT).getEnd(),
234 sun.getAllRanges().get(SunPhaseName.SUN_SET).getStart());
238 public void testRangesForCoherenceBetweenSunSetEndAndCivilDuskStart() {
239 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
241 assertEquals(sun.getAllRanges().get(SunPhaseName.SUN_SET).getEnd(),
242 sun.getAllRanges().get(SunPhaseName.CIVIL_DUSK).getStart());
246 public void testRangesForCoherenceBetweenCivilDuskEndAndNauticDuskStart() {
247 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
249 assertEquals(sun.getAllRanges().get(SunPhaseName.CIVIL_DUSK).getEnd(),
250 sun.getAllRanges().get(SunPhaseName.NAUTIC_DUSK).getStart());
254 public void testRangesForCoherenceBetweenNauticDuskEndAndAstroDuskStart() {
255 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
257 assertEquals(sun.getAllRanges().get(SunPhaseName.NAUTIC_DUSK).getEnd(),
258 sun.getAllRanges().get(SunPhaseName.ASTRO_DUSK).getStart());
262 public void testRangesForCoherenceBetweenAstroDuskEndAndNightStart() {
263 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
265 assertEquals(sun.getAllRanges().get(SunPhaseName.ASTRO_DUSK).getEnd(),
266 sun.getAllRanges().get(SunPhaseName.NIGHT).getStart());
270 public void testRangesForCoherenceBetweenAstroDuskEndAndEveningNightStart() {
271 Sun sun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE, false);
273 assertEquals(sun.getAllRanges().get(SunPhaseName.ASTRO_DUSK).getEnd(),
274 sun.getAllRanges().get(SunPhaseName.EVENING_NIGHT).getStart());
278 public void testIssue7642CivilDawnEnd() {
279 TimeZone tZone = TimeZone.getTimeZone("Europe/London");
280 Calendar tDate = SunCalcTest.newCalendar(2020, Calendar.MAY, 13, 5, 12, tZone);
282 Sun sun = sunCalc.getSunInfo(tDate, 53.524695, -2.4, 0.0, true);
283 assertEquals(SunPhaseName.CIVIL_DAWN, sun.getPhase().getName());
287 public void testIssue7642SunRiseStart() {
288 // SunCalc.ranges was not sorted, causing unexpected output in corner cases.
289 TimeZone tZone = TimeZone.getTimeZone("Europe/London");
290 Calendar tDate = SunCalcTest.newCalendar(2020, Calendar.MAY, 13, 5, 13, tZone);
292 Sun sun = sunCalc.getSunInfo(tDate, 53.524695, -2.4, 0.0, true);
293 assertEquals(SunPhaseName.SUN_RISE, sun.getPhase().getName());
297 public void testIssue7642DaylightStart() {
298 TimeZone tZone = TimeZone.getTimeZone("Europe/London");
299 Calendar tDate = SunCalcTest.newCalendar(2020, Calendar.MAY, 13, 5, 18, tZone);
301 Sun sun = sunCalc.getSunInfo(tDate, 53.524695, -2.4, 0.0, true);
302 assertEquals(SunPhaseName.DAYLIGHT, sun.getPhase().getName());
306 * Constructs a <code>GregorianCalendar</code> with the given date and time set
307 * for the provided time zone.
310 * the value used to set the <code>YEAR</code> calendar field in the
313 * the value used to set the <code>MONTH</code> calendar field in the
314 * calendar. Month value is 0-based. e.g., 0 for January.
316 * the value used to set the <code>DAY_OF_MONTH</code> calendar field
319 * the value used to set the <code>HOUR_OF_DAY</code> calendar field
322 * the value used to set the <code>MINUTE</code> calendar field in
325 * the given time zone.
328 private static Calendar newCalendar(int year, int month, int dayOfMonth, int hourOfDay, int minute, TimeZone zone) {
329 Calendar result = new GregorianCalendar(year, month, dayOfMonth, hourOfDay, minute);
330 result.setTimeZone(zone);
336 public void testAstroAndMeteoSeasons() {
337 Sun meteoSun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE,
339 Sun equiSun = sunCalc.getSunInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, AMSTERDAM_ALTITUDE,
342 assertEquals(meteoSun.getSeason().getSpring().get(Calendar.MONTH),
343 equiSun.getSeason().getSpring().get(Calendar.MONTH));
344 assertEquals(meteoSun.getSeason().getSpring().get(Calendar.YEAR),
345 equiSun.getSeason().getSpring().get(Calendar.YEAR));
346 assertEquals(1, meteoSun.getSeason().getSpring().get(Calendar.DAY_OF_MONTH));
347 assertFalse(meteoSun.getSeason().getSpring().get(Calendar.DAY_OF_MONTH) == equiSun.getSeason().getSpring()
348 .get(Calendar.DAY_OF_MONTH));