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).
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.