fix(caldav): expand RRULE for recurring events

This commit is contained in:
2026-09-06 15:49:09 +02:00
parent f35121b3dc
commit 8e946964ec
11 changed files with 1223 additions and 126 deletions
+20 -3
View File
@@ -27,8 +27,20 @@ import "regexp"
var tzidParamRe = regexp.MustCompile(`TZID=("?)([^";:\r\n]+)("?)`)
// tzidLineRe matches a standalone "TZID:<name>" property line, as found
// inside a VTIMEZONE component.
var tzidLineRe = regexp.MustCompile(`(?m)^TZID:([^\r\n]+)$`)
// inside a VTIMEZONE component. The trailing "(\r?)" capture group is
// required, not cosmetic: Go's RE2 engine only matches "$" in multi-line
// mode immediately before a literal "\n", not before "\r\n" — so without
// consuming (and re-emitting) an optional trailing "\r" explicitly, this
// regex silently fails to match on any CRLF-terminated feed (which is
// every Outlook/Exchange-published ICS feed in practice), leaving the
// VTIMEZONE's own TZID line un-normalized even though the "TZID=..."
// parameter form (tzidParamRe, used on DTSTART/DTEND/etc.) still matches
// fine. The practical effect was a VTIMEZONE whose declared TZID
// ("W. Europe Standard Time") never matched the now-normalized
// "TZID=Europe/Berlin" parameters referencing it elsewhere in the same
// object, so any code trying to find "the VTIMEZONE for Europe/Berlin"
// (see internal/icssub.Series.referencedTimezones) would never find one.
var tzidLineRe = regexp.MustCompile(`(?m)^TZID:([^\r\n]+)(\r?)$`)
// NormalizeTimeZones rewrites any recognized Windows timezone identifier
// in data to its IANA equivalent, leaving everything else (including
@@ -51,7 +63,12 @@ func NormalizeTimeZones(data []byte) []byte {
if !ok {
return m
}
return []byte("TZID:" + iana)
// sub[2] is the optional trailing "\r" the regex consumed as part
// of the match (see the doc comment above) — it must be re-emitted
// here, or a CRLF-terminated line loses its "\r" and every
// subsequent line in the file ends up misaligned relative to the
// original byte offsets a caller might have recorded.
return []byte("TZID:" + iana + string(sub[2]))
})
return data
}