javacalendarfind-occurrencesrfc5545ical4j

RFC5545. Calculate event occurrences for RRULE and EXDATE (EXRULE) at same time


I have to calculate event occurrences. Calculation should be based on recurrent event pattern. Recurrent event pattern is rfc5545 based.

I've found lib-recur library to calculate occurrences. Following description I see they provide calculation using the instances of a specific instance set (e.g. an rrule, an exrule, a list of rdates or exdates). I used calculation for RRULE, it seems works. I need in my logic find correctly all occurrences including into calculation EXDATE, EXRULE. But seems last exclusions does not support here. Small code example:

import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.recurrenceset.RecurrenceRuleAdapter;
import org.dmfs.rfc5545.recurrenceset.RecurrenceSet;
import org.dmfs.rfc5545.recurrenceset.RecurrenceSetIterator;
@Test
public void testSpecial() throws InvalidRecurrenceRuleException {
    RecurrenceRule recurrenceRule = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5;COUNT=3");
    RecurrenceRuleIterator it = recurrenceRule.iterator(DateTime.nowAndHere());
    int maxInstances = 10; // limit instances for rules that recur forever
    while (it.hasNext() && (!recurrenceRule.isInfinite() || maxInstances-- > 0)) {
        DateTime nextInstance = it.nextDateTime();
        // do something with nextInstance
        System.out.println(nextInstance);
    }
}

result:

20170714T163325

20180523T163325

20190523T163325

Also I know that google based library can provide core handlers for RRULES, EXRULE... based on rfc5545 standard. But I've not found how to calculate occurrences here also(including RRULES, EXDATE, EXRULE).

More example but for google-rfc-2445 library:

import com.google.ical.compat.jodatime.LocalDateIterable;
import com.google.ical.compat.jodatime.LocalDateIterator;
import com.google.ical.compat.jodatime.LocalDateIteratorFactory;
import com.google.ical.values.RRule;
@Test
public void test() throws ParseException {
    String sRule = "RRULE:FREQ=YEARLY;COUNT=3;INTERVAL=2;BYMONTH=5;BYMONTHDAY=22,23,24,25,26,27,28;BYDAY=MO";
    LocalDateIterable localDateIterable = LocalDateIteratorFactory
            .createLocalDateIterable(sRule, org.joda.time.LocalDate.now(), true);
    LocalDateIterator iterator = localDateIterable.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

Result:

2017-07-14

2019-05-27

2021-05-24

2023-05-22

Please share link to open source library that already has the complex occurrence calculation functionality (calculation should include RRULE, EXDATEs at same time).


Solution

  • lib-recur does support calculating recurrence sets. Though, the interface of this component is not perfect yet (and not very convenient).

    Here is a brief example of how it works with one RRULE and one EXRULE:

    // note: recurrence expansion takes place in a specific time zone
    TimeZone tz = TimeZone.getDefault();
    // the start of the recurring event
    DateTime start = new DateTime(tz, 2017, 5 - 1 /* zero based */, 23, 12, 0, 0);
    
    // parse the RRULE
    RecurrenceRule recurrenceRule = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5;COUNT=30");
    // parse the EXRULE (in this case every 3rd instance of the RRULE)
    RecurrenceRule exceptionRule = new RecurrenceRule("FREQ=YEARLY;INTERVAL=3;BYMONTHDAY=23;BYMONTH=5;COUNT=10");
    
    // create an empty RecurrenceSet
    // a recurrence set is the set of all actual instance of a recurring event and may consist of multiple instance and excludes sources 
    RecurrenceSet recurrenceSet = new RecurrenceSet();
    // add the instances of the RRULE
    recurrenceSet.addInstances(new RecurrenceRuleAdapter(recurrenceRule));
    // exclude the instances of the EXRULE
    recurrenceSet.addExceptions(new RecurrenceRuleAdapter(exceptionRule));
    
    RecurrenceSetIterator recurrenceSetIterator = recurrenceSet.iterator(start.getTimeZone(), start.getTimestamp());
    
    int maxInstances = 10; // limit instances for rules that recur forever
    // iterate as long as the recurrence set still has instances
    while (recurrenceSetIterator.hasNext() && (!recurrenceRule.isInfinite() || maxInstances-- > 0))
    {
        // get the next instance of the recurrence set
        DateTime nextInstance = new DateTime(start.getTimeZone(), recurrenceSetIterator.next());
        // do something with nextInstance
        System.out.println(nextInstance);
    }
    

    You can see that RecurrenceSet operates on long timestamps, which is far from ideal.

    To support RDATES and EXDATES you can use the RecurrenceList adapter. RecurrenceList either takes an array of timestamps or a time zone and a string of comma separated date-time or date values (like they are defined for RDATES and EXDATES):

    // add more RDATES instances 
    recurrenceSet.addInstances(new RecurrenceList("20171212T121212,20181212T121212", tz));
    
    // add EXDATES
    recurrenceSet.addExceptions(new RecurrenceList("20180523T120000,20190523T120000", tz));
    

    Note, in contrast to the RecurrenceRuleIterator stuff, this component doesn't have any test coverage yet.