ical4j / ical4j

A Java library for parsing and building iCalendar data models
https://www.ical4j.org
BSD 3-Clause "New" or "Revised" License
765 stars 201 forks source link

calculateRecurrenceSet does not discard invalid dates #12

Closed jtammen closed 5 years ago

jtammen commented 10 years ago

When I want to calculate the recurrence set of a monthly recurring event that starts on a date with day of month > 28, I am getting unexpected results.

Example:

VEvent vEvent = new VEvent();
Date startDate = new Date("20140630");
vEvent.getProperties().add(new DtStart(startDate, true));

String recurrenceRule = "FREQ=MONTHLY";
vEvent.getProperties().add(new RRule(new Recur(recurrenceRule)));

PeriodList recurrenceSet = vEvent.calculateRecurrenceSet(
   new Period(new DateTime(startDate), new DateTime("20150630T000000Z")));

This will give me the following periods:

20140630T000000Z/PT0S
20140730T000000Z/PT0S
20140830T000000Z/PT0S
20140930T000000Z/PT0S
20141030T000000Z/PT0S
20141130T000000Z/PT0S
20141230T000000Z/PT0S
20150130T000000Z/PT0S
20150228T000000Z/PT0S
20150328T000000Z/PT0S
20150428T000000Z/PT0S
20150528T000000Z/PT0S
20150628T000000Z/PT0S

As you can see, starting with February 2015, the date is changed to 28. I would either expect that the invalid date 2015-02-30 would be discarded and not converted to 2015-02-28 or that the next occurrence would be 2015-03-30 and not 2015-03-28.

Is this the intended behavior of ical4j or is it a bug?

Version: 1.0.3, but latest (1.0.5.2) is also affected.

thisfred commented 8 years ago

This appears to still be the case, and it's in direct conflict with the standard which states:

""" Recurrence rules may generate recurrence instances with an invalid date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM on a day where the local time is moved forward by an hour at 1:00 AM). Such recurrence instances MUST be ignored and MUST NOT be counted as part of the recurrence set. """

https://tools.ietf.org/html/rfc5545

this is what python's dateutil does:

>>> from dateutil.rrule import rrule, MONTHLY
>>> from datetime import datetime
>>> r = rrule(MONTHLY, dtstart=datetime(2016, 1, 30))
>>> r.between(datetime(2017, 1, 1), datetime(2017, 4, 1))
[datetime.datetime(2017, 1, 30, 0, 0), datetime.datetime(2017, 3, 30, 0, 0)]

whereas ical4j displays the behavior described in the previous comment.

benfortuna commented 5 years ago

This was a bug but I believe it should now be fixed. The results from the latest release as follows:

20140730T000000/PT0S
20140830T000000/PT0S
20140930T000000/PT0S
20141030T000000/PT0S
20141130T000000/PT0S
20141230T000000/PT0S
20150130T000000/PT0S
20150330T000000/PT0S
20150430T000000/PT0S
20150530T000000/PT0S
20150630T000000/PT0S

You can see with the latest implementation that Feb dates are skipped because there is no Feb 30th.

thisfred commented 5 years ago

Awesome, thank you for checking back on this!