Skip to content

Commit

Permalink
Add limiting RecurrenceSet decorators, closes #124 (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmfs authored Apr 7, 2024
1 parent 8ee6f28 commit 5ba0c83
Show file tree
Hide file tree
Showing 12 changed files with 585 additions and 9 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ jobs:
run: git push --tags

- name: Upload Test Results
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true # optional (default = false)
verbose: true # optional (default = false)
verbose: true # optional (default = false)
token: ${{ secrets.CODECOV_TOKEN }}
5 changes: 3 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
run: ./gradlew gitVersion check javadoc -P GITHUB_API_TOKEN=${{ secrets.GITHUB_TOKEN }}

- name: Upload Test Results
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true # optional (default = false)
verbose: true # optional (default = false)
verbose: true # optional (default = false)
token: ${{ secrets.CODECOV_TOKEN }}
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[![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)
[![codecov](https://codecov.io/gh/dmfs/lib-recur/branch/main/graph/badge.svg)](https://codecov.io/gh/dmfs/lib-recur)
[![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)
[![codecov](https://codecov.io/gh/dmfs/lib-recur/branch/main/graph/badge.svg)](https://codecov.io/gh/dmfs/lib-recur)
[![Confidence](https://img.shields.io/badge/Tested_with-Confidence-800000?labelColor=white)](https://saynotobugs.org/confidence)

# lib-recur

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



### Dealing with infinite rules

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

This will always stop iterating after at most 1000 instances.

### Limiting RecurrenceSets

You can limit a `RecurrenceSet` to the instances that precede a certain `DateTime`
using the `Preceding` decorator. This can also serve as a way to handle infinite rules:

```java
RecurrenceRule rule = new RecurrenceRule("FREQ=MONTHLY;BYMONTHDAY=23");
DateTime start = new DateTime(1982, 4 /* 0-based month numbers! */,23);
for (DateTime occurrence:new Preceding<>(
new DateTime(1983, 0, 1), // all instances before 1983
new OfRule(rule, start))) {
// do something with occurrence
}
```

The `Within` decorator combines `Preceding` and `FastForwarded` and only iterates
occurrences that fall in the given (right-open) interval.

```java
// a RecurrenceSet that only contains occurrences in 2024
// (assuming the original iterates all-day values)
RecurrenceSet occurrencesOf2024 = new Within(
DateTime.parse("20240101"),
DateTime.parse("20250101"),
recurrenceSet
);
```

Note, in both cases you must take care that the dates you supply have the same format (floating vs all-day vs absolute)
as the occurrences of your recurrence set.

### Determining the last instance of a RecurrenceSet

Finite, non-empty `RecurrenceSet`s have a last instance that can be determined with the `LastInstance` adapter.
Expand Down
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ if (project.hasProperty('SONATYPE_USERNAME') && project.hasProperty('SONATYPE_PA
}

dependencies {
compileOnly libs.srcless.annotations
annotationProcessor libs.srcless.processors
compileOnly 'org.eclipse.jdt:org.eclipse.jdt.annotation:2.2.600'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
Expand All @@ -80,7 +82,8 @@ dependencies {
testImplementation project("lib-recur-confidence")
testImplementation libs.jems2.testing
testImplementation libs.jems2.confidence
testImplementation 'org.saynotobugs:confidence-core:0.42.0'
testImplementation libs.confidence.core
testImplementation libs.confidence.engine
}


Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jems2-confidence = { module = "org.dmfs:jems2-confidence", version.ref = "jems2"

confidence-core = { module = "org.saynotobugs:confidence-core", version.ref = "confidence" }
confidence-test = { module = "org.saynotobugs:confidence-test", version.ref = "confidence" }
confidence-engine = { module = "org.saynotobugs:confidence-incubator", version.ref = "confidence" }

[bundles]
srcless-processors = ["srcless-processors", "nullless-processors"]
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/dmfs/rfc5545/RecurrenceSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
package org.dmfs.rfc5545;


import org.dmfs.srcless.annotations.composable.Composable;

/**
* A set of instances.
*/
@Composable
public interface RecurrenceSet extends Iterable<DateTime>
{
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.dmfs.rfc5545.instanceiterator;

import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.InstanceIterator;

import java.util.NoSuchElementException;

public final class PeekableInstanceIterator implements InstanceIterator
{
private final InstanceIterator mDelegate;
private DateTime mNext;
private boolean mHasNext;

public PeekableInstanceIterator(InstanceIterator delegate)
{
mDelegate = delegate;
pullNext();
}

@Override
public void fastForward(DateTime until)
{
if (mHasNext && mNext.before(until))
{
mDelegate.fastForward(until);
pullNext();
}
}

@Override
public boolean hasNext()
{
return mHasNext;
}

@Override
public DateTime next()
{
if (!mHasNext)
{
throw new NoSuchElementException("no further elements to return");
}
DateTime result = mNext;
pullNext();
return result;
}

public DateTime peek()
{
if (!mHasNext)
{
throw new NoSuchElementException("no further elements to peek at");
}
return mNext;
}

private void pullNext()
{
mHasNext = mDelegate.hasNext();
if (mHasNext)
{
mNext = mDelegate.next();
}

}
}
74 changes: 74 additions & 0 deletions src/main/java/org/dmfs/rfc5545/recurrenceset/Preceding.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.dmfs.rfc5545.recurrenceset;

import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.InstanceIterator;
import org.dmfs.rfc5545.RecurrenceSet;
import org.dmfs.rfc5545.instanceiterator.PeekableInstanceIterator;

import java.util.NoSuchElementException;

/**
* A {@link RecurrenceSet} of all elements of another {@link RecurrenceSet} that precede a
* given {@link DateTime}.
* A {@link Preceding} {@link RecurrenceSet} is always finite.
*
* <h2>Example</h2>
* <pre>{@code
* // a RecurrenceSet that only contains past occurrences.
* new Preceding(DateTime.now(), recurrenceSet());
* }</pre>
*/
public final class Preceding implements RecurrenceSet
{
private final DateTime mBoundary;
private final RecurrenceSet mDelegate;

public Preceding(DateTime boundary, RecurrenceSet delegate)
{
mBoundary = boundary;
mDelegate = delegate;
}

@Override
public InstanceIterator iterator()
{
PeekableInstanceIterator delegate = new PeekableInstanceIterator(mDelegate.iterator());
return new InstanceIterator()
{
@Override
public void fastForward(DateTime until)
{
delegate.fastForward(until);
}

@Override
public boolean hasNext()
{
return delegate.hasNext() && delegate.peek().before(mBoundary);
}

@Override
public DateTime next()
{
DateTime result = delegate.next();
if (!result.before(mBoundary))
{
throw new NoSuchElementException("No more elements");
}
return result;
}
};
}

@Override
public boolean isInfinite()
{
return false;
}

@Override
public boolean isFinite()
{
return true;
}
}
27 changes: 27 additions & 0 deletions src/main/java/org/dmfs/rfc5545/recurrenceset/Within.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.dmfs.rfc5545.recurrenceset;

import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.RecurrenceSet;
import org.dmfs.rfc5545.RecurrenceSetComposition;

/**
* {@link RecurrenceSet} of the elements of another {@link RecurrenceSet} that fall
* in the given right-open interval of time.
* A {@link Within} {@link RecurrenceSet} is always finite.
*
* <h2>Example</h2>
* <pre>{@code
* // every occurrence in 2024 (UTC)
* new Within(
* DateTime.parse("20240101T000000Z"),
* DateTime.parse("20250101T000000Z"),
* recurrenceSet());
* }</pre>
*/
public final class Within extends RecurrenceSetComposition
{
public Within(DateTime fromIncluding, DateTime toExcluding, RecurrenceSet delegate)
{
super(new Preceding(toExcluding, new FastForwarded(fromIncluding, delegate)));
}
}
Loading

0 comments on commit 5ba0c83

Please sign in to comment.