Fix range offset when combined with forceEvent (#19717)

The desired behavior isn't well defined, but this fixes a bug where any offset would effectively be ignored if "force" is in effect. 

Signed-off-by: Ravi Nadahar <nadahar@rediffmail.com>
This commit is contained in:
Nadahar
2025-11-27 21:12:16 +01:00
committed by GitHub
parent 201c457b50
commit 24cb9e9f4e
3 changed files with 137 additions and 20 deletions
@@ -136,11 +136,17 @@ public interface Job extends SchedulerRunnable, Runnable {
Calendar start = range.getStart();
Calendar end = range.getEnd();
if (config.forceEvent && start == null) {
start = getAdjustedEarliest(Calendar.getInstance(zone, locale), config);
}
if (config.forceEvent && end == null) {
end = getAdjustedLatest(Calendar.getInstance(zone, locale), config);
if (config.forceEvent) {
Calendar reference = start != null ? start : end;
if (reference == null) {
reference = Calendar.getInstance(zone, locale);
}
if (start == null) {
start = getAdjustedEarliest(truncateToMidnight(reference), config);
}
if (end == null) {
end = getAdjustedLatest(endOfDayDate(reference), config);
}
}
// depending on the location and configuration you might not have a valid range for day/night, so skip the
@@ -213,11 +213,15 @@ public class DateTimeUtils {
}
public static Calendar getAdjustedEarliest(Calendar cal, AstroChannelConfig config) {
return config.earliest == null ? cal : adjustTime(cal, getMinutesFromTime(config.earliest));
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(cal, minutes) : cal;
}
public static Calendar getAdjustedLatest(Calendar cal, AstroChannelConfig config) {
return config.latest == null ? cal : adjustTime(cal, getMinutesFromTime(config.latest));
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;
}
/**
@@ -230,20 +234,45 @@ public class DateTimeUtils {
cCal.add(Calendar.MINUTE, config.offset);
}
Calendar cEarliest = getAdjustedEarliest(cal, config);
if (cCal.before(cEarliest)) {
return cEarliest;
int minutes = getMinutesFromTime(config.earliest);
Calendar 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
Calendar cEarliest = getAdjustedEarliest(cCal, config);
if (cCal.before(cEarliest)) {
return cEarliest;
}
} else {
// Previous or next day
if (actual.before(threshold)) {
return getAdjustedEarliest(threshold, config);
}
}
}
Calendar cLatest = getAdjustedLatest(cal, config);
if (cCal.after(cLatest)) {
return cLatest;
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
Calendar cLatest = getAdjustedLatest(cCal, config);
if (cCal.after(cLatest)) {
return cLatest;
}
} else {
// Previous or next day
if (actual.after(threshold)) {
return getAdjustedLatest(threshold, config);
}
}
}
return cCal;
}
private static Calendar adjustTime(Calendar cal, int minutes) {
if (minutes > 0) {
static Calendar adjustTime(Calendar cal, int minutes) {
if (minutes >= 0) {
Calendar cTime = truncateToMidnight(cal);
cTime.add(Calendar.MINUTE, minutes);
return cTime;
@@ -256,12 +285,14 @@ public class DateTimeUtils {
}
/**
* Parses a HH:MM string and returns the minutes.
* Parses a HH:MM string and returns hours and minutes in minutes.
*
* @return The number of minutes from midnight, or {@code -1} if the string couldn't be parsed.
*/
private static int getMinutesFromTime(@Nullable String configTime) {
static int getMinutesFromTime(@Nullable String configTime) {
if (configTime != null) {
String time = configTime.trim();
if (!time.isEmpty()) {
if (!time.isBlank()) {
try {
if (!HHMM_PATTERN.matcher(time).matches()) {
throw new NumberFormatException();
@@ -276,9 +307,8 @@ public class DateTimeUtils {
"Can not parse astro channel configuration '{}' to hour and minutes, use pattern hh:mm, ignoring!",
time);
}
}
}
return 0;
return -1;
}
}
@@ -22,6 +22,7 @@ import java.util.TimeZone;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.astro.internal.calc.SeasonCalc;
import org.openhab.binding.astro.internal.config.AstroChannelConfig;
import org.openhab.binding.astro.internal.model.Season;
/**
@@ -83,6 +84,86 @@ public class DateTimeUtilsTest {
assertEquals(endOfDay, target2);
}
@Test
public void testCreateCalendarForToday() {
Calendar cal = DateTimeUtils.createCalendarForToday(8, 0, TIME_ZONE, Locale.ROOT);
assertEquals(8, cal.get(Calendar.HOUR_OF_DAY));
assertEquals(0, cal.get(Calendar.MINUTE));
assertEquals(0, cal.get(Calendar.SECOND));
assertEquals(0, cal.get(Calendar.MILLISECOND));
cal = DateTimeUtils.createCalendarForToday(22, 59, TIME_ZONE, Locale.ROOT);
assertEquals(22, cal.get(Calendar.HOUR_OF_DAY));
assertEquals(59, cal.get(Calendar.MINUTE));
assertEquals(0, cal.get(Calendar.SECOND));
assertEquals(0, cal.get(Calendar.MILLISECOND));
cal = DateTimeUtils.createCalendarForToday(0, 0, TIME_ZONE, Locale.ROOT);
assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
assertEquals(0, cal.get(Calendar.MINUTE));
assertEquals(0, cal.get(Calendar.SECOND));
assertEquals(0, cal.get(Calendar.MILLISECOND));
}
@Test
public void testAdjustTime() {
assertEquals(JAN_20_2020, DateTimeUtils.adjustTime(JAN_20_2020, 60));
assertNotSame(JAN_20_2020, DateTimeUtils.adjustTime(JAN_20_2020, 60));
assertEquals(JAN_20_2020, DateTimeUtils.adjustTime(JAN_20_2020, -1));
assertSame(JAN_20_2020, DateTimeUtils.adjustTime(JAN_20_2020, -2));
}
@Test
public void testApplyConfig() {
AstroChannelConfig config = new AstroChannelConfig();
assertEquals(JAN_20_2020.getTime(), DateTimeUtils.applyConfig(JAN_20_2020, config).getTime());
assertSame(JAN_20_2020, DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = "00:00";
assertEquals(JAN_20_2020, DateTimeUtils.applyConfig(JAN_20_2020, config));
assertSame(JAN_20_2020, DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = "00:01";
assertEquals(JAN_20_2020, DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = "03:33";
assertEquals(newCalendar(2020, Calendar.JANUARY, 20, 3, 33, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = null;
config.latest = "00:50";
assertEquals(newCalendar(2020, Calendar.JANUARY, 20, 0, 50, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.latest = null;
config.offset = -79;
assertEquals(newCalendar(2020, Calendar.JANUARY, 19, 23, 41, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = "03:33";
assertEquals(newCalendar(2020, Calendar.JANUARY, 20, 3, 33, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = null;
config.latest = "00:50";
assertEquals(newCalendar(2020, Calendar.JANUARY, 19, 23, 41, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.latest = null;
config.offset = 1504;
assertEquals(newCalendar(2020, Calendar.JANUARY, 21, 2, 4, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = "03:33";
assertEquals(newCalendar(2020, Calendar.JANUARY, 21, 2, 4, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.earliest = null;
config.latest = "21:12";
assertEquals(newCalendar(2020, Calendar.JANUARY, 20, 21, 12, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
config.offset = 135;
assertEquals(newCalendar(2020, Calendar.JANUARY, 20, 3, 15, TIME_ZONE),
DateTimeUtils.applyConfig(JAN_20_2020, config));
}
@Test
public void testGetMinutesFromTime() {
assertEquals(-1, DateTimeUtils.getMinutesFromTime(null));
assertEquals(-1, DateTimeUtils.getMinutesFromTime(" "));
assertEquals(-1, DateTimeUtils.getMinutesFromTime("2023"));
assertEquals(1223, DateTimeUtils.getMinutesFromTime("20:23"));
}
private static void assertNextSeason(Calendar expectedSeason, int expectedYear, Calendar date, Season season) {
final Calendar nextSeason = DateTimeUtils.getNext(date, season.getSpring(), season.getSummer(),
season.getAutumn(), season.getWinter());