From 4572ed6d3fa5bc7cf816dde2e288d8ae2c0c9a3d Mon Sep 17 00:00:00 2001 From: Paolo Lammens Date: Wed, 26 Apr 2023 15:26:48 +0200 Subject: [PATCH] feat: Text encoding for BYHOUR, BYMINUTE, BYSECOND --- lib/src/codecs/text/encoder.dart | 30 +++++++++++++++++++++++++++--- lib/src/codecs/text/l10n/en.dart | 19 +++++++++++++++++++ lib/src/codecs/text/l10n/l10n.dart | 4 ++++ lib/src/recurrence_rule.dart | 5 +---- test/codecs/text/daily_test.dart | 16 ++++++++++++++++ 5 files changed, 67 insertions(+), 7 deletions(-) diff --git a/lib/src/codecs/text/encoder.dart b/lib/src/codecs/text/encoder.dart index ac0ee03..5f25426 100644 --- a/lib/src/codecs/text/encoder.dart +++ b/lib/src/codecs/text/encoder.dart @@ -120,7 +120,9 @@ class RecurrenceRuleToTextEncoder extends Converter { combination: input.hasByWeekDays ? ListCombination.disjunctive : ListCombination.conjunctiveShort, - )); + )) + // [at 8:00 & 12:00] + ..add(_formatByTime(input)); } void _convertWeekly(RecurrenceRule input, StringBuffer output) { @@ -137,7 +139,9 @@ class RecurrenceRuleToTextEncoder extends Converter { variant: input.hasBySetPositions ? InOnVariant.instanceOf : InOnVariant.simple, - )); + )) + // [at 8:00 & 12:00] + ..add(_formatByTime(input)); } void _convertMonthly(RecurrenceRule input, StringBuffer output) { @@ -176,7 +180,9 @@ class RecurrenceRuleToTextEncoder extends Converter { combination: input.hasByWeekDays ? ListCombination.disjunctive : ListCombination.conjunctiveShort, - )); + )) + // [at 8:00 & 12:00] + ..add(_formatByTime(input)); } void _convertYearly(RecurrenceRule input, StringBuffer output) { @@ -283,6 +289,8 @@ class RecurrenceRuleToTextEncoder extends Converter { if (limits.isNotEmpty) { output.add(l10n.list(limits, ListCombination.conjunctiveLong)); } + + output.add(_formatByTime(input)); } String? _formatBySetPositions(RecurrenceRule input) { @@ -378,6 +386,22 @@ class RecurrenceRuleToTextEncoder extends Converter { variant: variant, ); } + + String? _formatByTime(RecurrenceRule input) { + final byHours = input.hasByHours ? input.byHours : {null}; + final byMinutes = input.hasByMinutes ? input.byMinutes : {null}; + final bySeconds = input.hasBySeconds ? input.bySeconds : {null}; + + final timesOfDay = [ + for (var hour in byHours) + for (var minute in byMinutes) + for (var second in bySeconds) + if (!(hour == null && minute == null && second == null)) + l10n.timeOfDay(hour, minute, second) + ]; + + return timesOfDay.isEmpty ? null : l10n.atTimesOfDay(timesOfDay); + } } extension on StringBuffer { diff --git a/lib/src/codecs/text/l10n/en.dart b/lib/src/codecs/text/l10n/en.dart index 3cda0d7..8b90417 100644 --- a/lib/src/codecs/text/l10n/en.dart +++ b/lib/src/codecs/text/l10n/en.dart @@ -181,4 +181,23 @@ class RruleL10nEn extends RruleL10n { return number < 0 ? '$string-to-last' : string; } + + @override + String timeOfDay(int? hour, int? minute, int? second) { + final minutesFormat = NumberFormat('00'); + final secondsFormat = minutesFormat; + + final parts = [ + if (hour != null) hour.toString(), + if (minute != null) ':${minutesFormat.format(minute)}', + if (minute == null && second != null) ':', + if (second != null) ':${secondsFormat.format(second)}' + ]; + return parts.join(); + } + + @override + String atTimesOfDay(List timesOfDay) { + return 'at ${list(timesOfDay, ListCombination.conjunctiveShort)}'; + } } diff --git a/lib/src/codecs/text/l10n/l10n.dart b/lib/src/codecs/text/l10n/l10n.dart index 98d2b1f..558e2df 100644 --- a/lib/src/codecs/text/l10n/l10n.dart +++ b/lib/src/codecs/text/l10n/l10n.dart @@ -103,6 +103,10 @@ abstract class RruleL10n { } String ordinal(int number); + + String timeOfDay(int? hour, int? minute, int? second); + + String atTimesOfDay(List timesOfDay); } enum InOnVariant { simple, also, instanceOf } diff --git a/lib/src/recurrence_rule.dart b/lib/src/recurrence_rule.dart index 6f29cec..48da989 100644 --- a/lib/src/recurrence_rule.dart +++ b/lib/src/recurrence_rule.dart @@ -363,13 +363,10 @@ class RecurrenceRule { /// Whether this rule can be converted to a human-readable string. /// - /// - Unsupported attributes: [bySeconds], [byMinutes], [byHours] /// - Unsupported frequencies (if any by-parts are specified): /// [Frequency.secondly], [Frequency.hourly], [Frequency.daily] bool get canFullyConvertToText { - if (hasBySeconds || hasByMinutes || hasByHours) { - return false; - } else if (frequency <= Frequency.daily) { + if (frequency <= Frequency.daily) { return true; } else if (hasBySetPositions || hasBySeconds || diff --git a/test/codecs/text/daily_test.dart b/test/codecs/text/daily_test.dart index f168338..4c0c1cb 100644 --- a/test/codecs/text/daily_test.dart +++ b/test/codecs/text/daily_test.dart @@ -107,4 +107,20 @@ void main() { string: 'RRULE:FREQ=DAILY;BYSETPOS=1,-2;BYMONTH=1,12;BYMONTHDAY=1,-1;BYDAY=MO,TH', ); + + // Time of day. + + testText( + 'Daily at 8:30 & 12:30', + string: 'RRULE:FREQ=DAILY;BYHOUR=8,12;BYMINUTE=30', + ); + + test('canConvertToText is true when byHours is specified', () async { + final rrule = RecurrenceRule(frequency: Frequency.daily, byHours: {8}); + expect(rrule.canFullyConvertToText, isTrue); + expect( + rrule.toText(l10n: await RruleL10nEn.create()), + equals('Daily at 8'), + ); + }); }