diff --git a/pom.xml b/pom.xml
index 03a117f..5c7e521 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,12 @@
1.2
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
diff --git a/src/main/java/de/ph87/homeautomation/Config.java b/src/main/java/de/ph87/homeautomation/Config.java
index 0cd7502..959b847 100644
--- a/src/main/java/de/ph87/homeautomation/Config.java
+++ b/src/main/java/de/ph87/homeautomation/Config.java
@@ -13,8 +13,6 @@ public class Config {
private double longitude = 6.9645334;
- private String timezone = "Europe/Berlin";
-
private boolean insertDemoData = false;
}
diff --git a/src/main/java/de/ph87/homeautomation/schedule/ScheduleCalculator.java b/src/main/java/de/ph87/homeautomation/schedule/ScheduleCalculator.java
index 3a429f1..5d30a36 100644
--- a/src/main/java/de/ph87/homeautomation/schedule/ScheduleCalculator.java
+++ b/src/main/java/de/ph87/homeautomation/schedule/ScheduleCalculator.java
@@ -11,6 +11,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.time.LocalDate;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Optional;
@@ -55,12 +57,12 @@ public class ScheduleCalculator {
entry.setNextClearTimestamp(null);
return;
}
- ZonedDateTime midnight = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
- ZonedDateTime next = calculateEntryForDay(entry, midnight);
+ LocalDate day = now.toLocalDate();
+ ZonedDateTime next = calculateEntryForDay(entry, day);
while (next != null && (!next.isAfter(now) || !isAfterLast(entry, next) || !isWeekdayEnabled(entry, next))) {
log.debug(" -- skipping: next={}", next);
- midnight = midnight.plusDays(1);
- next = calculateEntryForDay(entry, midnight);
+ day = day.plusDays(1);
+ next = calculateEntryForDay(entry, day);
}
log.debug(" => {}", next);
entry.setNextClearTimestamp(next);
@@ -70,14 +72,14 @@ public class ScheduleCalculator {
return entry.getLastClearTimestamp() == null || next.isAfter(entry.getLastClearTimestamp());
}
- private ZonedDateTime calculateEntryForDay(final ScheduleEntry entry, final ZonedDateTime midnight) {
+ private ZonedDateTime calculateEntryForDay(final ScheduleEntry entry, final LocalDate day) {
switch (entry.getType()) {
case TIME:
- return midnight.withHour(entry.getHour()).withMinute(entry.getMinute()).withSecond(entry.getSecond());
+ return day.atStartOfDay().atZone(ZoneId.systemDefault()).withHour(entry.getHour()).withMinute(entry.getMinute()).withSecond(entry.getSecond());
case SUNRISE:
case SUNSET:
final boolean sunrise = entry.getType() == ScheduleEntryType.SUNRISE;
- return astroCalculator.forDay(midnight, sunrise, entry.getZenith());
+ return astroCalculator.forDay(day, sunrise, entry.getZenith()).withLaterOffsetAtOverlap();
default:
log.error("AstroEvent not implemented: {}", entry.getType());
break;
diff --git a/src/main/java/de/ph87/homeautomation/schedule/astro/AstroCalculator.java b/src/main/java/de/ph87/homeautomation/schedule/astro/AstroCalculator.java
index 5bef5cb..b88714a 100644
--- a/src/main/java/de/ph87/homeautomation/schedule/astro/AstroCalculator.java
+++ b/src/main/java/de/ph87/homeautomation/schedule/astro/AstroCalculator.java
@@ -8,10 +8,12 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.GregorianCalendar;
+import java.util.TimeZone;
@Service
@RequiredArgsConstructor
@@ -19,25 +21,15 @@ public class AstroCalculator {
private final Config config;
- public ZonedDateTime next(final ZonedDateTime now, final boolean sunrise, final double zenith) {
- ZonedDateTime day = now.truncatedTo(ChronoUnit.DAYS);
- ZonedDateTime next;
- do {
- next = forDay(day, sunrise, zenith);
- day = day.plusDays(1);
- } while (next != null && !next.isAfter(now));
- return next;
- }
-
- public ZonedDateTime forDay(final ZonedDateTime midnight, final boolean sunrise, final double zenith) {
+ public ZonedDateTime forDay(final LocalDate day, final boolean sunrise, final double zenith) {
final Location location = new Location(config.getLatitude(), config.getLongitude());
- final SolarEventCalculator calculator = new SolarEventCalculator(location, config.getTimezone());
- final Calendar calendar = GregorianCalendar.from(midnight);
+ final SolarEventCalculator calculator = new SolarEventCalculator(location, TimeZone.getTimeZone("UTC"));
+ final Calendar calendar = GregorianCalendar.from(day.atStartOfDay(ZoneId.of("UTC")));
final Calendar nextCalendar = forDay(calculator, sunrise, new Zenith(zenith), calendar);
if (nextCalendar == null) {
return null;
}
- return ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextCalendar.getTimeInMillis()), midnight.getZone());
+ return ZonedDateTime.ofInstant(Instant.ofEpochMilli(nextCalendar.getTimeInMillis()), ZoneId.systemDefault());
}
private Calendar forDay(final SolarEventCalculator calculator, final boolean sunrise, final Zenith solarZenith, final Calendar calendar) {
diff --git a/src/test/java/de/ph87/homeautomation/schedule/ScheduleCalculatorDaylightSavingTimeTransitionTest.java b/src/test/java/de/ph87/homeautomation/schedule/ScheduleCalculatorDaylightSavingTimeTransitionTest.java
new file mode 100644
index 0000000..bbfbbe1
--- /dev/null
+++ b/src/test/java/de/ph87/homeautomation/schedule/ScheduleCalculatorDaylightSavingTimeTransitionTest.java
@@ -0,0 +1,70 @@
+package de.ph87.homeautomation.schedule;
+
+import de.ph87.homeautomation.Config;
+import de.ph87.homeautomation.schedule.astro.AstroCalculator;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Slf4j
+class ScheduleCalculatorDaylightSavingTimeTransitionTest {
+
+ private static final ZonedDateTime DAY_OF_DST_SET_BACK = ZonedDateTime.of(2024, 10, 27, 0, 0, 0, 0, ZoneId.of("Europe/Berlin"));
+
+ private static final AstroCalculator astroCalculator = new AstroCalculator(new Config());
+
+ @Test
+ void withHour() {
+ final ZonedDateTime morning = DAY_OF_DST_SET_BACK.withHour(6);
+ assertEquals(6, morning.getHour());
+ assertEquals(0, morning.getMinute());
+ assertEquals(0, morning.getSecond());
+ assertEquals(0, morning.getNano());
+ assertEquals(3600, morning.getOffset().getTotalSeconds());
+ }
+
+ @Test
+ void sunrise_dstSetBack() {
+ final ZonedDateTime morning = astroCalculator.forDay(DAY_OF_DST_SET_BACK.toLocalDate(), true, 90);
+ assertEquals(7, morning.getHour());
+ assertEquals(19, morning.getMinute());
+ assertEquals(0, morning.getSecond());
+ assertEquals(0, morning.getNano());
+ assertEquals(3600, morning.getOffset().getTotalSeconds());
+ }
+
+ @Test
+ void sunset_dstSetBack() {
+ final ZonedDateTime morning = astroCalculator.forDay(DAY_OF_DST_SET_BACK.toLocalDate(), false, 90);
+ assertEquals(17, morning.getHour());
+ assertEquals(13, morning.getMinute());
+ assertEquals(0, morning.getSecond());
+ assertEquals(0, morning.getNano());
+ assertEquals(3600, morning.getOffset().getTotalSeconds());
+ }
+
+ @Test
+ void sunrise_noDstChange() {
+ final ZonedDateTime morning = astroCalculator.forDay(DAY_OF_DST_SET_BACK.minusDays(1).toLocalDate(), true, 90);
+ assertEquals(8, morning.getHour());
+ assertEquals(17, morning.getMinute());
+ assertEquals(0, morning.getSecond());
+ assertEquals(0, morning.getNano());
+ assertEquals(7200, morning.getOffset().getTotalSeconds());
+ }
+
+ @Test
+ void sunset_noDstChange() {
+ final ZonedDateTime morning = astroCalculator.forDay(DAY_OF_DST_SET_BACK.minusDays(1).toLocalDate(), false, 90);
+ assertEquals(18, morning.getHour());
+ assertEquals(14, morning.getMinute());
+ assertEquals(0, morning.getSecond());
+ assertEquals(0, morning.getNano());
+ assertEquals(7200, morning.getOffset().getTotalSeconds());
+ }
+
+}
\ No newline at end of file