[astro] Adds moon_day and moon_phase icon sets (#20066)

* Adds Moon Day and Phase icon sets
* Readd lost icon set
* Remove local mode.
* Moves moonphase control
* Adressed Nadahar's code review

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital
2026-01-15 13:46:23 +01:00
committed by GitHub
parent 314dabf9bd
commit 92c706913a
39 changed files with 951 additions and 156 deletions
+3
View File
@@ -14,6 +14,9 @@ https://www.svgrepo.com/collection/zodiac-2/
Season Icon Set provided by Free SVG under Public Domain at
https://freesvg.org/seasons-icons76143
Moon Phase Icon set (Twemoji Emojis Collection) provided by SVG Repo under MIT License at
https://www.svgrepo.com/collection/twemoji-emojis/4?search=moon
== Source Code
https://github.com/openhab/openhab-addons
+3 -1
View File
@@ -34,9 +34,11 @@ This binding has its own IconProvider and makes available the following list of
| Icon Name | Dynamic | Illustration |
| --------------------------- | ------- | ------------------------------------------------- |
| oh:astro:zodiac | Yes | ![Zodiac](doc/images/zodiac.svg) |
| oh:astro:moon_day | Yes | ![Moon Age](doc/images/moon_day.svg) |
| oh:astro:moon_phase | Yes | ![Moon Phase](doc/images/moon_day.svg) |
| oh:astro:season | Yes | ![Season](doc/images/season.svg) |
| oh:astro:sun_eclipse | Yes | ![Sun Eclipse](doc/images/sun_eclipse.svg) |
| oh:astro:zodiac | Yes | ![Zodiac](doc/images/zodiac.svg) |
## Channels
@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffd983" d="M36 18H0C0 8.059 8.059 0 18 0s18 8.059 18 18z" />
<path fill="#66757f" d="M18 36C8.059 36 0 27.941 0 18h36c0 9.941-8.059 18-18 18z" />
<circle fill="#ffcc4d" cx="10.5" cy="-27.5" r="3.5" transform="rotate(90)" />
<circle fill="#5b6876" cx="24" cy="-20" r="3" transform="rotate(90)" />
<circle fill="#5b6876" cx="22.5" cy="-8.5" r="3.5" transform="rotate(90)" />
<circle fill="#5b6876" cx="21" cy="-30" r="2" transform="rotate(90)" />
<circle fill="#ffcc4d" cx="3" cy="-18" r="1" transform="rotate(90)" />
<circle fill="#5b6876" cx="30" cy="-27" r="1" transform="rotate(90)" />
<circle fill="#ffcc4d" cx="15" cy="-5" r="1" transform="rotate(90)" />
<circle fill="#5b6876" cx="32" cy="-17" r="2" transform="rotate(90)" />
<circle fill="#ffcc4d" cx="10" cy="-13" r="2" transform="rotate(90)" />
</svg>

After

Width:  |  Height:  |  Size: 939 B

@@ -18,16 +18,18 @@ 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.EclipseKind;
import org.openhab.binding.astro.internal.model.MoonPhaseName;
import org.openhab.binding.astro.internal.model.SeasonName;
import org.openhab.binding.astro.internal.model.ZodiacSign;
import org.openhab.core.i18n.TranslationProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.ui.icon.IconProvider;
import org.openhab.core.ui.icon.IconSet;
import org.openhab.core.ui.icon.IconSet.Format;
@@ -49,10 +51,13 @@ import org.slf4j.LoggerFactory;
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_SET = "zodiac";
private static final String MOON_DAY_SET = "moon_day";
private static final String MOON_PHASE_SET = "moon_phase";
private static final String SEASON_SET = "season";
private static final String SUN_ECLIPSE_SET = "sun_eclipse";
private static final Set<String> ICON_SETS = Set.of(SEASON_SET, SUN_ECLIPSE_SET, ZODIAC_SET);
private static final String ZODIAC_SET = "zodiac";
private static final Set<String> ICON_SETS = Set.of(MOON_DAY_SET, MOON_PHASE_SET, SEASON_SET, SUN_ECLIPSE_SET,
ZODIAC_SET);
private final Logger logger = LoggerFactory.getLogger(AstroIconProvider.class);
private final TranslationProvider i18nProvider;
@@ -89,31 +94,49 @@ public class AstroIconProvider implements IconProvider {
@Override
public @Nullable InputStream getIcon(String category, String iconSetId, @Nullable String state, Format format) {
String iconName = String.format(Locale.ROOT, "icon/%s.svg", category);
if (ICON_SETS.contains(category) && state != null) {
try {
Enum<?> stateEnum = switch (category) {
case ZODIAC_SET -> ZodiacSign.valueOf(state);
case SEASON_SET -> SeasonName.valueOf(state);
case SUN_ECLIPSE_SET -> EclipseKind.valueOf(state);
default -> throw new IllegalArgumentException("Category of icon not found: %s".formatted(category));
};
iconName = iconName.replace(".", "-%s.".formatted(stateEnum.name().toLowerCase(Locale.US)));
} catch (IllegalArgumentException e) {
// Invalid state for the icon set, we'll remain on default icon
logger.warn("Error retrieving icon name '{}' - using default: {}", state, e.getMessage());
String set = category.equals(MOON_PHASE_SET) ? MOON_DAY_SET : category;
String resourceWithoutState = "icon/" + set + "." + format.toString();
if (state == null) {
return getResource(resourceWithoutState);
}
try {
String iconState = switch (category) {
case SEASON_SET -> SeasonName.valueOf(state).name();
case ZODIAC_SET -> ZodiacSign.valueOf(state).name();
case SUN_ECLIPSE_SET -> EclipseKind.valueOf(state).name();
case MOON_PHASE_SET -> {
yield Integer.toString(MoonPhaseName.valueOf(state).getAgeDays());
}
case MOON_DAY_SET -> {
try {
var age = QuantityType.valueOf(state);
if (age.toUnit(Units.DAY) instanceof QuantityType ageInDays) {
yield Integer.toString(ageInDays.intValue());
}
} catch (NumberFormatException ignore) {
}
throw new IllegalArgumentException("Unable to use state '%s' for '%s'".formatted(state, category));
}
default -> throw new IllegalArgumentException("Icon category '%s' not found".formatted(category));
};
String resourceWithState = "icon/" + set + "-" + iconState + "." + format.toString();
return getResource(resourceWithState);
} catch (IllegalArgumentException e) {
logger.debug("Use icon {} as state {} is not found", resourceWithoutState, state);
return getResource(resourceWithoutState);
}
}
private @Nullable InputStream getResource(String iconName) {
if (bundle.getEntry(iconName.toLowerCase(Locale.ROOT)) instanceof URL iconResource) {
try (InputStream stream = iconResource.openStream()) {
byte[] icon = stream.readAllBytes();
return new ByteArrayInputStream(icon);
} catch (IOException e) {
logger.warn("Unable to load resource '{}': {}", iconResource.getPath(), e.getMessage());
}
}
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));
return null;
}
}
@@ -19,6 +19,7 @@ import java.math.RoundingMode;
import java.time.InstantSource;
import java.util.Calendar;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
@@ -47,10 +48,6 @@ import org.openhab.binding.astro.internal.util.DateTimeUtils;
*/
@NonNullByDefault
public class MoonCalc {
private static final double NEW_MOON = 0;
private static final double FULL_MOON = 0.5;
private static final double FIRST_QUARTER = 0.25;
private static final double LAST_QUARTER = 0.75;
private final InstantSource instantSource;
@@ -93,12 +90,8 @@ public class MoonCalc {
moon.setSet(new Range(set, set));
MoonPhase phase = moon.getPhase();
phase.setNew(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, NEW_MOON), zone, locale));
phase.setFirstQuarter(
DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FIRST_QUARTER), zone, locale));
phase.setFull(DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, FULL_MOON), zone, locale));
phase.setThirdQuarter(
DateTimeUtils.toCalendar(getNextPhase(calendar, julianDateMidnight, LAST_QUARTER), zone, locale));
phase.remarkablePhases().forEach(mp -> phase.setPhase(mp,
DateTimeUtils.toCalendar(getPhase(calendar, julianDateMidnight, mp, true), zone, locale)));
Eclipse eclipse = moon.getEclipse();
eclipse.getKinds().forEach(eclipseKind -> {
@@ -133,7 +126,7 @@ public class MoonCalc {
private void setMoonPhase(Calendar calendar, Moon moon, TimeZone zone, Locale locale) {
MoonPhase phase = moon.getPhase();
double julianDate = DateTimeUtils.dateToJulianDate(calendar);
double parentNewMoon = getPreviousPhase(calendar, julianDate, NEW_MOON);
double parentNewMoon = getPhase(calendar, julianDate, MoonPhaseName.NEW, false);
double age = Math.abs(parentNewMoon - julianDate);
Calendar parentNewMoonCal = DateTimeUtils.toCalendar(parentNewMoon, zone, locale);
if (parentNewMoonCal == null) {
@@ -153,20 +146,10 @@ public class MoonCalc {
phase.setAgeDegree(3.6 * agePercent);
double illumination = getIllumination(julianDate);
phase.setIllumination(illumination);
boolean isWaxing = age < (29.530588853 / 2);
if (DateTimeUtils.isSameDay(calendar, phase.getNew())) {
phase.setName(MoonPhaseName.NEW);
} else if (DateTimeUtils.isSameDay(calendar, phase.getFirstQuarter())) {
phase.setName(MoonPhaseName.FIRST_QUARTER);
} else if (DateTimeUtils.isSameDay(calendar, phase.getThirdQuarter())) {
phase.setName(MoonPhaseName.THIRD_QUARTER);
} else if (DateTimeUtils.isSameDay(calendar, phase.getFull())) {
phase.setName(MoonPhaseName.FULL);
} else if (illumination >= 0 && illumination < 50) {
phase.setName(isWaxing ? MoonPhaseName.WAXING_CRESCENT : MoonPhaseName.WANING_CRESCENT);
} else if (illumination >= 50 && illumination < 100) {
phase.setName(isWaxing ? MoonPhaseName.WAXING_GIBBOUS : MoonPhaseName.WANING_GIBBOUS);
}
Optional<MoonPhaseName> remarkablePhase = phase.remarkablePhases()
.filter(p -> DateTimeUtils.isSameDay(calendar, phase.getPhaseDate(p))).findFirst();
phase.setName(remarkablePhase.orElse(MoonPhaseName.fromAgePercent(agePercent / 100)));
}
/**
@@ -263,8 +246,8 @@ public class MoonCalc {
/**
* Calculates the moon phase.
*/
private double calcMoonPhase(double k, double mode) {
double kMod = Math.floor(k) + mode;
private double calcMoonPhase(double k, MoonPhaseName phase) {
double kMod = Math.floor(k) + phase.cycleProgress;
double t = kMod / 1236.85;
double e = varE(t);
double m = varM(kMod, t);
@@ -272,42 +255,45 @@ public class MoonCalc {
double f = varF(kMod, t);
double o = varO(kMod, t);
double jd = varJde(kMod, t);
if (mode == NEW_MOON) {
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 * 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 * 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;
switch (phase) {
case NEW:
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);
break;
case FULL:
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);
break;
default:
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 += MoonPhaseName.FIRST_QUARTER.equals(phase) ? w : -w;
}
return moonCorrection(jd, t, kMod);
}
@@ -400,30 +386,16 @@ public class MoonCalc {
}
/**
* Calculates the next moon phase.
* Searches the next moon phase in a given direction
*/
private double getNextPhase(Calendar cal, double midnightJd, double mode) {
private double getPhase(Calendar cal, double jd, MoonPhaseName phase, boolean forward) {
double tz = 0;
double phaseJd = 0;
do {
double k = varK(cal, tz);
tz += 1;
phaseJd = calcMoonPhase(k, mode);
} while (phaseJd <= midnightJd);
return phaseJd;
}
/**
* Calculates the previous moon phase.
*/
private double getPreviousPhase(Calendar cal, double jd, double mode) {
double tz = 0;
double phaseJd = 0;
do {
double k = varK(cal, tz);
tz -= 1;
phaseJd = calcMoonPhase(k, mode);
} while (phaseJd > jd);
tz += forward ? 1 : -1;
phaseJd = calcMoonPhase(k, phase);
} while (forward ? phaseJd <= jd : phaseJd > jd);
return phaseJd;
}
@@ -13,6 +13,9 @@
package org.openhab.binding.astro.internal.model;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Dimensionless;
@@ -31,10 +34,8 @@ import org.openhab.core.library.unit.Units;
*/
@NonNullByDefault
public class MoonPhase {
private @Nullable Calendar firstQuarter;
private @Nullable Calendar full;
private @Nullable Calendar thirdQuarter;
private @Nullable Calendar newCalendar;
private final Map<MoonPhaseName, @Nullable Calendar> phases = new HashMap<>(MoonPhaseName.values().length);
private double age;
private double illumination;
private double agePercent;
@@ -42,19 +43,19 @@ public class MoonPhase {
private @Nullable MoonPhaseName name;
public MoonPhase() {
phases.put(MoonPhaseName.FIRST_QUARTER, null);
phases.put(MoonPhaseName.FULL, null);
phases.put(MoonPhaseName.THIRD_QUARTER, null);
phases.put(MoonPhaseName.NEW, null);
}
/**
* Returns the date at which the moon is in the first quarter.
*/
@Nullable
public Calendar getFirstQuarter() {
return firstQuarter;
}
/**
* Sets the date at which the moon is in the first quarter.
*/
public void setFirstQuarter(@Nullable Calendar firstQuarter) {
this.firstQuarter = firstQuarter;
return getPhaseDate(MoonPhaseName.FIRST_QUARTER);
}
/**
@@ -62,14 +63,7 @@ public class MoonPhase {
*/
@Nullable
public Calendar getFull() {
return full;
}
/**
* Sets the date of the full moon.
*/
public void setFull(@Nullable Calendar full) {
this.full = full;
return getPhaseDate(MoonPhaseName.FULL);
}
/**
@@ -77,14 +71,7 @@ public class MoonPhase {
*/
@Nullable
public Calendar getThirdQuarter() {
return thirdQuarter;
}
/**
* Sets the date at which the moon is in the third quarter.
*/
public void setThirdQuarter(@Nullable Calendar thirdQuarter) {
this.thirdQuarter = thirdQuarter;
return getPhaseDate(MoonPhaseName.THIRD_QUARTER);
}
/**
@@ -92,14 +79,26 @@ public class MoonPhase {
*/
@Nullable
public Calendar getNew() {
return newCalendar;
return getPhaseDate(MoonPhaseName.NEW);
}
/**
* Sets the date of the new moon.
*/
public void setNew(@Nullable Calendar newCalendar) {
this.newCalendar = newCalendar;
@Nullable
public Calendar getPhaseDate(MoonPhaseName moonPhase) {
if (!phases.containsKey(moonPhase)) {
throw new IllegalArgumentException("MoonPhase does not handle %s".formatted(moonPhase.toString()));
}
return phases.get(moonPhase);
}
public void setPhase(MoonPhaseName moonPhase, @Nullable Calendar calendar) {
if (!phases.containsKey(moonPhase)) {
throw new IllegalArgumentException("MoonPhase does not handle %s".formatted(moonPhase.toString()));
}
phases.put(moonPhase, calendar);
}
public Stream<MoonPhaseName> remarkablePhases() {
return phases.keySet().stream();
}
/**
@@ -12,21 +12,49 @@
*/
package org.openhab.binding.astro.internal.model;
import static org.openhab.binding.astro.internal.util.AstroConstants.LUNAR_SYNODIC_MONTH_DAYS;
import java.util.Arrays;
import java.util.Comparator;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* All moon phases.
*
* @author Gerhard Riegler - Initial contribution
* @author Gaël L'hopital - added age equivalence in days & cycleProgressPercentage
*/
@NonNullByDefault
public enum MoonPhaseName {
NEW,
WAXING_CRESCENT,
FIRST_QUARTER,
WAXING_GIBBOUS,
FULL,
WANING_GIBBOUS,
THIRD_QUARTER,
WANING_CRESCENT
NEW(0.0),
WAXING_CRESCENT(0.125),
FIRST_QUARTER(0.25),
WAXING_GIBBOUS(0.375),
FULL(0.5),
WANING_GIBBOUS(0.625),
THIRD_QUARTER(0.75), // also called last quarter
WANING_CRESCENT(0.875);
public final double cycleProgress;
MoonPhaseName(double cycleProgress) {
this.cycleProgress = cycleProgress;
}
public int getAgeDays() {
return (int) ((LUNAR_SYNODIC_MONTH_DAYS - 1) * cycleProgress + 1);
}
public @Nullable static MoonPhaseName fromAgePercent(double agePercent) {
return (agePercent >= 0.0 && agePercent <= 1.0) ? Arrays.stream(values())
.min(Comparator.comparingDouble(p -> circularDistance(agePercent, p.cycleProgress))).orElseThrow() // impossible
: null;
}
private static double circularDistance(double a, double b) {
double d = Math.abs(a - b);
return Math.min(d, 1.0 - d);
}
}
@@ -25,7 +25,8 @@ public class AstroConstants {
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;
public static final double SOLAR_MEAN_MOTION_PER_SECOND = MathUtils.TWO_PI / TROPICAL_YEAR_SECONDS;
public static final double LUNAR_SYNODIC_MONTH_DAYS = 29.530588853;
/** Constant term of the E5 angle. */
public static final double E05_0 = 357.52910918;
@@ -96,7 +96,7 @@ channel-group-type.astro.sunZodiac.description = The zodiac of the sun
# channel types
channel-type.astro.age.label = Moon Age
channel-type.astro.age.description = The age of the moon in days
channel-type.astro.age.description = The age of the moon
channel-type.astro.ageDegree.label = Moon Age
channel-type.astro.ageDegree.description = The age of the moon
channel-type.astro.agePercent.label = Moon Age
@@ -462,9 +462,10 @@
</channel-type>
<channel-type id="age">
<item-type>Number:Time</item-type>
<item-type unitHint="d">Number:Time</item-type>
<label>Moon Age</label>
<description>The age of the moon in days</description>
<description>The age of the moon</description>
<category>oh:astro:moon_day</category>
<tags>
<tag>Calculation</tag>
<tag>Duration</tag>
@@ -506,6 +507,7 @@
<item-type>String</item-type>
<label>Moon Phase Name</label>
<description>The name of the current moon phase</description>
<category>oh:astro:moon_phase</category>
<tags>
<tag>Status</tag>
</tags>
@@ -615,6 +617,7 @@
<channel-group-type id="moonZodiac">
<label>Zodiac</label>
<description>The zodiac of the moon</description>
<category>oh:astro:zodiac</category>
<channels>
<channel id="sign" typeId="sign"/>
</channels>
@@ -0,0 +1,14 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<circle fill="#66757F" cx="18" cy="18" r="18" />
<g fill="#5B6876">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 552 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18-4.5-2.6-6-9.7-6-18s1.5-15.4 6-18C8.059 0 0 8.059 0 18z" />
<circle cx="9.375" cy="8.5" r="3.5" fill="#5b6876" />
<circle fill="#ffcc4d" cx="20" cy="16" r="3" />
<circle fill="#ffcc4d" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#ffcc4d" cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle fill="#ffcc4d" cx="15" cy="31" r="1" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#5b6876" />
</svg>

After

Width:  |  Height:  |  Size: 725 B

@@ -0,0 +1,14 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18-7.5-2.6-9-9.7-9-18s1.5-15.4 9-18C8.059 0 0 8.059 0 18z" />
<circle fill="#ffcc4d" cx="15" cy="8.5" r="3.5" />
<circle fill="#ffcc4d" cx="23.375" cy="16" r="3" />
<circle fill="#ffcc4d" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#ffcc4d" cx="24.375" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle cx="5.58" cy="10.15" r="2.136" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle fill="#ffcc4d" cx="15" cy="31" r="1" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle fill="#ffcc4d" cx="12.25" cy="24.125" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 797 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18C8 33.4 6 26.3 6 18S8 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle fill="#ffcc4d" cx="11.357" cy="9.014" r="3.5" />
<circle fill="#ffcc4d" cx="20" cy="16" r="3" />
<circle fill="#ffcc4d" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#ffcc4d" cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle fill="#ffcc4d" cx="15" cy="31" r="1" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle fill="#ffcc4d" cx="10" cy="23" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 725 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18C5.5 33.4 3 26.3 3 18S5.5 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle fill="#ffcc4d" cx="11.786" cy="8.543" r="3.5" />
<circle fill="#ffcc4d" cx="21.286" cy="16.043" r="3" />
<circle fill="#ffcc4d" cx="22.786" cy="27.543" r="3.5" />
<circle fill="#ffcc4d" cx="22.286" cy="6.043" r="2" />
<circle fill="#ffcc4d" cx="4.286" cy="18.043" r="1" />
<circle fill="#ffcc4d" cx="31.286" cy="9.043" r="1" />
<circle fill="#ffcc4d" cx="16.286" cy="31.043" r="1" />
<circle fill="#ffcc4d" cx="33.286" cy="19.043" r="2" />
<circle fill="#ffcc4d" cx="11.286" cy="23.043" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 800 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18C4.5 33.4 3 26.3 3 18S4.5 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle fill="#ffcc4d" cx="10.5" cy="8.5" r="3.5" />
<circle fill="#ffcc4d" cx="20" cy="16" r="3" />
<circle fill="#ffcc4d" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#ffcc4d" cx="21" cy="6" r="2" />
<circle fill="#ffcc4d" cx="4.125" cy="18" r="1" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle fill="#ffcc4d" cx="15" cy="31" r="1" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle fill="#ffcc4d" cx="10" cy="23" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 729 B

@@ -0,0 +1,14 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<g fill="#FFCC4D">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 552 B

@@ -0,0 +1,15 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36c9.2-2.6 15-9.7 15-18S27.2 2.6 18 0z" />
<g fill="#ffcc4d" transform="translate(-1.371 .129)">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 654 B

@@ -0,0 +1,15 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0 A18 18 0 0 1 18 36 C24.8 33.4 29 26.3 29 18 S24.8 2.6 18 0z" />
<g fill="#FFCC4D">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle fill="#5B6876" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle fill="#5B6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 658 B

@@ -0,0 +1,15 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0 A18 18 0 0 1 18 36 C23.4 33.4 27 26.3 27 18 S23.4 2.6 18 0z" />
<g fill="#FFCC4D">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle fill="#5B6876" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle fill="#5B6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 680 B

@@ -0,0 +1,15 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36c4.2-2.6 7-9.7 7-18S22.2 2.6 18 0z" />
<g fill="#ffcc4d" transform="translate(-1.971 .343)">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle fill="#5b6876" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle fill="#5b6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 682 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18 9.2-2.6 15-9.7 15-18S27.2 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#5b6876" />
<circle cx="20" cy="16" r="3" fill="#5b6876" />
<circle cx="21.5" cy="27.5" r="3.5" fill="#5b6876" />
<circle cx="21" cy="6" r="2" fill="#5b6876" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle cx="28.875" cy="9" r="1" fill="#5b6876" />
<circle cx="15" cy="31" r="1" fill="#5b6876" />
<circle cx="30.875" cy="19" r="2" fill="#5b6876" />
<circle cx="10" cy="23" r="2" fill="#5b6876" />
</svg>

After

Width:  |  Height:  |  Size: 734 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36c3-2.6 5-9.7 5-18S21 2.6 18 0z" />
<circle cx="9.857" cy="8.629" r="3.5" fill="#ffcc4d" />
<circle cx="16.871" cy="15.957" r="3" fill="#ffcc4d" />
<circle cx="18.286" cy="25.786" r="3.5" fill="#ffcc4d" />
<circle cx="18.9" cy="6.343" r="2" fill="#ffcc4d" />
<circle cx="2.357" cy="18.129" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="29.357" cy="9.129" r="1" />
<circle cx="14.357" cy="31.129" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="31.357" cy="19.129" r="2" />
<circle cx="9.357" cy="23.129" r="2" fill="#ffcc4d" />
</svg>

After

Width:  |  Height:  |  Size: 751 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36c1.5-2.6 2.5-9.7 2.5-18S19.5 2.6 18 0z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#ffcc4d" />
<circle cx="17.129" cy="18.057" r="3" fill="#ffcc4d" />
<circle fill="#5b6876" cx="23.857" cy="28.914" r="3.5" />
<circle fill="#5b6876" cx="22.886" cy="4.8" r="2" />
<circle cx="3" cy="18" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#ffcc4d" />
</svg>

After

Width:  |  Height:  |  Size: 717 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36V0z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#ffcc4d" />
<circle fill="#5b6876" cx="21.843" cy="15.271" r="3" />
<circle fill="#5b6876" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#5b6876" cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#ffcc4d" />
</svg>

After

Width:  |  Height:  |  Size: 672 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36c-1.5-2.6-3-9.7-3-18s1.5-15.4 3-18z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#ffcc4d" />
<circle fill="#5b6876" cx="20" cy="16" r="3" />
<circle fill="#5b6876" cx="26.686" cy="26.343" r="3.5" />
<circle fill="#5b6876" cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="30" cy="9" r="1" />
<circle fill="#5b6876" cx="20.271" cy="31.643" r="1" />
<circle fill="#5b6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#ffcc4d" />
</svg>

After

Width:  |  Height:  |  Size: 708 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M18 0a18 18 0 010 36c-4.5-2.6-6-9.7-6-18s1.5-15.4 6-18z" />
<circle cx="9.171" cy="7.814" r="3.5" fill="#ffcc4d" />
<circle fill="#5b6876" cx="20" cy="16" r="3" />
<circle fill="#5b6876" cx="24.5" cy="26.814" r="3.5" />
<circle fill="#5b6876" cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#ffcc4d" />
<circle fill="#5b6876" cx="30" cy="9" r="1" />
<circle fill="#5b6876" cx="16.157" cy="31.557" r="1" />
<circle fill="#5b6876" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#ffcc4d" />
</svg>

After

Width:  |  Height:  |  Size: 709 B

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="64"
width="64"
viewBox="0 0 36 36"
version="1.1"
id="svg9"
sodipodi:docname="moon_day-25.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs9" />
<sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="13.125"
inkscape:cx="32.038095"
inkscape:cy="32"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg9" />
<circle
fill="#ffd983"
cx="-18"
cy="-18"
r="18"
id="circle1"
transform="scale(-1)" />
<path
fill="#66757f"
d="M 36,18 C 36,8.059 27.941,0 18,0 c -4.2,2.6 -7,9.7 -7,18 0,8.3 2.8,15.4 7,18 9.941,0 18,-8.059 18,-18 z"
id="path1" />
<circle
cx="-25.5"
cy="-27.5"
r="3.5"
fill="#5b6876"
id="circle2"
transform="scale(-1)" />
<circle
cx="-16"
cy="-20"
r="3"
fill="#5b6876"
id="circle3"
transform="scale(-1)" />
<circle
cx="-16.75"
cy="-8.5"
r="3.5"
fill="#5b6876"
id="circle4"
transform="scale(-1)" />
<circle
cx="-16.125"
cy="-30"
r="2"
fill="#5b6876"
id="circle5"
transform="scale(-1)" />
<circle
cx="-33"
cy="-18"
r="1"
fill="#5b6876"
id="circle6"
transform="scale(-1)" />
<circle
fill="#ffcc4d"
cx="-6"
cy="-27"
r="1"
id="circle7"
transform="scale(-1)" />
<circle
fill="#ffcc4d"
cx="-4"
cy="-17"
r="2"
id="circle8"
transform="scale(-1)" />
<circle
cx="-26"
cy="-13"
r="2"
fill="#5b6876"
id="circle9"
transform="scale(-1)" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="64"
width="64"
viewBox="0 0 36 36"
version="1.1"
id="svg10"
sodipodi:docname="moon_day-26.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="13.125"
inkscape:cx="31.961905"
inkscape:cy="32"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<circle
fill="#ffd983"
cx="-18"
cy="-18"
r="18"
id="circle1"
transform="scale(-1)" />
<path
fill="#66757f"
d="M 36,18 C 36,8.059 27.941,0 18,0 12.6,2.6 9,9.7 9,18 c 0,8.3 3.6,15.4 9,18 9.941,0 18,-8.059 18,-18 z"
id="path1" />
<g
fill="#5b6876"
id="g10"
transform="rotate(180,18,18)">
<circle
cx="10.5"
cy="8.5"
r="3.5"
id="circle2" />
<circle
cx="20"
cy="16"
r="3"
id="circle3" />
<circle
cx="21.5"
cy="27.5"
r="3.5"
id="circle4" />
<circle
cx="21"
cy="6"
r="2"
id="circle5" />
<circle
cx="3"
cy="18"
r="1"
id="circle6" />
<circle
fill="#ffcc4d"
cx="30"
cy="9"
r="1"
id="circle7" />
<circle
cx="15"
cy="31"
r="1"
id="circle8" />
<circle
fill="#ffcc4d"
cx="32"
cy="19"
r="2"
id="circle9" />
<circle
cx="10"
cy="23"
r="2"
id="circle10" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="64"
width="64"
viewBox="0 0 36 36"
version="1.1"
id="svg10"
sodipodi:docname="moon_day-27.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="13.125"
inkscape:cx="32.038095"
inkscape:cy="32"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<circle
fill="#ffd983"
cx="-18"
cy="-18"
r="18"
id="circle1"
transform="scale(-1)" />
<path
fill="#66757f"
d="M 36,18 C 36,8.059 27.941,0 18,0 17.705,0 17.42,0.029 17.13,0.043 11.239,2.607 7,9.668 7,18 7,26.331 11.239,33.393 17.13,35.956 17.42,35.97 17.705,36 18,36 27.941,36 36,27.941 36,18 Z"
id="path1" />
<circle
fill="#5b6876"
cx="-25.5"
cy="-27.5"
r="3.5"
id="circle2"
transform="scale(-1)" />
<circle
fill="#5b6876"
cx="-16"
cy="-20"
r="3"
id="circle3"
transform="scale(-1)" />
<circle
fill="#5b6876"
cx="-14.5"
cy="-8.5"
r="3.5"
id="circle4"
transform="scale(-1)" />
<circle
fill="#5b6876"
cx="-15"
cy="-30"
r="2"
id="circle5"
transform="scale(-1)" />
<circle
fill="#5b6876"
cx="-33"
cy="-18"
r="1"
id="circle6"
transform="scale(-1)" />
<circle
fill="#ffcc4d"
cx="-6"
cy="-27"
r="1"
id="circle7"
transform="scale(-1)" />
<circle
fill="#5b6876"
cx="-21"
cy="-5"
r="1"
id="circle8"
transform="scale(-1)" />
<circle
fill="#ffcc4d"
cx="-4"
cy="-17"
r="2"
id="circle9"
transform="scale(-1)" />
<circle
fill="#5b6876"
cx="-26"
cy="-13"
r="2"
id="circle10"
transform="scale(-1)" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="64"
width="64"
viewBox="0 0 36 36"
version="1.1"
id="svg10"
sodipodi:docname="moon_day-28.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="13.125"
inkscape:cx="32.038095"
inkscape:cy="32"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<circle
fill="#ffd983"
cx="-18"
cy="-18"
r="18"
id="circle1"
transform="scale(-1)" />
<path
fill="#66757f"
d="M 36,18 C 36,8.059 27.941,0 18,0 8.8,2.6 3,9.7 3,18 c 0,8.3 5.8,15.4 15,18 9.941,0 18,-8.059 18,-18 z"
id="path1" />
<circle
cx="-25.5"
cy="-27.5"
r="3.5"
fill="#5b6876"
id="circle2"
transform="scale(-1)" />
<circle
cx="-16"
cy="-20"
r="3"
fill="#5b6876"
id="circle3"
transform="scale(-1)" />
<circle
cx="-14.5"
cy="-8.5"
r="3.5"
fill="#5b6876"
id="circle4"
transform="scale(-1)" />
<circle
cx="-15"
cy="-30"
r="2"
fill="#5b6876"
id="circle5"
transform="scale(-1)" />
<circle
cx="-33"
cy="-18"
r="1"
fill="#5b6876"
id="circle6"
transform="scale(-1)" />
<circle
cx="-7.125"
cy="-27"
r="1"
fill="#5b6876"
id="circle7"
transform="scale(-1)" />
<circle
cx="-21"
cy="-5"
r="1"
fill="#5b6876"
id="circle8"
transform="scale(-1)" />
<circle
cx="-5.125"
cy="-17"
r="2"
fill="#5b6876"
id="circle9"
transform="scale(-1)" />
<circle
cx="-26"
cy="-13"
r="2"
fill="#5b6876"
id="circle10"
transform="scale(-1)" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@@ -0,0 +1,14 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F"
d="M0 18c0 9.941 8.059 18 18 18 .295 0 .58-.029.87-.043C24.761 33.393 29 26.332 29 18 29 9.669 24.761 2.607 18.87.044 18.58.03 18.295 0 18 0 8.059 0 0 8.059 0 18z" />
<circle fill="#5B6876" cx="10.5" cy="8.5" r="3.5" />
<circle fill="#5B6876" cx="20" cy="16" r="3" />
<circle fill="#5B6876" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#5B6876" cx="21" cy="6" r="2" />
<circle fill="#5B6876" cx="3" cy="18" r="1" />
<circle fill="#FFCC4D" cx="30" cy="9" r="1" />
<circle fill="#5B6876" cx="15" cy="31" r="1" />
<circle fill="#FFCC4D" cx="32" cy="19" r="2" />
<circle fill="#5B6876" cx="10" cy="23" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 819 B

@@ -0,0 +1,15 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18 5.4-2.6 9-9.7 9-18S23.4 2.6 18 0C8.059 0 0 8.059 0 18z" />
<g fill="#5B6876">
<circle cx="10.5" cy="8.5" r="3.5" />
<circle cx="20" cy="16" r="3" />
<circle cx="21.5" cy="27.5" r="3.5" />
<circle cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" />
<circle fill="#FFCC4D" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" />
<circle fill="#FFCC4D" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 676 B

@@ -0,0 +1,12 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18 4.2-2.6 7-9.7 7-18S22.2 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#5b6876" />
<circle cx="20" cy="16" r="3" fill="#5b6876" />
<circle cx="19.25" cy="27.5" r="3.5" fill="#5b6876" />
<circle cx="19.875" cy="6" r="2" fill="#5b6876" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#5b6876" />
</svg>

After

Width:  |  Height:  |  Size: 678 B

@@ -0,0 +1,12 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18 3-2.6 5-9.7 5-18S21 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#5b6876" />
<circle cx="20" cy="16" r="3" fill="#5b6876" />
<circle cx="19.586" cy="27.086" r="2" fill="#5b6876" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#5b6876" />
</svg>

After

Width:  |  Height:  |  Size: 672 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18 1.5-2.6 2.5-9.7 2.5-18S19.5 2.6 18 0C8.059 0 0 8.059 0 18z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#5b6876" />
<circle cx="17.3" cy="17.714" r="3" fill="#5b6876" />
<circle fill="#ffcc4d" cx="23.75" cy="27.5" r="3.5" />
<circle fill="#ffcc4d" cx="22.125" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle cx="15" cy="31" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#5b6876" />
</svg>

After

Width:  |  Height:  |  Size: 739 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<path fill="#FFD983" d="M18 0v36c9.941 0 18-8.059 18-18S27.941 0 18 0z" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18V0C8.059 0 0 8.059 0 18z" />
<circle fill="#FFCC4D" cx="25.5" cy="8.5" r="3.5" />
<circle fill="#5B6876" cx="12" cy="16" r="3" />
<circle fill="#5B6876" cx="13.5" cy="27.5" r="3.5" />
<circle fill="#5B6876" cx="15" cy="6" r="2" />
<circle fill="#FFCC4D" cx="33" cy="18" r="1" />
<circle fill="#5B6876" cx="6" cy="9" r="1" />
<circle fill="#FFCC4D" cx="21" cy="31" r="1" />
<circle fill="#5B6876" cx="4" cy="19" r="2" />
<circle fill="#FFCC4D" cx="26" cy="23" r="2" />
</svg>

After

Width:  |  Height:  |  Size: 729 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<circle fill="#FFD983" cx="18" cy="18" r="18" />
<path fill="#66757F" d="M0 18c0 9.941 8.059 18 18 18-1.5-2.6-3-9.7-3-18s1.5-15.4 3-18C8.059 0 0 8.059 0 18z" />
<circle cx="10.5" cy="8.5" r="3.5" fill="#5b6876" />
<circle fill="#ffcc4d" cx="20" cy="16" r="3" />
<circle fill="#ffcc4d" cx="21.5" cy="27.5" r="3.5" />
<circle fill="#ffcc4d" cx="21" cy="6" r="2" />
<circle cx="3" cy="18" r="1" fill="#5b6876" />
<circle fill="#ffcc4d" cx="30" cy="9" r="1" />
<circle fill="#ffcc4d" cx="19.714" cy="34.043" r="1" />
<circle fill="#ffcc4d" cx="32" cy="19" r="2" />
<circle cx="10" cy="23" r="2" fill="#5b6876" />
</svg>

After

Width:  |  Height:  |  Size: 732 B

@@ -0,0 +1,13 @@
<svg height="64" width="64" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffd983" d="M36 18H0C0 8.059 8.059 0 18 0s18 8.059 18 18z" />
<path fill="#66757f" d="M18 36C8.059 36 0 27.941 0 18h36c0 9.941-8.059 18-18 18z" />
<circle fill="#ffcc4d" cx="10.5" cy="-27.5" r="3.5" transform="rotate(90)" />
<circle fill="#5b6876" cx="24" cy="-20" r="3" transform="rotate(90)" />
<circle fill="#5b6876" cx="22.5" cy="-8.5" r="3.5" transform="rotate(90)" />
<circle fill="#5b6876" cx="21" cy="-30" r="2" transform="rotate(90)" />
<circle fill="#ffcc4d" cx="3" cy="-18" r="1" transform="rotate(90)" />
<circle fill="#5b6876" cx="30" cy="-27" r="1" transform="rotate(90)" />
<circle fill="#ffcc4d" cx="15" cy="-5" r="1" transform="rotate(90)" />
<circle fill="#5b6876" cx="32" cy="-17" r="2" transform="rotate(90)" />
<circle fill="#ffcc4d" cx="10" cy="-13" r="2" transform="rotate(90)" />
</svg>

After

Width:  |  Height:  |  Size: 939 B