* @author Michael Wodniok - Initial contribution
* @author Andrew Fiddian-Green - Support for Command Tags embedded in the Event description
* @author Michael Wodniok - Added last_update-channel and additional needed handling of it
+ * @author Michael Wodniok - Changed calculation of Future for refresh of channels
*/
@NonNullByDefault
public class ICalendarHandler extends BaseBridgeHandler implements CalendarUpdateListener {
return;
}
final Instant now = Instant.now();
+ Instant nextRegularUpdate = null;
if (currentCalendar.isEventPresent(now)) {
final Event currentEvent = currentCalendar.getCurrentEvent(now);
if (currentEvent == null) {
"Could not schedule next update of states, due to unexpected behaviour of calendar implementation.");
return;
}
+ nextRegularUpdate = currentEvent.end;
+ }
+
+ final Event nextEvent = currentCalendar.getNextEvent(now);
+ final ICalendarConfiguration currentConfig = this.configuration;
+ if (currentConfig == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Something is broken, the configuration is not available.");
+ return;
+ }
+ if (nextEvent != null) {
+ if (nextRegularUpdate == null || nextEvent.start.isBefore(nextRegularUpdate)) {
+ nextRegularUpdate = nextEvent.start;
+ }
+ }
+
+ if (nextRegularUpdate != null) {
updateJobFuture = scheduler.schedule(() -> {
ICalendarHandler.this.updateStates();
ICalendarHandler.this.rescheduleCalendarStateUpdate();
- }, currentEvent.end.getEpochSecond() - now.getEpochSecond(), TimeUnit.SECONDS);
- logger.debug("Scheduled update in {} seconds", currentEvent.end.getEpochSecond() - now.getEpochSecond());
+ }, nextRegularUpdate.getEpochSecond() - now.getEpochSecond(), TimeUnit.SECONDS);
+ logger.debug("Scheduled update in {} seconds", nextRegularUpdate.getEpochSecond() - now.getEpochSecond());
} else {
- final Event nextEvent = currentCalendar.getNextEvent(now);
- final ICalendarConfiguration currentConfig = this.configuration;
- if (currentConfig == null) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "Something is broken, the configuration is not available.");
- return;
- }
- if (nextEvent == null) {
- updateJobFuture = scheduler.schedule(() -> {
- ICalendarHandler.this.rescheduleCalendarStateUpdate();
- }, 1L, TimeUnit.DAYS);
- logger.debug("Scheduled reschedule in 1 day");
- } else {
- updateJobFuture = scheduler.schedule(() -> {
- ICalendarHandler.this.updateStates();
- ICalendarHandler.this.rescheduleCalendarStateUpdate();
- }, nextEvent.start.getEpochSecond() - now.getEpochSecond(), TimeUnit.SECONDS);
- logger.debug("Scheduled update in {} seconds", nextEvent.start.getEpochSecond() - now.getEpochSecond());
-
- }
+ updateJobFuture = scheduler.schedule(() -> {
+ ICalendarHandler.this.rescheduleCalendarStateUpdate();
+ }, 1L, TimeUnit.DAYS);
+ logger.debug("Scheduled reschedule in 1 day");
}
}
* @author Andrew Fiddian-Green - Methods getJustBegunEvents() & getJustEndedEvents()
* @author Michael Wodniok - Extension for filtered events
* @author Michael Wodniok - Added logic for events moved with "RECURRENCE-ID" (issue 9647)
+ * @author Michael Wodniok - Extended logic for defined behavior with parallel current events
+ * (issue 10808)
*/
@NonNullByDefault
class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
final List<VEvent> positiveEvents = new ArrayList<VEvent>();
classifyEvents(positiveEvents, negativeEvents);
+ VEventWPeriod earliestEndingEvent = null;
+
for (final VEvent currentEvent : positiveEvents) {
final DateIterator startDates = this.getRecurredEventDateIterator(currentEvent);
final Duration duration = getEventLength(currentEvent);
if (startInstant.isBefore(instant) && endInstant.isAfter(instant)) {
final Uid eventUid = currentEvent.getUid();
if (eventUid == null || !isCounteredBy(startInstant, eventUid, negativeEvents)) {
- return new VEventWPeriod(currentEvent, startInstant, endInstant);
+ if (earliestEndingEvent == null || endInstant.isBefore(earliestEndingEvent.end)) {
+ earliestEndingEvent = new VEventWPeriod(currentEvent, startInstant, endInstant);
+ }
}
}
if (startInstant.isAfter(instant.plus(duration))) {
}
}
- return null;
+ return earliestEndingEvent;
}
/**
* @author Michael Wodniok - Initial contribution.
* @author Andrew Fiddian-Green - Tests for Command Tag code
* @author Michael Wodniok - Extended Tests for filtered Events
- *
+ * @author Michael Wodniok - Extended Test for parallel current events
*/
public class BiweeklyPresentableCalendarTest {
private AbstractPresentableCalendar calendar;
private AbstractPresentableCalendar calendar2;
private AbstractPresentableCalendar calendar3;
private AbstractPresentableCalendar calendar_issue9647;
+ private AbstractPresentableCalendar calendar_issue10808;
@BeforeEach
public void setUp() throws IOException, CalendarException {
calendar3 = new BiweeklyPresentableCalendar(new FileInputStream("src/test/resources/test3.ics"));
calendar_issue9647 = new BiweeklyPresentableCalendar(
new FileInputStream("src/test/resources/test-issue9647.ics"));
+ calendar_issue10808 = new BiweeklyPresentableCalendar(
+ new FileInputStream("src/test/resources/test-issue10808.ics"));
}
/**
Event nonExistingEvent = calendar.getCurrentEvent(Instant.parse("2019-09-09T09:07:00Z"));
assertNull(nonExistingEvent);
+
+ Event currentEvent2 = calendar_issue10808.getCurrentEvent(Instant.parse("2021-06-05T17:10:05Z"));
+ assertNotNull(currentEvent2);
+ assertTrue("Test event 1".contentEquals(currentEvent2.title));
+
+ Event currentEvent3 = calendar_issue10808.getCurrentEvent(Instant.parse("2021-06-05T17:13:05Z"));
+ assertNotNull(currentEvent3);
+ assertTrue("Test event 2".contentEquals(currentEvent3.title));
+
+ Event currentEvent4 = calendar_issue10808.getCurrentEvent(Instant.parse("2021-06-05T17:18:05Z"));
+ assertNotNull(currentEvent4);
+ assertTrue("Test event 1".contentEquals(currentEvent4.title));
}
/**
--- /dev/null
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//SabreDAV//SabreDAV//EN
+X-WR-CALNAME:Test
+X-APPLE-CALENDAR-COLOR:#499AA2
+REFRESH-INTERVAL;VALUE=DURATION:PT4H
+X-PUBLISHED-TTL:PT4H
+BEGIN:VTIMEZONE
+TZID:Europe/Paris
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0200
+TZNAME:CEST
+DTSTART:19700329T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+TZOFFSETTO:+0100
+TZNAME:CET
+DTSTART:19701025T030000
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20210107T155730Z
+DTSTAMP:20210107T160226Z
+LAST-MODIFIED:20210605T163408Z
+SEQUENCE:4
+UID:bd676ec9-0cdb-4c8d-a6dd-9e3fcf77a45f
+DTSTART;TZID=Europe/Paris:20210605T191000
+DTEND;TZID=Europe/Paris:20210605T192000
+SUMMARY:Test event 1
+DESCRIPTION:BEGIN:DemoString:EHLO1
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20210107T160405Z
+DTSTAMP:20210107T160405Z
+LAST-MODIFIED:20210605T163512Z
+UID:7d1ecca5-1ddd-4932-b096-d034c8d7f5aa
+DTSTART;TZID=Europe/Paris:20210605T191200
+DTEND;TZID=Europe/Paris:20210605T191600
+SUMMARY:Test event 2
+DESCRIPTION:BEGIN:DemoString:EHLO2
+END:VEVENT
+END:VCALENDAR