From 91dc80449bdcbacab9d5072afb8b028bb4c37284 Mon Sep 17 00:00:00 2001 From: Michael Wodniok Date: Mon, 4 Jan 2021 10:53:25 +0100 Subject: [PATCH] [icalendar] Fixed issues with moved events and wrong displayed events (#9678) * [icalendar] Fixed issues with moved events and wrong displayed events * [icalendar] Changed test to use local time in test-calendar of #9647 Fixes #9647. Handling for RFC 5545's RECURRENCE-ID field inside of events and rounded begin (and based on it end) down in subsecond precision. The calendar does not define a timezone. On a machine that uses a different timezone than Europe/Berlin the test failed. As the behavior itself is specified, the test was changed to use local time for its dates. Signed-off-by: Michael Wodniok --- .../internal/handler/EventFilterHandler.java | 3 +- .../logic/BiweeklyPresentableCalendar.java | 49 ++++++-- .../BiweeklyPresentableCalendarTest.java | 20 +++ .../src/test/resources/test-issue9647.ics | 114 ++++++++++++++++++ 4 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 bundles/org.openhab.binding.icalendar/src/test/resources/test-issue9647.ics diff --git a/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/handler/EventFilterHandler.java b/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/handler/EventFilterHandler.java index abc9703b6e..c58cd16f21 100644 --- a/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/handler/EventFilterHandler.java +++ b/bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/handler/EventFilterHandler.java @@ -56,6 +56,7 @@ import org.slf4j.LoggerFactory; * The {@link EventFilterHandler} filters events from a calendar and presents them in a dynamic way. * * @author Michael Wodniok - Initial Contribution + * @author Michael Wodniok - Fixed subsecond search if rounding to unit */ @NonNullByDefault public class EventFilterHandler extends BaseThingHandler implements CalendarUpdateListener { @@ -331,7 +332,7 @@ public class EventFilterHandler extends BaseThingHandler implements CalendarUpda case HOUR: refDT = refDT.with(ChronoField.MINUTE_OF_HOUR, 0); case MINUTE: - refDT = refDT.with(ChronoField.SECOND_OF_MINUTE, 0); + refDT = refDT.with(ChronoField.SECOND_OF_MINUTE, 0).with(ChronoField.NANO_OF_SECOND, 0); } reference = refDT.toInstant(); } 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 0d7dfb0e71..4f6d23edba 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 @@ -34,6 +34,7 @@ import biweekly.component.VEvent; import biweekly.io.TimezoneAssignment; import biweekly.io.TimezoneInfo; import biweekly.io.text.ICalReader; +import biweekly.parameter.Range; import biweekly.property.Comment; import biweekly.property.Contact; import biweekly.property.DateEnd; @@ -41,10 +42,12 @@ import biweekly.property.DateStart; import biweekly.property.Description; import biweekly.property.DurationProperty; import biweekly.property.Location; +import biweekly.property.RecurrenceId; import biweekly.property.Status; import biweekly.property.Summary; import biweekly.property.TextProperty; import biweekly.property.Uid; +import biweekly.util.ICalDate; import biweekly.util.com.google.ical.compat.javautil.DateIterator; /** @@ -55,6 +58,7 @@ import biweekly.util.com.google.ical.compat.javautil.DateIterator; * @author Michael Wodniok - Initial contribution * @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) */ @NonNullByDefault class BiweeklyPresentableCalendar extends AbstractPresentableCalendar { @@ -252,7 +256,7 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar { int foundInSeries = 0; while (positiveBeginDates.hasNext()) { final Instant begInst = positiveBeginDates.next().toInstant(); - if (begInst.isAfter(frameEnd)) { + if (begInst.isAfter(frameEnd) || begInst.equals(frameEnd)) { break; } Duration duration = getEventLength(positiveEvent); @@ -293,8 +297,15 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar { for (final VEvent currentEvent : usedCalendar.getEvents()) { final Status eventStatus = currentEvent.getStatus(); boolean positive = (eventStatus == null || (eventStatus.isTentative() || eventStatus.isConfirmed())); - final Collection positiveOrNegativeEvents = (positive ? positiveEvents : negativeEvents); - positiveOrNegativeEvents.add(currentEvent); + final RecurrenceId eventRecurrenceId = currentEvent.getRecurrenceId(); + if (positive && eventRecurrenceId != null) { + // RecurrenceId moves an event. This blocks other events of series and creates a new single instance + positiveEvents.add(currentEvent); + negativeEvents.add(currentEvent); + } else { + final Collection positiveOrNegativeEvents = (positive ? positiveEvents : negativeEvents); + positiveOrNegativeEvents.add(currentEvent); + } } } @@ -386,12 +397,32 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar { for (final VEvent counterEvent : counterEvents) { final Uid counterEventUid = counterEvent.getUid(); if (counterEventUid != null && eventUid.getValue().contentEquals(counterEventUid.getValue())) { - final DateIterator counterStartDates = getRecurredEventDateIterator(counterEvent); - counterStartDates.advanceTo(Date.from(startInstant)); - if (counterStartDates.hasNext()) { - final Instant counterStartInstant = counterStartDates.next().toInstant(); - if (counterStartInstant.equals(startInstant)) { - return true; + final RecurrenceId counterRecurrenceId = counterEvent.getRecurrenceId(); + if (counterRecurrenceId != null) { + ICalDate recurrenceDate = counterRecurrenceId.getValue(); + if (recurrenceDate != null) { + Instant recurrenceInstant = Instant.ofEpochMilli(recurrenceDate.getTime()); + if (recurrenceInstant.equals(startInstant)) { + return true; + } + Range futureOrPast = counterRecurrenceId.getRange(); + if (futureOrPast != null && futureOrPast.equals(Range.THIS_AND_FUTURE) + && startInstant.isAfter(recurrenceInstant)) { + return true; + } + if (futureOrPast != null && futureOrPast.equals(Range.THIS_AND_PRIOR) + && startInstant.isBefore(recurrenceInstant)) { + return true; + } + } + } else { + final DateIterator counterStartDates = getRecurredEventDateIterator(counterEvent); + counterStartDates.advanceTo(Date.from(startInstant)); + if (counterStartDates.hasNext()) { + final Instant counterStartInstant = counterStartDates.next().toInstant(); + if (counterStartInstant.equals(startInstant)) { + return true; + } } } } 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 670724ae56..680f057d39 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 @@ -17,6 +17,8 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.FileInputStream; import java.io.IOException; import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.List; import org.junit.jupiter.api.BeforeEach; @@ -44,12 +46,15 @@ public class BiweeklyPresentableCalendarTest { private AbstractPresentableCalendar calendar; private AbstractPresentableCalendar calendar2; private AbstractPresentableCalendar calendar3; + private AbstractPresentableCalendar calendar_issue9647; @BeforeEach public void setUp() throws IOException, CalendarException { calendar = new BiweeklyPresentableCalendar(new FileInputStream("src/test/resources/test.ics")); calendar2 = new BiweeklyPresentableCalendar(new FileInputStream("src/test/resources/test2.ics")); calendar3 = new BiweeklyPresentableCalendar(new FileInputStream("src/test/resources/test3.ics")); + calendar_issue9647 = new BiweeklyPresentableCalendar( + new FileInputStream("src/test/resources/test-issue9647.ics")); } /** @@ -585,5 +590,20 @@ public class BiweeklyPresentableCalendarTest { List realFilteredEvents6 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-15T06:00:00Z"), Instant.parse("2019-12-31T00:00:00Z"), null, 3); assertEquals(0, realFilteredEvents6.size()); + + List realFilteredEvents7 = calendar_issue9647.getFilteredEventsBetween( + LocalDate.parse("2021-01-01").atStartOfDay(ZoneId.systemDefault()).toInstant(), + LocalDate.parse("2021-01-02").atStartOfDay(ZoneId.systemDefault()).toInstant(), null, 3); + assertEquals(0, realFilteredEvents7.size()); + + Event[] expectedFilteredEvents8 = new Event[] { + new Event("Restabfall", LocalDate.parse("2021-01-04").atStartOfDay(ZoneId.systemDefault()).toInstant(), + LocalDate.parse("2021-01-05").atStartOfDay(ZoneId.systemDefault()).toInstant(), ""), + new Event("Gelbe Tonne", LocalDate.parse("2021-01-04").atStartOfDay(ZoneId.systemDefault()).toInstant(), + LocalDate.parse("2021-01-05").atStartOfDay(ZoneId.systemDefault()).toInstant(), "") }; + List realFilteredEvents8 = calendar_issue9647.getFilteredEventsBetween( + 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[] {})); } } diff --git a/bundles/org.openhab.binding.icalendar/src/test/resources/test-issue9647.ics b/bundles/org.openhab.binding.icalendar/src/test/resources/test-issue9647.ics new file mode 100644 index 0000000000..dfa92a591c --- /dev/null +++ b/bundles/org.openhab.binding.icalendar/src/test/resources/test-issue9647.ics @@ -0,0 +1,114 @@ +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Entsorgung +X-WR-TIMEZONE:Europe/Amsterdam +X-WR-CALDESC:Entleerungstermine Mülltonnen +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210104 +DTEND;VALUE=DATE:20210105 +DTSTAMP:20210102T150339Z +UID:pseudo7346893o7r8328zheh@google.com +RECURRENCE-ID;VALUE=DATE:20210101 +CREATED:20200708T110847Z +DESCRIPTION: +LAST-MODIFIED:20210102T135837Z +LOCATION: +SEQUENCE:8 +STATUS:CONFIRMED +SUMMARY:Restabfall +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20210104 +DTEND;VALUE=DATE:20210105 +DTSTAMP:20210102T150339Z +UID:pseudo352763jug7bhd7528237t@google.com +RECURRENCE-ID;VALUE=DATE:20210101 +CREATED:20200708T110659Z +DESCRIPTION: +LAST-MODIFIED:20210102T135834Z +LOCATION: +SEQUENCE:8 +STATUS:CONFIRMED +SUMMARY:Gelbe Tonne +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20201226 +DTEND;VALUE=DATE:20201227 +DTSTAMP:20210102T150339Z +UID:pseudo32zhibh75fvdv763b713@google.com +RECURRENCE-ID;VALUE=DATE:20201225 +CREATED:20200708T110333Z +DESCRIPTION: +LAST-MODIFIED:20200708T111046Z +LOCATION: +SEQUENCE:1 +STATUS:CONFIRMED +SUMMARY:Blaue Tonne +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20200717 +DTEND;VALUE=DATE:20200718 +RRULE:FREQ=WEEKLY;WKST=MO;INTERVAL=2;BYDAY=FR +DTSTAMP:20210102T150339Z +UID:pseudo7346893o7r8328zheh@google.com +CREATED:20200708T110847Z +DESCRIPTION: +LAST-MODIFIED:20200708T110851Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Restabfall +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20200714 +DTEND;VALUE=DATE:20200715 +RRULE:FREQ=WEEKLY;WKST=MO;BYDAY=TU +DTSTAMP:20210102T150339Z +UID:pseudolo2p0394z2edxc1223@google.com +CREATED:20200708T110802Z +DESCRIPTION: +LAST-MODIFIED:20200708T110820Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Bio-Tonne +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20200717 +DTEND;VALUE=DATE:20200718 +RRULE:FREQ=WEEKLY;WKST=MO;INTERVAL=2;BYDAY=FR +DTSTAMP:20210102T150339Z +UID:pseudo352763jug7bhd7528237t@google.com +CREATED:20200708T110659Z +DESCRIPTION: +LAST-MODIFIED:20200708T110729Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Gelbe Tonne +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20200710 +DTEND;VALUE=DATE:20200711 +RRULE:FREQ=WEEKLY;WKST=MO;INTERVAL=2;BYDAY=FR +DTSTAMP:20210102T150339Z +UID:pseudo32zhibh75fvdv763b713@google.com +CREATED:20200708T110333Z +DESCRIPTION: +LAST-MODIFIED:20200708T110720Z +LOCATION: +SEQUENCE:0 +STATUS:CONFIRMED +SUMMARY:Blaue Tonne +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR -- 2.47.3