fix(import): make .ics import survive events the provider rejects (#225)

importEvents inserted every event in one unguarded loop, so a single row
the provider refused — it throws on a malformed RRULE straight out of
insert — aborted the batch and left the user with "couldn't read this
file" and nothing imported. Each event is now isolated and rejects are
counted into IcsImportSummary.failed and shown. sanitizeRrule drops empty
and malformed rule parts before the write.

Alongside that, several things a foreign file carries that we dropped:
VTODO components (silently lost, now imported as events), EXDATE, the
CATEGORIES calendar name, and colours — X-FOSSIFY-EVENT-COLOR / COLOR /
the category colour plus the X-SMT-* legacy spellings, snapped in Oklab
to the nearest key a palette account publishes since those reject a raw
EVENT_COLOR.

Two correctness fixes on our side: an all-day event may no longer end at
or before it starts (the provider expands a zero-length series into no
instances, so it just disappears), and imported all-day reminders now go
through the same encoding as hand-created ones instead of firing at UTC
midnight. A VALARM trigger pointing after the start is read as a time of
day rather than clamped to zero.

Note for later: their all-day DTEND is RFC-correct — endTS anchors at
noon of the last day and the exporter's +12h rounds it to the following
midnight. Do not "fix" it by sniffing PRODID; the holiday files bundled
in Fossify are conformant and name Fossify in theirs. One is kept as a
fixture.

Refs #225
This commit is contained in:
2026-08-19 20:17:51 +02:00
parent d03985eada
commit 2ed18e9938
19 changed files with 1271 additions and 84 deletions

View File

@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- **A failed import no longer costs you the whole file.** One event the calendar
refuses used to abort the entire import with nothing on screen but "couldn't
read this file". Events are now added one at a time: anything rejected is
counted and shown, and the rest still arrive. A faulty repeat rule in the file
is repaired instead of being handed on ([#225]).
- **Imported reminders fire at the hour you chose**, not at midnight UTC — the
same rule the app already applied to all-day events you create yourself
([#225]).
- **An all-day event with no end date, or one ending the day it starts, imports
as a one-day event** instead of a zero-length one that never appears ([#225]).
### Added
- **More of an imported `.ics` survives the trip**: tasks come across as events
rather than being dropped in silence, deleted occurrences of a repeating event
stay deleted, the file's own calendar name is picked up, and event colours are
carried over — matched to the closest colour your calendar's account offers, so
a migrated calendar still reads at a glance ([#225]).
## [2.19.2] — 2026-08-17
### Changed
@@ -1455,3 +1474,4 @@ automatically, with zero telemetry and no internet permission.
[#192]: https://codeberg.org/jlmakiola/calendula/issues/192
[#196]: https://codeberg.org/jlmakiola/calendula/issues/196
[#214]: https://codeberg.org/jlmakiola/calendula/issues/214
[#225]: https://codeberg.org/jlmakiola/calendula/issues/225