AstroCalculator DST change FIX

This commit is contained in:
Patrick Haßel 2024-10-28 10:41:21 +01:00
parent bef7cc4a6e
commit 277e321480
5 changed files with 92 additions and 24 deletions

View File

@ -66,6 +66,12 @@
<version>1.2</version> <version>1.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

View File

@ -13,8 +13,6 @@ public class Config {
private double longitude = 6.9645334; private double longitude = 6.9645334;
private String timezone = "Europe/Berlin";
private boolean insertDemoData = false; private boolean insertDemoData = false;
} }

View File

@ -11,6 +11,8 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Comparator; import java.util.Comparator;
import java.util.Optional; import java.util.Optional;
@ -55,12 +57,12 @@ public class ScheduleCalculator {
entry.setNextClearTimestamp(null); entry.setNextClearTimestamp(null);
return; return;
} }
ZonedDateTime midnight = now.withHour(0).withMinute(0).withSecond(0).withNano(0); LocalDate day = now.toLocalDate();
ZonedDateTime next = calculateEntryForDay(entry, midnight); ZonedDateTime next = calculateEntryForDay(entry, day);
while (next != null && (!next.isAfter(now) || !isAfterLast(entry, next) || !isWeekdayEnabled(entry, next))) { while (next != null && (!next.isAfter(now) || !isAfterLast(entry, next) || !isWeekdayEnabled(entry, next))) {
log.debug(" -- skipping: next={}", next); log.debug(" -- skipping: next={}", next);
midnight = midnight.plusDays(1); day = day.plusDays(1);
next = calculateEntryForDay(entry, midnight); next = calculateEntryForDay(entry, day);
} }
log.debug(" => {}", next); log.debug(" => {}", next);
entry.setNextClearTimestamp(next); entry.setNextClearTimestamp(next);
@ -70,14 +72,14 @@ public class ScheduleCalculator {
return entry.getLastClearTimestamp() == null || next.isAfter(entry.getLastClearTimestamp()); 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()) { switch (entry.getType()) {
case TIME: 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 SUNRISE:
case SUNSET: case SUNSET:
final boolean sunrise = entry.getType() == ScheduleEntryType.SUNRISE; final boolean sunrise = entry.getType() == ScheduleEntryType.SUNRISE;
return astroCalculator.forDay(midnight, sunrise, entry.getZenith()); return astroCalculator.forDay(day, sunrise, entry.getZenith()).withLaterOffsetAtOverlap();
default: default:
log.error("AstroEvent not implemented: {}", entry.getType()); log.error("AstroEvent not implemented: {}", entry.getType());
break; break;

View File

@ -8,10 +8,12 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Calendar; import java.util.Calendar;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.TimeZone;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@ -19,25 +21,15 @@ public class AstroCalculator {
private final Config config; private final Config config;
public ZonedDateTime next(final ZonedDateTime now, final boolean sunrise, final double zenith) { public ZonedDateTime forDay(final LocalDate day, 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) {
final Location location = new Location(config.getLatitude(), config.getLongitude()); final Location location = new Location(config.getLatitude(), config.getLongitude());
final SolarEventCalculator calculator = new SolarEventCalculator(location, config.getTimezone()); final SolarEventCalculator calculator = new SolarEventCalculator(location, TimeZone.getTimeZone("UTC"));
final Calendar calendar = GregorianCalendar.from(midnight); final Calendar calendar = GregorianCalendar.from(day.atStartOfDay(ZoneId.of("UTC")));
final Calendar nextCalendar = forDay(calculator, sunrise, new Zenith(zenith), calendar); final Calendar nextCalendar = forDay(calculator, sunrise, new Zenith(zenith), calendar);
if (nextCalendar == null) { if (nextCalendar == null) {
return 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) { private Calendar forDay(final SolarEventCalculator calculator, final boolean sunrise, final Zenith solarZenith, final Calendar calendar) {

View File

@ -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());
}
}