]> git.basschouten.com Git - openhab-addons.git/commitdiff
[icalendar] Fixed issues with moved events and wrong displayed events (#9678)
authorMichael Wodniok <michi@noorganization.org>
Mon, 4 Jan 2021 09:53:25 +0000 (10:53 +0100)
committerGitHub <noreply@github.com>
Mon, 4 Jan 2021 09:53:25 +0000 (10:53 +0100)
* [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 <michi@noorganization.org>
bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/handler/EventFilterHandler.java
bundles/org.openhab.binding.icalendar/src/main/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendar.java
bundles/org.openhab.binding.icalendar/src/test/java/org/openhab/binding/icalendar/internal/logic/BiweeklyPresentableCalendarTest.java
bundles/org.openhab.binding.icalendar/src/test/resources/test-issue9647.ics [new file with mode: 0644]

index abc9703b6e6f1b624c31f74588910084e6b9c9f1..c58cd16f212d8fec74d3036ef5b1bae3e34e3ca7 100644 (file)
@@ -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();
                 }
index 0d7dfb0e713a4ff96df8ca0bf240a6dd2cd7928c..4f6d23edba647ffbf5530fbdcae9c944508ffade 100644 (file)
@@ -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<VEvent> 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<VEvent> 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;
+                        }
                     }
                 }
             }
index 670724ae560e9148e9f1d8e74e9b48ee607036d9..680f057d3920a776abf5ff7779a9589aab7a7c3d 100644 (file)
@@ -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<Event> realFilteredEvents6 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-15T06:00:00Z"),
                 Instant.parse("2019-12-31T00:00:00Z"), null, 3);
         assertEquals(0, realFilteredEvents6.size());
+
+        List<Event> 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<Event> 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 (file)
index 0000000..dfa92a5
--- /dev/null
@@ -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