diff --git a/bundles/org.openhab.binding.icalendar/pom.xml b/bundles/org.openhab.binding.icalendar/pom.xml index 0c12afd983a6..768f07224abf 100644 --- a/bundles/org.openhab.binding.icalendar/pom.xml +++ b/bundles/org.openhab.binding.icalendar/pom.xml @@ -17,12 +17,12 @@ net.sf.biweekly biweekly - 0.6.4 + 0.6.6 compile com.fasterxml.jackson.core - * + jackson-databind @@ -39,12 +39,6 @@ ${jackson.version} compile - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - compile - com.fasterxml.jackson.core jackson-databind diff --git a/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendar.java b/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendar.java index 61d2f9a25184..d38e9e4454ef 100644 --- a/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendar.java +++ b/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendar.java @@ -88,55 +88,14 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar { @Override public List getJustBegunEvents(Instant frameBegin, Instant frameEnd) { - final List eventList = new ArrayList<>(); - // process all the events in the iCalendar - for (final VEvent event : usedCalendar.getEvents()) { - // iterate over all begin dates - final DateIterator begDates = getRecurredEventDateIterator(event); - while (begDates.hasNext()) { - final Instant begInst = begDates.next().toInstant(); - if (begInst.isBefore(frameBegin)) { - continue; - } else if (begInst.isAfter(frameEnd)) { - break; - } - // fall through => means we are within the time frame - Duration duration = getEventLength(event); - if (duration == null) { - duration = Duration.ofMinutes(1); - } - eventList.add(new VEventWPeriod(event, begInst, begInst.plus(duration)).toEvent()); - break; - } - } - return eventList; + return this.getVEventWPeriodsBetween(frameBegin, frameEnd, 0).stream().map(e -> e.toEvent()) + .collect(Collectors.toList()); } @Override public List getJustEndedEvents(Instant frameBegin, Instant frameEnd) { - final List eventList = new ArrayList<>(); - // process all the events in the iCalendar - for (final VEvent event : usedCalendar.getEvents()) { - final Duration duration = getEventLength(event); - if (duration == null) { - continue; - } - // iterate over all begin dates - final DateIterator begDates = getRecurredEventDateIterator(event); - while (begDates.hasNext()) { - final Instant begInst = begDates.next().toInstant(); - final Instant endInst = begInst.plus(duration); - if (endInst.isBefore(frameBegin)) { - continue; - } else if (endInst.isAfter(frameEnd)) { - break; - } - // fall through => means we are within the time frame - eventList.add(new VEventWPeriod(event, begInst, endInst).toEvent()); - break; - } - } - return eventList; + return this.getVEventWPeriodsBetween(frameBegin, frameEnd, 0, true).stream().map(e -> e.toEvent()) + .collect(Collectors.toList()); } @Override @@ -247,6 +206,20 @@ public List getFilteredEventsBetween(Instant begin, Instant end, @Nullabl * @return All events which begin in the time frame. */ private List getVEventWPeriodsBetween(Instant frameBegin, Instant frameEnd, int maximumPerSeries) { + return this.getVEventWPeriodsBetween(frameBegin, frameEnd, maximumPerSeries, false); + } + + /** + * Finds events which begin in the given frame by end time and date + * + * @param frameBegin Begin of the frame where to search events. + * @param frameEnd End of the time frame where to search events. The Instant is inclusive when searchByEnd is true. + * @param maximumPerSeries Limit the results per series. Set to 0 for no limit. + * @param searchByEnd Whether to search by begin of the event or by end. + * @return All events which begin in the time frame. + */ + private List getVEventWPeriodsBetween(Instant frameBegin, Instant frameEnd, int maximumPerSeries, + boolean searchByEnd) { final List positiveEvents = new ArrayList<>(); final List negativeEvents = new ArrayList<>(); classifyEvents(positiveEvents, negativeEvents); @@ -254,16 +227,22 @@ private List getVEventWPeriodsBetween(Instant frameBegin, Instant final List eventList = new ArrayList<>(); for (final VEvent positiveEvent : positiveEvents) { final DateIterator positiveBeginDates = getRecurredEventDateIterator(positiveEvent); - positiveBeginDates.advanceTo(Date.from(frameBegin)); + Duration duration = getEventLength(positiveEvent); + if (duration == null) { + duration = Duration.ZERO; + } + positiveBeginDates.advanceTo(Date.from(frameBegin.minus(searchByEnd ? duration : Duration.ZERO))); int foundInSeries = 0; while (positiveBeginDates.hasNext()) { final Instant begInst = positiveBeginDates.next().toInstant(); - if (begInst.isAfter(frameEnd) || begInst.equals(frameEnd)) { + if ((!searchByEnd && (begInst.isAfter(frameEnd) || begInst.equals(frameEnd))) + || (searchByEnd && begInst.plus(duration).isAfter(frameEnd))) { break; } - Duration duration = getEventLength(positiveEvent); - if (duration == null) { - duration = Duration.ZERO; + // biweekly is not as precise as java.time. An exact check is required. + if ((!searchByEnd && begInst.isBefore(frameBegin)) + || (searchByEnd && begInst.plus(duration).isBefore(frameBegin))) { + continue; } final VEventWPeriod resultingVEWP = new VEventWPeriod(positiveEvent, begInst, begInst.plus(duration)); diff --git a/bundles/org.openhab.binding.icalendar/src/test/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendarTest.java b/bundles/org.openhab.binding.icalendar/src/test/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendarTest.java index 7c60c4fb68f3..c4857336c7da 100644 --- a/bundles/org.openhab.binding.icalendar/src/test/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendarTest.java +++ b/bundles/org.openhab.binding.icalendar/src/test/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendarTest.java @@ -49,6 +49,7 @@ public class BiweeklyPresentableCalendarTest { private AbstractPresentableCalendar calendar3; private AbstractPresentableCalendar calendar_issue9647; private AbstractPresentableCalendar calendar_issue10808; + private AbstractPresentableCalendar calendar_issue11084; @BeforeEach public void setUp() throws IOException, CalendarException { @@ -59,6 +60,8 @@ public void setUp() throws IOException, CalendarException { new FileInputStream("src/test/resources/test-issue9647.ics")); calendar_issue10808 = new BiweeklyPresentableCalendar( new FileInputStream("src/test/resources/test-issue10808.ics")); + calendar_issue11084 = new BiweeklyPresentableCalendar( + new FileInputStream("src/test/resources/test-issue11084.ics")); } /** @@ -132,6 +135,13 @@ public void testGetCurrentEvent() { Event currentEvent4 = calendar_issue10808.getCurrentEvent(Instant.parse("2021-06-05T17:18:05Z")); assertNotNull(currentEvent4); assertTrue("Test event 1".contentEquals(currentEvent4.title)); + + Event currentEvent5 = calendar_issue11084.getCurrentEvent(Instant.parse("2021-08-16T16:30:05Z")); + assertNull(currentEvent5); + + Event currentEvent6 = calendar_issue11084.getCurrentEvent(Instant.parse("2021-08-16T16:45:05Z")); + assertNotNull(currentEvent6); + assertTrue("TEST_REPEATING_EVENT_3".contentEquals(currentEvent6.title)); } /** @@ -563,6 +573,17 @@ public void testCommandTagCode() { cmd7 = cmdTags.get(7).getCommand(); assertNotNull(cmd7); assertEquals(DecimalType.class, cmd7.getClass()); + + // issue 11084: Command tags from moved events are also executed + List events2 = calendar_issue11084.getJustBegunEvents(Instant.parse("2021-08-16T16:29:55Z"), + Instant.parse("2021-08-16T17:00:05Z")); + assertEquals(1, events2.size()); + assertEquals(Instant.parse("2021-08-16T16:45:00Z"), events2.get(0).start); + + List events3 = calendar_issue11084.getJustEndedEvents(Instant.parse("2021-08-16T16:29:55Z"), + Instant.parse("2021-08-16T17:00:05Z")); + assertEquals(1, events3.size()); + assertEquals(Instant.parse("2021-08-16T17:00:00Z"), events3.get(0).end); } @SuppressWarnings("null") @@ -621,5 +642,9 @@ public void testGetFilteredEventsBetween() { LocalDate.parse("2021-01-04").atStartOfDay(ZoneId.systemDefault()).toInstant(), LocalDate.parse("2021-01-05").atStartOfDay(ZoneId.systemDefault()).toInstant(), null, 3); assertArrayEquals(expectedFilteredEvents8, realFilteredEvents8.toArray(new Event[] {})); + + List realFilteredEvents9 = calendar_issue11084.getFilteredEventsBetween( + Instant.parse("2021-08-16T16:45:00.123456Z"), Instant.parse("2021-08-16T16:46:00.768643Z"), null, 3); + assertEquals(0, realFilteredEvents9.size()); } } diff --git a/bundles/org.openhab.binding.icalendar/src/test/resources/test-issue11084.ics b/bundles/org.openhab.binding.icalendar/src/test/resources/test-issue11084.ics new file mode 100644 index 000000000000..38c208b09ea0 --- /dev/null +++ b/bundles/org.openhab.binding.icalendar/src/test/resources/test-issue11084.ics @@ -0,0 +1,56 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:ohtest +X-WR-TIMEZONE:UTC +BEGIN:VTIMEZONE +TZID:Europe/Brussels +X-LIC-LOCATION:Europe/Brussels +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 +DTSTART;TZID=Europe/Brussels:20210816T184500 +DTEND;TZID=Europe/Brussels:20210816T190000 +DTSTAMP:20210816T174418Z +UID:pseudo7346893o7r8328zheh@google.com +RECURRENCE-ID;TZID=Europe/Brussels:20210816T183000 +CREATED:20210816T161602Z +DESCRIPTION:BEGIN:E_Test_Cal:ON +LAST-MODIFIED:20210816T162009Z +LOCATION: +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:TEST_REPEATING_EVENT_3 +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Brussels:20210816T183000 +DTEND;TZID=Europe/Brussels:20210816T184500 +RRULE:FREQ=DAILY +DTSTAMP:20210816T174418Z +UID:pseudo7346893o7r8328zheh@google.com +CREATED:20210816T161602Z +DESCRIPTION:BEGIN:E_Test_Cal:ON +LAST-MODIFIED:20210816T162009Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:TEST_REPEATING_EVENT_3 +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR