Skip to content

Commit

Permalink
Merge pull request #20 from polRk/main
Browse files Browse the repository at this point in the history
Add ready for use RRule instance with caching
  • Loading branch information
JonasWanke authored May 6, 2021
2 parents 20af1fa + ee6803e commit 86945d9
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 2 deletions.
58 changes: 58 additions & 0 deletions lib/src/cache.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:meta/meta.dart';

import 'utils.dart';

@immutable
class CacheKey {
const CacheKey({
required this.start,
this.after,
this.includeAfter = false,
this.before,
this.includeBefore = false,
});

final DateTime start;
final DateTime? after;
final bool includeAfter;
final DateTime? before;
final bool includeBefore;

@override
int get hashCode {
return hashList([
start,
after,
includeAfter,
before,
includeBefore,
]);
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;

return other is CacheKey &&
other.after == after &&
other.includeAfter == includeAfter &&
other.before == before &&
other.includeBefore == includeBefore;
}
}

class Cache {
final Map<CacheKey, List<DateTime>> _results = {};

@visibleForTesting
Map<CacheKey, List<DateTime>> get results => _results;

void add(CacheKey key, List<DateTime> data) {
_results[key]= data;
}

List<DateTime>? get(CacheKey key) {
return _results[key];
}
}
17 changes: 17 additions & 0 deletions lib/src/iteration/iteration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ import 'time_set.dart';
Iterable<DateTime> getRecurrenceRuleInstances(
RecurrenceRule rrule, {
required DateTime start,
DateTime? after,
bool includeAfter = false,
DateTime? before,
bool includeBefore = false,
}) sync* {
assert(start.isValidRruleDateTime);
assert(after.isValidRruleDateTime);
assert(before.isValidRruleDateTime);
if (after != null) assert(after >= start);
if (before != null) assert(before >= start);

rrule = _prepare(rrule, start);

Expand All @@ -44,7 +52,16 @@ Iterable<DateTime> getRecurrenceRuleInstances(

for (final result in results) {
if (rrule.until != null && result > rrule.until!) return;
if (before != null) {
if (!includeBefore && result >= before) return;
if (includeBefore && result > before) return;
}

if (result < start) continue;
if (after != null) {
if (!includeAfter && result <= after) continue;
if (includeAfter && result < after) continue;
}

yield result;
if (count != null) {
Expand Down
63 changes: 61 additions & 2 deletions lib/src/recurrence_rule.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'dart:collection';
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:meta/meta.dart';

import 'by_week_day_entry.dart';
import 'cache.dart';
import 'codecs/string/decoder.dart';
import 'codecs/string/encoder.dart';
import 'codecs/string/string.dart';
Expand Down Expand Up @@ -31,6 +33,7 @@ class RecurrenceRule {
Set<int> byMonths = const {},
Set<int> bySetPositions = const {},
this.weekStart,
this.shouldCacheResults = false,
}) : assert(count == null || count >= 1),
assert(until.isValidRruleDateTime),
assert(until == null || count == null),
Expand Down Expand Up @@ -167,9 +170,65 @@ class RecurrenceRule {
/// Returns [weekStart] or [DateTime.monday] if that is not set.
int get actualWeekStart => weekStart ?? DateTime.monday;

Iterable<DateTime> getInstances({required DateTime start}) {
final bool shouldCacheResults;

final Cache _cache = Cache();

@visibleForTesting
Cache get cache => _cache;

Iterable<DateTime> getInstances({
required DateTime start,
DateTime? after,
bool includeAfter = false,
DateTime? before,
bool includeBefore = false,
}) {
assert(start.isValidRruleDateTime);
return getRecurrenceRuleInstances(this, start: start);
assert(after.isValidRruleDateTime);
assert(before.isValidRruleDateTime);

return getRecurrenceRuleInstances(
this,
start: start,
after: after,
includeAfter: includeAfter,
before: before,
includeBefore: includeBefore,
);
}

List<DateTime> getAllInstances({
required DateTime start,
DateTime? after,
bool includeAfter = false,
DateTime? before,
bool includeBefore = false,
}) {
final key = CacheKey(
start: start,
after: after,
includeAfter: includeAfter,
before: before,
includeBefore: includeBefore,
);

final fromCache = _cache.get(key);
if (fromCache != null) return fromCache;

final results = getInstances(
start: start,
after: after,
includeAfter: includeAfter,
before: before,
includeBefore: includeBefore,
).toList(growable: false);

if (shouldCacheResults) {
_cache.add(key, results);
}

return results;
}

@override
Expand Down
22 changes: 22 additions & 0 deletions test/cache_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:rrule/src/cache.dart';
import 'package:test/test.dart';

void main() {
test('should add data to the cache', () {
final cache = Cache();
final key = CacheKey(start: DateTime.now());
final results = <DateTime>[];
cache.add(key, results);

expect(cache.results, containsPair(key, results));
});

test('should return data from the cache by key', () {
final cache = Cache();
final key = CacheKey(start: DateTime.now());
final results = <DateTime>[];
cache.add(key, results);

expect(cache.get(key), results);
});
}
74 changes: 74 additions & 0 deletions test/recurrence_rule_test.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:rrule/rrule.dart';
import 'package:rrule/src/cache.dart';
import 'package:test/test.dart';

void main() {
Expand Down Expand Up @@ -49,6 +50,79 @@ void main() {
});
});

group('cache', () {
test('should store instances in the cache', () {
final rrule = RecurrenceRule(
frequency: Frequency.monthly,
until: DateTime.utc(2021),
shouldCacheResults: true,
);
final instances = rrule.getAllInstances(start: DateTime.utc(2020));

expect(rrule.cache.get(CacheKey(start: DateTime.utc(2020))), instances);
});
});

group('getAllInstances', () {
test('should support date after inclusive', () {
final rrule = RecurrenceRule(
frequency: Frequency.monthly,
until: DateTime.utc(2021),
);

final instances = rrule.getAllInstances(
start: DateTime.utc(2020),
after: DateTime.utc(2020, 5),
includeAfter: true,
);

expect(instances.first, DateTime.utc(2020, 5));
});

test('should support date after exclusive', () {
final rrule = RecurrenceRule(
frequency: Frequency.monthly,
until: DateTime.utc(2021),
);

final instances = rrule.getAllInstances(
start: DateTime.utc(2020),
after: DateTime.utc(2020, 5),
);

expect(instances.first, DateTime.utc(2020, 6));
});

test('should support date before inclusive', () {
final rrule = RecurrenceRule(
frequency: Frequency.monthly,
until: DateTime.utc(2021),
);

final instances = rrule.getAllInstances(
start: DateTime.utc(2020),
before: DateTime.utc(2020, 5),
includeBefore: true,
);

expect(instances.last, DateTime.utc(2020, 5));
});

test('should support date before exclusive', () {
final rrule = RecurrenceRule(
frequency: Frequency.monthly,
until: DateTime.utc(2021),
);

final instances = rrule.getAllInstances(
start: DateTime.utc(2020),
before: DateTime.utc(2020, 5),
);

expect(instances.last, DateTime.utc(2020, 4));
});
});

test(
'#19: No warning for creating a RRULE with BYWEEKNO, but with non-YEARLY frequency',
() {
Expand Down

0 comments on commit 86945d9

Please sign in to comment.