[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 <michi@noorganization.org>
pull/9685/head
Michael Wodniok 2021-01-04 10:53:25 +01:00 committed by GitHub
parent a63acf000e
commit 91dc80449b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 176 additions and 10 deletions

View 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();
}

View 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;
}
}
}
}

View 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[] {}));
}
}

View File

@ -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