package jfxtras.icalendarfx.itip; import static org.junit.Assert.assertEquals; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Test; import jfxtras.icalendarfx.ICalendarStaticComponents; import jfxtras.icalendarfx.VCalendar; import jfxtras.icalendarfx.components.VEvent; import jfxtras.icalendarfx.components.VPrimary; import jfxtras.icalendarfx.properties.calendar.Version; import jfxtras.icalendarfx.properties.component.change.DateTimeStamp; import jfxtras.icalendarfx.properties.component.recurrence.RecurrenceRule; import jfxtras.icalendarfx.properties.component.recurrence.rrule.FrequencyType; import jfxtras.icalendarfx.properties.component.recurrence.rrule.RecurrenceRuleValue; import jfxtras.icalendarfx.properties.component.relationship.RelatedTo; import jfxtras.icalendarfx.properties.component.relationship.UniqueIdentifier; public class HandleRecurrencesTest { @Test // edit ALL with 2 recurrences in date range public void canEditAllWithRecurrences() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponent1 = ICalendarStaticComponents.getDaily1(); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponent1)); mainVCalendar.setVEvents(vComponents); // make recurrences VEvent vComponentRecurrence = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRuleValue) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 10, 0)) .withSummary("recurrence summary") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)); mainVCalendar.addChild(vComponentRecurrence); VEvent vComponentRecurrence2 = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRuleValue) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 19, 10, 0)) .withSummary("recurrence summary2") .withDateTimeStart(LocalDateTime.of(2016, 5, 19, 7, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 19, 8, 30)); mainVCalendar.addChild(vComponentRecurrence2); String iTIPMessage = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:PUBLISH" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20151109T090000" + System.lineSeparator() + "DTEND:20151109T103000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Daily1 Summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "SEQUENCE:1" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR" + System.lineSeparator() + "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:CANCEL" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160517T083000" + System.lineSeparator() + "DTEND:20160517T093000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:recurrence summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RECURRENCE-ID:20160517T100000" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160519T073000" + System.lineSeparator() + "DTEND:20160519T083000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:recurrence summary2" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RECURRENCE-ID:20160519T100000" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; mainVCalendar.processITIPMessage(iTIPMessage); assertEquals(1, vComponents.size()); VEvent myComponent1 = vComponents.get(0); VEvent expectedVComponent = ICalendarStaticComponents.getDaily1() .withSequence(1); expectedVComponent.setDateTimeStart(LocalDateTime.of(2015, 11, 9, 9, 0)); expectedVComponent.setDateTimeEnd(LocalDateTime.of(2015, 11, 9, 10, 30)); assertEquals(expectedVComponent, myComponent1); } /* edits a repeatable event, with one recurrence, with ALL selection. * The edit deletes the recurrence and edits the repeatable event. */ @Test public void canProcessPublishReplaceRepeatableAll() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponentEdited = ICalendarStaticComponents.getDaily1(); // make recurrence VEvent vComponentRecurrence = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRuleValue) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 10, 0)) .withSummary("recurrence summary") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponentEdited, vComponentRecurrence)); mainVCalendar.setVEvents(vComponents); // Publish change to ALL VEvents (recurrence gets deleted) String publish = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:PUBLISH" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:2.0" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20151109T090000" + System.lineSeparator() + "DTEND:20151109T103000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Edited summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "SEQUENCE:1" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; String cancel = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:CANCEL" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:2.0" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160517T083000" + System.lineSeparator() + "DTEND:20160517T093000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:recurrence summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RECURRENCE-ID:20160517T100000" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; mainVCalendar.processITIPMessage(VCalendar.parse(publish)); mainVCalendar.processITIPMessage(VCalendar.parse(cancel)); VCalendar expectedVCalendar = new VCalendar(); VEvent expectedVComponent = ICalendarStaticComponents.getDaily1() .withSummary("Edited summary") .withSequence(1) .withDateTimeStart(LocalDateTime.of(2015, 11, 9, 9, 0)) .withDateTimeEnd(LocalDateTime.of(2015, 11, 9, 10, 30)); expectedVCalendar.addChild(expectedVComponent); assertEquals(expectedVCalendar, mainVCalendar); } @Test // edit ALL and ignore 2 recurrences in date range - tests changing Recurrence-ID of recurrences to match parent's change public void canEditAllIgnoreRecurrences() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponent1 = ICalendarStaticComponents.getDaily1(); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponent1)); mainVCalendar.setVEvents(vComponents); // make recurrences VEvent vComponentRecurrence2 = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 10, 0)) .withSummary("recurrence summary") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)); vComponents.add(vComponentRecurrence2); VEvent vComponentRecurrence3 = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 19, 10, 0)) .withSummary("recurrence summary2") .withDateTimeStart(LocalDateTime.of(2016, 5, 19, 7, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 19, 8, 30)); vComponents.add(vComponentRecurrence3); String iTIPMessage = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:PUBLISH" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20151109T090000" + System.lineSeparator() + "DTEND:20151109T103000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Daily1 Summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "SEQUENCE:1" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160517T083000" + System.lineSeparator() + "DTEND:20160517T093000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:recurrence summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RECURRENCE-ID:20160517T090000" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160519T073000" + System.lineSeparator() + "DTEND:20160519T083000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:recurrence summary2" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RECURRENCE-ID:20160519T090000" + System.lineSeparator() + "END:VEVENT"; mainVCalendar.processITIPMessage(iTIPMessage); Collections.sort(vComponents, VPrimary.DTSTART_COMPARATOR); assertEquals(3, vComponents.size()); VEvent myComponent1 = vComponents.get(0); VEvent myComponent2 = vComponents.get(1); VEvent myComponent3 = vComponents.get(2); VEvent expectedVComponent = ICalendarStaticComponents.getDaily1() .withSequence(1); expectedVComponent.setDateTimeStart(LocalDateTime.of(2015, 11, 9, 9, 0)); expectedVComponent.setDateTimeEnd(LocalDateTime.of(2015, 11, 9, 10, 30)); assertEquals(expectedVComponent, myComponent1); VEvent expectedComponent2 = new VEvent(vComponentRecurrence2) .withSummary("recurrence summary") .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 9, 0)) .withDateTimeStart("20160517T083000") .withDateTimeEnd("20160517T093000"); assertEquals(expectedComponent2, myComponent2); VEvent expectedComponent3 = new VEvent(vComponentRecurrence3) .withSummary("recurrence summary2") .withRecurrenceId(LocalDateTime.of(2016, 5, 19, 9, 0)) .withDateTimeStart("20160519T073000") .withDateTimeEnd("20160519T083000"); assertEquals(expectedComponent3, myComponent3); } @Test // with a recurrence in between new date range - remove special recurrence, replaces with normal recurrence public void canEditThisAndFutureWithRecurrence() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponentEdited = ICalendarStaticComponents.getDaily1(); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponentEdited)); mainVCalendar.setVEvents(vComponents); // make recurrence VEvent vComponentRecurrence = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 10, 0)) .withSummary("recurrence summary") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)); vComponents.add(vComponentRecurrence); String iTIPMessage = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:REQUEST" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20151109T100000" + System.lineSeparator() + "DTEND:20151109T110000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Daily1 Summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY;UNTIL=20160515T170000Z" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "SEQUENCE:1" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR" + System.lineSeparator() + "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:PUBLISH" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160516T090000" + System.lineSeparator() + "DTEND:20160516T103000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Edited summary" + System.lineSeparator() + "DTSTAMP:20160914T200517Z" + System.lineSeparator() + "UID:20160914T130517-0jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RELATED-TO:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; mainVCalendar.processITIPMessage(iTIPMessage); assertEquals(2, vComponents.size()); Collections.sort(vComponents, VPrimary.DTSTART_COMPARATOR); VEvent myComponentFuture = vComponents.get(1); VEvent myComponentOriginal = vComponents.get(0); VEvent expectedOriginalEdited = ICalendarStaticComponents.getDaily1() .withSequence(1); expectedOriginalEdited.getRecurrenceRule().getValue() .setUntil(LocalDateTime.of(2016, 5, 15, 10, 0).atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("Z"))); assertEquals(expectedOriginalEdited, myComponentOriginal); RelatedTo relatedTo = RelatedTo.parse(vComponentEdited.getUniqueIdentifier().getValue()); VEvent expectedComponentFuture = ICalendarStaticComponents.getDaily1() .withDateTimeStart(LocalDateTime.of(2016, 5, 16, 9, 0)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 16, 10, 30)) .withRelatedTo(Arrays.asList(relatedTo)) .withSummary("Edited summary") .withUniqueIdentifier(new UniqueIdentifier(myComponentFuture.getUniqueIdentifier())) .withDateTimeStamp(new DateTimeStamp(myComponentFuture.getDateTimeStamp())); assertEquals(expectedComponentFuture, myComponentFuture); } @Test // with a recurrence in between new date range - special recurrence stays unmodified. public void canEditThisAndFutureAllIgnoreRecurrence() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponentOriginal = ICalendarStaticComponents.getDaily1(); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponentOriginal)); mainVCalendar.setVEvents(vComponents); // make recurrence before VEvent vComponentRecurrenceBefore = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2015, 12, 25, 10, 0)) .withSummary("recurrence summary before") .withDateTimeStart(LocalDateTime.of(2015, 12, 26, 0, 30)) .withDateTimeEnd(LocalDateTime.of(2015, 12, 26, 2, 30)); vComponents.add(vComponentRecurrenceBefore); // make recurrence after VEvent vComponentRecurrenceAfter = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 10, 0)) .withSummary("recurrence summary after") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)); vComponents.add(vComponentRecurrenceAfter); String iTIPMessage = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:REQUEST" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20151109T100000" + System.lineSeparator() + "DTEND:20151109T110000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Daily1 Summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY;UNTIL=20160515T170000Z" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "SEQUENCE:1" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + // NOTE: Yahoo calendar deletes the recurrence before the new UNTIL date. It may be necessary to include the before recurrences in the REQUEST message. "END:VCALENDAR" + System.lineSeparator() + "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:PUBLISH" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160516T090000" + System.lineSeparator() + "DTEND:20160516T103000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Edited summary" + System.lineSeparator() + "DTSTAMP:20160918T235541Z" + System.lineSeparator() + "UID:20160918T165541-0jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RELATED-TO:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DTSTART:20160517T083000" + System.lineSeparator() + "DTEND:20160517T093000" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:recurrence summary after" + System.lineSeparator() + "DTSTAMP:20160918T235541Z" + System.lineSeparator() + "UID:20160918T165541-0jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "RECURRENCE-ID:20160517T090000" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; mainVCalendar.processITIPMessage(iTIPMessage); assertEquals(4, vComponents.size()); Collections.sort(vComponents, VPrimary.DTSTART_COMPARATOR); VEvent myComponentOriginal = vComponents.get(0); // vComponent #1 is recurrence before (unchanged) VEvent myComponentFuture = vComponents.get(2); VEvent myComponentRecurrence = vComponents.get(3); VEvent expectedOriginalEdited = ICalendarStaticComponents.getDaily1() .withSequence(1); expectedOriginalEdited.getRecurrenceRule().getValue() .setUntil(LocalDateTime.of(2016, 5, 15, 10, 0).atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("Z"))); assertEquals(expectedOriginalEdited, myComponentOriginal); RelatedTo relatedTo = RelatedTo.parse(vComponentOriginal.getUniqueIdentifier().getValue()); VEvent expectedComponentFuture = ICalendarStaticComponents.getDaily1() .withDateTimeStart(LocalDateTime.of(2016, 5, 16, 9, 0)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 16, 10, 30)) .withRelatedTo(Arrays.asList(relatedTo)) .withSummary("Edited summary") .withUniqueIdentifier(new UniqueIdentifier(myComponentFuture.getUniqueIdentifier())) .withDateTimeStamp(new DateTimeStamp(myComponentFuture.getDateTimeStamp())); assertEquals(expectedComponentFuture, myComponentFuture); VEvent expectedvComponentRecurrence = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 9, 0)) .withSummary("recurrence summary after") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)) .withDateTimeStamp(new DateTimeStamp(myComponentFuture.getDateTimeStamp())) .withUniqueIdentifier(new UniqueIdentifier(expectedComponentFuture.getUniqueIdentifier())); assertEquals(expectedvComponentRecurrence, myComponentRecurrence); } @Test // with a recurrence in between new date range, from whole-day to time-based - special recurrence stays unmodified. public void canEditWholeDayToTimeBasedThisAndFutureIgnoreRecurrence() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponentOriginal = ICalendarStaticComponents.getWholeDayDaily1(); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponentOriginal)); mainVCalendar.setVEvents(vComponents); VEvent vComponentEdited = new VEvent(vComponentOriginal); // make recurrence VEvent vComponentRecurrence = ICalendarStaticComponents.getWholeDayDaily1() .withRecurrenceRule((RecurrenceRuleValue) null) .withRecurrenceId(LocalDate.of(2016, 5, 17)) .withSummary("recurrence summary") .withDateTimeStart(ZonedDateTime.of(LocalDateTime.of(2016, 5, 17, 8, 30), ZoneId.of("Europe/London"))) .withDateTimeEnd(ZonedDateTime.of(LocalDateTime.of(2016, 5, 17, 9, 30), ZoneId.of("Europe/London"))); vComponents.add(vComponentRecurrence); String iTIPMessage = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:REQUEST" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group06" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-010@jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY;UNTIL=20160514" + System.lineSeparator() + "DTSTART;VALUE=DATE:20151108" + System.lineSeparator() + "ORGANIZER;CN=Issac Newton:mailto:isaac@greatscientists.org" + System.lineSeparator() + "DTEND;VALUE=DATE:20151109" + System.lineSeparator() + "SEQUENCE:1" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR" + System.lineSeparator() + "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:PUBLISH" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group06" + System.lineSeparator() + "DTSTAMP:20160919T032343Z" + System.lineSeparator() + "UID:20160918T202343-0jfxtras.org" + System.lineSeparator() + "RRULE:FREQ=DAILY" + System.lineSeparator() + "DTSTART;TZID=Europe/London:20160515T090000" + System.lineSeparator() + "ORGANIZER;CN=Issac Newton:mailto:isaac@greatscientists.org" + System.lineSeparator() + "DTEND;TZID=Europe/London:20160515T103000" + System.lineSeparator() + "SUMMARY:Edited summary" + System.lineSeparator() + "RELATED-TO:20150110T080000-010@jfxtras.org" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group06" + System.lineSeparator() + "DTSTAMP:20160919T032343Z" + System.lineSeparator() + "UID:20160918T202343-0jfxtras.org" + System.lineSeparator() + "DTSTART;TZID=Europe/London:20160517T083000" + System.lineSeparator() + "ORGANIZER;CN=Issac Newton:mailto:isaac@greatscientists.org" + System.lineSeparator() + "DTEND;TZID=Europe/London:20160517T093000" + System.lineSeparator() + "RECURRENCE-ID;TZID=Europe/London:20160517T090000" + System.lineSeparator() + "SUMMARY:recurrence summary" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; mainVCalendar.processITIPMessage(iTIPMessage); assertEquals(3, vComponents.size()); Collections.sort(vComponents, VPrimary.DTSTART_COMPARATOR); VEvent myComponentOriginal = vComponents.get(0); VEvent newVComponentFuture = vComponents.get(1); VEvent myComponentRecurrence = vComponents.get(2); VEvent expectedOriginalEdited = ICalendarStaticComponents.getWholeDayDaily1() .withSequence(1); expectedOriginalEdited.getRecurrenceRule().getValue() .setUntil(LocalDate.of(2016, 5, 14)); assertEquals(expectedOriginalEdited, myComponentOriginal); RelatedTo relatedTo = RelatedTo.parse(vComponentEdited.getUniqueIdentifier().getValue()); VEvent expectedComponentFuture = ICalendarStaticComponents.getWholeDayDaily1() .withDateTimeStart(ZonedDateTime.of(LocalDateTime.of(2016, 5, 15, 9, 0), ZoneId.of("Europe/London"))) .withDateTimeEnd(ZonedDateTime.of(LocalDateTime.of(2016, 5, 15, 10, 30), ZoneId.of("Europe/London"))) .withSummary("Edited summary") .withRelatedTo(Arrays.asList(relatedTo)) .withUniqueIdentifier(new UniqueIdentifier(newVComponentFuture.getUniqueIdentifier())) .withDateTimeStamp(new DateTimeStamp(newVComponentFuture.getDateTimeStamp())); assertEquals(expectedComponentFuture, newVComponentFuture); VEvent expectedvComponentRecurrence = ICalendarStaticComponents.getWholeDayDaily1() .withUniqueIdentifier(new UniqueIdentifier(newVComponentFuture.getUniqueIdentifier())) .withDateTimeStamp(new DateTimeStamp(newVComponentFuture.getDateTimeStamp())) // TODO - Decide if I should recycle old DTSTAMP? .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(ZonedDateTime.of(LocalDateTime.of(2016, 5, 17, 9, 0), ZoneId.of("Europe/London"))) .withSummary("recurrence summary") .withDateTimeStart(ZonedDateTime.of(LocalDateTime.of(2016, 5, 17, 8, 30), ZoneId.of("Europe/London"))) .withDateTimeEnd(ZonedDateTime.of(LocalDateTime.of(2016, 5, 17, 9, 30), ZoneId.of("Europe/London"))) .withUniqueIdentifier(new UniqueIdentifier(expectedComponentFuture.getUniqueIdentifier())); assertEquals(expectedvComponentRecurrence, myComponentRecurrence); } @Test // makes sure when recurrence deleted the parent gets an EXDATE public void canDeleteThisAndFutureWithRecurrence() { VCalendar mainVCalendar = new VCalendar(); VEvent vComponent1 = ICalendarStaticComponents.getDaily1(); final List<VEvent> vComponents = new ArrayList<>(Arrays.asList(vComponent1)); mainVCalendar.setVEvents(vComponents); // make recurrence VEvent vComponentRecurrence = ICalendarStaticComponents.getDaily1() .withRecurrenceRule((RecurrenceRule) null) .withRecurrenceId(LocalDateTime.of(2016, 5, 17, 10, 0)) .withSummary("recurrence summary") .withDateTimeStart(LocalDateTime.of(2016, 5, 17, 8, 30)) .withDateTimeEnd(LocalDateTime.of(2016, 5, 17, 9, 30)); vComponents.add(vComponentRecurrence); String iTIPMessage = "BEGIN:VCALENDAR" + System.lineSeparator() + "METHOD:CANCEL" + System.lineSeparator() + "PRODID:-//Example/ExampleCalendarClient//EN" + System.lineSeparator() + "VERSION:" + Version.DEFAULT_ICALENDAR_SPECIFICATION_VERSION + System.lineSeparator() + "BEGIN:VEVENT" + System.lineSeparator() + "CATEGORIES:group05" + System.lineSeparator() + "DESCRIPTION:Daily1 Description" + System.lineSeparator() + "SUMMARY:Daily1 Summary" + System.lineSeparator() + "DTSTAMP:20150110T080000Z" + System.lineSeparator() + "UID:20150110T080000-004@jfxtras.org" + System.lineSeparator() + "ORGANIZER;CN=Papa Smurf:mailto:papa@smurf.org" + System.lineSeparator() + "STATUS:CANCELLED" + System.lineSeparator() + "RECURRENCE-ID;RANGE=THISANDFUTURE:20160515T100000" + System.lineSeparator() + "END:VEVENT" + System.lineSeparator() + "END:VCALENDAR"; List<String> log = mainVCalendar.processITIPMessage(iTIPMessage); assertEquals(1, vComponents.size()); VEvent myComponent1 = vComponents.get(0); VEvent expectedVComponent = ICalendarStaticComponents.getDaily1() .withSequence(1); RecurrenceRuleValue newRRule = new RecurrenceRuleValue() .withFrequency(FrequencyType.DAILY) .withUntil(LocalDateTime.of(2016, 5, 14, 10, 0).atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("Z"))); expectedVComponent.setRecurrenceRule(newRRule); assertEquals(expectedVComponent, myComponent1); } }