This method is a generator that works for PHP 5.3/5.4 (using static variables)
The main idea is: a brute force made fast by not relying on date() functions
There is one big loop that examines every interval of the given frequency
(so every day, every week, every month or every year), constructs an
array of all the yeardays of the interval (for daily frequencies, the array
only has one element, for weekly 7, and so on), and then filters out any
day that do no match BYXXX parts.
The algorithm does not try to be "smart" in calculating the increment of
the loop. That is, for a rule like "every day in January for 10 years"
the algorithm will loop through every day of the year, each year, generating
some 3650 iterations (+ some to account for the leap years).
This is a bit counter-intuitive, as it is obvious that the loop could skip
all the days in February till December since they are never going to match.
Fortunately, this approach is still super fast because it doesn't rely
on date() or DateTime functions, and instead does all the date operations
manually, either arithmetically or using arrays as converters.
Another quirk of this approach is that because the granularity is by day,
higher frequencies (hourly, minutely and secondly) have to have
their own special loops within the main loop, making the whole thing quite
convoluted.
Moreover, at such frequencies, the brute-force approach starts to really
suck. For example, a rule like
"Every minute, every Jan 1st between 10:00 and 10:59, for 10 years"
requires a tremendous amount of useless iterations to jump from Jan 1st 10:59
at year 1 to Jan 1st 10.00 at year 2.
In order to make a "smart jump", we would have to have a way to determine
the gap between the next occurence arithmetically. I think that would require
to analyze each "BYXXX" rule part that "Limit" the set (see the RFC page 43)
at the given frequency. For example, a YEARLY frequency doesn't need "smart
jump" at all; MONTHLY and WEEKLY frequencies only need to check BYMONTH;
DAILY frequency needs to check BYMONTH, BYMONTHDAY and BYDAY, and so on.
The check probably has to be done in reverse order, e.g. for DAILY frequencies
attempt to jump to the next weekday (BYDAY) or next monthday (BYMONTHDAY)
(I don't know yet which one first), and then if that results in a change of
month, attempt to jump to the next BYMONTH, and so on.