Skip to content

Commit 5ba0c83

Browse files
authored
Add limiting RecurrenceSet decorators, closes #124 (#136)
1 parent 8ee6f28 commit 5ba0c83

File tree

12 files changed

+585
-9
lines changed

12 files changed

+585
-9
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ jobs:
3232
run: git push --tags
3333

3434
- name: Upload Test Results
35-
uses: codecov/codecov-action@v3
35+
uses: codecov/codecov-action@v4
3636
with:
3737
fail_ci_if_error: true # optional (default = false)
38-
verbose: true # optional (default = false)
38+
verbose: true # optional (default = false)
39+
token: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/pr.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ jobs:
2222
run: ./gradlew gitVersion check javadoc -P GITHUB_API_TOKEN=${{ secrets.GITHUB_TOKEN }}
2323

2424
- name: Upload Test Results
25-
uses: codecov/codecov-action@v3
25+
uses: codecov/codecov-action@v4
2626
with:
2727
fail_ci_if_error: true # optional (default = false)
28-
verbose: true # optional (default = false)
28+
verbose: true # optional (default = false)
29+
token: ${{ secrets.CODECOV_TOKEN }}

README.md

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
[![Build](https://github.com/dmfs/lib-recur/actions/workflows/main.yml/badge.svg?label=main)](https://github.com/dmfs/lib-recur/actions/workflows/main.yml)
2-
[![codecov](https://codecov.io/gh/dmfs/lib-recur/branch/main/graph/badge.svg)](https://codecov.io/gh/dmfs/lib-recur)
1+
[![Build](https://github.com/dmfs/lib-recur/actions/workflows/main.yml/badge.svg?label=main)](https://github.com/dmfs/lib-recur/actions/workflows/main.yml)
2+
[![codecov](https://codecov.io/gh/dmfs/lib-recur/branch/main/graph/badge.svg)](https://codecov.io/gh/dmfs/lib-recur)
3+
[![Confidence](https://img.shields.io/badge/Tested_with-Confidence-800000?labelColor=white)](https://saynotobugs.org/confidence)
34

45
# lib-recur
56

@@ -152,8 +153,6 @@ RecurrenceSet merged = new FastForwarded(
152153
Note, that `new FastForwarded(fastForwardTo, new OfRule(rrule, start))` and `new OfRule(rrule, fastForwardTo)` are not necessarily the same
153154
set of occurrences.
154155

155-
156-
157156
### Dealing with infinite rules
158157

159158
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.
@@ -170,6 +169,37 @@ for (DateTime occurrence:new First<>(1000, new OfRule(rule, start))) {
170169

171170
This will always stop iterating after at most 1000 instances.
172171

172+
### Limiting RecurrenceSets
173+
174+
You can limit a `RecurrenceSet` to the instances that precede a certain `DateTime`
175+
using the `Preceding` decorator. This can also serve as a way to handle infinite rules:
176+
177+
```java
178+
RecurrenceRule rule = new RecurrenceRule("FREQ=MONTHLY;BYMONTHDAY=23");
179+
DateTime start = new DateTime(1982, 4 /* 0-based month numbers! */,23);
180+
for (DateTime occurrence:new Preceding<>(
181+
new DateTime(1983, 0, 1), // all instances before 1983
182+
new OfRule(rule, start))) {
183+
// do something with occurrence
184+
}
185+
```
186+
187+
The `Within` decorator combines `Preceding` and `FastForwarded` and only iterates
188+
occurrences that fall in the given (right-open) interval.
189+
190+
```java
191+
// a RecurrenceSet that only contains occurrences in 2024
192+
// (assuming the original iterates all-day values)
193+
RecurrenceSet occurrencesOf2024 = new Within(
194+
DateTime.parse("20240101"),
195+
DateTime.parse("20250101"),
196+
recurrenceSet
197+
);
198+
```
199+
200+
Note, in both cases you must take care that the dates you supply have the same format (floating vs all-day vs absolute)
201+
as the occurrences of your recurrence set.
202+
173203
### Determining the last instance of a RecurrenceSet
174204

175205
Finite, non-empty `RecurrenceSet`s have a last instance that can be determined with the `LastInstance` adapter.

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ if (project.hasProperty('SONATYPE_USERNAME') && project.hasProperty('SONATYPE_PA
7171
}
7272

7373
dependencies {
74+
compileOnly libs.srcless.annotations
75+
annotationProcessor libs.srcless.processors
7476
compileOnly 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.2.600'
7577
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
7678
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
@@ -80,7 +82,8 @@ dependencies {
8082
testImplementation project("lib-recur-confidence")
8183
testImplementation libs.jems2.testing
8284
testImplementation libs.jems2.confidence
83-
testImplementation 'org.saynotobugs:confidence-core:0.42.0'
85+
testImplementation libs.confidence.core
86+
testImplementation libs.confidence.engine
8487
}
8588

8689

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jems2-confidence = { module = "org.dmfs:jems2-confidence", version.ref = "jems2"
2222

2323
confidence-core = { module = "org.saynotobugs:confidence-core", version.ref = "confidence" }
2424
confidence-test = { module = "org.saynotobugs:confidence-test", version.ref = "confidence" }
25+
confidence-engine = { module = "org.saynotobugs:confidence-incubator", version.ref = "confidence" }
2526

2627
[bundles]
2728
srcless-processors = ["srcless-processors", "nullless-processors"]

src/main/java/org/dmfs/rfc5545/RecurrenceSet.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
package org.dmfs.rfc5545;
1919

2020

21+
import org.dmfs.srcless.annotations.composable.Composable;
22+
2123
/**
2224
* A set of instances.
2325
*/
26+
@Composable
2427
public interface RecurrenceSet extends Iterable<DateTime>
2528
{
2629
/**
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.dmfs.rfc5545.instanceiterator;
2+
3+
import org.dmfs.rfc5545.DateTime;
4+
import org.dmfs.rfc5545.InstanceIterator;
5+
6+
import java.util.NoSuchElementException;
7+
8+
public final class PeekableInstanceIterator implements InstanceIterator
9+
{
10+
private final InstanceIterator mDelegate;
11+
private DateTime mNext;
12+
private boolean mHasNext;
13+
14+
public PeekableInstanceIterator(InstanceIterator delegate)
15+
{
16+
mDelegate = delegate;
17+
pullNext();
18+
}
19+
20+
@Override
21+
public void fastForward(DateTime until)
22+
{
23+
if (mHasNext && mNext.before(until))
24+
{
25+
mDelegate.fastForward(until);
26+
pullNext();
27+
}
28+
}
29+
30+
@Override
31+
public boolean hasNext()
32+
{
33+
return mHasNext;
34+
}
35+
36+
@Override
37+
public DateTime next()
38+
{
39+
if (!mHasNext)
40+
{
41+
throw new NoSuchElementException("no further elements to return");
42+
}
43+
DateTime result = mNext;
44+
pullNext();
45+
return result;
46+
}
47+
48+
public DateTime peek()
49+
{
50+
if (!mHasNext)
51+
{
52+
throw new NoSuchElementException("no further elements to peek at");
53+
}
54+
return mNext;
55+
}
56+
57+
private void pullNext()
58+
{
59+
mHasNext = mDelegate.hasNext();
60+
if (mHasNext)
61+
{
62+
mNext = mDelegate.next();
63+
}
64+
65+
}
66+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.dmfs.rfc5545.recurrenceset;
2+
3+
import org.dmfs.rfc5545.DateTime;
4+
import org.dmfs.rfc5545.InstanceIterator;
5+
import org.dmfs.rfc5545.RecurrenceSet;
6+
import org.dmfs.rfc5545.instanceiterator.PeekableInstanceIterator;
7+
8+
import java.util.NoSuchElementException;
9+
10+
/**
11+
* A {@link RecurrenceSet} of all elements of another {@link RecurrenceSet} that precede a
12+
* given {@link DateTime}.
13+
* A {@link Preceding} {@link RecurrenceSet} is always finite.
14+
*
15+
* <h2>Example</h2>
16+
* <pre>{@code
17+
* // a RecurrenceSet that only contains past occurrences.
18+
* new Preceding(DateTime.now(), recurrenceSet());
19+
* }</pre>
20+
*/
21+
public final class Preceding implements RecurrenceSet
22+
{
23+
private final DateTime mBoundary;
24+
private final RecurrenceSet mDelegate;
25+
26+
public Preceding(DateTime boundary, RecurrenceSet delegate)
27+
{
28+
mBoundary = boundary;
29+
mDelegate = delegate;
30+
}
31+
32+
@Override
33+
public InstanceIterator iterator()
34+
{
35+
PeekableInstanceIterator delegate = new PeekableInstanceIterator(mDelegate.iterator());
36+
return new InstanceIterator()
37+
{
38+
@Override
39+
public void fastForward(DateTime until)
40+
{
41+
delegate.fastForward(until);
42+
}
43+
44+
@Override
45+
public boolean hasNext()
46+
{
47+
return delegate.hasNext() && delegate.peek().before(mBoundary);
48+
}
49+
50+
@Override
51+
public DateTime next()
52+
{
53+
DateTime result = delegate.next();
54+
if (!result.before(mBoundary))
55+
{
56+
throw new NoSuchElementException("No more elements");
57+
}
58+
return result;
59+
}
60+
};
61+
}
62+
63+
@Override
64+
public boolean isInfinite()
65+
{
66+
return false;
67+
}
68+
69+
@Override
70+
public boolean isFinite()
71+
{
72+
return true;
73+
}
74+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.dmfs.rfc5545.recurrenceset;
2+
3+
import org.dmfs.rfc5545.DateTime;
4+
import org.dmfs.rfc5545.RecurrenceSet;
5+
import org.dmfs.rfc5545.RecurrenceSetComposition;
6+
7+
/**
8+
* {@link RecurrenceSet} of the elements of another {@link RecurrenceSet} that fall
9+
* in the given right-open interval of time.
10+
* A {@link Within} {@link RecurrenceSet} is always finite.
11+
*
12+
* <h2>Example</h2>
13+
* <pre>{@code
14+
* // every occurrence in 2024 (UTC)
15+
* new Within(
16+
* DateTime.parse("20240101T000000Z"),
17+
* DateTime.parse("20250101T000000Z"),
18+
* recurrenceSet());
19+
* }</pre>
20+
*/
21+
public final class Within extends RecurrenceSetComposition
22+
{
23+
public Within(DateTime fromIncluding, DateTime toExcluding, RecurrenceSet delegate)
24+
{
25+
super(new Preceding(toExcluding, new FastForwarded(fromIncluding, delegate)));
26+
}
27+
}

0 commit comments

Comments
 (0)