ical-recurrence: ignore instances whose local time does not exist
Replaces #11, which GitHub closed automatically when #23 was merged and its
base branch deleted. Same two commits, now based on `master`.
An hour that a daylight saving transition skips has no instant that maps to
it, so resolving a local time inside it lands on an instant belonging to a
different local time. Zurich skips 02:00–03:00 on the 27th of March 2022, and
`02:30` on that day came out as `01:30`.
```
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.
```
## Commits
1. **red** — the gap test, a `COUNT` test, and a guard for the ambiguous side. 261 passing / 2 failing.
2. **green** — the fix. 263 passing / 0 failing.
## Filtering happens before COUNT
```
[...] and BYSETPOS; then COUNT and UNTIL are evaluated.
```
An ignored instance never becomes an occurrence, and `COUNT` counts
occurrences and is evaluated last, so a skipped instance must not use up one
of the count. `leapdays.ics` has always relied on that for the invalid-date
half of the same sentence, so both halves now behave alike. There is a test
pinning it: `FREQ=DAILY;COUNT=4` across the transition yields four instances,
not three.
## Notes
- The predicate is a plain function rather than an action in `R`, which keeps
the occurrence list lazy so a `COUNT` still stops as soon as it has enough
instead of generating everything up to the limit and filtering afterwards.
- Threading it took one new parameter on `recurRecurrenceRuleDateTimeStarts`
(single caller) and an internal `recurRecurrenceRuleLocalTimesWhere`, with
`recurRecurrenceRuleLocalTimes` kept as a wrapper, so no frequency test
changed.
- `DTSTART` is exempt: the rule did not generate it, and it defines the first
instance whatever it says.
- The ambiguous side of a transition is covered by a test that passes before
and after, so this fix cannot quietly change it.