Version
2.2.1
What happens
February never has a 30th, so this rule has no occurrences. Asking for the
second one never returns:
import ICAL from "ical.js";
const iter = ICAL.Recur.fromString("FREQ=DAILY;BYMONTH=2;BYMONTHDAY=30")
.iterator(ICAL.Time.fromString("2026-01-01T00:00:00"));
iter.next(); // 2026-01-01T00:00:00
iter.next(); // never returns
FREQ=HOURLY;BYMONTH=2;BYMONTHDAY=30 hangs the same way. MONTHLY and
YEARLY do not, which is the tell.
Cause
The do ... while in next keeps stepping until check_contracting_rules
accepts the instance it has landed on. MONTHLY and YEARLY bail out of it
via invalid_count, but those counters only tick when the expansion itself
rejected the period, not when the contracting parts did, and the other
frequencies have no bail-out at all. So for a rule whose contracting parts
exclude everything the expansion produces, the loop runs for ever.
I will open a PR.
Version
2.2.1
What happens
February never has a 30th, so this rule has no occurrences. Asking for the
second one never returns:
FREQ=HOURLY;BYMONTH=2;BYMONTHDAY=30 hangs the same way. MONTHLY and
YEARLY do not, which is the tell.
Cause
The do ... while in next keeps stepping until check_contracting_rules
accepts the instance it has landed on. MONTHLY and YEARLY bail out of it
via invalid_count, but those counters only tick when the expansion itself
rejected the period, not when the contracting parts did, and the other
frequencies have no bail-out at all. So for a rule whose contracting parts
exclude everything the expansion produces, the loop runs for ever.
I will open a PR.