-
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
7 changed files
with
246 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import 'dart:convert'; | ||
|
||
import 'package:meta/meta.dart'; | ||
|
||
import '../../recurrence_rule.dart'; | ||
import '../string/decoder.dart'; | ||
|
||
@immutable | ||
class RecurrenceRuleFromJsonDecoder | ||
extends Converter<Map<String, dynamic>, RecurrenceRule> { | ||
const RecurrenceRuleFromJsonDecoder(); | ||
|
||
static const _byWeekDayEntryDecoder = ByWeekDayEntryFromStringDecoder(); | ||
|
||
@override | ||
RecurrenceRule convert(Map<String, dynamic> input) { | ||
final rawUntil = input['until'] as String?; | ||
final rawCount = input['count'] as int?; | ||
if (rawUntil != null && rawCount != null) { | ||
throw FormatException('Both `until` and `count` are specified.'); | ||
} | ||
final rawWeekStart = input['wkst'] as String?; | ||
return RecurrenceRule( | ||
frequency: frequencyFromString(input['freq'] as String), | ||
until: rawUntil == null ? null : _parseDateTime(rawUntil), | ||
count: rawCount, | ||
interval: input['interval'] as int?, | ||
bySeconds: _parseIntSet('bysecond', input['bysecond']), | ||
byMinutes: _parseIntSet('byminute', input['byminute']), | ||
byHours: _parseIntSet('byhour', input['byhour']), | ||
byWeekDays: | ||
_parseSet('byday', input['byday'], _byWeekDayEntryDecoder.convert), | ||
byMonthDays: _parseIntSet('bymonthday', input['bymonthday']), | ||
byYearDays: _parseIntSet('byyearday', input['byyearday']), | ||
byWeeks: _parseIntSet('byweekno', input['byweekno']), | ||
byMonths: _parseIntSet('bymonth', input['bymonth']), | ||
bySetPositions: _parseIntSet('bysetpos', input['bysetpos']), | ||
weekStart: rawWeekStart == null ? null : weekDayFromString(rawWeekStart), | ||
); | ||
} | ||
|
||
DateTime _parseDateTime(String string) { | ||
const year = r'(?<year>\d{4})'; | ||
const month = r'(?<month>\d{2})'; | ||
const day = r'(?<day>\d{2})'; | ||
const hour = r'(?<hour>\d{2})'; | ||
const minute = r'(?<minute>\d{2})'; | ||
const second = r'(?<second>\d{2})'; | ||
final regEx = RegExp( | ||
'^$year-$month-${day}T$hour:$minute:${second}Z?\$', | ||
); | ||
|
||
final match = regEx.firstMatch(string)!; | ||
return DateTime.utc( | ||
int.parse(match.namedGroup('year')!), | ||
int.parse(match.namedGroup('month')!), | ||
int.parse(match.namedGroup('day')!), | ||
int.parse(match.namedGroup('hour')!), | ||
int.parse(match.namedGroup('minute')!), | ||
int.parse(match.namedGroup('second')!), | ||
); | ||
} | ||
|
||
Set<int> _parseIntSet(String name, dynamic json) => | ||
_parseSet<int, int>(name, json, (it) => it); | ||
Set<R> _parseSet<T, R>(String name, dynamic json, R Function(T) parse) { | ||
if (json == null) { | ||
return const {}; | ||
} else if (json is T) { | ||
return {parse(json)}; | ||
} else if (json is List<T>) { | ||
return json.map(parse).toSet(); | ||
} else { | ||
throw FormatException('Invalid JSON in `$name`.'); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import 'dart:convert'; | ||
|
||
import 'package:meta/meta.dart'; | ||
|
||
import '../../recurrence_rule.dart'; | ||
import '../string/encoder.dart'; | ||
import '../string/ical.dart'; | ||
|
||
@immutable | ||
class RecurrenceRuleToJsonOptions { | ||
const RecurrenceRuleToJsonOptions({ | ||
this.isTimeUtc = false, | ||
}); | ||
|
||
/// If true, all date/time strings will be suffixed with a 'Z' to indicate | ||
/// they are in UTC. | ||
final bool isTimeUtc; | ||
} | ||
|
||
@immutable | ||
class RecurrenceRuleToJsonEncoder | ||
extends Converter<RecurrenceRule, Map<String, dynamic>> { | ||
const RecurrenceRuleToJsonEncoder({ | ||
this.options = const RecurrenceRuleToJsonOptions(), | ||
}); | ||
|
||
static const _byWeekDayEntryEncoder = ByWeekDayEntryToStringEncoder(); | ||
|
||
final RecurrenceRuleToJsonOptions options; | ||
|
||
@override | ||
Map<String, dynamic> convert(RecurrenceRule input) { | ||
return <String, dynamic>{ | ||
'freq': frequencyToString(input.frequency), | ||
if (input.until != null) 'until': _formatDateTime(input.until!), | ||
if (input.count != null) 'count': input.count, | ||
if (input.interval != null) 'interval': input.interval, | ||
if (input.bySeconds.isNotEmpty) 'bysecond': input.bySeconds.toList(), | ||
if (input.byMinutes.isNotEmpty) 'byminute': input.byMinutes.toList(), | ||
if (input.byHours.isNotEmpty) 'byhour': input.byHours.toList(), | ||
if (input.byWeekDays.isNotEmpty) | ||
'byday': input.byWeekDays.map(_byWeekDayEntryEncoder.convert).toList(), | ||
if (input.byMonthDays.isNotEmpty) | ||
'bymonthday': input.byMonthDays.toList(), | ||
if (input.byYearDays.isNotEmpty) 'byyearday': input.byYearDays.toList(), | ||
if (input.byWeeks.isNotEmpty) 'byweekno': input.byWeeks.toList(), | ||
if (input.byMonths.isNotEmpty) 'bymonth': input.byMonths.toList(), | ||
if (input.bySetPositions.isNotEmpty) | ||
'bysetpos': input.bySetPositions.toList(), | ||
if (input.weekStart != null) 'wkst': weekDayToString(input.weekStart!), | ||
}; | ||
} | ||
|
||
String _formatDateTime(DateTime dateTime) { | ||
// Modified version of `dateTime.toIso8601String()` without sub-second | ||
// precision. | ||
assert( | ||
0 <= dateTime.year && dateTime.year <= iCalMaxYear, | ||
'Years with more than four digits are not supported.', | ||
); | ||
|
||
String twoDigits(int n) => n < 10 ? '0$n' : '$n'; | ||
|
||
String fourDigits(int n) { | ||
final absolute = n.abs(); | ||
final sign = n < 0 ? '-' : ''; | ||
if (absolute >= 1000) return '$n'; | ||
if (absolute >= 100) return '${sign}0$absolute'; | ||
if (absolute >= 10) return '${sign}00$absolute'; | ||
return '${sign}000$absolute'; | ||
} | ||
|
||
final year = fourDigits(dateTime.year); | ||
final month = twoDigits(dateTime.month); | ||
final day = twoDigits(dateTime.day); | ||
final hour = twoDigits(dateTime.hour); | ||
final minute = twoDigits(dateTime.minute); | ||
final second = twoDigits(dateTime.second); | ||
final utcSuffix = options.isTimeUtc ? 'Z' : ''; | ||
return '$year-$month-${day}T$hour:$minute:$second$utcSuffix'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import 'package:rrule/rrule.dart'; | ||
|
||
import '../utils.dart'; | ||
|
||
void main() { | ||
// There are more tests in `../../rrule_ical_test.dart`. | ||
|
||
testJsonCodec( | ||
'Annually on the 2nd Monday & the last Sunday of the month in October, 5 times', | ||
RecurrenceRule( | ||
frequency: Frequency.yearly, | ||
count: 5, | ||
byWeekDays: { | ||
ByWeekDayEntry(DateTime.sunday, -1), | ||
ByWeekDayEntry(DateTime.monday, 2), | ||
}, | ||
byMonths: {10}, | ||
), | ||
json: <String, dynamic>{ | ||
'freq': 'YEARLY', | ||
'count': 5, | ||
'byday': ['-1SU', '2MO'], | ||
// 'bymonth': 10, | ||
'bymonth': [10], | ||
}, | ||
); | ||
|
||
testJsonCodec( | ||
'Every other month on the 1st, 15th & last day, until Tuesday, October 1, 2013 12:00:00 AM', | ||
RecurrenceRule( | ||
frequency: Frequency.monthly, | ||
interval: 2, | ||
byMonthDays: {1, 15, -1}, | ||
until: DateTime.utc(2013, 10, 1), | ||
), | ||
json: <String, dynamic>{ | ||
'freq': 'MONTHLY', | ||
// 'until': '2013-10-01' | ||
'until': '2013-10-01T00:00:00', | ||
'interval': 2, | ||
// 'bymonthday': [1, 15, -1], | ||
'bymonthday': [-1, 1, 15], | ||
}, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters