]> git.basschouten.com Git - openhab-addons.git/blob
85d56291b21880488c2d1efffb9a7da0377346c7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.astro.internal.calc;
14
15 import java.util.Calendar;
16
17 import org.openhab.binding.astro.internal.model.Season;
18 import org.openhab.binding.astro.internal.model.SeasonName;
19 import org.openhab.binding.astro.internal.util.DateTimeUtils;
20
21 /**
22  * Calculates the seasons of the year.
23  *
24  * @author Gerhard Riegler - Initial contribution
25  * @see based on the calculations of http://stellafane.org/misc/equinox.html
26  */
27 public class SeasonCalc {
28     private int currentYear;
29     private Season currentSeason;
30
31     /**
32      * Returns the seasons of the year of the specified calendar.
33      */
34     public Season getSeason(Calendar calendar, double latitude, boolean useMeteorologicalSeason) {
35         int year = calendar.get(Calendar.YEAR);
36         boolean isSouthernHemisphere = latitude < 0.0;
37         Season season = currentSeason;
38         if (currentYear != year) {
39             season = new Season();
40             if (!isSouthernHemisphere) {
41                 season.setSpring(calcEquiSol(0, year));
42                 season.setSummer(calcEquiSol(1, year));
43                 season.setAutumn(calcEquiSol(2, year));
44                 season.setWinter(calcEquiSol(3, year));
45             } else {
46                 season.setSpring(calcEquiSol(2, year));
47                 season.setSummer(calcEquiSol(3, year));
48                 season.setAutumn(calcEquiSol(0, year));
49                 season.setWinter(calcEquiSol(1, year));
50             }
51             currentSeason = season;
52             currentYear = year;
53         }
54
55         if (useMeteorologicalSeason) {
56             atMidnightOfFirstMonthDay(season.getSpring());
57             atMidnightOfFirstMonthDay(season.getSummer());
58             atMidnightOfFirstMonthDay(season.getAutumn());
59             atMidnightOfFirstMonthDay(season.getWinter());
60         }
61
62         season.setName(!isSouthernHemisphere ? getCurrentSeasonNameNorthern(calendar)
63                 : getCurrentSeasonNameSouthern(calendar));
64         return season;
65     }
66
67     private void atMidnightOfFirstMonthDay(Calendar calendar) {
68         calendar.set(Calendar.DAY_OF_MONTH, 1);
69         calendar.set(Calendar.HOUR_OF_DAY, 0);
70         calendar.set(Calendar.MINUTE, 0);
71         calendar.set(Calendar.SECOND, 0);
72         calendar.set(Calendar.MILLISECOND, 0);
73     }
74
75     /**
76      * Returns the current season name for the northern hemisphere.
77      */
78     private SeasonName getCurrentSeasonNameNorthern(Calendar calendar) {
79         long currentMillis = calendar.getTimeInMillis();
80         if (currentMillis < currentSeason.getSpring().getTimeInMillis()
81                 || currentMillis >= currentSeason.getWinter().getTimeInMillis()) {
82             return SeasonName.WINTER;
83         } else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
84                 && currentMillis < currentSeason.getSummer().getTimeInMillis()) {
85             return SeasonName.SPRING;
86         } else if (currentMillis >= currentSeason.getSummer().getTimeInMillis()
87                 && currentMillis < currentSeason.getAutumn().getTimeInMillis()) {
88             return SeasonName.SUMMER;
89         } else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
90                 && currentMillis < currentSeason.getWinter().getTimeInMillis()) {
91             return SeasonName.AUTUMN;
92         }
93         return null;
94     }
95
96     /**
97      * Returns the current season name for the southern hemisphere.
98      */
99     private SeasonName getCurrentSeasonNameSouthern(Calendar calendar) {
100         long currentMillis = calendar.getTimeInMillis();
101         if (currentMillis < currentSeason.getAutumn().getTimeInMillis()
102                 || currentMillis >= currentSeason.getSummer().getTimeInMillis()) {
103             return SeasonName.SUMMER;
104         } else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
105                 && currentMillis < currentSeason.getWinter().getTimeInMillis()) {
106             return SeasonName.AUTUMN;
107         } else if (currentMillis >= currentSeason.getWinter().getTimeInMillis()
108                 && currentMillis < currentSeason.getSpring().getTimeInMillis()) {
109             return SeasonName.WINTER;
110         } else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
111                 && currentMillis < currentSeason.getSummer().getTimeInMillis()) {
112             return SeasonName.SPRING;
113         }
114         return null;
115     }
116
117     /**
118      * Calculates the date of the season.
119      */
120     private Calendar calcEquiSol(int season, int year) {
121         double estimate = calcInitial(season, year);
122         double t = (estimate - 2451545.0) / 36525;
123         double w = 35999.373 * t - 2.47;
124         double dl = 1 + 0.0334 * cosDeg(w) + 0.0007 * cosDeg(2 * w);
125         double s = periodic24(t);
126         double julianDate = estimate + ((0.00001 * s) / dl);
127         return DateTimeUtils.toCalendar(julianDate);
128     }
129
130     /**
131      * Calculate an initial guess of the Equinox or Solstice of a given year.
132      */
133     private double calcInitial(int season, int year) {
134         double y = (year - 2000) / 1000d;
135         switch (season) {
136             case 0:
137                 return 2451623.80984 + 365242.37404 * y + 0.05169 * Math.pow(y, 2) - 0.00411 * Math.pow(y, 3)
138                         - 0.00057 * Math.pow(y, 4);
139             case 1:
140                 return 2451716.56767 + 365241.62603 * y + 0.00325 * Math.pow(y, 2) + 0.00888 * Math.pow(y, 3)
141                         - 0.00030 * Math.pow(y, 4);
142             case 2:
143                 return 2451810.21715 + 365242.01767 * y - 0.11575 * Math.pow(y, 2) + 0.00337 * Math.pow(y, 3)
144                         + 0.00078 * Math.pow(y, 4);
145             case 3:
146                 return 2451900.05952 + 365242.74049 * y - 0.06223 * Math.pow(y, 2) - 0.00823 * Math.pow(y, 3)
147                         + 0.00032 * Math.pow(y, 4);
148         }
149         return 0;
150     }
151
152     /**
153      * Calculate 24 periodic terms
154      */
155     private double periodic24(double T) {
156         int[] a = new int[] { 485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12,
157                 12, 9, 8 };
158         double[] b = new double[] { 324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81,
159                 297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73,
160                 15.45 };
161         double[] c = new double[] { 1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906,
162                 9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029,
163                 31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074 };
164
165         double result = 0;
166         for (int i = 0; i < 24; i++) {
167             result += a[i] * cosDeg(b[i] + (c[i] * T));
168         }
169         return result;
170     }
171
172     /**
173      * Cosinus of a degree value.
174      */
175     private double cosDeg(double deg) {
176         return Math.cos(deg * Math.PI / 180);
177     }
178 }