Skip to content

Commit 3da3ecb

Browse files
committed
Refactor RecurrenceSet API, closes #131
This implements a major refactoring of the RecurrenceSet API. The previous implementations have been removed / deprecated.
1 parent 001095f commit 3da3ecb

File tree

69 files changed

+2941
-1894
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2941
-1894
lines changed

.gitignore

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,7 @@
66
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
77

88
# User-specific stuff:
9-
.idea/workspace.xml
10-
.idea/tasks.xml
11-
.idea/dictionaries
12-
.idea/vcs.xml
13-
.idea/jsLibraryMappings.xml
14-
15-
# Sensitive or high-churn files:
16-
.idea/dataSources.ids
17-
.idea/dataSources.xml
18-
.idea/dataSources.local.xml
19-
.idea/sqlDataSources.xml
20-
.idea/dynamic.xml
21-
.idea/uiDesigner.xml
22-
23-
# Gradle:
24-
.idea/gradle.xml
25-
.idea/libraries
26-
27-
# Mongo Explorer plugin:
28-
.idea/mongoSettings.xml
9+
.idea
2910

3011
## File-based project format:
3112
*.iws

.idea/codeStyleSettings.xml

Lines changed: 0 additions & 9 deletions
This file was deleted.

.idea/compiler.xml

Lines changed: 0 additions & 21 deletions
This file was deleted.

.idea/copyright/Apache.xml

Lines changed: 0 additions & 6 deletions
This file was deleted.

.idea/copyright/profiles_settings.xml

Lines changed: 0 additions & 3 deletions
This file was deleted.

.idea/misc.xml

Lines changed: 0 additions & 6 deletions
This file was deleted.

.idea/modules.xml

Lines changed: 0 additions & 23 deletions
This file was deleted.

README.md

Lines changed: 92 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -27,135 +27,144 @@ The iterator has support for [RSCALE](https://tools.ietf.org/html/rfc7529). At t
2727

2828
RSCALE is supported in all RFC2445 and RFC5545 modes.
2929

30-
## Example code
30+
## Recurrence Set API
3131

32-
### Iterating instances
32+
In addition to interpreting recurrence rules, this library provides a set of classes to determine the result of any combination of rrules, rdates and exdates (and exrules, for that matter) as specified in RFC 5545.
3333

34-
The basic use case is to iterate over all instances of a given rule starting on a specific day. Note that some rules may recur forever. In that case you must limit the number of instances in order to avoid an infinite loop.
34+
Version 0.16.0 introduces a new API that is slightly different from the previous one. The new API fixes a few design issues that
35+
made the code more complex than necessary.
3536

36-
The following code iterates over the instances of a recurrence rule:
37+
There is a new interface called `RecurrenceSet` that is implemented by a couple of adapters, decorators and composites. A `RecurrenceSet`
38+
represents the set of occurrences of a recurrence rule or list or any combination of them (including exclusions).
3739

38-
39-
```java
40-
DateTime start = RecurrenceRuleIterator it = rule.iterator(start);
40+
`RecurrenceSet` extends the `Iterable` interface, so it can be used with any `Iterable` decorator from the jems2 library and in `for` loops.
4141

42-
int maxInstances = 100; // limit instances for rules that recur forever
42+
### Iterating RRules
4343

44-
while (it.hasNext() && (!rule.isInfinite() || maxInstances-- > 0))
45-
{
46-
DateTime nextInstance = it.nextDateTime();
47-
// do something with nextInstance
48-
}
49-
```
44+
The most common use case is probably just iterating the occurrences of recurrence rules. Although you still can do this using the `RecurrenceRuleIterator`
45+
returned by `RecurrenceRule.iterator(DateTime)`, you may be better off using the `OfRule` adapter that implements the regular
46+
`Iterable` interface.
5047

51-
### Iterating Recurrence Sets
48+
#### Examples
5249

53-
This library also supports processing of EXRULEs, RDATEs and EXDATEs, i.e. complete recurrence sets.
50+
```java
51+
RecurrenceSet occurrences = new OfRule(rrule, startDate);
52+
```
5453

55-
In order to iterate a recurrence set you first compose the set from its components:
54+
You can combine this with the `First` or `While` decorators from the jems2 library to guard against infinite rules and use it to
55+
loop over the occurrences.
5656

5757
```java
58-
RecurrenceRule rule = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5");
59-
60-
DateTime firstInstance = new DateTime(1982, 4 /* 0-based month numbers! */,23);
58+
for (DateTime occurrence:new First<>(1000, // iterate at most/the first 1000 occurrences
59+
new OfRule(rrule, startDate))) {
60+
// do something with occurrence
61+
}
62+
```
6163

62-
for (DateTime instance:new RecurrenceSet(firstInstance, new RuleInstances(rule))) {
63-
// do something with instance
64+
```java
65+
for (DateTime occurrence:new While<>(endDate::isAfter, // stop at "endDate"
66+
new OfRule(rrule, startDate))) {
67+
// do something with occurrence
6468
}
6569
```
6670

67-
`RecurrenceSet` takes two `InstanceIterable` arguments the first one is expected to iterate the actual
68-
occurrences, the second, optional one iterates exceptions:
71+
#### Handling first instances that don't match the RRULE
72+
73+
Note that `OfRule` does not iterate the start date if it doesn't match the RRULE. If you want to
74+
iterate any non-synchronized first date, use `OfRuleAndFirst` instead!
6975

7076
```java
71-
RecurrenceRule rule = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5");
77+
new OfRule(
78+
new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=24;BYMONTH=5"),
79+
DateTime.parse("19820523"))
80+
```
81+
results in
82+
```
83+
19820524,19830524,19840524,19850524…
84+
```
85+
Note that `19820523` is not among the results because it doesn't match the rule as it doesn't fall on the 24th.
7286

73-
DateTime firstInstance = new DateTime(1982, 4 /* 0-based month numbers! */,23);
87+
However,
7488

75-
for (DateTime instance:
76-
new RecurrenceSet(firstInstance,
77-
new RuleInstances(rule),
78-
new InstanceList(exceptions))) {
79-
// do something with instance
80-
}
89+
```java
90+
new OfRuleAndFirst(
91+
new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=24;BYMONTH=5"),
92+
DateTime.parse("19820523"))
93+
```
94+
results in
95+
```
96+
19820523,19820524,19830524,19840524,19850524…
8197
```
8298

83-
You can compose multiple rules or `InstanceList`s using `Composite` like this
99+
### Iterating RDates and ExDates
84100

85-
```java
86-
RecurrenceRule rule1 = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5");
87-
RecurrenceRule rule2 = new RecurrenceRule("FREQ=MONTHLY;BYMONTHDAY=20");
101+
Similarly, iterating comma separated Date or DateTime lists (i.e. `RDATE` and `EXDATE` ) can be done with the `OfList` adapter.
88102

89-
DateTime firstInstance = new DateTime(1982, 4 /* 0-based month numbers! */,23);
103+
#### Example
90104

91-
for (DateTime instance:
92-
new RecurrenceSet(firstInstance,
93-
new Composite(new RuleInstances(rule1), new RuleInstances(rule2)),
94-
new InstanceList(exceptions))) {
95-
// do something with instance
105+
```java
106+
for (DateTime occurrence:new OfList(timeZone, rdates)) {
107+
// do something with occurrence
96108
}
97109
```
98110

99-
or simply by providing a `List` of `InstanceIterable`s:
111+
### Combining multiple Rules and/or Lists
100112

101-
```java
102-
RecurrenceRule rule1 = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5");
103-
RecurrenceRule rule2 = new RecurrenceRule("FREQ=MONTHLY;BYMONTHDAY=20");
113+
You can merge the occurrences of multiple sets with the `Merged` class. A `Merged` `RecurrenceSet` iterates the occurrences
114+
of all given `RecurrenceSet`s in chronological order.
104115

105-
DateTime firstInstance = new DateTime(1982, 4 /* 0-based month numbers! */,23);
116+
#### Example
106117

107-
for (DateTime instance:
108-
new RecurrenceSet(firstInstance,
109-
List.of(new RuleInstances(rule1), new RuleInstances(rule2)),
110-
new InstanceList(exceptions))) {
111-
// do something with instance
112-
}
118+
```java
119+
RecurrenceSet merged = new Merged(
120+
new OfRule(rule, start),
121+
new OfList(timezone, rdates)
122+
);
113123
```
114124

115-
#### Handling first instances that don't match the RRULE
125+
The result iterates the occurrences of both, the rule and the rdates in chronological order.
126+
127+
### Excluding Exceptions
116128

117-
Note that `RuleInstances` does not iterate the start date if it doesn't match the RRULE. If you want to
118-
iterate any non-synchronized first date, use `FirstAndRuleInstances` instead!
129+
Exceptions can be excluded by composing occurrences and exceptions using `WithExceptions` like in
119130

120131
```java
121-
new RecurrenceSet(DateTime.parse("19820523"),
122-
new RuleInstances(
123-
new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=24;BYMONTH=5")))) {
124-
// do something with instance
125-
}
126-
```
127-
results in
132+
RecurrenceSet withoutExceptions = new WithExceptions(
133+
new OfRule(rule, start),
134+
new OfList(timezone, exdates));
128135
```
129-
19820524,19830524,19840524,19850524…
130-
```
131-
Note that `19820523` is not among the results.
132136

133-
However,
137+
This `RecurrenceSet` contains all the occurrences iterated by the given rule, except those in the exdates list. Note that these must be exact matches,
138+
i.e. the exdate `20240216` does *not* result in the exclusion of `20240216T120000` nor of `20240216T000000`.
139+
140+
### Fast forwarding
141+
142+
Sometimes you might want to skip all the instances prior to a given date. This can be achieved by applying the `FastForwarded` decorator like in
134143

135144
```java
136-
new RecurrenceSet(DateTime.parse("19820523"),
137-
new RuleInstances(
138-
new FirstAndRuleInstances("FREQ=YEARLY;BYMONTHDAY=24;BYMONTH=5")))) {
139-
// do something with instance
140-
}
141-
```
142-
results in
143-
```
144-
19820523,19820524,19830524,19840524,19850524…
145+
RecurrenceSet merged = new FastForwarded(
146+
fastForwardToDate,
147+
new Merged(
148+
new OfRule(rule, start),
149+
new OfList(timezone, rdates)));
145150
```
146151

152+
Note, that `new FastForwarded(fastForwardTo, new OfRule(rrule, start))` and `new OfRule(rrule, fastForwardTo)` are not necessarily the same
153+
set of occurrences.
154+
155+
147156

148-
#### Dealing with infinite rules
157+
### Dealing with infinite rules
149158

150-
Be aware that RRULEs are infinite if they specify neither `COUNT` nor `UNTIL`. This might easily result in an infinite loop when you just iterate over the recurrence set like above.
159+
Be aware that RRULEs are infinite if they specify neither `COUNT` nor `UNTIL`. This might easily result in an infinite loop if not taken care of.
151160

152-
One way to address this is by adding a decorator like `First` from the `jems2` library:
161+
As stated above, a simple way to deal with this is by applying a decorator like `First` or `While` from the jems2 library:
153162

154163
```java
155164
RecurrenceRule rule = new RecurrenceRule("FREQ=YEARLY;BYMONTHDAY=23;BYMONTH=5");
156-
DateTime firstInstance = new DateTime(1982, 4 /* 0-based month numbers! */,23);
157-
for (DateTime instance: new First(1000, new RecurrenceSet(firstInstance, new RuleInstances(rule)))) {
158-
// do something with instance
165+
DateTime start = new DateTime(1982, 4 /* 0-based month numbers! */,23);
166+
for (DateTime occurrence: new First<>(1000, new OfRule(rule, start))) {
167+
// do something with occurrence
159168
}
160169
```
161170

@@ -233,4 +242,4 @@ There are at least two other implentations of recurrence iterators for Java:
233242

234243
## License
235244

236-
Copyright (c) Marten Gajda 2022, licensed under Apache2.
245+
Copyright (c) Marten Gajda 2024, licensed under Apache2.

0 commit comments

Comments
 (0)