diff --git a/bundles/org.openhab.binding.astro/NOTICE b/bundles/org.openhab.binding.astro/NOTICE
index 38d625e349232..630238a2a4858 100644
--- a/bundles/org.openhab.binding.astro/NOTICE
+++ b/bundles/org.openhab.binding.astro/NOTICE
@@ -8,6 +8,9 @@ This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
+Zodiac Icon Set provided by SVG Repo under CC0 License (Public Domain) at
+https://www.svgrepo.com/collection/zodiac-2/
+
== Source Code
https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.astro/README.md b/bundles/org.openhab.binding.astro/README.md
index 7720f592f26e8..d59443ed98aeb 100644
--- a/bundles/org.openhab.binding.astro/README.md
+++ b/bundles/org.openhab.binding.astro/README.md
@@ -28,6 +28,26 @@ Optionally, a refresh `interval` (in seconds) can be defined to also calculate p
Season calculation can be switched from equinox based calculation to meteorological based (starting on the first day of the given month).
This is done by setting `useMeteorologicalSeason` to true in the advanced setting of the sun.
+## Provided icon set
+
+This binding has its own IconProvider and makes available the following list of icons
+
+| Icon Name | Dynamic | Illustration |
+|-----------------------------|---------|--------------|
+| oh:astro:zodiac | Yes |  |
+| oh:astro:zodiac-aquarius | Yes |  |
+| oh:astro:zodiac-aries | Yes |  |
+| oh:astro:zodiac-cancer | Yes |  |
+| oh:astro:zodiac-capricorn | Yes |  |
+| oh:astro:zodiac-gemini | Yes |  |
+| oh:astro:zodiac-leo | Yes |  |
+| oh:astro:zodiac-libra | Yes |  |
+| oh:astro:zodiac-pisces | Yes |  |
+| oh:astro:zodiac-sagittarius | Yes |  |
+| oh:astro:zodiac-scorpio | Yes |  |
+| oh:astro:zodiac-taurus | Yes |  |
+| oh:astro:zodiac-virgo | Yes |  |
+
## Channels
- **thing** `sun`
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-aquarius.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-aquarius.svg
new file mode 100644
index 0000000000000..3a88a3423ef81
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-aquarius.svg
@@ -0,0 +1,34 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-aries.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-aries.svg
new file mode 100644
index 0000000000000..0b280bc9e8477
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-aries.svg
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-cancer.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-cancer.svg
new file mode 100644
index 0000000000000..db1454717cb5c
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-cancer.svg
@@ -0,0 +1,59 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-capricorn.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-capricorn.svg
new file mode 100644
index 0000000000000..19b12e2a605e2
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-capricorn.svg
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-gemini.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-gemini.svg
new file mode 100644
index 0000000000000..fe05172131ab7
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-gemini.svg
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-leo.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-leo.svg
new file mode 100644
index 0000000000000..4feff0d275cd9
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-leo.svg
@@ -0,0 +1,66 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-libra.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-libra.svg
new file mode 100644
index 0000000000000..654cfb348dffd
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-libra.svg
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-pisces.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-pisces.svg
new file mode 100644
index 0000000000000..62e47f2f35176
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-pisces.svg
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-sagittarius.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-sagittarius.svg
new file mode 100644
index 0000000000000..8d0f7221df2e9
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-sagittarius.svg
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-scorpio.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-scorpio.svg
new file mode 100644
index 0000000000000..8ab4273fe83c0
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-scorpio.svg
@@ -0,0 +1,167 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-taurus.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-taurus.svg
new file mode 100644
index 0000000000000..1fadfb8b7b88c
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-taurus.svg
@@ -0,0 +1,57 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac-virgo.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac-virgo.svg
new file mode 100644
index 0000000000000..62a6032a95fb5
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac-virgo.svg
@@ -0,0 +1,105 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/doc/images/zodiac.svg b/bundles/org.openhab.binding.astro/doc/images/zodiac.svg
new file mode 100644
index 0000000000000..4d436646593af
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/doc/images/zodiac.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/AstroIconProvider.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/AstroIconProvider.java
new file mode 100755
index 0000000000000..b4bffd037ae6c
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/AstroIconProvider.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.astro.internal;
+
+import static org.openhab.binding.astro.internal.AstroBindingConstants.BINDING_ID;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.astro.internal.model.ZodiacSign;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.ui.icon.IconProvider;
+import org.openhab.core.ui.icon.IconSet;
+import org.openhab.core.ui.icon.IconSet.Format;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AstroIconProvider} is the class providing binding related icons.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@Component(service = { IconProvider.class, AstroIconProvider.class })
+@NonNullByDefault
+public class AstroIconProvider implements IconProvider {
+ private static final String DEFAULT_LABEL = "Astro Icons";
+ private static final String DEFAULT_DESCRIPTION = "Icons provided for the Astro Binding";
+ private static final String ZODIAC_ICON = "zodiac";
+
+ private final Logger logger = LoggerFactory.getLogger(AstroIconProvider.class);
+ private final TranslationProvider i18nProvider;
+ private final Bundle bundle;
+
+ @Activate
+ public AstroIconProvider(final BundleContext context, final @Reference TranslationProvider i18nProvider) {
+ this.i18nProvider = i18nProvider;
+ this.bundle = context.getBundle();
+ }
+
+ @Override
+ public Set getIconSets() {
+ return getIconSets(null);
+ }
+
+ @Override
+ public Set getIconSets(@Nullable Locale locale) {
+ String label = getText("label", DEFAULT_LABEL, locale);
+ String description = getText("description", DEFAULT_DESCRIPTION, locale);
+
+ return Set.of(new IconSet(BINDING_ID, label, description, Set.of(Format.SVG)));
+ }
+
+ private String getText(String entry, String defaultValue, @Nullable Locale locale) {
+ String text = locale == null ? null : i18nProvider.getText(bundle, "iconset." + entry, defaultValue, locale);
+ return text == null ? defaultValue : text;
+ }
+
+ @Override
+ public @Nullable Integer hasIcon(String category, String iconSetId, Format format) {
+ return Format.SVG.equals(format) && iconSetId.equals(BINDING_ID) && category.equals(ZODIAC_ICON) ? 0 : null;
+ }
+
+ @Override
+ public @Nullable InputStream getIcon(String category, String iconSetId, @Nullable String state, Format format) {
+ String iconName = "icon/%s.svg".formatted(category);
+ if (category.equals(ZODIAC_ICON) && state != null) {
+ try {
+ var sign = ZodiacSign.valueOf(state);
+ iconName = iconName.replace(".", "-%s.".formatted(sign.name().toLowerCase(Locale.US)));
+ } catch (IllegalArgumentException ignore) {
+ // Invalid state for the icon set, we'll remain on default icon
+ }
+ }
+
+ String result = "";
+
+ URL iconResource = bundle.getEntry(iconName);
+ try (InputStream stream = iconResource.openStream()) {
+ result = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ logger.warn("Unable to load resource '{}': {}", iconResource.getPath(), e.getMessage());
+ }
+
+ return result.isEmpty() ? null : new ByteArrayInputStream(result.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java
index 9335c88255a29..9ef60d7ff5ed2 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/action/AstroActions.java
@@ -13,6 +13,7 @@
package org.openhab.binding.astro.internal.action;
import java.time.ZonedDateTime;
+import java.util.Locale;
import javax.measure.quantity.Angle;
@@ -119,7 +120,7 @@ public void setThingHandler(@Nullable ThingHandler handler) {
AstroThingHandler theHandler = this.handler;
if (theHandler != null) {
if (theHandler instanceof SunHandler sunHandler) {
- SunPhaseName phase = SunPhaseName.valueOf(phaseName.toUpperCase());
+ SunPhaseName phase = SunPhaseName.valueOf(phaseName.toUpperCase(Locale.US));
return sunHandler.getEventTime(phase, date != null ? date : ZonedDateTime.now(),
moment == null || AstroBindingConstants.EVENT_START.equalsIgnoreCase(moment));
} else {
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/MoonCalc.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/MoonCalc.java
index 50238bc63ef1e..b7ffafc6c3ea3 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/MoonCalc.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/MoonCalc.java
@@ -12,6 +12,8 @@
*/
package org.openhab.binding.astro.internal.calc;
+import static org.openhab.binding.astro.internal.util.MathUtils.*;
+
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
@@ -28,8 +30,6 @@
import org.openhab.binding.astro.internal.model.MoonPhaseName;
import org.openhab.binding.astro.internal.model.Position;
import org.openhab.binding.astro.internal.model.Range;
-import org.openhab.binding.astro.internal.model.Zodiac;
-import org.openhab.binding.astro.internal.model.ZodiacSign;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/**
@@ -181,9 +181,9 @@ private double[] getRiseSet(Calendar calendar, double latitude, double longitude
double moonJd = Math.floor(DateTimeUtils.midnightDateToJulianDate(calendar)) - 2400000.0;
moonJd -= ((calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / 60000.0) / 1440.0;
- double sphi = SN(phi);
- double cphi = CS(phi);
- double sinho = SN(8.0 / 60.0);
+ double sphi = sinDeg(phi);
+ double cphi = cosDeg(phi);
+ double sinho = sinDeg(8.0 / 60.0);
int hour = 1;
double utrise = -1;
@@ -269,40 +269,40 @@ private double calcMoonPhase(double k, double mode) {
double o = var_o(kMod, t);
double jd = var_jde(kMod, t);
if (mode == NEW_MOON) {
- jd += -.4072 * SN(m1) + .17241 * e * SN(m) + .01608 * SN(2 * m1) + .01039 * SN(2 * f)
- + .00739 * e * SN(m1 - m) - .00514 * e * SN(m1 + m) + .00208 * e * e * SN(2 * m)
- - .00111 * SN(m1 - 2 * f) - .00057 * SN(m1 + 2 * f);
- jd += .00056 * e * SN(2 * m1 + m) - .00042 * SN(3 * m1) + .00042 * e * SN(m + 2 * f)
- + .00038 * e * SN(m - 2 * f) - .00024 * e * SN(2 * m1 - m) - .00017 * SN(o)
- - .00007 * SN(m1 + 2 * m) + .00004 * SN(2 * m1 - 2 * f);
- jd += .00004 * SN(3 * m) + .00003 * SN(m1 + m - 2 * f) + .00003 * SN(2 * m1 + 2 * f)
- - .00003 * SN(m1 + m + 2 * f) + .00003 * SN(m1 - m + 2 * f) - .00002 * SN(m1 - m - 2 * f)
- - .00002 * SN(3 * m1 + m);
- jd += .00002 * SN(4 * m1);
+ jd += -.4072 * sinDeg(m1) + .17241 * e * sinDeg(m) + .01608 * sinDeg(2 * m1) + .01039 * sinDeg(2 * f)
+ + .00739 * e * sinDeg(m1 - m) - .00514 * e * sinDeg(m1 + m) + .00208 * e * e * sinDeg(2 * m)
+ - .00111 * sinDeg(m1 - 2 * f) - .00057 * sinDeg(m1 + 2 * f);
+ jd += .00056 * e * sinDeg(2 * m1 + m) - .00042 * sinDeg(3 * m1) + .00042 * e * sinDeg(m + 2 * f)
+ + .00038 * e * sinDeg(m - 2 * f) - .00024 * e * sinDeg(2 * m1 - m) - .00017 * sinDeg(o)
+ - .00007 * sinDeg(m1 + 2 * m) + .00004 * sinDeg(2 * m1 - 2 * f);
+ jd += .00004 * sinDeg(3 * m) + .00003 * sinDeg(m1 + m - 2 * f) + .00003 * sinDeg(2 * m1 + 2 * f)
+ - .00003 * sinDeg(m1 + m + 2 * f) + .00003 * sinDeg(m1 - m + 2 * f)
+ - .00002 * sinDeg(m1 - m - 2 * f) - .00002 * sinDeg(3 * m1 + m);
+ jd += .00002 * sinDeg(4 * m1);
} else if (mode == FULL_MOON) {
- jd += -.40614 * SN(m1) + .17302 * e * SN(m) + .01614 * SN(2 * m1) + .01043 * SN(2 * f)
- + .00734 * e * SN(m1 - m) - .00515 * e * SN(m1 + m) + .00209 * e * e * SN(2 * m)
- - .00111 * SN(m1 - 2 * f) - .00057 * SN(m1 + 2 * f);
- jd += .00056 * e * SN(2 * m1 + m) - .00042 * SN(3 * m1) + .00042 * e * SN(m + 2 * f)
- + .00038 * e * SN(m - 2 * f) - .00024 * e * SN(2 * m1 - m) - .00017 * SN(o)
- - .00007 * SN(m1 + 2 * m) + .00004 * SN(2 * m1 - 2 * f);
- jd += .00004 * SN(3 * m) + .00003 * SN(m1 + m - 2 * f) + .00003 * SN(2 * m1 + 2 * f)
- - .00003 * SN(m1 + m + 2 * f) + .00003 * SN(m1 - m + 2 * f) - .00002 * SN(m1 - m - 2 * f)
- - .00002 * SN(3 * m1 + m);
- jd += .00002 * SN(4 * m1);
+ jd += -.40614 * sinDeg(m1) + .17302 * e * sinDeg(m) + .01614 * sinDeg(2 * m1) + .01043 * sinDeg(2 * f)
+ + .00734 * e * sinDeg(m1 - m) - .00515 * e * sinDeg(m1 + m) + .00209 * e * e * sinDeg(2 * m)
+ - .00111 * sinDeg(m1 - 2 * f) - .00057 * sinDeg(m1 + 2 * f);
+ jd += .00056 * e * sinDeg(2 * m1 + m) - .00042 * sinDeg(3 * m1) + .00042 * e * sinDeg(m + 2 * f)
+ + .00038 * e * sinDeg(m - 2 * f) - .00024 * e * sinDeg(2 * m1 - m) - .00017 * sinDeg(o)
+ - .00007 * sinDeg(m1 + 2 * m) + .00004 * sinDeg(2 * m1 - 2 * f);
+ jd += .00004 * sinDeg(3 * m) + .00003 * sinDeg(m1 + m - 2 * f) + .00003 * sinDeg(2 * m1 + 2 * f)
+ - .00003 * sinDeg(m1 + m + 2 * f) + .00003 * sinDeg(m1 - m + 2 * f)
+ - .00002 * sinDeg(m1 - m - 2 * f) - .00002 * sinDeg(3 * m1 + m);
+ jd += .00002 * sinDeg(4 * m1);
} else {
- jd += -.62801 * SN(m1) + .17172 * e * SN(m) - .01183 * e * SN(m1 + m) + .00862 * SN(2 * m1)
- + .00804 * SN(2 * f) + .00454 * e * SN(m1 - m) + .00204 * e * e * SN(2 * m) - .0018 * SN(m1 - 2 * f)
- - .0007 * SN(m1 + 2 * f);
- jd += -.0004 * SN(3 * m1) - .00034 * e * SN(2 * m1 - m) + .00032 * e * SN(m + 2 * f)
- + .00032 * e * SN(m - 2 * f) - .00028 * e * e * SN(m1 + 2 * m) + .00027 * e * SN(2 * m1 + m)
- - .00017 * SN(o);
- jd += -.00005 * SN(m1 - m - 2 * f) + .00004 * SN(2 * m1 + 2 * f) - .00004 * SN(m1 + m + 2 * f)
- + .00004 * SN(m1 - 2 * m) + .00003 * SN(m1 + m - 2 * f) + .00003 * SN(3 * m)
- + .00002 * SN(2 * m1 - 2 * f);
- jd += .00002 * SN(m1 - m + 2 * f) - .00002 * SN(3 * m1 + m);
- double w = .00306 - .00038 * e * CS(m) + .00026 * CS(m1) - .00002 * CS(m1 - m) + .00002 * CS(m1 + m)
- + .00002 * CS(2 * f);
+ jd += -.62801 * sinDeg(m1) + .17172 * e * sinDeg(m) - .01183 * e * sinDeg(m1 + m) + .00862 * sinDeg(2 * m1)
+ + .00804 * sinDeg(2 * f) + .00454 * e * sinDeg(m1 - m) + .00204 * e * e * sinDeg(2 * m)
+ - .0018 * sinDeg(m1 - 2 * f) - .0007 * sinDeg(m1 + 2 * f);
+ jd += -.0004 * sinDeg(3 * m1) - .00034 * e * sinDeg(2 * m1 - m) + .00032 * e * sinDeg(m + 2 * f)
+ + .00032 * e * sinDeg(m - 2 * f) - .00028 * e * e * sinDeg(m1 + 2 * m)
+ + .00027 * e * sinDeg(2 * m1 + m) - .00017 * sinDeg(o);
+ jd += -.00005 * sinDeg(m1 - m - 2 * f) + .00004 * sinDeg(2 * m1 + 2 * f) - .00004 * sinDeg(m1 + m + 2 * f)
+ + .00004 * sinDeg(m1 - 2 * m) + .00003 * sinDeg(m1 + m - 2 * f) + .00003 * sinDeg(3 * m)
+ + .00002 * sinDeg(2 * m1 - 2 * f);
+ jd += .00002 * sinDeg(m1 - m + 2 * f) - .00002 * sinDeg(3 * m1 + m);
+ double w = .00306 - .00038 * e * cosDeg(m) + .00026 * cosDeg(m1) - .00002 * cosDeg(m1 - m)
+ + .00002 * cosDeg(m1 + m) + .00002 * cosDeg(2 * f);
jd += (mode == FIRST_QUARTER) ? w : -w;
}
return moonCorrection(jd, t, kMod);
@@ -317,26 +317,29 @@ private double getEclipse(double k, EclipseType typ, EclipseKind eclipse) {
double f = var_f(kMod, t);
double jd = 0;
double ringTest = 0;
- if (SN(Math.abs(f)) <= .36) {
+ if (sinDeg(Math.abs(f)) <= .36) {
double o = var_o(kMod, t);
- double f1 = f - .02665 * SN(o);
+ double f1 = f - .02665 * sinDeg(o);
double a1 = 299.77 + .107408 * kMod - .009173 * t * t;
double e = var_e(t);
double m = var_m(kMod, t);
double m1 = var_m1(kMod, t);
- double p = .207 * e * SN(m) + .0024 * e * SN(2 * m) - .0392 * SN(m1) + .0116 * SN(2 * m1)
- - .0073 * e * SN(m1 + m) + .0067 * e * SN(m1 - m) + .0118 * SN(2 * f1);
- double q = 5.2207 - .0048 * e * CS(m) + .002 * e * CS(2 * m) - .3299 * CS(m1) - .006 * e * CS(m1 + m)
- + .0041 * e * CS(m1 - m);
- double g = (p * CS(f1) + q * SN(f1)) * (1 - .0048 * CS(Math.abs(f1)));
- double u = .0059 + .0046 * e * CS(m) - .0182 * CS(m1) + .0004 * CS(2 * m1) - .0005 * CS(m + m1);
+ double p = .207 * e * sinDeg(m) + .0024 * e * sinDeg(2 * m) - .0392 * sinDeg(m1) + .0116 * sinDeg(2 * m1)
+ - .0073 * e * sinDeg(m1 + m) + .0067 * e * sinDeg(m1 - m) + .0118 * sinDeg(2 * f1);
+ double q = 5.2207 - .0048 * e * cosDeg(m) + .002 * e * cosDeg(2 * m) - .3299 * cosDeg(m1)
+ - .006 * e * cosDeg(m1 + m) + .0041 * e * cosDeg(m1 - m);
+ double g = (p * cosDeg(f1) + q * sinDeg(f1)) * (1 - .0048 * cosDeg(Math.abs(f1)));
+ double u = .0059 + .0046 * e * cosDeg(m) - .0182 * cosDeg(m1) + .0004 * cosDeg(2 * m1)
+ - .0005 * cosDeg(m + m1);
jd = var_jde(kMod, t);
- jd += (typ == EclipseType.MOON) ? -.4065 * SN(m1) + .1727 * e * SN(m) : -.4075 * SN(m1) + .1721 * e * SN(m);
-
- jd += .0161 * SN(2 * m1) - .0097 * SN(2 * f1) + .0073 * e * SN(m1 - m) - .005 * e * SN(m1 + m)
- - .0023 * SN(m1 - 2 * f1) + .0021 * e * SN(2 * m);
- jd += .0012 * SN(m1 + 2 * f1) + .0006 * e * SN(2 * m1 + m) - .0004 * SN(3 * m1) - .0003 * e * SN(m + 2 * f1)
- + .0003 * SN(a1) - .0002 * e * SN(m - 2 * f1) - .0002 * e * SN(2 * m1 - m) - .0002 * SN(o);
+ jd += (typ == EclipseType.MOON) ? -.4065 * sinDeg(m1) + .1727 * e * sinDeg(m)
+ : -.4075 * sinDeg(m1) + .1721 * e * sinDeg(m);
+
+ jd += .0161 * sinDeg(2 * m1) - .0097 * sinDeg(2 * f1) + .0073 * e * sinDeg(m1 - m)
+ - .005 * e * sinDeg(m1 + m) - .0023 * sinDeg(m1 - 2 * f1) + .0021 * e * sinDeg(2 * m);
+ jd += .0012 * sinDeg(m1 + 2 * f1) + .0006 * e * sinDeg(2 * m1 + m) - .0004 * sinDeg(3 * m1)
+ - .0003 * e * sinDeg(m + 2 * f1) + .0003 * sinDeg(a1) - .0002 * e * sinDeg(m - 2 * f1)
+ - .0002 * e * sinDeg(2 * m1 - m) - .0002 * sinDeg(o);
switch (typ) {
case MOON:
if ((1.0248 - u - Math.abs(g)) / .545 <= 0) {
@@ -387,9 +390,9 @@ private double getIllumination(double jd) {
double d = 297.8502042 + 445267.11151686 * t - .00163 * t * t + t * t * t / 545868 - t * t * t * t / 113065000;
double m = 357.5291092 + 35999.0502909 * t - .0001536 * t * t + t * t * t / 24490000;
double m1 = 134.9634114 + 477198.8676313 * t + .008997 * t * t + t * t * t / 69699 - t * t * t * t / 14712000;
- double i = 180 - d - 6.289 * SN(m1) + 2.1 * SN(m) - 1.274 * SN(2 * d - m1) - .658 * SN(2 * d)
- - .241 * SN(2 * m1) - .110 * SN(d);
- return (1 + CS(i)) / 2 * 100.0;
+ double i = 180 - d - 6.289 * sinDeg(m1) + 2.1 * sinDeg(m) - 1.274 * sinDeg(2 * d - m1) - .658 * sinDeg(2 * d)
+ - .241 * sinDeg(2 * m1) - .110 * sinDeg(d);
+ return (1 + cosDeg(i)) / 2 * 100.0;
}
/**
@@ -447,16 +450,17 @@ private double getApogee(double julianDate, double decimalYear) {
double m = 347.3477 + 27.1577721 * k - .0008323 * t * t - .000001 * t * t * t;
double f = 316.6109 + 364.5287911 * k - .0125131 * t * t - .0000148 * t * t * t;
jd = 2451534.6698 + 27.55454988 * k - .0006886 * t * t - .000001098 * t * t * t + .0000000052 * t * t
- + .4392 * SN(2 * d) + .0684 * SN(4 * d) + (.0456 - .00011 * t) * SN(m)
- + (.0426 - .00011 * t) * SN(2 * d - m) + .0212 * SN(2 * f);
- jd += -.0189 * SN(d) + .0144 * SN(6 * d) + .0113 * SN(4 * d - m) + .0047 * SN(2 * d + 2 * f)
- + .0036 * SN(d + m) + .0035 * SN(8 * d) + .0034 * SN(6 * d - m) - .0034 * SN(2 * d - 2 * f)
- + .0022 * SN(2 * d - 2 * m) - .0017 * SN(3 * d);
- jd += .0013 * SN(4 * d + 2 * f) + .0011 * SN(8 * d - m) + .001 * SN(4 * d - 2 * m) + .0009 * SN(10 * d)
- + .0007 * SN(3 * d + m) + .0006 * SN(2 * m) + .0005 * SN(2 * d + m) + .0005 * SN(2 * d + 2 * m)
- + .0004 * SN(6 * d + 2 * f);
- jd += .0004 * SN(6 * d - 2 * m) + .0004 * SN(10 * d - m) - .0004 * SN(5 * d) - .0004 * SN(4 * d - 2 * f)
- + .0003 * SN(2 * f + m) + .0003 * SN(12 * d) + .0003 * SN(2 * d + 2 * f - m) - .0003 * SN(d - m);
+ + .4392 * sinDeg(2 * d) + .0684 * sinDeg(4 * d) + (.0456 - .00011 * t) * sinDeg(m)
+ + (.0426 - .00011 * t) * sinDeg(2 * d - m) + .0212 * sinDeg(2 * f);
+ jd += -.0189 * sinDeg(d) + .0144 * sinDeg(6 * d) + .0113 * sinDeg(4 * d - m) + .0047 * sinDeg(2 * d + 2 * f)
+ + .0036 * sinDeg(d + m) + .0035 * sinDeg(8 * d) + .0034 * sinDeg(6 * d - m)
+ - .0034 * sinDeg(2 * d - 2 * f) + .0022 * sinDeg(2 * d - 2 * m) - .0017 * sinDeg(3 * d);
+ jd += .0013 * sinDeg(4 * d + 2 * f) + .0011 * sinDeg(8 * d - m) + .001 * sinDeg(4 * d - 2 * m)
+ + .0009 * sinDeg(10 * d) + .0007 * sinDeg(3 * d + m) + .0006 * sinDeg(2 * m)
+ + .0005 * sinDeg(2 * d + m) + .0005 * sinDeg(2 * d + 2 * m) + .0004 * sinDeg(6 * d + 2 * f);
+ jd += .0004 * sinDeg(6 * d - 2 * m) + .0004 * sinDeg(10 * d - m) - .0004 * sinDeg(5 * d)
+ - .0004 * sinDeg(4 * d - 2 * f) + .0003 * sinDeg(2 * f + m) + .0003 * sinDeg(12 * d)
+ + .0003 * sinDeg(2 * d + 2 * f - m) - .0003 * sinDeg(d - m);
k += 1;
} while (jd < julianDate);
return jd;
@@ -475,24 +479,28 @@ private double getPerigee(double julianDate, double decimalYear) {
double m = 347.3477 + 27.1577721 * k - .0008323 * t * t - .000001 * t * t * t;
double f = 316.6109 + 364.5287911 * k - .0125131 * t * t - .0000148 * t * t * t;
jd = 2451534.6698 + 27.55454988 * k - .0006886 * t * t - .000001098 * t * t * t + .0000000052 * t * t
- - 1.6769 * SN(2 * d) + .4589 * SN(4 * d) - .1856 * SN(6 * d) + .0883 * SN(8 * d);
- jd += -(.0773 + .00019 * t) * SN(2 * d - m) + (.0502 - .00013 * t) * SN(m) - .046 * SN(10 * d)
- + (.0422 - .00011 * t) * SN(4 * d - m) - .0256 * SN(6 * d - m) + .0253 * SN(12 * d) + .0237 * SN(d);
- jd += .0162 * SN(8 * d - m) - .0145 * SN(14 * d) + .0129 * SN(2 * f) - .0112 * SN(3 * d)
- - .0104 * SN(10 * d - m) + .0086 * SN(16 * d) + .0069 * SN(12 * d - m) + .0066 * SN(5 * d)
- - .0053 * SN(2 * d + 2 * f);
- jd += -.0052 * SN(18 * d) - .0046 * SN(14 * d - m) - .0041 * SN(7 * d) + .004 * SN(2 * d + m)
- + .0032 * SN(20 * d) - .0032 * SN(d + m) + .0031 * SN(16 * d - m);
- jd += -.0029 * SN(4 * d + m) - .0027 * SN(2 * d - 2 * m) + .0024 * SN(4 * d - 2 * m)
- - .0021 * SN(6 * d - 2 * m) - .0021 * SN(22 * d) - .0021 * SN(18 * d - m);
- jd += .0019 * SN(6 * d + m) - .0018 * SN(11 * d) - .0014 * SN(8 * d + m) - .0014 * SN(4 * d - 2 * f)
- - .0014 * SN(6 * d - 2 * f) + .0014 * SN(3 * d + m) - .0014 * SN(5 * d + m) + .0013 * SN(13 * d);
- jd += .0013 * SN(20 * d - m) + .0011 * SN(3 * d + 2 * m) - .0011 * SN(4 * d + 2 * f - 2 * m)
- - .001 * SN(d + 2 * m) - .0009 * SN(22 * d - m) - .0008 * SN(4 * f) + .0008 * SN(6 * d - 2 * f)
- + .0008 * SN(2 * d - 2 * f + m);
- jd += .0007 * SN(2 * m) + .0007 * SN(2 * f - m) + .0007 * SN(2 * d + 4 * f) - .0006 * SN(2 * f - 2 * m)
- - .0006 * SN(2 * d - 2 * f + 2 * m) + .0006 * SN(24 * d) + .0005 * SN(4 * d - 4 * f)
- + .0005 * SN(2 * d + 2 * m) - .0004 * SN(d - m) + .0027 * SN(9 * d) + .0027 * SN(4 * d + 2 * f);
+ - 1.6769 * sinDeg(2 * d) + .4589 * sinDeg(4 * d) - .1856 * sinDeg(6 * d) + .0883 * sinDeg(8 * d);
+ jd += -(.0773 + .00019 * t) * sinDeg(2 * d - m) + (.0502 - .00013 * t) * sinDeg(m) - .046 * sinDeg(10 * d)
+ + (.0422 - .00011 * t) * sinDeg(4 * d - m) - .0256 * sinDeg(6 * d - m) + .0253 * sinDeg(12 * d)
+ + .0237 * sinDeg(d);
+ jd += .0162 * sinDeg(8 * d - m) - .0145 * sinDeg(14 * d) + .0129 * sinDeg(2 * f) - .0112 * sinDeg(3 * d)
+ - .0104 * sinDeg(10 * d - m) + .0086 * sinDeg(16 * d) + .0069 * sinDeg(12 * d - m)
+ + .0066 * sinDeg(5 * d) - .0053 * sinDeg(2 * d + 2 * f);
+ jd += -.0052 * sinDeg(18 * d) - .0046 * sinDeg(14 * d - m) - .0041 * sinDeg(7 * d)
+ + .004 * sinDeg(2 * d + m) + .0032 * sinDeg(20 * d) - .0032 * sinDeg(d + m)
+ + .0031 * sinDeg(16 * d - m);
+ jd += -.0029 * sinDeg(4 * d + m) - .0027 * sinDeg(2 * d - 2 * m) + .0024 * sinDeg(4 * d - 2 * m)
+ - .0021 * sinDeg(6 * d - 2 * m) - .0021 * sinDeg(22 * d) - .0021 * sinDeg(18 * d - m);
+ jd += .0019 * sinDeg(6 * d + m) - .0018 * sinDeg(11 * d) - .0014 * sinDeg(8 * d + m)
+ - .0014 * sinDeg(4 * d - 2 * f) - .0014 * sinDeg(6 * d - 2 * f) + .0014 * sinDeg(3 * d + m)
+ - .0014 * sinDeg(5 * d + m) + .0013 * sinDeg(13 * d);
+ jd += .0013 * sinDeg(20 * d - m) + .0011 * sinDeg(3 * d + 2 * m) - .0011 * sinDeg(4 * d + 2 * f - 2 * m)
+ - .001 * sinDeg(d + 2 * m) - .0009 * sinDeg(22 * d - m) - .0008 * sinDeg(4 * f)
+ + .0008 * sinDeg(6 * d - 2 * f) + .0008 * sinDeg(2 * d - 2 * f + m);
+ jd += .0007 * sinDeg(2 * m) + .0007 * sinDeg(2 * f - m) + .0007 * sinDeg(2 * d + 4 * f)
+ - .0006 * sinDeg(2 * f - 2 * m) - .0006 * sinDeg(2 * d - 2 * f + 2 * m) + .0006 * sinDeg(24 * d)
+ + .0005 * sinDeg(4 * d - 4 * f) + .0005 * sinDeg(2 * d + 2 * m) - .0004 * sinDeg(d - m)
+ + .0027 * sinDeg(9 * d) + .0027 * sinDeg(4 * d + 2 * f);
k += 1;
} while (jd < julianDate);
return jd;
@@ -546,20 +554,12 @@ private double[] calcMoon(double t) {
return new double[] { dec, ra };
}
- private double CS(double x) {
- return Math.cos(x * SunCalc.DEG2RAD);
- }
-
- private double SN(double x) {
- return Math.sin(x * SunCalc.DEG2RAD);
- }
-
private double SINALT(double moonJd, int hour, double lambda, double cphi, double sphi) {
double jdo = moonJd + hour / 24.0;
double t = (jdo - 51544.5) / 36525.0;
double[] decra = calcMoon(t);
double tau = 15.0 * (LMST(jdo, lambda) - decra[1]);
- return sphi * SN(decra[0]) + cphi * CS(decra[0]) * CS(tau);
+ return sphi * sinDeg(decra[0]) + cphi * cosDeg(decra[0]) * cosDeg(tau);
}
private double LMST(double moonJd, double lambda) {
@@ -636,14 +636,14 @@ private double var_k(Calendar cal, double tz) {
private double moonCorrection(double jd, double t, double k) {
double ret = jd;
- ret += .000325 * SN(299.77 + .107408 * k - .009173 * t * t) + .000165 * SN(251.88 + .016321 * k)
- + .000164 * SN(251.83 + 26.651886 * k) + .000126 * SN(349.42 + 36.412478 * k)
- + .00011 * SN(84.66 + 18.206239 * k);
- ret += .000062 * SN(141.74 + 53.303771 * k) + .00006 * SN(207.14 + 2.453732 * k)
- + .000056 * SN(154.84 + 7.30686 * k) + .000047 * SN(34.52 + 27.261239 * k)
- + .000042 * SN(207.19 + .121824 * k) + .00004 * SN(291.34 + 1.844379 * k);
- ret += .000037 * SN(161.72 + 24.198154 * k) + .000035 * SN(239.56 + 25.513099 * k)
- + .000023 * SN(331.55 + 3.592518 * k);
+ ret += .000325 * sinDeg(299.77 + .107408 * k - .009173 * t * t) + .000165 * sinDeg(251.88 + .016321 * k)
+ + .000164 * sinDeg(251.83 + 26.651886 * k) + .000126 * sinDeg(349.42 + 36.412478 * k)
+ + .00011 * sinDeg(84.66 + 18.206239 * k);
+ ret += .000062 * sinDeg(141.74 + 53.303771 * k) + .00006 * sinDeg(207.14 + 2.453732 * k)
+ + .000056 * sinDeg(154.84 + 7.30686 * k) + .000047 * sinDeg(34.52 + 27.261239 * k)
+ + .000042 * sinDeg(207.19 + .121824 * k) + .00004 * sinDeg(291.34 + 1.844379 * k);
+ ret += .000037 * sinDeg(161.72 + 24.198154 * k) + .000035 * sinDeg(239.56 + 25.513099 * k)
+ + .000023 * sinDeg(331.55 + 3.592518 * k);
return ret;
}
@@ -663,7 +663,7 @@ private double getCoefficient(double d, double m, double m1, double f) {
-1897, -2117, 2354, 0, 0, -1423, -1117, -1571, -1739, 0, -4421, 0, 0, 0, 0, 1165, 0, 0, 8752 };
double sr = 0;
for (int t = 0; t < 60; t++) {
- sr += kr[t] * CS(kd[t] * d + km[t] * m + km1[t] * m1 + kf[t] * f);
+ sr += kr[t] * cosDeg(kd[t] * d + km[t] * m + km1[t] * m1 + kf[t] * f);
}
return sr;
}
@@ -672,35 +672,35 @@ private double getCoefficient(double d, double m, double m1, double f) {
* Sets the azimuth, elevation and zodiac in the moon object.
*/
private void setAzimuthElevationZodiac(double julianDate, double latitude, double longitude, Moon moon) {
- double lat = latitude * SunCalc.DEG2RAD;
- double lon = longitude * SunCalc.DEG2RAD;
+ double lat = Math.toRadians(latitude);
+ double lon = Math.toRadians(longitude);
double gmst = toGMST(julianDate);
- double lmst = toLMST(gmst, lon) * 15. * SunCalc.DEG2RAD;
+ double lmst = toLMST(gmst, lon) * Math.toRadians(15);
double d = julianDate - 2447891.5;
- double anomalyMean = 360 * SunCalc.DEG2RAD / 365.242191 * d + 4.87650757829735 - 4.935239984568769;
- double nu = anomalyMean + 360.0 * SunCalc.DEG2RAD / Math.PI * 0.016713 * Math.sin(anomalyMean);
+ double anomalyMean = Math.toRadians(360) / 365.242191 * d + 4.87650757829735 - 4.935239984568769;
+ double nu = anomalyMean + Math.toRadians(360.0) / Math.PI * 0.016713 * Math.sin(anomalyMean);
double sunLon = mod2Pi(nu + 4.935239984568769);
- double l0 = 318.351648 * SunCalc.DEG2RAD;
- double p0 = 36.340410 * SunCalc.DEG2RAD;
- double n0 = 318.510107 * SunCalc.DEG2RAD;
- double i = 5.145396 * SunCalc.DEG2RAD;
- double l = 13.1763966 * SunCalc.DEG2RAD * d + l0;
- double mMoon = l - 0.1114041 * SunCalc.DEG2RAD * d - p0;
- double n = n0 - 0.0529539 * SunCalc.DEG2RAD * d;
+ double l0 = Math.toRadians(318.351648);
+ double p0 = Math.toRadians(36.340410);
+ double n0 = Math.toRadians(318.510107);
+ double i = Math.toRadians(5.145396);
+ double l = Math.toRadians(13.1763966) * d + l0;
+ double mMoon = l - Math.toRadians(0.1114041) * d - p0;
+ double n = n0 - Math.toRadians(0.0529539) * d;
double c = l - sunLon;
- double ev = 1.2739 * SunCalc.DEG2RAD * Math.sin(2 * c - mMoon);
- double ae = 0.1858 * SunCalc.DEG2RAD * Math.sin(anomalyMean);
- double a3 = 0.37 * SunCalc.DEG2RAD * Math.sin(anomalyMean);
+ double ev = Math.toRadians(1.2739) * Math.sin(2 * c - mMoon);
+ double ae = Math.toRadians(0.1858) * Math.sin(anomalyMean);
+ double a3 = Math.toRadians(0.37) * Math.sin(anomalyMean);
double mMoon2 = mMoon + ev - ae - a3;
- double ec = 6.2886 * SunCalc.DEG2RAD * Math.sin(mMoon2);
- double a4 = 0.214 * SunCalc.DEG2RAD * Math.sin(2 * mMoon2);
+ double ec = Math.toRadians(6.2886) * Math.sin(mMoon2);
+ double a4 = Math.toRadians(0.214) * Math.sin(2 * mMoon2);
double l2 = l + ev + ec - ae + a4;
- double v = 0.6583 * SunCalc.DEG2RAD * Math.sin(2 * (l2 - sunLon));
+ double v = Math.toRadians(0.6583) * Math.sin(2 * (l2 - sunLon));
double l3 = l2 + v;
- double n2 = n - 0.16 * SunCalc.DEG2RAD * Math.sin(anomalyMean);
+ double n2 = n - Math.toRadians(0.16) * Math.sin(anomalyMean);
double moonLon = mod2Pi(n2 + Math.atan2(Math.sin(l3 - n2) * Math.cos(i), Math.cos(l3 - n2)));
double moonLat = Math.asin(Math.sin(l3 - n2) * Math.sin(i));
@@ -713,21 +713,10 @@ private void setAzimuthElevationZodiac(double julianDate, double latitude, doubl
double[] azAlt = equ2AzAlt(raDecTopo[0], raDecTopo[1], lat, lmst);
Position position = moon.getPosition();
- position.setAzimuth(azAlt[0] * SunCalc.RAD2DEG);
- position.setElevation(azAlt[1] * SunCalc.RAD2DEG + refraction(azAlt[1]));
-
- // zodiac
- double idxd = Math.floor(moonLon * SunCalc.RAD2DEG / 30);
- int idx = 0;
- if (idxd < 0) {
- idx = (int) (Math.ceil(idxd));
- } else {
- idx = (int) (Math.floor(idxd));
- }
+ position.setAzimuth(Math.toDegrees(azAlt[0]));
+ position.setElevation(Math.toDegrees(azAlt[1]) + refraction(azAlt[1]));
- if (idx >= 0 || idx <= ZodiacSign.values().length) {
- moon.setZodiac(new Zodiac(ZodiacSign.values()[idx]));
- }
+ moon.setZodiac(ZodiacCalc.calculate(moonLon, null));
}
private double mod2Pi(double x) {
@@ -765,8 +754,8 @@ private double[] equ2AzAlt(double ra, double dec, double geolat, double lmst) {
*/
private double[] ecl2Equ(double lat, double lon, double jd) {
double t = (jd - 2451545.0) / 36525.0;
- double eps = (23. + (26 + 21.45 / 60.) / 60. + t * (-46.815 + t * (-0.0006 + t * 0.00181)) / 3600.)
- * SunCalc.DEG2RAD;
+ double eps = Math
+ .toRadians(23. + (26 + 21.45 / 60.) / 60. + t * (-46.815 + t * (-0.0006 + t * 0.00181)) / 3600.);
double coseps = Math.cos(eps);
double sineps = Math.sin(eps);
@@ -816,7 +805,7 @@ private double toGMST(double jd) {
* Convert greenwich mean sidereal time to local mean sidereal time.
*/
private double toLMST(double gmst, double lon) {
- return mod(gmst + SunCalc.RAD2DEG * lon / 15., 24.);
+ return mod(gmst + Math.toDegrees(lon) / 15., 24.);
}
/**
@@ -841,7 +830,7 @@ private double getCenterDistance(double lat) {
private double refraction(double alt) {
int pressure = 1015;
int temperature = 10;
- double altdeg = alt * SunCalc.RAD2DEG;
+ double altdeg = Math.toDegrees(alt);
if (altdeg < -2 || altdeg >= 90) {
return 0;
@@ -861,7 +850,7 @@ private double refraction(double alt) {
for (int i = 0; i < 3; i++) {
n = y + (7.31 / (y + 4.4));
- n = 1.0 / Math.tan(n * SunCalc.DEG2RAD);
+ n = 1.0 / Math.tan(Math.toRadians(n));
d = n * p / (60.0 + q * (n + 39.0));
n = y - y0;
y0 = d - d0 - n;
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SeasonCalc.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SeasonCalc.java
index c703fd8ff547d..8d46b95970066 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SeasonCalc.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SeasonCalc.java
@@ -21,6 +21,7 @@
import org.openhab.binding.astro.internal.model.Season;
import org.openhab.binding.astro.internal.model.SeasonName;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
+import org.openhab.binding.astro.internal.util.MathUtils;
/**
* Calculates the seasons of the year.
@@ -159,9 +160,9 @@ private SeasonName getCurrentSeasonNameSouthern(Calendar calendar) {
@Nullable
private Calendar calcEquiSol(int season, int year, TimeZone zone, Locale locale) {
double estimate = calcInitial(season, year);
- double t = (estimate - 2451545.0) / 36525;
+ double t = DateTimeUtils.toJulianCenturies(estimate);
double w = 35999.373 * t - 2.47;
- double dl = 1 + 0.0334 * cosDeg(w) + 0.0007 * cosDeg(2 * w);
+ double dl = 1 + 0.0334 * MathUtils.cosDeg(w) + 0.0007 * MathUtils.cosDeg(2 * w);
double s = periodic24(t);
double julianDate = estimate + ((0.00001 * s) / dl);
return DateTimeUtils.toCalendar(julianDate, zone, locale);
@@ -204,15 +205,8 @@ private double periodic24(double T) {
double result = 0;
for (int i = 0; i < 24; i++) {
- result += a[i] * cosDeg(b[i] + (c[i] * T));
+ result += a[i] * MathUtils.cosDeg(b[i] + (c[i] * T));
}
return result;
}
-
- /**
- * Cosinus of a degree value.
- */
- private double cosDeg(double deg) {
- return Math.cos(deg * Math.PI / 180);
- }
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunCalc.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunCalc.java
index 2b39b50b89260..3a47851f01fe6 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunCalc.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunCalc.java
@@ -33,6 +33,7 @@
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPhaseName;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
+import org.openhab.binding.astro.internal.util.MathUtils;
/**
* Calculates the SunPosition (azimuth, elevation) and Sun data.
@@ -45,28 +46,25 @@
public class SunCalc {
private static final double J2000 = 2451545.0;
private static final double SC = 1367; // Solar constant in W/m²
- public static final double DEG2RAD = Math.PI / 180;
- public static final double RAD2DEG = 180. / Math.PI;
- private static final double M0 = 357.5291 * DEG2RAD;
- private static final double M1 = 0.98560028 * DEG2RAD;
+ private static final double M0 = Math.toRadians(357.5291);
+ private static final double M1 = Math.toRadians(0.98560028);
private static final double J0 = 0.0009;
private static final double J1 = 0.0053;
private static final double J2 = -0.0069;
- private static final double C1 = 1.9148 * DEG2RAD;
- private static final double C2 = 0.0200 * DEG2RAD;
- private static final double C3 = 0.0003 * DEG2RAD;
- private static final double P = 102.9372 * DEG2RAD;
- private static final double E = 23.45 * DEG2RAD;
- private static final double TH0 = 280.1600 * DEG2RAD;
- private static final double TH1 = 360.9856235 * DEG2RAD;
+ private static final double C1 = Math.toRadians(1.9148);
+ private static final double C2 = Math.toRadians(0.0200);
+ private static final double C3 = Math.toRadians(0.0003);
+ private static final double P = Math.toRadians(102.9372);
+ private static final double E = Math.toRadians(23.45);
+ private static final double TH0 = Math.toRadians(280.1600);
+ private static final double TH1 = Math.toRadians(360.9856235);
private static final double SUN_ANGLE = -0.83;
- private static final double SUN_DIAMETER = 0.53 * DEG2RAD; // sun diameter
- private static final double H0 = SUN_ANGLE * DEG2RAD;
- private static final double H1 = -6.0 * DEG2RAD; // nautical twilight angle
- private static final double H2 = -12.0 * DEG2RAD; // astronomical twilight
- // angle
- private static final double H3 = -18.0 * DEG2RAD; // darkness angle
+ private static final double SUN_DIAMETER = Math.toRadians(0.53); // sun diameter
+ private static final double H0 = Math.toRadians(SUN_ANGLE);
+ private static final double H1 = Math.toRadians(-6.0); // nautical twilight angle
+ private static final double H2 = Math.toRadians(-12.0); // astronomical twilight angle
+ private static final double H3 = Math.toRadians(-18.0); // darkness angle
private static final double MINUTES_PER_DAY = 60 * 24;
private static final int CURVE_TIME_INTERVAL = 20; // 20 minutes
private static final double JD_ONE_MINUTE_FRACTION = 1.0 / 60 / 24;
@@ -76,8 +74,8 @@ public class SunCalc {
*/
public void setPositionalInfo(Calendar calendar, double latitude, double longitude, @Nullable Double altitude,
Sun sun) {
- double lw = -longitude * DEG2RAD;
- double phi = latitude * DEG2RAD;
+ double lw = Math.toRadians(-longitude);
+ double phi = Math.toRadians(latitude);
double j = DateTimeUtils.dateToJulianDate(calendar);
double m = getSolarMeanAnomaly(j);
@@ -87,8 +85,8 @@ public void setPositionalInfo(Calendar calendar, double latitude, double longitu
double a = getRightAscension(lsun);
double th = getSiderealTime(j, lw);
- double azimuth = getAzimuth(th, a, phi, d) / DEG2RAD;
- double elevation = getElevation(th, a, phi, d) / DEG2RAD;
+ double azimuth = Math.toDegrees(getAzimuth(th, a, phi, d));
+ double elevation = Math.toDegrees(getElevation(th, a, phi, d));
double shadeLength = getShadeLength(elevation);
Position position = sun.getPosition();
@@ -103,14 +101,14 @@ public void setPositionalInfo(Calendar calendar, double latitude, double longitu
* Calculates sun radiation data.
*/
private void setRadiationInfo(Calendar calendar, double elevation, @Nullable Double altitude, Sun sun) {
- double sinAlpha = Math.sin(DEG2RAD * elevation);
+ double sinAlpha = MathUtils.sinDeg(elevation);
int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR);
int daysInYear = calendar.getActualMaximum(Calendar.DAY_OF_YEAR);
// Direct Solar Radiation (in W/m²) at the atmosphere entry
// At sunrise/sunset - calculations limits are reached
- double rOut = (elevation > 3) ? SC * (0.034 * Math.cos(DEG2RAD * (360 * dayOfYear / daysInYear)) + 1) : 0;
+ double rOut = (elevation > 3) ? SC * (0.034 * MathUtils.cosDeg(360 * dayOfYear / daysInYear) + 1) : 0;
double altitudeRatio = (altitude != null) ? 1 / Math.pow((1 - (6.5 / 288) * (altitude / 1000.0)), 5.256) : 1;
double m = (Math.sqrt(1229 + Math.pow(614 * sinAlpha, 2)) - 614 * sinAlpha) * altitudeRatio;
@@ -154,8 +152,8 @@ public Sun getSunInfo(Calendar calendar, double latitude, double longitude, @Nul
private Sun getSunInfo(Calendar calendar, double latitude, double longitude, @Nullable Double altitude,
boolean onlyAstro, boolean useMeteorologicalSeason, TimeZone zone, Locale locale) {
- double lw = -longitude * DEG2RAD;
- double phi = latitude * DEG2RAD;
+ double lw = Math.toRadians(-longitude);
+ double phi = Math.toRadians(latitude);
double j = DateTimeUtils.midnightDateToJulianDate(calendar) + 0.5;
double n = getJulianCycle(j, lw);
double js = getApproxSolarTransit(0, lw, n);
@@ -271,8 +269,7 @@ private Sun getSunInfo(Calendar calendar, double latitude, double longitude, @Nu
}
});
- SunZodiacCalc zodiacCalc = new SunZodiacCalc(zone, locale);
- zodiacCalc.getZodiac(calendar).ifPresent(z -> sun.setZodiac(z));
+ sun.setZodiac(ZodiacCalc.calculate(lsun, calendar.toInstant()));
SeasonCalc seasonCalc = new SeasonCalc();
sun.setSeason(seasonCalc.getSeason(calendar, latitude, useMeteorologicalSeason, zone, locale));
@@ -304,11 +301,11 @@ private Calendar addDays(Calendar calendar, int days) {
// all the following methods are translated to java based on the javascript
// calculations of http://www.suncalc.net
private double getJulianCycle(double j, double lw) {
- return Math.round(j - J2000 - J0 - lw / (2 * Math.PI));
+ return Math.round(j - J2000 - J0 - lw / MathUtils.TWO_PI);
}
private double getApproxSolarTransit(double ht, double lw, double n) {
- return J2000 + J0 + (ht + lw) / (2 * Math.PI) + n;
+ return J2000 + J0 + (ht + lw) / MathUtils.TWO_PI + n;
}
private double getSolarMeanAnomaly(double js) {
@@ -349,7 +346,7 @@ private double getElevation(double th, double a, double phi, double d) {
}
private double getShadeLength(double elevation) {
- return 1 / Math.tan(elevation * DEG2RAD);
+ return 1 / MathUtils.tanDeg(elevation);
}
private double getHourAngle(double h, double phi, double d) {
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/ZodiacCalc.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/ZodiacCalc.java
new file mode 100644
index 0000000000000..7cf9279b474c0
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/ZodiacCalc.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.astro.internal.calc;
+
+import java.time.Duration;
+import java.time.Instant;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.astro.internal.model.Zodiac;
+import org.openhab.binding.astro.internal.model.ZodiacSign;
+import org.openhab.binding.astro.internal.util.AstroConstants;
+import org.openhab.binding.astro.internal.util.MathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Calculates the zodiac sign from the current ecliptic longitude of the object (sun/moon).
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class ZodiacCalc {
+ private static final Logger logger = LoggerFactory.getLogger(ZodiacCalc.class);
+
+ /**
+ * Returns a {@link Zodiac} built from the calculated sign for the given instant. The start and end instants are
+ * estimated using the mean motion along the ecliptic.
+ */
+ public static Zodiac calculate(double eclipticLongitude, @Nullable Instant referenceInstant) {
+ double normalizedLongitude = MathUtils.mod2Pi(eclipticLongitude);
+ int index = (int) (normalizedLongitude / ZodiacSign.getRadiansPerSign());
+
+ Instant start = null;
+ Instant end = null;
+ if (referenceInstant != null) {
+ double radiansIntoSign = normalizedLongitude - index * ZodiacSign.getRadiansPerSign();
+ start = referenceInstant.minus(angleToDuration(radiansIntoSign));
+ end = referenceInstant.plus(angleToDuration(ZodiacSign.getRadiansPerSign() - radiansIntoSign));
+ }
+ try {
+ return new Zodiac(index, start, end);
+ } catch (IllegalArgumentException e) {
+ logger.warn("Error defining Zodiac: {}", e.getMessage());
+ return Zodiac.NULL;
+ }
+ }
+
+ private static Duration angleToDuration(double angle) {
+ long millis = Math.round((angle / AstroConstants.SOLAR_MEAN_MOTION_PER_SECOND) * 1000);
+ if (millis < 0) {
+ millis = 0;
+ }
+ return Duration.ofMillis(millis);
+ }
+}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java
index a0f5be98cb730..46f95a2d8b9ef 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/handler/AstroThingHandler.java
@@ -18,7 +18,9 @@
import java.lang.invoke.MethodHandles;
import java.text.SimpleDateFormat;
+import java.time.Instant;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
@@ -312,23 +314,35 @@ public void triggerEvent(String channelId, String event) {
/**
* Adds the provided {@link Job} to the queue (cannot be {@code null})
*/
- public void schedule(Job job, Calendar eventAt) {
- long sleepTime;
+ private void schedule(Job job, long sleepTimeMs) {
monitor.lock();
try {
tidyScheduledFutures();
- sleepTime = eventAt.getTimeInMillis() - new Date().getTime();
- ScheduledFuture> future = scheduler.schedule(job, sleepTime, TimeUnit.MILLISECONDS);
+ ScheduledFuture> future = scheduler.schedule(job, sleepTimeMs, TimeUnit.MILLISECONDS);
scheduledFutures.add(future);
} finally {
monitor.unlock();
}
+ }
+
+ /**
+ * Adds the provided {@link Job} to the queue (cannot be {@code null})
+ */
+ public void schedule(Job job, Calendar eventAt) {
+ long sleepTime = eventAt.getTimeInMillis() - new Date().getTime();
+ schedule(job, sleepTime);
if (logger.isDebugEnabled()) {
final String formattedDate = this.isoFormatter.format(eventAt.getTime());
logger.debug("Scheduled {} in {}ms (at {})", job, sleepTime, formattedDate);
}
}
+ public void schedule(Job job, Instant eventAt) {
+ long sleepTime = Instant.now().until(eventAt, ChronoUnit.MILLIS);
+ schedule(job, sleepTime);
+ logger.debug("Scheduled {} in {}ms (at {})", job, sleepTime, eventAt);
+ }
+
private void tidyScheduledFutures() {
monitor.lock();
try {
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/DailyJobSun.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/DailyJobSun.java
index b693a6dcbd1c4..0f1154b854aff 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/DailyJobSun.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/DailyJobSun.java
@@ -16,6 +16,7 @@
import static org.openhab.binding.astro.internal.job.Job.*;
import static org.openhab.binding.astro.internal.model.SunPhaseName.*;
+import java.time.Instant;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
@@ -26,7 +27,6 @@
import org.openhab.binding.astro.internal.model.Planet;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Sun;
-import org.openhab.binding.astro.internal.model.SunZodiac;
/**
* Daily scheduled jobs For Sun planet
@@ -124,15 +124,13 @@ public void run() {
});
// schedule republish jobs
- SunZodiac sunZodiac;
- Calendar cal = (sunZodiac = sun.getZodiac()) == null ? null : sunZodiac.getEnd();
- if (cal != null) {
- schedulePublishPlanet(handler, cal, zone, locale);
+ if (sun.getZodiac().getEnd() instanceof Instant when) {
+ schedulePublishPlanet(handler, when);
}
schedulePublishPlanet(handler, sun.getSeason().getNextSeason(), zone, locale);
// schedule phase jobs
- cal = sun.getRise().getStart();
+ Calendar cal = sun.getRise().getStart();
if (cal != null) {
scheduleSunPhase(handler, SUN_RISE, cal, zone, locale);
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/Job.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/Job.java
index f48e95f0e69e4..e52327826f818 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/Job.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/job/Job.java
@@ -17,6 +17,7 @@
import static org.openhab.binding.astro.internal.util.DateTimeUtils.*;
import java.lang.invoke.MethodHandles;
+import java.time.Instant;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
@@ -61,6 +62,21 @@ static void schedule(AstroThingHandler astroHandler, Job job, Calendar eventAt,
}
}
+ /**
+ * Schedules the provided {@link Job} instance
+ *
+ * @param astroHandler the {@link AstroThingHandler} instance
+ * @param job the {@link Job} instance to schedule
+ * @param eventAt the {@link Instant} instance denoting scheduled instant
+ */
+ static void schedule(AstroThingHandler astroHandler, Job job, Instant eventAt) {
+ try {
+ astroHandler.schedule(job, eventAt);
+ } catch (Exception ex) {
+ logger.error("{}", ex.getMessage(), ex);
+ }
+ }
+
/**
* Schedules an {@link EventJob} instance
*
@@ -169,6 +185,17 @@ static void schedulePublishPlanet(AstroThingHandler astroHandler, Calendar event
schedule(astroHandler, publishJob, eventAt, zone, locale);
}
+ /**
+ * Schedules Planet events
+ *
+ * @param astroHandler the {@link AstroThingHandler} instance
+ * @param when the {@link Instant} instance denoting scheduled instant
+ */
+ static void schedulePublishPlanet(AstroThingHandler astroHandler, Instant when) {
+ Job publishJob = new PublishPlanetJob(astroHandler);
+ schedule(astroHandler, publishJob, when);
+ }
+
/**
* Schedules {@link SunPhaseJob}
*
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Moon.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Moon.java
index 1dc653b579950..d2f99235c913f 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Moon.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Moon.java
@@ -27,7 +27,7 @@ public class Moon extends RiseSet implements Planet {
private MoonDistance distance = new MoonDistance();
private Eclipse eclipse = new Eclipse(EclipseKind.PARTIAL, EclipseKind.TOTAL);
private Position position = new Position();
- private Zodiac zodiac = new Zodiac(null);
+ private Zodiac zodiac = Zodiac.NULL;
/**
* Returns the moon phase.
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Sun.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Sun.java
index 834d479ddc74f..213f52b0b62f6 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Sun.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Sun.java
@@ -32,7 +32,7 @@ public class Sun extends RiseSet implements Planet {
private Position position = new Position();
- private @Nullable SunZodiac zodiac;
+ private Zodiac zodiac = Zodiac.NULL;
private Season season = new Season(TimeZone.getDefault(), Locale.getDefault());
@@ -249,15 +249,14 @@ public void setPosition(Position position) {
/**
* Returns the zodiac.
*/
- @Nullable
- public SunZodiac getZodiac() {
+ public Zodiac getZodiac() {
return zodiac;
}
/**
* Sets the zodiac.
*/
- public void setZodiac(@Nullable SunZodiac zodiac) {
+ public void setZodiac(Zodiac zodiac) {
this.zodiac = zodiac;
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Zodiac.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Zodiac.java
index 2fcbbc1be1397..1d96772ddc81d 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Zodiac.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/Zodiac.java
@@ -12,6 +12,8 @@
*/
package org.openhab.binding.astro.internal.model;
+import java.time.Instant;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -22,10 +24,25 @@
*/
@NonNullByDefault
public class Zodiac {
- private @Nullable ZodiacSign sign;
+ public static final Zodiac NULL = new Zodiac();
+
+ private final @Nullable ZodiacSign sign;
+ private final @Nullable Instant start;
+ private final @Nullable Instant end;
+
+ private Zodiac() {
+ this.sign = null;
+ this.start = null;
+ this.end = null;
+ }
- public Zodiac(@Nullable ZodiacSign sign) {
- this.sign = sign;
+ public Zodiac(int index, @Nullable Instant start, @Nullable Instant end) {
+ if (index < 0 || index >= ZodiacSign.values().length) {
+ throw new IllegalArgumentException("Index value %d out of range".formatted(index));
+ }
+ this.sign = ZodiacSign.values()[index];
+ this.start = start;
+ this.end = end;
}
/**
@@ -35,4 +52,20 @@ public Zodiac(@Nullable ZodiacSign sign) {
public ZodiacSign getSign() {
return sign;
}
+
+ /**
+ * Returns the start instant of the zodiac sign.
+ */
+ @Nullable
+ public Instant getStart() {
+ return start;
+ }
+
+ /**
+ * Returns the end instant of the zodiac sign.
+ */
+ @Nullable
+ public Instant getEnd() {
+ return end;
+ }
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/ZodiacSign.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/ZodiacSign.java
index 1f8bb784aa45d..b7591092122fb 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/ZodiacSign.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/ZodiacSign.java
@@ -13,6 +13,7 @@
package org.openhab.binding.astro.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.astro.internal.util.MathUtils;
/**
* All zodiac signs.
@@ -32,5 +33,9 @@ public enum ZodiacSign {
SAGITTARIUS,
CAPRICORN,
AQUARIUS,
- PISCES
+ PISCES;
+
+ public static double getRadiansPerSign() {
+ return MathUtils.TWO_PI / ZodiacSign.values().length;
+ }
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/AstroConstants.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/AstroConstants.java
new file mode 100644
index 0000000000000..16794e00187b7
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/AstroConstants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.astro.internal.util;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Common constants used across the binding
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class AstroConstants {
+ public static final double SECONDS_PER_DAY = 60 * 60 * 24;
+ public static final double MILLISECONDS_PER_DAY = 1000 * SECONDS_PER_DAY;
+ public static final double TROPICAL_YEAR_DAYS = 365.242189;
+ public static final double TROPICAL_YEAR_SECONDS = TROPICAL_YEAR_DAYS * SECONDS_PER_DAY;
+ public static final double SOLAR_MEAN_MOTION_PER_SECOND = MathUtils.TWO_PI / AstroConstants.TROPICAL_YEAR_SECONDS;
+
+ /** Constructor */
+ private AstroConstants() {
+ throw new IllegalAccessError("Non-instantiable");
+ }
+}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/DateTimeUtils.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/DateTimeUtils.java
index e1aa060bd1c1e..e7a157e1cf1c5 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/DateTimeUtils.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/DateTimeUtils.java
@@ -12,6 +12,8 @@
*/
package org.openhab.binding.astro.internal.util;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
@@ -34,8 +36,10 @@ public class DateTimeUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(DateTimeUtils.class);
private static final Pattern HHMM_PATTERN = Pattern.compile("^([0-1][0-9]|2[0-3])(:[0-5][0-9])$");
- private static final double J1970 = 2440588.0;
- private static final double MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;
+ public static final double JD_J2000 = 2451545.0; // 2000-01-01 12:00
+ public static final double JD_UNIX_EPOCH = 2440587.5; // 1970-01-01 00:00 UTC
+ private static final double J1970 = JD_UNIX_EPOCH + 0.5; // 1970-01-01 12:00 UTC (julian solar noon)
+ private static final int JULIAN_CENTURY_DAYS = 36525; // Length of a Julian Century in days
/** Constructor */
private DateTimeUtils() {
@@ -51,6 +55,13 @@ public static Calendar truncateToSecond(Calendar calendar) {
return cal;
}
+ /**
+ * Truncates the time from the instant object.
+ */
+ public static Instant truncateToSecond(Instant instant) {
+ return instant.truncatedTo(ChronoUnit.SECONDS);
+ }
+
/**
* Truncates the time from the calendar object.
*/
@@ -60,6 +71,13 @@ public static Calendar truncateToMinute(Calendar calendar) {
return cal;
}
+ /**
+ * Truncates the time from the instant object.
+ */
+ public static Instant truncateToMinute(Instant instant) {
+ return instant.truncatedTo(ChronoUnit.MINUTES);
+ }
+
/**
* Truncates the time from the calendar object.
*/
@@ -70,6 +88,13 @@ public static Calendar truncateToMidnight(Calendar calendar) {
return cal;
}
+ /**
+ * Truncates the time from the instant object.
+ */
+ public static Instant truncateToMidnight(Instant instant) {
+ return instant.truncatedTo(ChronoUnit.DAYS);
+ }
+
/**
* Creates a Range object within the specified months and days. The start
* time is midnight, the end time is end of the day.
@@ -102,7 +127,7 @@ public static Calendar toCalendar(double julianDate, TimeZone zone, Locale local
if (Double.compare(julianDate, Double.NaN) == 0 || julianDate == 0) {
return null;
}
- long millis = (long) ((julianDate + 0.5 - J1970) * MILLISECONDS_PER_DAY);
+ long millis = (long) ((julianDate + 0.5 - J1970) * AstroConstants.MILLISECONDS_PER_DAY);
Calendar cal = Calendar.getInstance(zone, locale);
cal.setTimeInMillis(millis);
return cal;
@@ -112,7 +137,7 @@ public static Calendar toCalendar(double julianDate, TimeZone zone, Locale local
* Returns the julian date from the calendar object.
*/
public static double dateToJulianDate(Calendar calendar) {
- return calendar.getTimeInMillis() / MILLISECONDS_PER_DAY - 0.5 + J1970;
+ return calendar.getTimeInMillis() / AstroConstants.MILLISECONDS_PER_DAY - 0.5 + J1970;
}
/**
@@ -132,6 +157,13 @@ public static Calendar endOfDayDate(Calendar calendar) {
return cal;
}
+ /**
+ * Returns the end of day from the instant object.
+ */
+ public static Instant endOfDayDate(Instant instant) {
+ return truncateToMidnight(instant).plus(1, ChronoUnit.DAYS).minusMillis(1);
+ }
+
/**
* Returns the year of the calendar object as a decimal value.
*/
@@ -218,12 +250,24 @@ public static Calendar getAdjustedEarliest(Calendar cal, AstroChannelConfig conf
return minutes > 0 ? adjustTime(cal, minutes) : cal;
}
+ public static Instant getAdjustedEarliest(Instant instant, AstroChannelConfig config) {
+ int minutes = getMinutesFromTime(config.earliest);
+ // MainUI sets earliest to 00:00 if unconfigured, which is why zero must be treated as such
+ return minutes > 0 ? adjustTime(instant, minutes) : instant;
+ }
+
public static Calendar getAdjustedLatest(Calendar cal, AstroChannelConfig config) {
int minutes = getMinutesFromTime(config.latest);
// MainUI sets latest to 00:00 if unconfigured, which is why zero must be treated as such
return minutes > 0 ? adjustTime(cal, minutes) : cal;
}
+ public static Instant getAdjustedLatest(Instant instant, AstroChannelConfig config) {
+ int minutes = getMinutesFromTime(config.latest);
+ // MainUI sets latest to 00:00 if unconfigured, which is why zero must be treated as such
+ return minutes > 0 ? adjustTime(instant, minutes) : instant;
+ }
+
/**
* Applies the config to the given calendar.
*/
@@ -271,6 +315,52 @@ public static Calendar applyConfig(Calendar cal, AstroChannelConfig config) {
return cCal;
}
+ /**
+ * Applies the config to the given calendar.
+ */
+ public static Instant applyConfig(Instant cal, AstroChannelConfig config) {
+ Instant cCal = cal;
+ if (config.offset != 0) {
+ cCal = cCal.plus(config.offset, ChronoUnit.MINUTES);
+ }
+
+ int minutes = getMinutesFromTime(config.earliest);
+ Instant threshold, actual;
+ // MainUI sets earliest to 00:00 if unconfigured, which is why zero must be treated as such
+ if (minutes > 0) {
+ if ((threshold = truncateToMidnight(cal)).equals(actual = truncateToMidnight(cCal))) {
+ // Same day
+ Instant cEarliest = getAdjustedEarliest(cCal, config);
+ if (cCal.isBefore(cEarliest)) {
+ return cEarliest;
+ }
+ } else {
+ // Previous or next day
+ if (actual.isBefore(threshold)) {
+ return getAdjustedEarliest(threshold, config);
+ }
+ }
+ }
+ minutes = getMinutesFromTime(config.latest);
+ // MainUI sets latest to 00:00 if unconfigured, which is why zero must be treated as such
+ if (minutes > 0) {
+ if ((threshold = endOfDayDate(cal)).equals(actual = endOfDayDate(cCal))) {
+ // Same day
+ Instant cLatest = getAdjustedLatest(cCal, config);
+ if (cCal.isAfter(cLatest)) {
+ return cLatest;
+ }
+ } else {
+ // Previous or next day
+ if (actual.isAfter(threshold)) {
+ return getAdjustedLatest(threshold, config);
+ }
+ }
+ }
+
+ return cCal;
+ }
+
static Calendar adjustTime(Calendar cal, int minutes) {
if (minutes >= 0) {
Calendar cTime = truncateToMidnight(cal);
@@ -280,6 +370,17 @@ static Calendar adjustTime(Calendar cal, int minutes) {
return cal;
}
+ static Instant adjustTime(Instant instant, int minutes) {
+ if (minutes >= 0) {
+ return truncateToMidnight(instant).plus(minutes, ChronoUnit.MINUTES);
+ }
+ return instant;
+ }
+
+ public static double toJulianCenturies(double jd) {
+ return (jd - JD_J2000) / JULIAN_CENTURY_DAYS;
+ }
+
public static Calendar createCalendarForToday(int hour, int minute, TimeZone zone, Locale locale) {
return DateTimeUtils.adjustTime(Calendar.getInstance(zone, locale), hour * 60 + minute);
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/MathUtils.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/MathUtils.java
new file mode 100644
index 0000000000000..d8d3dd69679fa
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/MathUtils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.astro.internal.util;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Common used DateTime functions.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class MathUtils {
+ public static final double TWO_PI = 2 * Math.PI;
+
+ /** Constructor */
+ private MathUtils() {
+ throw new IllegalAccessError("Non-instantiable");
+ }
+
+ public static double mod2Pi(double x) {
+ return mod(x, TWO_PI);
+ }
+
+ public static double mod(double a, double b) {
+ return a - Math.floor(a / b) * b;
+ }
+
+ /**
+ * Cosinus of a degree value.
+ */
+ public static double cosDeg(double deg) {
+ return Math.cos(Math.toRadians(deg));
+ }
+
+ /**
+ * Tangent of a degree value.
+ */
+ public static double tanDeg(double deg) {
+ return Math.tan(Math.toRadians(deg));
+ }
+
+ /**
+ * Sinus of a degree value.
+ */
+ public static double sinDeg(double deg) {
+ return Math.sin(Math.toRadians(deg));
+ }
+}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/PropertyUtils.java b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/PropertyUtils.java
index ecd8bc0017284..c7b7e9a0a0e19 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/PropertyUtils.java
+++ b/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/util/PropertyUtils.java
@@ -15,6 +15,7 @@
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Calendar;
@@ -63,6 +64,9 @@ public static State getState(ChannelUID channelUID, AstroChannelConfig config, O
cal.setTimeZone(zone);
ZonedDateTime zoned = gregorianCal.toZonedDateTime().withFixedOffsetZone();
return new DateTimeType(zoned);
+ } else if (value instanceof Instant instant) {
+ Instant configuredInstant = DateTimeUtils.applyConfig(instant, config);
+ return new DateTimeType(configuredInstant.atZone(zone.toZoneId()).withFixedOffsetZone());
} else if (value instanceof Number) {
BigDecimal decimalValue = new BigDecimal(value.toString()).setScale(2, RoundingMode.HALF_UP);
return new DecimalType(decimalValue);
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/i18n/astro.properties b/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/i18n/astro.properties
index e61681d9bcb41..579768275fc28 100644
--- a/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/i18n/astro.properties
+++ b/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/i18n/astro.properties
@@ -235,3 +235,8 @@ channel-group-type.astro.moonEclipse.channel.partial.description = Timestamp of
discovery.astro.sun.local.label = Local Sun
discovery.astro.moon.local.label = Local Moon
+
+# iconprovider
+
+iconset.label = Astro Icons
+iconset.description = Icons provided for the Astro Binding
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/thing/channels.xml
index 25703097213a7..749681a1c0400 100644
--- a/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/thing/channels.xml
+++ b/bundles/org.openhab.binding.astro/src/main/resources/OH-INF/thing/channels.xml
@@ -172,6 +172,7 @@
String
The sign of the zodiac
+ oh:astro:zodiac
Status
Info
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-aquarius.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-aquarius.svg
new file mode 100644
index 0000000000000..3a88a3423ef81
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-aquarius.svg
@@ -0,0 +1,34 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-aries.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-aries.svg
new file mode 100644
index 0000000000000..0b280bc9e8477
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-aries.svg
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-cancer.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-cancer.svg
new file mode 100644
index 0000000000000..db1454717cb5c
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-cancer.svg
@@ -0,0 +1,59 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-capricorn.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-capricorn.svg
new file mode 100644
index 0000000000000..19b12e2a605e2
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-capricorn.svg
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-gemini.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-gemini.svg
new file mode 100644
index 0000000000000..fe05172131ab7
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-gemini.svg
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-leo.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-leo.svg
new file mode 100644
index 0000000000000..4feff0d275cd9
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-leo.svg
@@ -0,0 +1,66 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-libra.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-libra.svg
new file mode 100644
index 0000000000000..654cfb348dffd
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-libra.svg
@@ -0,0 +1,26 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-pisces.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-pisces.svg
new file mode 100644
index 0000000000000..62e47f2f35176
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-pisces.svg
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-sagittarius.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-sagittarius.svg
new file mode 100644
index 0000000000000..8d0f7221df2e9
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-sagittarius.svg
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-scorpio.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-scorpio.svg
new file mode 100644
index 0000000000000..8ab4273fe83c0
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-scorpio.svg
@@ -0,0 +1,167 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-taurus.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-taurus.svg
new file mode 100644
index 0000000000000..1fadfb8b7b88c
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-taurus.svg
@@ -0,0 +1,57 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-virgo.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-virgo.svg
new file mode 100644
index 0000000000000..62a6032a95fb5
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac-virgo.svg
@@ -0,0 +1,105 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac.svg b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac.svg
new file mode 100644
index 0000000000000..4d436646593af
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/main/resources/icon/zodiac.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/MoonCalcTest.java b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/MoonCalcTest.java
index 59fea66ba3bf9..c70d3ae49d5a6 100644
--- a/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/MoonCalcTest.java
+++ b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/MoonCalcTest.java
@@ -22,7 +22,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.astro.internal.model.Moon;
-import org.openhab.binding.astro.internal.model.ZodiacSign;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/***
@@ -122,14 +121,6 @@ public void testGetMoonInfoForSetAccuracy() {
setStart.getTimeInMillis(), ACCURACY_IN_MILLIS);
}
- @Test
- public void testGetMoonInfoForZodiac() {
- Moon moon = moonCalc.getMoonInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, TIME_ZONE, Locale.ROOT);
- moonCalc.setPositionalInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, moon, TIME_ZONE, Locale.ROOT);
-
- assertEquals(ZodiacSign.SAGITTARIUS, moon.getZodiac().getSign());
- }
-
@Test
public void testGetMoonInfoForMoonPositionAccuracy() {
Moon moon = moonCalc.getMoonInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, TIME_ZONE, Locale.ROOT);
diff --git a/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/MoonZodiacCalcTest.java b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/MoonZodiacCalcTest.java
new file mode 100644
index 0000000000000..2f3fd40387b8e
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/MoonZodiacCalcTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.astro.internal.calc.zodiac;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.openhab.binding.astro.internal.calc.MoonCalc;
+import org.openhab.binding.astro.internal.model.Moon;
+import org.openhab.binding.astro.internal.model.ZodiacSign;
+import org.openhab.binding.astro.internal.util.DateTimeUtils;
+
+/***
+ * Specific unit tests to check if {@link MoonCalc} generates correct data for
+ * Amsterdam city on 27 February 2019. In particular the following cases are
+ * covered:
+ *
+ * - checks if generated data are the same (with some accuracy) as produced by
+ * haevens-above.com
+ *
+ *
+ * @author Leo Siepel - Initial contribution
+ * @see Heavens Above Moon
+ */
+public class MoonZodiacCalcTest {
+
+ private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("Europe/Amsterdam");
+ private static final Calendar FEB_27_2019 = MoonZodiacCalcTest.newCalendar(2019, Calendar.FEBRUARY, 27, 1, 0,
+ TIME_ZONE);
+ private static final double AMSTERDAM_LATITUDE = 52.367607;
+ private static final double AMSTERDAM_LONGITUDE = 4.8978293;
+
+ private MoonCalc moonCalc;
+
+ @BeforeEach
+ public void init() {
+ moonCalc = new MoonCalc();
+ }
+
+ @Test
+ public void testGetMoonInfoForZodiac() {
+ Moon moon = moonCalc.getMoonInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, TIME_ZONE, Locale.ROOT);
+ moonCalc.setPositionalInfo(FEB_27_2019, AMSTERDAM_LATITUDE, AMSTERDAM_LONGITUDE, moon, TIME_ZONE, Locale.ROOT);
+
+ assertEquals(ZodiacSign.SAGITTARIUS, moon.getZodiac().getSign());
+ }
+
+ /***
+ * Constructs a GregorianCalendar with the given date and time set
+ * for the provided time zone.
+ *
+ * @param year
+ * the value used to set the YEAR calendar field in the
+ * calendar.
+ * @param month
+ * the value used to set the MONTH calendar field in the
+ * calendar. Month value is 0-based. e.g., 0 for January.
+ * @param dayOfMonth
+ * the value used to set the DAY_OF_MONTH calendar field
+ * in the calendar.
+ * @param hourOfDay
+ * the value used to set the HOUR_OF_DAY calendar field
+ * in the calendar.
+ * @param minute
+ * the value used to set the MINUTE calendar field in
+ * the calendar.
+ * @param zone
+ * the given time zone.
+ * @return
+ */
+ private static Calendar newCalendar(int year, int month, int dayOfMonth, int hourOfDay, int minute, TimeZone zone) {
+ Calendar result = new GregorianCalendar(zone, Locale.ROOT);
+ result.set(year, month, dayOfMonth, hourOfDay, minute);
+
+ return DateTimeUtils.truncateToMinute(result);
+ }
+}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunZodiacCalc.java b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/OldSunZodiacCalc.java
similarity index 95%
rename from bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunZodiacCalc.java
rename to bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/OldSunZodiacCalc.java
index 203894f68db9e..e36f2aeafe15d 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/calc/SunZodiacCalc.java
+++ b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/OldSunZodiacCalc.java
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.astro.internal.calc;
+package org.openhab.binding.astro.internal.calc.zodiac;
import java.util.ArrayList;
import java.util.Calendar;
@@ -22,7 +22,6 @@
import java.util.TimeZone;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.astro.internal.model.SunZodiac;
import org.openhab.binding.astro.internal.model.ZodiacSign;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
@@ -32,13 +31,13 @@
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
-public class SunZodiacCalc {
+public class OldSunZodiacCalc {
private Map> zodiacsByYear = new HashMap<>();
private final TimeZone zone;
private final Locale locale;
- public SunZodiacCalc(TimeZone zone, Locale locale) {
+ public OldSunZodiacCalc(TimeZone zone, Locale locale) {
this.zone = zone;
this.locale = locale;
}
diff --git a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/SunZodiac.java b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/SunZodiac.java
similarity index 81%
rename from bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/SunZodiac.java
rename to bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/SunZodiac.java
index d5d392c725e53..677cd9d41fb21 100644
--- a/bundles/org.openhab.binding.astro/src/main/java/org/openhab/binding/astro/internal/model/SunZodiac.java
+++ b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/SunZodiac.java
@@ -10,12 +10,14 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.astro.internal.model;
+package org.openhab.binding.astro.internal.calc.zodiac;
import java.util.Calendar;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.astro.internal.model.Range;
+import org.openhab.binding.astro.internal.model.ZodiacSign;
/**
* Extends the zodiac with a date range.
@@ -23,14 +25,16 @@
* @author Gerhard Riegler - Initial contribution
*/
@NonNullByDefault
-public class SunZodiac extends Zodiac {
+public class SunZodiac {
+ private final @Nullable ZodiacSign sign;
+
private Range range;
/**
* Creates a Zodiac with a sign and a range.
*/
public SunZodiac(ZodiacSign sign, Range range) {
- super(sign);
+ this.sign = sign;
this.range = range;
}
@@ -50,6 +54,11 @@ public Calendar getEnd() {
return range.getEnd();
}
+ @Nullable
+ public ZodiacSign getSign() {
+ return sign;
+ }
+
/**
* Returns true, if the zodiac is valid on the specified calendar object.
*/
diff --git a/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/SunZodiacCalcTest.java b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/SunZodiacCalcTest.java
new file mode 100644
index 0000000000000..62a8c7680f36c
--- /dev/null
+++ b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/calc/zodiac/SunZodiacCalcTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2010-2025 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.astro.internal.calc.zodiac;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.openhab.binding.astro.internal.calc.ZodiacCalc;
+import org.openhab.binding.astro.internal.model.Zodiac;
+import org.openhab.binding.astro.internal.model.ZodiacSign;
+
+/**
+ * Tests for {@link ZodiacCalc}.
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+public class SunZodiacCalcTest {
+
+ private static Stream zodiacAngles() {
+ return Stream.of(arguments(Math.toRadians(0), ZodiacSign.ARIES),
+ arguments(Math.toRadians(29.9), ZodiacSign.ARIES), arguments(Math.toRadians(30), ZodiacSign.TAURUS),
+ arguments(Math.toRadians(59.9), ZodiacSign.TAURUS), arguments(Math.toRadians(90), ZodiacSign.CANCER),
+ arguments(Math.toRadians(210), ZodiacSign.SCORPIO), arguments(Math.toRadians(330), ZodiacSign.PISCES),
+ arguments(Math.toRadians(-0.1), ZodiacSign.PISCES));
+ }
+
+ @ParameterizedTest
+ @MethodSource("zodiacAngles")
+ public void testCalcZodiacSignFromLongitude(double longitude, ZodiacSign expected) {
+ assertEquals(expected, ZodiacCalc.calculate(longitude, null).getSign());
+ }
+
+ @Test
+ public void testCalcZodiacCreatesSunZodiacWithRangeFromInstant() {
+ Instant referenceInstant = Instant.parse("2025-08-05T12:00:00Z");
+ Zodiac zodiac = ZodiacCalc.calculate(Math.toRadians(120), referenceInstant);
+ assertEquals(ZodiacSign.LEO, zodiac.getSign());
+ var start = zodiac.getStart();
+ var end = zodiac.getEnd();
+ assertNotNull(start);
+ assertNotNull(end);
+ assertFalse(start.isAfter(referenceInstant));
+ assertTrue(end.isAfter(referenceInstant));
+
+ Duration length = Duration.between(start, end);
+ assertTrue(length.toDays() >= 29 && length.toDays() <= 32);
+ }
+
+ private static Stream sunZodiacCalcComparison() {
+ double offset = ZodiacSign.getRadiansPerSign() / 2;
+ return Stream.of(
+ arguments(ZodiacSign.ARIES, newCalendar(2025, Calendar.APRIL, 5),
+ offset + 0 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.TAURUS, newCalendar(2025, Calendar.MAY, 5),
+ offset + 1 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.GEMINI, newCalendar(2025, Calendar.JUNE, 5),
+ offset + 2 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.CANCER, newCalendar(2025, Calendar.JULY, 5),
+ offset + 3 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.LEO, newCalendar(2025, Calendar.AUGUST, 5),
+ offset + 4 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.VIRGO, newCalendar(2025, Calendar.SEPTEMBER, 5),
+ offset + 5 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.LIBRA, newCalendar(2025, Calendar.OCTOBER, 5),
+ offset + 6 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.SCORPIO, newCalendar(2025, Calendar.NOVEMBER, 5),
+ offset + 7 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.SAGITTARIUS, newCalendar(2025, Calendar.DECEMBER, 5),
+ offset + 8 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.CAPRICORN, newCalendar(2025, Calendar.JANUARY, 10),
+ offset + 9 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.AQUARIUS, newCalendar(2025, Calendar.FEBRUARY, 5),
+ offset + 10 * ZodiacSign.getRadiansPerSign()),
+ arguments(ZodiacSign.PISCES, newCalendar(2025, Calendar.MARCH, 5),
+ offset + 11 * ZodiacSign.getRadiansPerSign()));
+ }
+
+ @ParameterizedTest
+ @MethodSource("sunZodiacCalcComparison")
+ public void testPositionBasedResultMatchesDateBased(ZodiacSign expectedSign, Calendar date, double longitude) {
+ OldSunZodiacCalc dateCalc = new OldSunZodiacCalc(TimeZone.getTimeZone("UTC"), Locale.ROOT);
+
+ ZodiacSign dateBasedSign = dateCalc.getZodiac(date).map(SunZodiac::getSign).orElseThrow();
+ ZodiacSign positionBasedSign = ZodiacCalc.calculate(longitude, null).getSign();
+
+ assertEquals(expectedSign, dateBasedSign);
+ assertEquals(expectedSign, positionBasedSign);
+ }
+
+ private static Calendar newCalendar(int year, int month, int day) {
+ Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"), Locale.ROOT);
+ cal.set(year, month, day, 12, 0, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ return cal;
+ }
+}
diff --git a/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/util/DateTimeUtilsTest.java b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/util/DateTimeUtilsTest.java
index 4e06880f6c7bd..a246337d0c236 100644
--- a/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/util/DateTimeUtilsTest.java
+++ b/bundles/org.openhab.binding.astro/src/test/java/org/openhab/binding/astro/internal/util/DateTimeUtilsTest.java
@@ -14,6 +14,7 @@
import static org.junit.jupiter.api.Assertions.*;
+import java.time.Instant;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;
@@ -29,6 +30,7 @@
* Test class for {@link DateTimeUtils}.
*
* @author Hilbrand Bouwkamp - Initial contribution
+ * @author Gaël L'hopital - Added tests for Instant usage
*/
public class DateTimeUtilsTest {
@@ -84,6 +86,20 @@ void testTruncate() {
assertEquals(endOfDay, target2);
}
+ @Test
+ void testTruncateInstant() {
+ Instant instant = Instant.parse("2024-03-12T10:15:30.123456Z");
+ assertEquals(Instant.parse("2024-03-12T10:15:30Z"), DateTimeUtils.truncateToSecond(instant));
+ assertEquals(Instant.parse("2024-03-12T10:15:00Z"), DateTimeUtils.truncateToMinute(instant));
+ assertEquals(Instant.parse("2024-03-12T00:00:00Z"), DateTimeUtils.truncateToMidnight(instant));
+ }
+
+ @Test
+ void testEndOfDayDateInstant() {
+ Instant instant = Instant.parse("2024-03-12T10:15:30Z");
+ assertEquals(Instant.parse("2024-03-12T23:59:59.999Z"), DateTimeUtils.endOfDayDate(instant));
+ }
+
@Test
public void testCreateCalendarForToday() {
Calendar cal = DateTimeUtils.createCalendarForToday(8, 0, TIME_ZONE, Locale.ROOT);
@@ -111,6 +127,26 @@ public void testAdjustTime() {
assertSame(JAN_20_2020, DateTimeUtils.adjustTime(JAN_20_2020, -2));
}
+ @Test
+ public void testAdjustTimeInstant() {
+ Instant instant = Instant.parse("2024-03-12T10:15:30Z");
+ assertEquals(Instant.parse("2024-03-12T01:00:00Z"), DateTimeUtils.adjustTime(instant, 60));
+ assertEquals(Instant.parse("2024-03-12T00:00:00Z"), DateTimeUtils.adjustTime(instant, 0));
+ assertSame(instant, DateTimeUtils.adjustTime(instant, -1));
+ }
+
+ @Test
+ public void testGetAdjustedLatestInstant() {
+ AstroChannelConfig config = new AstroChannelConfig();
+ Instant instant = Instant.parse("2024-03-12T10:15:30Z");
+ config.latest = "02:30";
+ assertEquals(Instant.parse("2024-03-12T02:30:00Z"), DateTimeUtils.getAdjustedLatest(instant, config));
+ config.latest = "00:00";
+ assertSame(instant, DateTimeUtils.getAdjustedLatest(instant, config));
+ config.latest = null;
+ assertSame(instant, DateTimeUtils.getAdjustedLatest(instant, config));
+ }
+
@Test
public void testApplyConfig() {
AstroChannelConfig config = new AstroChannelConfig();