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 | ![Zodiac](doc/images/zodiac.svg) | +| oh:astro:zodiac-aquarius | Yes | ![Aquarius](doc/images/zodiac-aquarius.svg) | +| oh:astro:zodiac-aries | Yes | ![Aries](doc/images/zodiac-aries.svg) | +| oh:astro:zodiac-cancer | Yes | ![Cancer](doc/images/zodiac-cancer.svg) | +| oh:astro:zodiac-capricorn | Yes | ![Capricorn](doc/images/zodiac-capricorn.svg) | +| oh:astro:zodiac-gemini | Yes | ![Gemini](doc/images/zodiac-gemini.svg) | +| oh:astro:zodiac-leo | Yes | ![Leo](doc/images/zodiac-leo.svg) | +| oh:astro:zodiac-libra | Yes | ![Libra](doc/images/zodiac-libra.svg) | +| oh:astro:zodiac-pisces | Yes | ![Pisces](doc/images/zodiac-pisces.svg) | +| oh:astro:zodiac-sagittarius | Yes | ![Sagittarius](doc/images/zodiac-sagittarius.svg) | +| oh:astro:zodiac-scorpio | Yes | ![Scorpio](doc/images/zodiac-scorpio.svg) | +| oh:astro:zodiac-taurus | Yes | ![Taurus](doc/images/zodiac-taurus.svg) | +| oh:astro:zodiac-virgo | Yes | ![Virgo](doc/images/zodiac-virgo.svg) | + ## 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();