diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ead009f..02e5b03 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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) \ No newline at end of file + verbose: true # optional (default = false) + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ef74b75..2dceb61 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -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) \ No newline at end of file + verbose: true # optional (default = false) + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index aec6d5b..b0c52fb 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. @@ -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. diff --git a/build.gradle b/build.gradle index 4f5ed49..b9150eb 100644 --- a/build.gradle +++ b/build.gradle @@ -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' @@ -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 } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cc9bb57..784ede0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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"] diff --git a/src/main/java/org/dmfs/rfc5545/RecurrenceSet.java b/src/main/java/org/dmfs/rfc5545/RecurrenceSet.java index ded2141..614fadc 100644 --- a/src/main/java/org/dmfs/rfc5545/RecurrenceSet.java +++ b/src/main/java/org/dmfs/rfc5545/RecurrenceSet.java @@ -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 { /** diff --git a/src/main/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIterator.java b/src/main/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIterator.java new file mode 100644 index 0000000..e42c018 --- /dev/null +++ b/src/main/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIterator.java @@ -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(); + } + + } +} diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/Preceding.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/Preceding.java new file mode 100644 index 0000000..08e2431 --- /dev/null +++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/Preceding.java @@ -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. + * + *

Example

+ *
{@code
+ * // a RecurrenceSet that only contains past occurrences.
+ * new Preceding(DateTime.now(), recurrenceSet());
+ * }
+ */ +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; + } +} diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/Within.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/Within.java new file mode 100644 index 0000000..4e9cb52 --- /dev/null +++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/Within.java @@ -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. + * + *

Example

+ *
{@code
+ * // every occurrence in 2024 (UTC)
+ * new Within(
+ *   DateTime.parse("20240101T000000Z"),
+ *   DateTime.parse("20250101T000000Z"),
+ *   recurrenceSet());
+ * }
+ */ +public final class Within extends RecurrenceSetComposition +{ + public Within(DateTime fromIncluding, DateTime toExcluding, RecurrenceSet delegate) + { + super(new Preceding(toExcluding, new FastForwarded(fromIncluding, delegate))); + } +} diff --git a/src/test/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIteratorTest.java b/src/test/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIteratorTest.java new file mode 100644 index 0000000..6d95f7f --- /dev/null +++ b/src/test/java/org/dmfs/rfc5545/instanceiterator/PeekableInstanceIteratorTest.java @@ -0,0 +1,189 @@ +package org.dmfs.rfc5545.instanceiterator; + +import org.dmfs.jems2.ThrowingFunction; +import org.dmfs.rfc5545.DateTime; +import org.dmfs.rfc5545.recurrenceset.OfList; +import org.junit.jupiter.api.Test; +import org.saynotobugs.confidence.description.Text; +import org.saynotobugs.confidence.quality.object.Throwing; + +import java.util.NoSuchElementException; + +import static org.saynotobugs.confidence.Assertion.assertThat; +import static org.saynotobugs.confidence.quality.Core.*; + +class PeekableInstanceIteratorTest +{ + @Test + void testEmpty() + { + assertThat(() -> new PeekableInstanceIterator(new EmptyIterator()), + allOf( + mutatedBy( + new Text("nothing"), + testee -> {}, + soIt(allOf(not(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext"))), + has((ThrowingFunction) iterator -> iterator::next, throwing(NoSuchElementException.class)), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))) + )); + } + + + @Test + void test() + { + assertThat(() -> new PeekableInstanceIterator(new OfList("20240406,20240408,20240410,20240412").iterator()), + allOf( + mutatedBy( + new Text("nothing"), + testee -> {}, + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has(PeekableInstanceIterator::next, equalTo(DateTime.parse("20240406"))), + has(PeekableInstanceIterator::peek, equalTo(DateTime.parse("20240408")))))), + mutatedBy( + new Text("pulling one element"), + testee -> { + testee.peek(); + testee.hasNext(); + testee.next(); + }, + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has(PeekableInstanceIterator::next, equalTo(DateTime.parse("20240408"))), + has(PeekableInstanceIterator::peek, equalTo(DateTime.parse("20240410")))))), + mutatedBy( + new Text("pulling two elements"), + testee -> { + testee.next(); + testee.next(); + }, + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has(PeekableInstanceIterator::next, equalTo(DateTime.parse("20240410"))), + has(PeekableInstanceIterator::peek, equalTo(DateTime.parse("20240412")))))), + mutatedBy( + new Text("pulling three elements"), + testee -> { + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + }, + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has((ThrowingFunction) PeekableInstanceIterator::next, equalTo(DateTime.parse("20240412"))), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))), + mutatedBy( + new Text("pulling four elements"), + testee -> { + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + }, + soIt(allOf(not(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext"))), + has((ThrowingFunction) iterator -> iterator::next, throwing(NoSuchElementException.class)), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))) + )); + } + + + @Test + void testFastForward() + { + assertThat(() -> new PeekableInstanceIterator(new OfList("20240406,20240408,20240410,20240412").iterator()), + allOf( + mutatedBy( + new Text("fast forwarding to element before first"), + testee -> testee.fastForward(DateTime.parse("20240210")), + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has(PeekableInstanceIterator::next, equalTo(DateTime.parse("20240406"))), + has(PeekableInstanceIterator::peek, equalTo(DateTime.parse("20240408")))))), + mutatedBy( + new Text("fast forwarding to first element"), + testee -> testee.fastForward(DateTime.parse("20240406")), + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has(PeekableInstanceIterator::next, equalTo(DateTime.parse("20240406"))), + has(PeekableInstanceIterator::peek, equalTo(DateTime.parse("20240408")))))), + mutatedBy( + new Text("fast forwarding to third element"), + testee -> testee.fastForward(DateTime.parse("20240410")), + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has(PeekableInstanceIterator::next, equalTo(DateTime.parse("20240410"))), + has(PeekableInstanceIterator::peek, equalTo(DateTime.parse("20240412")))))), + mutatedBy( + new Text("fast forwarding to third element and pulling one element"), + testee -> { + testee.fastForward(DateTime.parse("20240410")); + testee.peek(); + testee.hasNext(); + testee.next(); + }, + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has((ThrowingFunction) PeekableInstanceIterator::next, equalTo(DateTime.parse("20240412"))), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))), + mutatedBy( + new Text("fast forwarding to third element and pulling two element"), + testee -> { + testee.fastForward(DateTime.parse("20240410")); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + }, + soIt(allOf(not(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext"))), + has((ThrowingFunction) iterator -> iterator::next, throwing(NoSuchElementException.class)), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))), + mutatedBy( + new Text("fast forwarding to last element"), + testee -> { + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.fastForward(DateTime.parse("20240412")); + }, + soIt(allOf(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext")), + has((ThrowingFunction) PeekableInstanceIterator::next, equalTo(DateTime.parse("20240412"))), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))), + mutatedBy( + new Text("fast forwarding beyond last element"), + testee -> { + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.peek(); + testee.hasNext(); + testee.next(); + testee.fastForward(DateTime.parse("20240413")); + }, + soIt(allOf(not(satisfies(PeekableInstanceIterator::hasNext, new Text("hasNext"))), + has((ThrowingFunction) iterator -> iterator::next, throwing(NoSuchElementException.class)), + has((ThrowingFunction) iterator -> iterator::peek, throwing(NoSuchElementException.class))))) + + )); + } +} \ No newline at end of file diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/PrecedingTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/PrecedingTest.java new file mode 100644 index 0000000..332ef47 --- /dev/null +++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/PrecedingTest.java @@ -0,0 +1,107 @@ +package org.dmfs.rfc5545.recurrenceset; + +import org.dmfs.rfc5545.DateTime; +import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException; +import org.dmfs.rfc5545.recur.RecurrenceRule; +import org.saynotobugs.confidence.junit5.engine.Assertion; +import org.saynotobugs.confidence.junit5.engine.Confidence; +import org.saynotobugs.confidence.junit5.engine.assertion.AssertionThat; + +import static org.dmfs.rfc5545.confidence.Recur.emptyRecurrenceSet; +import static org.dmfs.rfc5545.confidence.Recur.finite; +import static org.saynotobugs.confidence.quality.Core.*; + +@Confidence +class PrecedingTest +{ + + Assertion preceding_empty_RecurrenceSet_remains_empty = + new AssertionThat<>( + new Preceding(DateTime.parse("20240406"), + new OfList("")), + is(emptyRecurrenceSet())); + + Assertion preceding_first_element_is_empty = + new AssertionThat<>( + new Preceding(DateTime.parse("20240406"), + new OfList("20240505,20240606,20240707")), + is(emptyRecurrenceSet())); + + Assertion preceding_second_element_is_first_element = + new AssertionThat<>( + new Preceding(DateTime.parse("20240406"), + new OfList("20240404,20240505,20240606,20240707")), + is(allOf( + finite(), + iterates(DateTime.parse("20240404")) + ))); + + Assertion preceding_third_exactly_matching_element_is_first_two_elements = + new AssertionThat<>( + new Preceding(DateTime.parse("20240606"), + new OfList("20240404,20240505,20240606,20240707")), + is(allOf( + finite(), + iterates( + DateTime.parse("20240404"), + DateTime.parse("20240505")) + ))); + + Assertion preceding_after_last_element_is_all_elements = + new AssertionThat<>( + new Preceding(DateTime.parse("20240909"), + new OfList("20240404,20240505,20240606,20240707")), + is(allOf( + finite(), + iterates( + DateTime.parse("20240404"), + DateTime.parse("20240505"), + DateTime.parse("20240606"), + DateTime.parse("20240707")) + ))); + + Assertion fast_forwarded_preceding_some_values = + new AssertionThat<>( + new FastForwarded(DateTime.parse("20240505"), + new Preceding(DateTime.parse("20240707"), + new OfList("20240404,20240505,20240606,20240707"))), + is(allOf( + finite(), + iterates( + DateTime.parse("20240505"), + DateTime.parse("20240606")) + ))); + + Assertion fast_forwarded_preceding_no_values = + new AssertionThat<>( + new FastForwarded(DateTime.parse("20240606"), + new Preceding(DateTime.parse("20240606"), + new OfList("20240404,20240505,20240606,20240707"))), + is(emptyRecurrenceSet())); + + Assertion preceding_an_infinite_set_is_finite = + new AssertionThat<>( + new Preceding(DateTime.parse("20240410"), + new OfRule(recurrenceRule("FREQ=DAILY"), DateTime.parse("20240406"))), + is(allOf( + finite(), + iterates( + DateTime.parse("20240406"), + DateTime.parse("20240407"), + DateTime.parse("20240408"), + DateTime.parse("20240409"))) + )); + + // hmm, the confidence engine approach doesn't play well with throwing constructors + private static RecurrenceRule recurrenceRule(String rule) + { + try + { + return new RecurrenceRule(rule); + } + catch (InvalidRecurrenceRuleException e) + { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/WithinTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/WithinTest.java new file mode 100644 index 0000000..0880753 --- /dev/null +++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/WithinTest.java @@ -0,0 +1,74 @@ +package org.dmfs.rfc5545.recurrenceset; + +import org.dmfs.rfc5545.DateTime; +import org.saynotobugs.confidence.junit5.engine.Assertion; +import org.saynotobugs.confidence.junit5.engine.Confidence; +import org.saynotobugs.confidence.junit5.engine.assertion.AssertionThat; + +import static org.dmfs.rfc5545.confidence.Recur.emptyRecurrenceSet; +import static org.dmfs.rfc5545.confidence.Recur.finite; +import static org.saynotobugs.confidence.quality.Core.*; + +@Confidence +class WithinTest +{ + + Assertion within_empty_RecurrenceSet_remains_empty = + new AssertionThat<>( + new Within( + DateTime.parse("20240406"), + DateTime.parse("20240408"), + new OfList("")), + is(emptyRecurrenceSet())); + + Assertion within_before_delegate_is_empty = + new AssertionThat<>( + new Within( + DateTime.parse("20240306"), + DateTime.parse("20240406"), + new OfList("20240505,20240606,20240707")), + is(emptyRecurrenceSet())); + + Assertion within_after_delegate_is_empty = + new AssertionThat<>( + new Within( + DateTime.parse("20241006"), + DateTime.parse("20241106"), + new OfList("20240505,20240606,20240707")), + is(emptyRecurrenceSet())); + + Assertion within_entire_delegate_is_delegate = + new AssertionThat<>( + new Within( + DateTime.parse("20240106"), + DateTime.parse("20241106"), + new OfList("20240505,20240606,20240707")), + is(allOf( + finite(), + iterates( + DateTime.parse("20240505"), + DateTime.parse("20240606"), + DateTime.parse("20240707") + ) + ))); + + Assertion within_partial_delegate_is_partial_delegate = + new AssertionThat<>( + new Within( + DateTime.parse("20240506"), + DateTime.parse("20240706"), + new OfList("20240505,20240606,20240707")), + is(allOf( + finite(), + iterates(DateTime.parse("20240606")) + ))); + + Assertion within_same_start_and_end_is_empty = + new AssertionThat<>( + new Within( + DateTime.parse("20240606"), + DateTime.parse("20240606"), + new OfList("20240505,20240606,20240707")), + is(emptyRecurrenceSet())); + +} \ No newline at end of file