58 lines
2.2 KiB
Dart
58 lines
2.2 KiB
Dart
/// Parses recurrence-rule strings produced by the `rrule_generator` widget
|
|
/// into a clean RRULE plus a separate list of exception dates.
|
|
///
|
|
/// `rrule_generator` appends excluded dates as `;EXDATE=20260514T000000,...`
|
|
/// to the rule string, which is **not** valid iCalendar — `EXDATE` is its
|
|
/// own property, not an RRULE parameter. Anything reading the raw string
|
|
/// with a strict parser (Syncfusion, package:rrule) silently drops the
|
|
/// EXDATE bit, so exclusions never apply. Split the two here.
|
|
class ParsedRRule {
|
|
final String rule;
|
|
final List<DateTime> exceptions;
|
|
const ParsedRRule({required this.rule, required this.exceptions});
|
|
|
|
static const empty = ParsedRRule(rule: '', exceptions: []);
|
|
}
|
|
|
|
ParsedRRule parseRRuleWithExceptions(String input) {
|
|
if (input.isEmpty) return ParsedRRule.empty;
|
|
final match = RegExp(r';EXDATE=([^;]+)$').firstMatch(input);
|
|
final exceptions = match == null
|
|
? const <DateTime>[]
|
|
: match
|
|
.group(1)!
|
|
.split(',')
|
|
.map(_parseExdate)
|
|
.whereType<DateTime>()
|
|
.toList();
|
|
// Keep the rule string as-is (still has its `RRULE:` content-line prefix
|
|
// when present). Both Syncfusion's `Appointment.recurrenceRule` and
|
|
// `package:rrule`'s `RecurrenceRule.fromString` parse it correctly with
|
|
// the prefix; stripping it was a regression that confused Syncfusion's
|
|
// weekly BYDAY decoder on multi-day rules.
|
|
final rule = match == null ? input : input.substring(0, match.start);
|
|
return ParsedRRule(rule: rule, exceptions: exceptions);
|
|
}
|
|
|
|
/// Parses one `yyyyMMddTHHmmss` chunk into a local-time [DateTime].
|
|
DateTime? _parseExdate(String s) {
|
|
if (s.length < 15 || s[8] != 'T') return null;
|
|
final year = int.tryParse(s.substring(0, 4));
|
|
final month = int.tryParse(s.substring(4, 6));
|
|
final day = int.tryParse(s.substring(6, 8));
|
|
final hour = int.tryParse(s.substring(9, 11));
|
|
final minute = int.tryParse(s.substring(11, 13));
|
|
final second = int.tryParse(s.substring(13, 15));
|
|
if ([
|
|
year,
|
|
month,
|
|
day,
|
|
hour,
|
|
minute,
|
|
second,
|
|
].any((e) => e == null)) {
|
|
return null;
|
|
}
|
|
return DateTime(year!, month!, day!, hour!, minute!, second!);
|
|
}
|