+{
+ public ParsedDates(TimeZone timeZone, String datesList)
+ {
+ super(new Mapped<>(dateString -> DateTime.parse(timeZone, dateString),
+ new Sieved<>(new Not<>(String::isEmpty),
+ new Seq<>(datesList.split(",")))));
+ }
+
+
+ public ParsedDates(String datesList)
+ {
+ super(new Mapped<>(DateTime::parse,
+ new Sieved<>(new Not<>(String::isEmpty),
+ new Seq<>(datesList.split(",")))));
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/RecurrenceSet.java b/src/main/java/org/dmfs/rfc5545/iterable/RecurrenceSet.java
index 1d9e767..dea3550 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/RecurrenceSet.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/RecurrenceSet.java
@@ -21,6 +21,7 @@
import org.dmfs.rfc5545.iterable.instanceiterable.Composite;
import org.dmfs.rfc5545.iterable.instanceiterable.EmptyIterable;
import org.dmfs.rfc5545.iterable.instanceiterator.EffectiveInstancesIterator;
+import org.dmfs.rfc5545.recurrenceset.Difference;
import java.util.Iterator;
import java.util.TimeZone;
@@ -30,7 +31,10 @@
* An {@link Iterable} of a recurrence set.
*
* The recurrence set is determined from a number of {@link InstanceIterable}s providing instances and exceptions.
+ *
+ * @deprecated in favour of {@link Difference}
*/
+@Deprecated
public final class RecurrenceSet implements Iterable
{
private final DateTime mFirst;
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/Composite.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/Composite.java
index 0e44c08..dfda4f6 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/Composite.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/Composite.java
@@ -27,7 +27,10 @@
/**
* A composite {@link InstanceIterable} composed of other {@link InstanceIterable}s. This {@link InstanceIterator}
* returned by this class returns the instances of all given {@link InstanceIterable}s in chronological order.
+ *
+ * @deprecated in favour of {@link org.dmfs.rfc5545.recurrenceset.Merged}
*/
+@Deprecated
public final class Composite implements InstanceIterable
{
private final InstanceIterable mDelegate;
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/EmptyIterable.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/EmptyIterable.java
index c7d9c10..4f50d6f 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/EmptyIterable.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/EmptyIterable.java
@@ -25,7 +25,10 @@
/**
* An {@link InstanceIterable} that doesn't have any instances.
+ *
+ * @deprecated without replacement
*/
+@Deprecated
public final class EmptyIterable implements InstanceIterable
{
public static final InstanceIterable INSTANCE = new EmptyIterable();
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FastForwarded.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FastForwarded.java
index f4cffe3..e8b4014 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FastForwarded.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FastForwarded.java
@@ -24,7 +24,10 @@
/**
* An {@link InstanceIterable} that fast forwards the iteration to a given instant. All instances prior to that instant will be skipped.
+ *
+ * @deprecated in favour of {@link org.dmfs.rfc5545.recurrenceset.FastForwarded}
*/
+@Deprecated
public final class FastForwarded implements InstanceIterable
{
private final long mTimeStamp;
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FirstAndRuleInstances.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FirstAndRuleInstances.java
index f188ebf..026f477 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FirstAndRuleInstances.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/FirstAndRuleInstances.java
@@ -24,11 +24,15 @@
import org.dmfs.rfc5545.iterable.instanceiterator.CountLimitedRecurrenceRuleIterator;
import org.dmfs.rfc5545.recur.RecurrenceRule;
import org.dmfs.rfc5545.recur.RecurrenceRuleIterator;
+import org.dmfs.rfc5545.recurrenceset.OfRuleAndFirst;
/**
* Implements {@link InstanceIterable} for a {@link RecurrenceRule} that also returns any non-synchronized first instance.
+ *
+ * @deprecated in favour of {@link OfRuleAndFirst}
*/
+@Deprecated
public final class FirstAndRuleInstances implements InstanceIterable
{
/**
@@ -40,8 +44,7 @@ public final class FirstAndRuleInstances implements InstanceIterable
/**
* Create a new adapter for the given rule and start.
*
- * @param rule
- * The recurrence rule to adapt to.
+ * @param rule The recurrence rule to adapt to.
*/
public FirstAndRuleInstances(RecurrenceRule rule)
{
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/InstanceList.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/InstanceList.java
index 0212440..c43c2f9 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/InstanceList.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/InstanceList.java
@@ -21,6 +21,7 @@
import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics;
import org.dmfs.rfc5545.iterable.InstanceIterable;
import org.dmfs.rfc5545.iterable.InstanceIterator;
+import org.dmfs.rfc5545.recurrenceset.OfList;
import java.util.Arrays;
import java.util.TimeZone;
@@ -28,7 +29,10 @@
/**
* An {@link InstanceIterable} of a given list of instances.
+ *
+ * @deprecated in favour of {@link OfList}
*/
+@Deprecated
public final class InstanceList implements InstanceIterable
{
@@ -46,10 +50,8 @@ public final class InstanceList implements InstanceIterable
/**
* Create an adapter for the instances in list
.
*
- * @param list
- * A comma separated list of instances using the date-time format as defined in RFC 5545.
- * @param timeZone
- * The time zone to apply to the instances.
+ * @param list A comma separated list of instances using the date-time format as defined in RFC 5545.
+ * @param timeZone The time zone to apply to the instances.
*/
public InstanceList(String list, TimeZone timeZone)
{
@@ -60,12 +62,9 @@ public InstanceList(String list, TimeZone timeZone)
/**
* Create an adapter for the instances in list
.
*
- * @param calendarMetrics
- * The calendar scale to use.
- * @param list
- * A comma separated list of instances using the date-time format as defined in RFC 5545.
- * @param timeZone
- * The time zone to apply to the instances.
+ * @param calendarMetrics The calendar scale to use.
+ * @param list A comma separated list of instances using the date-time format as defined in RFC 5545.
+ * @param timeZone The time zone to apply to the instances.
*/
public InstanceList(CalendarMetrics calendarMetrics, String list, TimeZone timeZone)
{
@@ -94,8 +93,7 @@ public InstanceList(CalendarMetrics calendarMetrics, String list, TimeZone timeZ
/**
* Create an adapter for the instances in list
.
*
- * @param instances
- * An array of instance time stamps in milliseconds.
+ * @param instances An array of instance time stamps in milliseconds.
*/
public InstanceList(long[] instances)
{
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/RuleInstances.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/RuleInstances.java
index 1edf3a2..60d43d9 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/RuleInstances.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterable/RuleInstances.java
@@ -22,12 +22,16 @@
import org.dmfs.rfc5545.iterable.InstanceIterator;
import org.dmfs.rfc5545.recur.RecurrenceRule;
import org.dmfs.rfc5545.recur.RecurrenceRuleIterator;
+import org.dmfs.rfc5545.recurrenceset.OfRule;
/**
* Implements {@link InstanceIterable} for a {@link RecurrenceRule}. That only iterates instances that match the {@link RecurrenceRule}.
* Any non-synchronized first instance is not returned.
+ *
+ * @deprecated in favour of {@link OfRule}
*/
+@Deprecated
public final class RuleInstances implements InstanceIterable
{
@@ -40,8 +44,7 @@ public final class RuleInstances implements InstanceIterable
/**
* Create a new adapter for the given rule and start.
*
- * @param rule
- * The recurrence rule to adapt to.
+ * @param rule The recurrence rule to adapt to.
*/
public RuleInstances(RecurrenceRule rule)
{
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/Composite.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/Composite.java
index ac7d3de..0ba17a5 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/Composite.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/Composite.java
@@ -32,7 +32,10 @@
/**
* An {@link InstanceIterator} which iterates the elements of other {@link InstanceIterator} in chronological order.
+ *
+ * @deprecated in favour of {@link org.dmfs.rfc5545.instanceiterator.Merged}
*/
+@Deprecated
public final class Composite implements InstanceIterator
{
private List mHelpers;
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/CountLimitedRecurrenceRuleIterator.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/CountLimitedRecurrenceRuleIterator.java
index ced99a7..79ed453 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/CountLimitedRecurrenceRuleIterator.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/CountLimitedRecurrenceRuleIterator.java
@@ -25,7 +25,10 @@
/**
* An {@link InstanceIterator} which limits the number of iterated instances.
+ *
+ * @deprecated in favour of {@link org.dmfs.rfc5545.instanceiterator.CountLimitedRecurrenceRuleIterator}
*/
+@Deprecated
public final class CountLimitedRecurrenceRuleIterator implements InstanceIterator
{
private final RecurrenceRuleIterator mDelegate;
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EffectiveInstancesIterator.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EffectiveInstancesIterator.java
index ef949eb..2ae7ac1 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EffectiveInstancesIterator.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EffectiveInstancesIterator.java
@@ -25,7 +25,10 @@
/**
* An iterator for recurrence sets. It takes a number of {@link InstanceIterator}s for instances and exceptions and iterates all resulting instances
* (i.e. only the instances, not the exceptions).
+ *
+ * @deprecated in favour of {@link org.dmfs.rfc5545.instanceiterator.EffectiveInstancesIterator}
*/
+@Deprecated
public final class EffectiveInstancesIterator implements InstanceIterator
{
/**
@@ -45,10 +48,8 @@ public final class EffectiveInstancesIterator implements InstanceIterator
/**
* Create a new recurrence iterator for specific lists of instances and exceptions.
*
- * @param instances
- * The instances, must not be null
or empty.
- * @param exceptions
- * The exceptions, may be null.
+ * @param instances The instances, must not be null
or empty.
+ * @param exceptions The exceptions, may be null.
*/
public EffectiveInstancesIterator(InstanceIterator instances, InstanceIterator exceptions)
{
@@ -74,9 +75,7 @@ public boolean hasNext()
* Get the next instance of this set. Do not call this if {@link #hasNext()} returns false
.
*
* @return The time in milliseconds since the epoch of the next instance.
- *
- * @throws ArrayIndexOutOfBoundsException
- * if there are no more instances.
+ * @throws ArrayIndexOutOfBoundsException if there are no more instances.
*/
@Override
public long next()
@@ -94,8 +93,7 @@ public long next()
/**
* Fast-forward to the next instance at or after the given date.
*
- * @param until
- * The date to fast-forward to in milliseconds since the epoch.
+ * @param until The date to fast-forward to in milliseconds since the epoch.
*/
@Override
public void fastForward(long until)
diff --git a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EmptyIterator.java b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EmptyIterator.java
index 3948dc0..0df51a9 100644
--- a/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EmptyIterator.java
+++ b/src/main/java/org/dmfs/rfc5545/iterable/instanceiterator/EmptyIterator.java
@@ -24,7 +24,10 @@
/**
* An {@link InstanceIterator} without any instances.
+ *
+ * @deprecated in favour of {@link org.dmfs.rfc5545.instanceiterator.EmptyIterator}
*/
+@Deprecated
public final class EmptyIterator implements InstanceIterator
{
public static final InstanceIterator INSTANCE = new EmptyIterator();
diff --git a/src/main/java/org/dmfs/rfc5545/optional/Last.java b/src/main/java/org/dmfs/rfc5545/optional/Last.java
new file mode 100644
index 0000000..59a0ac1
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/optional/Last.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.optional;
+
+import org.dmfs.jems2.Optional;
+
+import java.util.Iterator;
+
+/**
+ * TODO: move to jems2
+ */
+final class Last implements Optional
+{
+ private final Iterable extends T> mIterable;
+
+ public Last(Iterable extends T> iterable)
+ {
+ this.mIterable = iterable;
+ }
+
+ @Override
+ public boolean isPresent()
+ {
+ return mIterable.iterator().hasNext();
+ }
+
+ @Override
+ public T value()
+ {
+ Iterator extends T> iterator = mIterable.iterator();
+ T result = iterator.next();
+ while (iterator.hasNext())
+ {
+ result = iterator.next();
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/optional/LastInstance.java b/src/main/java/org/dmfs/rfc5545/optional/LastInstance.java
new file mode 100644
index 0000000..a037f98
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/optional/LastInstance.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.optional;
+
+import org.dmfs.jems2.Optional;
+import org.dmfs.jems2.optional.DelegatingOptional;
+import org.dmfs.jems2.optional.Restrained;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.RecurrenceSet;
+
+/**
+ * The {@link Optional} last {@link DateTime} of a {@link RecurrenceSet}.
+ * The value is absent when the {@link RecurrenceSet} is empty or infinite.
+ */
+public final class LastInstance extends DelegatingOptional
+{
+ public LastInstance(RecurrenceSet recurrenceSet)
+ {
+ super(new Restrained<>(() -> !recurrenceSet.isInfinite(), new Last<>(recurrenceSet)));
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/optional/package-info.java b/src/main/java/org/dmfs/rfc5545/optional/package-info.java
new file mode 100644
index 0000000..750644f
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/optional/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@NonNullByDefault
+package org.dmfs.rfc5545.optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
\ No newline at end of file
diff --git a/src/main/java/org/dmfs/rfc5545/package-info.java b/src/main/java/org/dmfs/rfc5545/package-info.java
new file mode 100644
index 0000000..826547c
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@NonNullByDefault
+package org.dmfs.rfc5545;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
\ No newline at end of file
diff --git a/src/main/java/org/dmfs/rfc5545/recur/RecurrenceRuleIterator.java b/src/main/java/org/dmfs/rfc5545/recur/RecurrenceRuleIterator.java
index f80083b..6852111 100644
--- a/src/main/java/org/dmfs/rfc5545/recur/RecurrenceRuleIterator.java
+++ b/src/main/java/org/dmfs/rfc5545/recur/RecurrenceRuleIterator.java
@@ -12,14 +12,16 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
+ *
*/
package org.dmfs.rfc5545.recur;
import org.dmfs.rfc5545.DateTime;
import org.dmfs.rfc5545.Instance;
+import org.dmfs.rfc5545.InstanceIterator;
import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics;
+import org.eclipse.jdt.annotation.NonNull;
import java.util.TimeZone;
@@ -30,7 +32,7 @@
*
* @author Marten Gajda
*/
-public final class RecurrenceRuleIterator
+public final class RecurrenceRuleIterator implements InstanceIterator
{
/**
* The previous iterator instance. This is null
for the {@link FreqIterator}.
@@ -75,10 +77,8 @@ public final class RecurrenceRuleIterator
/**
* Creates a new {@link RecurrenceRuleIterator} that gets its input from ruleIterator
.
*
- * @param ruleIterator
- * The last {@link RuleIterator} in the chain of iterators.
- * @param start
- * The first instance to iterate.
+ * @param ruleIterator The last {@link RuleIterator} in the chain of iterators.
+ * @param start The first instance to iterate.
*/
RecurrenceRuleIterator(RuleIterator ruleIterator, DateTime start, CalendarMetrics calendarMetrics)
{
@@ -148,13 +148,13 @@ public DateTime nextDateTime()
if (mAllDay)
{
return new DateTime(mCalendarMetrics, Instance.year(nextInstance), Instance.month(nextInstance),
- Instance.dayOfMonth(nextInstance));
+ Instance.dayOfMonth(nextInstance));
}
else
{
return new DateTime(mCalendarMetrics, mTimeZone, Instance.year(nextInstance), Instance.month(nextInstance),
- Instance.dayOfMonth(nextInstance),
- Instance.hour(nextInstance), Instance.minute(nextInstance), Instance.second(nextInstance));
+ Instance.dayOfMonth(nextInstance),
+ Instance.hour(nextInstance), Instance.minute(nextInstance), Instance.second(nextInstance));
}
}
@@ -164,6 +164,12 @@ public boolean hasNext()
return mNextInstance != Long.MIN_VALUE;
}
+ @Override
+ public DateTime next()
+ {
+ return nextDateTime();
+ }
+
/**
* Peek at the next instance to be returned by {@link #nextMillis()} without actually iterating it. Calling this method (even multiple times) won't affect
@@ -207,14 +213,14 @@ public DateTime peekDateTime()
if (mAllDay)
{
return mNextDateTime = new DateTime(mCalendarMetrics, Instance.year(nextInstance),
- Instance.month(nextInstance), Instance.dayOfMonth(nextInstance));
+ Instance.month(nextInstance), Instance.dayOfMonth(nextInstance));
}
else
{
return mNextDateTime = new DateTime(mCalendarMetrics, mTimeZone, Instance.year(nextInstance),
- Instance.month(nextInstance),
- Instance.dayOfMonth(nextInstance), Instance.hour(nextInstance), Instance.minute(nextInstance),
- Instance.second(nextInstance));
+ Instance.month(nextInstance),
+ Instance.dayOfMonth(nextInstance), Instance.hour(nextInstance), Instance.minute(nextInstance),
+ Instance.second(nextInstance));
}
}
@@ -223,8 +229,7 @@ public DateTime peekDateTime()
* Skip the given number of instances. Note: After calling this method you should call {@link #hasNext()} before you continue because
* there might be less than skip
instances left when you call this.
*
- * @param skip
- * The number of instances to skip.
+ * @param skip The number of instances to skip.
*/
public void skip(int skip)
{
@@ -258,8 +263,7 @@ public void skip(int skip)
* Skip all instances up to a specific date. Note: After calling this method you should call {@link #hasNext()} before you continue
* because there might no more instances left if there is an UNTIL or COUNT part in the rule.
*
- * @param until
- * The time stamp of earliest date to be returned by the next call to {@link #nextMillis()} or {@link #nextDateTime()}.
+ * @param until The time stamp of earliest date to be returned by the next call to {@link #nextMillis()} or {@link #nextDateTime()}.
*/
public void fastForward(long until)
{
@@ -298,17 +302,16 @@ public void fastForward(long until)
* Skip all instances up to a specific date. Note: After calling this method you should call {@link #hasNext()} before you continue
* because there might no more instances left if there is an UNTIL or COUNT part in the rule.
*
- * @param until
- * The earliest date to be returned by the next call to {@link #nextMillis()} or {@link #nextDateTime()}.
+ * @param until The earliest date to be returned by the next call to {@link #nextMillis()} or {@link #nextDateTime()}.
*/
- public void fastForward(DateTime until)
+ public void fastForward(@NonNull DateTime until)
{
if (!hasNext())
{
return;
}
- DateTime untilDate = until.shiftTimeZone(mTimeZone);
+ DateTime untilDate = until.isAllDay() ? until.startOfDay().shiftTimeZone(mTimeZone) : until.shiftTimeZone(mTimeZone);
// convert until to an instance
long untilInstance = untilDate.getInstance();
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/AbstractRecurrenceAdapter.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/AbstractRecurrenceAdapter.java
deleted file mode 100644
index 5da9ad2..0000000
--- a/src/main/java/org/dmfs/rfc5545/recurrenceset/AbstractRecurrenceAdapter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2013 Marten Gajda
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import java.util.TimeZone;
-
-
-/**
- * An abstract adapter for recurrence instance sets. This represents the instances of a specific instance set (e.g. an rrule, an exrule, a list of rdates or
- * exdates)
- *
- * @author Marten Gajda
- */
-@Deprecated
-public abstract class AbstractRecurrenceAdapter
-{
-
- interface InstanceIterator
- {
-
- /**
- * Check if there is at least one more instance to iterate.
- *
- * @return true
if the next call to {@link #next()} will return another instance, false
otherwise.
- */
- abstract boolean hasNext();
-
- /**
- * Get the next instance of this set. Do not call this if {@link #hasNext()} returns false
.
- *
- * @return The time in milliseconds since the epoch of the next instance.
- *
- * @throws ArrayIndexOutOfBoundsException
- * if there are no more instances.
- */
- abstract long next();
-
- /**
- * Skip all instances till until
. If until
is an instance itself it will be the next iterated instance. If the rule doesn't
- * recur till that date the next call to {@link #hasNext()} will return false
.
- *
- * @param until
- * A time stamp of the date to fast forward to.
- */
- abstract void fastForward(long until);
-
- }
-
-
- /**
- * Get an iterator for this adapter.
- *
- * @param timezone
- * The {@link TimeZone} of the first instance.
- * @param start
- * The start date in milliseconds since the epoch.
- */
- abstract InstanceIterator getIterator(TimeZone timezone, long start);
-
- /**
- * Returns whether this adapter iterates an infinite number of instances.
- *
- * @return true
if the instances in this adapter are not limited, false
otherwise.
- */
- abstract boolean isInfinite();
-
- /**
- * Returns the last instance this adapter will iterate or {@link Long#MAX_VALUE} if {@link #isInfinite()} returns true
.
- *
- * @param timezone
- * The {@link TimeZone} of the first instance.
- * @param start
- * The start date in milliseconds since the epoch.
- *
- * @return The last instance in milliseconds since the epoch.
- */
- abstract long getLastInstance(TimeZone timezone, long start);
-}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/CompositeIterator.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/CompositeIterator.java
deleted file mode 100644
index 6ab8a8f..0000000
--- a/src/main/java/org/dmfs/rfc5545/recurrenceset/CompositeIterator.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright 2020 Marten Gajda
- *
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.jems2.comparator.By;
-import org.dmfs.jems2.iterable.Mapped;
-import org.dmfs.jems2.iterable.Sieved;
-import org.dmfs.jems2.single.Collected;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-
-/**
- * An {@link AbstractRecurrenceAdapter.InstanceIterator} which iterates the elements of other {@link AbstractRecurrenceAdapter.InstanceIterator} in sorted
- * order.
- */
-@Deprecated
-public final class CompositeIterator implements AbstractRecurrenceAdapter.InstanceIterator
-{
- private List mHelpers;
-
-
- public CompositeIterator(Iterable delegates)
- {
- mHelpers = new Collected<>(
- ArrayList::new,
- new Mapped<>(
- Helper::new,
- new Sieved<>(
- AbstractRecurrenceAdapter.InstanceIterator::hasNext,
- delegates))).value();
- Collections.sort(mHelpers, new By<>(helper -> helper.nextValue));
-
- // resolve duplicates
- for (int i = mHelpers.size(); i > 1; --i)
- {
- if (mHelpers.get(i - 1).nextValue == mHelpers.get(i - 2).nextValue)
- {
- if (mHelpers.get(i - 1).iterator.hasNext())
- {
- bubbleUp(i - 1);
- }
- else
- {
- mHelpers.remove(i - 1);
- }
- }
- }
- }
-
-
- @Override
- public boolean hasNext()
- {
- return mHelpers.size() > 0;
- }
-
-
- @Override
- public long next()
- {
- if (!hasNext())
- {
- throw new NoSuchElementException("No more elements to iterate");
- }
- long result = mHelpers.get(0).nextValue;
- advance();
- return result;
- }
-
-
- @Override
- public void fastForward(long until)
- {
- for (int i = mHelpers.size() - 1; i >= 0; --i)
- {
- Helper it = mHelpers.get(i);
- if (it.nextValue < until)
- {
- it.iterator.fastForward(until);
- if (it.iterator.hasNext())
- {
- it.nextValue = it.iterator.next();
- }
- else
- {
- mHelpers.remove(i);
- }
- }
- }
- Collections.sort(mHelpers, new By<>(helper -> helper.nextValue));
- }
-
-
- private void advance()
- {
- if (mHelpers.size() == 1)
- {
- Helper helper = mHelpers.get(0);
- if (helper.iterator.hasNext())
- {
- helper.nextValue = helper.iterator.next();
- }
- else
- {
- mHelpers.clear();
- }
- }
- else
- {
- Helper helper = mHelpers.get(0);
- if (helper.iterator.hasNext())
- {
- bubbleUp(0);
- }
- else
- {
- mHelpers.remove(0);
- }
- }
- }
-
-
- private void bubbleUp(int pos)
- {
- // pull the next element and let it bubble up to its position
- final List helpers = mHelpers;
- Helper first = helpers.get(pos);
- long next = first.iterator.next();
- while (pos < helpers.size() - 1 && next >= helpers.get(pos + 1).nextValue)
- {
- if (next == helpers.get(pos + 1).nextValue)
- {
- // value already present, skip this one
- if (first.iterator.hasNext())
- {
- next = first.iterator.next();
- }
- else
- {
- // this one has no more elements
- helpers.remove(pos);
- return;
- }
- }
- helpers.set(pos, helpers.get(pos + 1));
- pos++;
- }
- first.nextValue = next;
- helpers.set(pos, first);
- }
-
-
- private final static class Helper
- {
- private long nextValue;
- private final AbstractRecurrenceAdapter.InstanceIterator iterator;
-
-
- private Helper(AbstractRecurrenceAdapter.InstanceIterator iterator)
- {
- this(iterator.next(), iterator);
- }
-
-
- private Helper(long nextValue, AbstractRecurrenceAdapter.InstanceIterator iterator)
- {
- this.nextValue = nextValue;
- this.iterator = iterator;
- }
- }
-}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/Difference.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/Difference.java
new file mode 100644
index 0000000..bfaf2f5
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/Difference.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.rfc5545.InstanceIterator;
+import org.dmfs.rfc5545.RecurrenceSet;
+import org.dmfs.rfc5545.instanceiterator.EffectiveInstancesIterator;
+
+/**
+ * A {@link RecurrenceSet} that contains all the instances of a given {@link RecurrenceSet} except the ones that are
+ * in the exceptions {@link RecurrenceSet}.
+ */
+public final class Difference implements RecurrenceSet
+{
+ private final RecurrenceSet mMinuend;
+ private final RecurrenceSet mSubtrahend;
+
+ public Difference(RecurrenceSet minuend, RecurrenceSet subtrahend)
+ {
+ mMinuend = minuend;
+ mSubtrahend = subtrahend;
+ }
+
+ @Override
+ public InstanceIterator iterator()
+ {
+ return new EffectiveInstancesIterator(mMinuend.iterator(), mSubtrahend.iterator());
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return mMinuend.isInfinite();
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/FastForwarded.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/FastForwarded.java
new file mode 100644
index 0000000..05aff8c
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/FastForwarded.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.jems2.iterable.Seq;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.InstanceIterator;
+import org.dmfs.rfc5545.RecurrenceSet;
+
+
+/**
+ * A {@link RecurrenceSet} that fast forwards the iteration to a given instant.
+ * All instances prior to that instant will be skipped.
+ */
+public final class FastForwarded implements RecurrenceSet
+{
+ private final DateTime mTimeStamp;
+ private final RecurrenceSet mDelegate;
+
+
+ public FastForwarded(DateTime fastForwardTo, RecurrenceSet... delegate)
+ {
+ this(fastForwardTo, new Seq<>(delegate));
+ }
+
+
+ public FastForwarded(DateTime fastForwardTo, Iterable delegate)
+ {
+ this(fastForwardTo, new Merged(delegate));
+ }
+
+
+ public FastForwarded(DateTime timeStamp, RecurrenceSet delegate)
+ {
+ mTimeStamp = timeStamp;
+ mDelegate = delegate;
+ }
+
+
+ @Override
+ public InstanceIterator iterator()
+ {
+ InstanceIterator iterator = mDelegate.iterator();
+ iterator.fastForward(mTimeStamp);
+ return iterator;
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return mDelegate.isInfinite();
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/Merged.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/Merged.java
new file mode 100644
index 0000000..6b7a6f2
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/Merged.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.jems2.iterable.Mapped;
+import org.dmfs.jems2.iterable.Seq;
+import org.dmfs.jems2.iterable.Sieved;
+import org.dmfs.rfc5545.InstanceIterator;
+import org.dmfs.rfc5545.RecurrenceSet;
+
+
+/**
+ * A composite {@link RecurrenceSet} composed of other {@link RecurrenceSet}s. This {@link RecurrenceSet}
+ * iterates the instances of all given {@link RecurrenceSet}s in chronological order.
+ */
+public final class Merged implements RecurrenceSet
+{
+ private final RecurrenceSet mDelegate;
+
+
+ public Merged(RecurrenceSet... delegates)
+ {
+ this(new Seq<>(delegates));
+ }
+
+
+ public Merged(Iterable delegates)
+ {
+ this(
+ new RecurrenceSet()
+ {
+ @Override
+ public InstanceIterator iterator()
+ {
+ return new org.dmfs.rfc5545.instanceiterator.Merged(new Mapped<>(RecurrenceSet::iterator, delegates));
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return new Sieved<>(RecurrenceSet::isInfinite, delegates).iterator().hasNext();
+ }
+
+ });
+ }
+
+
+ private Merged(RecurrenceSet delegate)
+ {
+ mDelegate = delegate;
+ }
+
+
+ @Override
+ public InstanceIterator iterator()
+ {
+ return mDelegate.iterator();
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return mDelegate.isInfinite();
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/OfList.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/OfList.java
new file mode 100644
index 0000000..969ad84
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/OfList.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.jems2.comparator.By;
+import org.dmfs.jems2.iterable.Expanded;
+import org.dmfs.jems2.iterable.Frozen;
+import org.dmfs.jems2.iterable.Seq;
+import org.dmfs.jems2.iterable.Sorted;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.InstanceIterator;
+import org.dmfs.rfc5545.RecurrenceSet;
+import org.dmfs.rfc5545.instanceiterator.EmptyIterator;
+import org.dmfs.rfc5545.instanceiterator.FastForwardable;
+import org.dmfs.rfc5545.iterable.ParsedDates;
+
+import java.util.Iterator;
+import java.util.TimeZone;
+
+
+/**
+ * A {@link RecurrenceSet} of a given list of instances, typically provided in the form of {@code RDATE}s or {@code EXDATE}s.
+ *
+ * Note, that this class does not support parsing the {@code PERIOD} type specified in
+ * RFC 5545, section 3.3.9.
+ */
+public final class OfList implements RecurrenceSet
+{
+ private final Iterable mInstances;
+
+
+ public OfList(TimeZone timeZone, String... instances)
+ {
+ this(timeZone, new Seq<>(instances));
+ }
+
+ public OfList(TimeZone timeZone, Iterable instances)
+ {
+ this(new Expanded<>(list -> new ParsedDates(timeZone, list), instances));
+ }
+
+ public OfList(TimeZone timeZone, String instances)
+ {
+ this(new ParsedDates(timeZone, instances));
+ }
+
+ public OfList(String... instances)
+ {
+ this(new Expanded<>(ParsedDates::new, new Seq<>(instances)));
+ }
+
+ public OfList(String instances)
+ {
+ this(new ParsedDates(instances));
+ }
+
+ public OfList(DateTime... instances)
+ {
+ this(new Seq<>(instances));
+ }
+
+ public OfList(Iterable instances)
+ {
+ mInstances = new Frozen<>(new Sorted<>(new By<>(DateTime::getTimestamp), instances));
+ }
+
+ @Override
+ public InstanceIterator iterator()
+ {
+ Iterator delegate = mInstances.iterator();
+ return delegate.hasNext()
+ ? new FastForwardable(delegate.next(), delegate.hasNext() ? delegate : EmptyIterator.INSTANCE)
+ : EmptyIterator.INSTANCE;
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return false;
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/OfRule.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/OfRule.java
new file mode 100644
index 0000000..39a18e8
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/OfRule.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.InstanceIterator;
+import org.dmfs.rfc5545.RecurrenceSet;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+
+/**
+ * The {@link RecurrenceSet} of a single {@link RecurrenceRule}.
+ */
+public final class OfRule implements RecurrenceSet
+{
+ private final RecurrenceRule mRecurrenceRule;
+ private final DateTime mFirstInstance;
+
+ public OfRule(RecurrenceRule recurrenceRule, DateTime firstInstance)
+ {
+ mRecurrenceRule = recurrenceRule;
+ mFirstInstance = firstInstance;
+ }
+
+ @Override
+ public InstanceIterator iterator()
+ {
+ return mRecurrenceRule.iterator(mFirstInstance);
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return mRecurrenceRule.isInfinite();
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirst.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirst.java
new file mode 100644
index 0000000..cf4ad8f
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirst.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.CountLimitedRecurrenceRuleIterator;
+import org.dmfs.rfc5545.instanceiterator.Merged;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.dmfs.rfc5545.recur.RecurrenceRuleIterator;
+
+
+/**
+ * The {@link RecurrenceSet} of a single {@link RecurrenceRule} that also returns any non-synchronized first instance.
+ */
+public final class OfRuleAndFirst implements RecurrenceSet
+{
+ private final RecurrenceRule mRecurrenceRule;
+ private final DateTime mFirst;
+
+ /**
+ * Create a new adapter for the given rule and start.
+ *
+ * @param rule The recurrence rule to adapt to.
+ */
+ public OfRuleAndFirst(RecurrenceRule rule, DateTime first)
+ {
+ mRecurrenceRule = rule;
+ mFirst = first;
+ }
+
+ @Override
+ public InstanceIterator iterator()
+ {
+ RecurrenceRuleIterator ruleIterator = mRecurrenceRule.iterator(mFirst);
+ if (!ruleIterator.peekDateTime().equals(mFirst))
+ {
+ return new Merged(
+ new OfList(mFirst).iterator(),
+ mRecurrenceRule.getCount() != null
+ // we have a count limited rule and an unsynced start date
+ // since the start date counts as the first element, the RRULE iterator should return one less element.
+ ? new CountLimitedRecurrenceRuleIterator(ruleIterator, mRecurrenceRule.getCount() - 1)
+ : ruleIterator);
+ }
+ return ruleIterator;
+ }
+
+ @Override
+ public boolean isInfinite()
+ {
+ return mRecurrenceRule.isInfinite();
+ }
+}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceList.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceList.java
deleted file mode 100644
index 768c277..0000000
--- a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceList.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2013 Marten Gajda
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.rfc5545.DateTime;
-import org.dmfs.rfc5545.calendarmetrics.CalendarMetrics;
-
-import java.util.Arrays;
-import java.util.TimeZone;
-
-
-/**
- * A concrete {@link AbstractRecurrenceAdapter} for lists of instances. You can provide the instances in a String or in an array of longs.
- *
- * @author Marten Gajda
- */
-@Deprecated
-public final class RecurrenceList extends AbstractRecurrenceAdapter
-{
- class InstanceIterator implements AbstractRecurrenceAdapter.InstanceIterator
- {
- private int mNext;
-
-
- @Override
- public boolean hasNext()
- {
- return mNext < mCount;
- }
-
-
- @Override
- public long next()
- {
- if (mNext >= mCount)
- {
- throw new ArrayIndexOutOfBoundsException("No more instances to iterate.");
- }
- return mInstances[mNext++];
- }
-
-
- @Override
- public void fastForward(long until)
- {
- int count = mCount;
- int next = mNext;
- long[] instances = mInstances;
- while (next < count && instances[next] < until)
- {
- ++next;
- }
- mNext = next;
- }
- }
-
-
- /**
- * The instances to iterate.
- */
- private final long[] mInstances;
-
- /**
- * The number of instances in {@link #mInstances}.
- */
- private final int mCount;
-
-
- /**
- * Create an adapter for the instances in list
.
- *
- * @param list
- * A comma separated list of instances using the date-time format as defined in RFC 5545.
- * @param timeZone
- * The time zone to apply to the instances.
- */
- public RecurrenceList(String list, TimeZone timeZone)
- {
- this(DateTime.GREGORIAN_CALENDAR_SCALE, list, timeZone);
- }
-
-
- /**
- * Create an adapter for the instances in list
.
- *
- * @param calendarMetrics
- * The calendar scale to use.
- * @param list
- * A comma separated list of instances using the date-time format as defined in RFC 5545.
- * @param timeZone
- * The time zone to apply to the instances.
- */
- public RecurrenceList(CalendarMetrics calendarMetrics, String list, TimeZone timeZone)
- {
- if (list == null || list.length() == 0)
- {
- mInstances = null;
- mCount = 0;
- return;
- }
-
- String[] instances = list.split(",");
- mInstances = new long[instances.length];
- int count = 0;
-
- for (String instanceString : instances)
- {
- DateTime instance = DateTime.parse(calendarMetrics, timeZone, instanceString);
- mInstances[count] = instance.getTimestamp();
- ++count;
- }
- mCount = count;
- Arrays.sort(mInstances);
- }
-
-
- /**
- * Create an adapter for the instances in list
.
- *
- * @param instances
- * An array of instance time stamps in milliseconds.
- */
- public RecurrenceList(long[] instances)
- {
- mInstances = new long[instances.length];
- System.arraycopy(instances, 0, mInstances, 0, instances.length);
- mCount = instances.length;
- Arrays.sort(mInstances);
- }
-
-
- @Override
- InstanceIterator getIterator(TimeZone timezone, long start)
- {
- return new InstanceIterator();
- }
-
-
- @Override
- boolean isInfinite()
- {
- // the given lists are always finite
- return false;
- }
-
-
- @Override
- long getLastInstance(TimeZone timezone, long start)
- {
- long[] instances = mInstances;
- return instances[instances.length - 1];
- }
-}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceRuleAdapter.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceRuleAdapter.java
deleted file mode 100644
index 9b27578..0000000
--- a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceRuleAdapter.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2013 Marten Gajda
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.rfc5545.recur.RecurrenceRule;
-import org.dmfs.rfc5545.recur.RecurrenceRuleIterator;
-
-import java.util.TimeZone;
-
-
-/**
- * Implements {@link AbstractRecurrenceAdapter} for a {@link RecurrenceRule}.
- *
- * @author Marten Gajda
- */
-@Deprecated
-public final class RecurrenceRuleAdapter extends AbstractRecurrenceAdapter
-{
-
- class InstanceIterator implements AbstractRecurrenceAdapter.InstanceIterator
- {
- private final RecurrenceRuleIterator mIterator;
-
-
- public InstanceIterator(RecurrenceRuleIterator iterator)
- {
- mIterator = iterator;
- }
-
-
- @Override
- public boolean hasNext()
- {
- return mIterator.hasNext();
- }
-
-
- @Override
- public long next()
- {
- return mIterator.nextMillis();
- }
-
-
- @Override
- public void fastForward(long until)
- {
- mIterator.fastForward(until);
- }
-
- }
-
-
- /**
- * The recurrence rule.
- */
- private final RecurrenceRule mRrule;
-
-
- /**
- * Create a new adapter for the given rule and start.
- *
- * @param rule
- * The recurrence rule to adapt to.
- */
- public RecurrenceRuleAdapter(RecurrenceRule rule)
- {
- mRrule = rule;
- }
-
-
- @Override
- AbstractRecurrenceAdapter.InstanceIterator getIterator(TimeZone timezone, long start)
- {
- RecurrenceRuleIterator ruleIterator = mRrule.iterator(start, timezone);
- AbstractRecurrenceAdapter.InstanceIterator iterator = new InstanceIterator(ruleIterator);
- if (mRrule.getCount() != null && ruleIterator.peekMillis() != start)
- {
- // we have a count limited rule and an unsynched start date
- // since the start date counts as the first element, the RRULE iterator should return one less element.
- iterator = new CountLimitedRecurrenceRuleIterator(ruleIterator, mRrule.getCount() - 1);
- }
- return iterator;
- }
-
-
- @Override
- boolean isInfinite()
- {
- return mRrule.isInfinite();
- }
-
-
- @Override
- long getLastInstance(TimeZone timezone, long start)
- {
- if (isInfinite())
- {
- return Long.MAX_VALUE;
- }
-
- RecurrenceRuleIterator iterator = mRrule.iterator(start, timezone);
- iterator.skipAllButLast();
-
- long lastInstance = Long.MIN_VALUE;
- if (iterator.hasNext())
- {
- lastInstance = iterator.nextMillis();
- }
- return lastInstance;
- }
-}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSet.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSet.java
deleted file mode 100644
index fdee376..0000000
--- a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSet.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright (C) 2013 Marten Gajda
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.rfc5545.recurrenceset.AbstractRecurrenceAdapter.InstanceIterator;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.TimeZone;
-
-
-/**
- * A recurrence set. A recurrence set consists of all instances defined by a recurrence rule or a list if instances except for exception instances. Exception
- * instances are defined by exceptions rules or lists of exception instances. This class allows you to add any number of recurrence rules, recurrence
- * instances, exception rules and exception instance. It returns an {@link Iterator} that iterates all resulting instances.
- *
- * @author Marten Gajda
- */
-@Deprecated
-public class RecurrenceSet
-{
-
- /**
- * All the instances in the set. Not all of them may be iterated, since instances that are exceptions will be skipped.
- */
- private final List mInstances = new ArrayList();
-
- /**
- * All exceptions in the set.
- */
- private List mExceptions = null;
-
- /**
- * Indicates if the recurrence set is infinite.
- */
- private boolean mIsInfinite = false;
-
-
- /**
- * Add instances to the set of instances.
- *
- * @param adapter
- * An {@link AbstractRecurrenceAdapter} that defines instances.
- */
- public void addInstances(AbstractRecurrenceAdapter adapter)
- {
- mInstances.add(adapter);
-
- // the entire set is infinite if there is at least one infinite instance set
- mIsInfinite |= adapter.isInfinite();
- }
-
-
- /**
- * Add exceptions to the set of instances (i.e. effectively remove instances from the instance set).
- *
- * @param adapter
- * An {@link AbstractRecurrenceAdapter} that defines instances.
- */
- public void addExceptions(AbstractRecurrenceAdapter adapter)
- {
- if (mExceptions == null)
- {
- mExceptions = new ArrayList();
- }
- mExceptions.add(adapter);
- }
-
-
- /**
- * Get an iterator for the specified start time.
- *
- * @param timezone
- * The {@link TimeZone} of the first instance.
- * @param start
- * The start time in milliseconds since the epoch.
- *
- * @return A {@link RecurrenceSetIterator} that iterates all instances.
- */
- public RecurrenceSetIterator iterator(TimeZone timezone, long start)
- {
- return iterator(timezone, start, Long.MAX_VALUE);
- }
-
-
- /**
- * Return a new {@link RecurrenceSetIterator} for this recurrence set.
- *
- * @param timezone
- * The {@link TimeZone} of the first instance.
- * @param start
- * The start time in milliseconds since the epoch.
- * @param end
- * The end of the time range to iterate in milliseconds since the epoch.
- *
- * @return A {@link RecurrenceSetIterator} that iterates all instances.
- */
- public RecurrenceSetIterator iterator(TimeZone timezone, long start, long end)
- {
- List instances = new ArrayList(mInstances.size());
- // make sure we add the start as the first instance
- instances.add(new RecurrenceList(new long[] { start }).getIterator(timezone, start));
- for (AbstractRecurrenceAdapter adapter : mInstances)
- {
- instances.add(adapter.getIterator(timezone, start));
- }
-
- List exceptions = null;
- if (mExceptions != null)
- {
- exceptions = new ArrayList(mExceptions.size());
- for (AbstractRecurrenceAdapter adapter : mExceptions)
- {
- exceptions.add(adapter.getIterator(timezone, start));
- }
- }
- return new RecurrenceSetIterator(instances, exceptions).setEnd(end);
- }
-
-
- /**
- * Returns whether this {@link RecurrenceSet} contains an infinite number of instances.
- *
- * @return true
if the instances in this {@link RecurrenceSet} is infinite, false
otherwise.
- */
- public boolean isInfinite()
- {
- return mIsInfinite;
- }
-
-
- public long getLastInstance(TimeZone timezone, long start)
- {
- if (isInfinite())
- {
- throw new IllegalStateException("can not calculate the last instance of an infinite recurrence set");
- }
-
- if (mExceptions != null && mExceptions.size() > 0)
- {
- /*
- * This is the difficult case.
- *
- * The last instance of the given rules might be an exception. Unfortunately there doesn't seem to be an easier way to get the very last instance
- * than by iterating all instances.
- */
- long last = Long.MIN_VALUE;
-
- RecurrenceSetIterator iterator = iterator(timezone, start);
- while (iterator.hasNext())
- {
- last = iterator.next();
- }
- return last;
- }
-
- if (mInstances.size() == 1)
- {
- // simple case, only one set of instances
- return mInstances.get(0).getLastInstance(timezone, start);
- }
-
- // We have multiple instance sets, but no exceptions. That means we just have to determine the maximum instance over all sets.
- long last = Long.MIN_VALUE;
- for (AbstractRecurrenceAdapter adapter : mInstances)
- {
- long lastOfAdapter = adapter.getLastInstance(timezone, start);
- if (lastOfAdapter > last)
- {
- last = lastOfAdapter;
- }
- }
-
- return last;
- }
-}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSetIterator.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSetIterator.java
deleted file mode 100644
index 59eada8..0000000
--- a/src/main/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSetIterator.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2013 Marten Gajda
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.rfc5545.recurrenceset.AbstractRecurrenceAdapter.InstanceIterator;
-
-import java.util.List;
-import java.util.Locale;
-
-
-/**
- * An iterator for recurrence sets. It takes a number of {@link AbstractRecurrenceAdapter}s for instances and exceptions and iterates all resulting instances
- * (i.e. only the instances, not the exceptions). This class doesn't implement the {@link InstanceIterator} interface for one reasons:
- An
- * {@link InstanceIterator} always returns an {@link Object}, so instead of a primitive
long
we would have to return a {@link Long}. That is an
- * additional object which doesn't have any advantage.
- *
- * @author Marten Gajda
- */
-@Deprecated
-public class RecurrenceSetIterator
-{
- /**
- * Throw if we skipped this many instances in a line, because they were exceptions.
- */
- private final static int MAX_SKIPPED_INSTANCES = 1000;
-
- private final InstanceIterator mInstances;
-
- private final InstanceIterator mExceptions;
-
- private long mIterateEnd = Long.MAX_VALUE;
-
- private long mNextInstance = Long.MIN_VALUE;
-
- private long mNextException = Long.MIN_VALUE;
-
-
- /**
- * Create a new recurrence iterator for specific lists of instances and exceptions.
- *
- * @param instances
- * The instances, must not be null
or empty.
- * @param exceptions
- * The exceptions, may be null.
- */
- RecurrenceSetIterator(List instances, List exceptions)
- {
- mInstances = instances.size() == 1 ? instances.get(0) : new CompositeIterator(instances);
- mExceptions = exceptions == null || exceptions.isEmpty() ? new EmptyIterator() :
- exceptions.size() == 1 ? exceptions.get(0) : new CompositeIterator(exceptions);
- pullNext();
- }
-
-
- /**
- * Set the iteration end. The iterator will stop if the next instance is after the given date, no matter how many instances are still to come. This needs to
- * be set before you start iterating, otherwise you may get wrong results.
- *
- * @param end
- * The date at which to stop the iteration in milliseconds since the epoch.
- */
- RecurrenceSetIterator setEnd(long end)
- {
- mIterateEnd = end;
- return this;
- }
-
-
- /**
- * Check if there is at least one more instance to iterate.
- *
- * @return true
if the next call to {@link #next()} will return another instance, false
otherwise.
- */
- public boolean hasNext()
- {
- return mNextInstance < mIterateEnd;
- }
-
-
- /**
- * Get the next instance of this set. Do not call this if {@link #hasNext()} returns false
.
- *
- * @return The time in milliseconds since the epoch of the next instance.
- *
- * @throws ArrayIndexOutOfBoundsException
- * if there are no more instances.
- */
- public long next()
- {
- if (!hasNext())
- {
- throw new ArrayIndexOutOfBoundsException("no more elements");
- }
- long result = mNextInstance;
- pullNext();
- return result;
- }
-
-
- /**
- * Fast forward to the next instance at or after the given date.
- *
- * @param until
- * The date to fast forward to in milliseconds since the epoch.
- */
- public void fastForward(long until)
- {
- if (mNextInstance < until)
- {
- mInstances.fastForward(until);
- mExceptions.fastForward(until);
- pullNext();
- }
- }
-
-
- private void pullNext()
- {
- long next = Long.MAX_VALUE;
- long nextException = mNextException;
- int skipableInstances = MAX_SKIPPED_INSTANCES;
- while (mInstances.hasNext())
- {
- next = mInstances.next();
- while (nextException < next)
- {
- nextException = mExceptions.hasNext() ? mExceptions.next() : Long.MAX_VALUE;
- }
- if (nextException > next)
- {
- break;
- }
- if (--skipableInstances <= 0)
- {
- throw new RuntimeException(String.format(Locale.ENGLISH, "Skipped too many (%d) instances", MAX_SKIPPED_INSTANCES));
- }
- // we've skipped the next instance, this might have bene the last one
- next = Long.MAX_VALUE;
- }
- mNextInstance = next;
- mNextException = nextException;
- }
-}
diff --git a/src/main/java/org/dmfs/rfc5545/recurrenceset/package-info.java b/src/main/java/org/dmfs/rfc5545/recurrenceset/package-info.java
new file mode 100644
index 0000000..e26d540
--- /dev/null
+++ b/src/main/java/org/dmfs/rfc5545/recurrenceset/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@NonNullByDefault
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/iterable/ParsedDatesTest.java b/src/test/java/org/dmfs/rfc5545/iterable/ParsedDatesTest.java
new file mode 100644
index 0000000..a05549c
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/iterable/ParsedDatesTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.iterable;
+
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.iterable.ParsedDates;
+import org.junit.jupiter.api.Test;
+import org.saynotobugs.confidence.quality.Core;
+
+import static java.util.TimeZone.getTimeZone;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class ParsedDatesTest
+{
+ @Test
+ void testEmpty()
+ {
+ assertThat(new ParsedDates(""), is(emptyIterable()));
+ }
+
+ @Test
+ void testSingletonDate()
+ {
+ assertThat(new ParsedDates("20240216"), Core.iterates(DateTime.parse("20240216")));
+ }
+
+ @Test
+ void testMultipleDates()
+ {
+ assertThat(new ParsedDates("20240216,20240217,20240218"),
+ iterates(
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218")));
+ }
+
+ @Test
+ void testSingletonDateTime()
+ {
+ assertThat(new ParsedDates("20240216T123456"), iterates(DateTime.parse("20240216T123456")));
+ }
+
+ @Test
+ void testMultipleDateTimes()
+ {
+ assertThat(new ParsedDates("20240216T123456,20240217T123456,20240218T123456"),
+ iterates(
+ DateTime.parse("20240216T123456"),
+ DateTime.parse("20240217T123456"),
+ DateTime.parse("20240218T123456")));
+ }
+
+ @Test
+ void testAbsoluteSingletonDateTime()
+ {
+ assertThat(new ParsedDates(getTimeZone("Europe/Berlin"), "20240216T123456"),
+ iterates(DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T123456")));
+ }
+
+ @Test
+ void testAbsoluteMultipleDateTimes()
+ {
+ assertThat(new ParsedDates(getTimeZone("Europe/Berlin"), "20240216T123456,20240217T123456,20240218T123456"),
+ iterates(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T123456"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240217T123456"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240218T123456")));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/optional/LastInstanceTest.java b/src/test/java/org/dmfs/rfc5545/optional/LastInstanceTest.java
new file mode 100644
index 0000000..c602c62
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/optional/LastInstanceTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.optional;
+
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.dmfs.rfc5545.recurrenceset.OfList;
+import org.dmfs.rfc5545.recurrenceset.OfRule;
+import org.junit.jupiter.api.Test;
+
+import static org.dmfs.jems2.confidence.Jems2.absent;
+import static org.dmfs.jems2.confidence.Jems2.present;
+import static org.dmfs.jems2.iterable.EmptyIterable.emptyIterable;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.is;
+
+class LastInstanceTest
+{
+ @Test
+ void testEmptyList()
+ {
+ assertThat(new LastInstance(new OfList(emptyIterable())),
+ is(absent()));
+ }
+
+
+ @Test
+ void testSingleton()
+ {
+ assertThat(new LastInstance(new OfList(DateTime.parse("20240215"))),
+ is(present(DateTime.parse("20240215"))));
+ }
+
+ @Test
+ void testFiniteRule() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new LastInstance(
+ new OfRule(
+ new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))),
+ is(present(DateTime.parse("20240219"))));
+ }
+
+
+ @Test
+ void testInfiniteRule() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new LastInstance(
+ new OfRule(
+ new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215"))),
+ is(absent()));
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/DifferenceTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/DifferenceTest.java
new file mode 100644
index 0000000..c0acb65
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/DifferenceTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.jems2.iterable.EmptyIterable;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.junit.jupiter.api.Test;
+
+import static org.dmfs.rfc5545.confidence.Recur.*;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class DifferenceTest
+{
+ @Test
+ void testEmptyInstancesAndExceptions()
+ {
+ assertThat(new Difference(
+ new OfList(new EmptyIterable<>()),
+ new OfList(new EmptyIterable<>())
+ ),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testEmptyInstancesAndNonEmptyExceptions() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Difference(
+ new OfList(new EmptyIterable<>()),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240224T120000"))),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testInstancesEqualExceptions() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Difference(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240224T120000")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240224T120000"))),
+ is(emptyRecurrenceSet()));
+ }
+
+
+ @Test
+ void testInstancesAndExceptions() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Difference(
+ new OfRule(new RecurrenceRule("FREQ=HOURLY;INTERVAL=12;COUNT=10"), DateTime.parse("20240224T120000")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240224T000000"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240224T120000"),
+ DateTime.parse("20240225T120000"),
+ DateTime.parse("20240226T120000"),
+ DateTime.parse("20240227T120000"),
+ DateTime.parse("20240228T120000"),
+ DateTime.parse("20240229T000000"))));
+ }
+
+
+ /**
+ * See Issue 93
+ */
+ @Test
+ void test_github_issue_93() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new Difference(
+ new OfRule(new RecurrenceRule("FREQ=WEEKLY;UNTIL=20200511T000000Z;BYDAY=TU"), DateTime.parse("20200414T160000Z")),
+ new OfList(DateTime.parse("20200421T160000Z"), DateTime.parse("20200505T160000Z"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20200414T160000Z"),
+ DateTime.parse("20200428T160000Z"))));
+ }
+
+
+ @Test
+ void test_multiple_rules_with_same_values_and_count() throws InvalidRecurrenceRuleException
+ {
+ DateTime start = new DateTime(2019, 1, 1);
+
+ assertThat(
+ new Difference(
+ new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;BYDAY=MO,TU,WE"), start),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;BYDAY=WE,TH,FR;COUNT=10"), start),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;BYDAY=WE,FR,SA;COUNT=5"), start)
+ ),
+ new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;BYDAY=MO,TH;UNTIL=20190212"), start),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;BYDAY=MO;COUNT=4"), start),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;BYDAY=TH,FR"), start)
+ )
+ ),
+ allOf(
+ is(infinite()),
+ startsWith(
+ new DateTime(2019, 1, 2), // SA
+ new DateTime(2019, 1, 5), // TU
+ new DateTime(2019, 1, 6), // WE
+ new DateTime(2019, 1, 9), // SA
+ new DateTime(2019, 1, 12), // TU
+ new DateTime(2019, 1, 13), // WE
+ new DateTime(2019, 1, 19), // TU
+ new DateTime(2019, 1, 20), // WE
+ new DateTime(2019, 1, 26), // TU
+ new DateTime(2019, 1, 27), // WE
+ new DateTime(2019, 2, 4), // MO
+ new DateTime(2019, 2, 5), // TU
+ new DateTime(2019, 2, 6) // WE
+ )));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/FastForwardedTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/FastForwardedTest.java
new file mode 100644
index 0000000..e933301
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/FastForwardedTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.jems2.iterable.EmptyIterable;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.Duration;
+import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.junit.jupiter.api.Test;
+
+import static org.dmfs.rfc5545.confidence.Recur.*;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class FastForwardedTest
+{
+ @Test
+ void testFastForwardEmptySet()
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240225"),
+ new OfList(new EmptyIterable<>())),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testFastForwardBeyondLastInstance() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240225"),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testFastForwardRule() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240218"),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+
+ @Test
+ void testFastForwardRuleWithUnsyncedStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240218"),
+ new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR;COUNT=3"), DateTime.parse("20240215"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240223"))));
+ }
+
+
+ @Test
+ void testFastForwardRuleWithUnsyncedStartMultipleInstances() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240218"),
+ new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR;COUNT=5"), DateTime.parse("20240207"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240223"),
+ DateTime.parse("20240301"))));
+ }
+
+ @Test
+ void testFastForwardList()
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240218"),
+ new OfList(
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+
+ @Test
+ void testFastForwardMultiple() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240218"),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215")),
+ new OfList(
+ DateTime.parse("20240213"),
+ DateTime.parse("20240214"),
+ DateTime.parse("20240220"),
+ DateTime.parse("20240221"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"),
+ DateTime.parse("20240220"),
+ DateTime.parse("20240221"))));
+ }
+
+
+ @Test
+ void testFastForwardWithExceptions() throws InvalidRecurrenceRuleException
+ {
+ assertThat(
+ new FastForwarded(DateTime.parse("20240218"),
+ new Difference(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=7"), DateTime.parse("20240215")),
+ new OfList(
+ DateTime.parse("20240217"),
+ DateTime.parse("20240220")))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"),
+ DateTime.parse("20240221"))));
+ }
+
+ /**
+ * See Issue 61
+ */
+ @Test
+ void testGithubIssue61() throws InvalidRecurrenceRuleException
+ {
+ DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
+ assertThat(
+ new FastForwarded(
+ new DateTime(DateTime.UTC, 2019, 1, 1, 22, 0, 0),
+ new Merged(
+ new OfRule(new RecurrenceRule("FREQ=HOURLY;INTERVAL=5"), start),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;INTERVAL=1"), start)
+ )),
+ allOf(
+ is(infinite()),
+ startsWith(
+ new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 1, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 6, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 11, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 16, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 21, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 2, 0, 0))));
+ }
+
+ /**
+ * See Issue 85
+ */
+ @Test
+ void testGithubIssue85() throws InvalidRecurrenceRuleException
+ {
+ DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
+ assertThat(
+ new FastForwarded(start,
+ new OfRule(new RecurrenceRule("FREQ=DAILY;INTERVAL=1"), start)),
+ allOf(
+ is(infinite()),
+ startsWith(
+ new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 4, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 5, 0, 0, 0))));
+ }
+
+
+ @Test
+ void testFastForwardIntoPast() throws InvalidRecurrenceRuleException
+ {
+ DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
+ assertThat(
+ new FastForwarded(start.addDuration(new Duration(-1, 10)),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;INTERVAL=1"), start)),
+ allOf(
+ is(infinite()),
+ startsWith(
+ new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 4, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 5, 0, 0, 0))));
+ }
+
+
+ @Test
+ void testFastForwardSkipping1stInstance() throws InvalidRecurrenceRuleException
+ {
+ DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
+ assertThat(
+ new FastForwarded(start.addDuration(new Duration(1, 0, 1)),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;INTERVAL=1"), start)),
+ allOf(
+ is(infinite()),
+ startsWith(
+ new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 4, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 5, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 6, 0, 0, 0))));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/MergedTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/MergedTest.java
new file mode 100644
index 0000000..e8c1c06
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/MergedTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.jems2.iterable.EmptyIterable;
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.junit.jupiter.api.Test;
+
+import static org.dmfs.rfc5545.confidence.Recur.*;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class MergedTest
+{
+ @Test
+ void testMergeSingleEmpty()
+ {
+ assertThat(new Merged(
+ new OfList(new EmptyIterable<>())),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testMergeMultipleEmpty()
+ {
+ assertThat(new Merged(
+ new OfList(new EmptyIterable<>()),
+ new OfList(new EmptyIterable<>())),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testMergeSingleFinite() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+
+ @Test
+ void testMergeSingleFiniteWithEmpty() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215")),
+ new OfList(new EmptyIterable<>())),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+ @Test
+ void testMergeEmptyWithSingleFinite() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfList(new EmptyIterable<>()),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+ @Test
+ void testSingleInfinite() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215"))),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+
+ @Test
+ void testTwoFiniteDelegates() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215T180000")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215T120000"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240215T120000"),
+ DateTime.parse("20240215T180000"),
+ DateTime.parse("20240216T120000"),
+ DateTime.parse("20240216T180000"),
+ DateTime.parse("20240217T120000"),
+ DateTime.parse("20240217T180000"),
+ DateTime.parse("20240218T120000"),
+ DateTime.parse("20240218T180000"),
+ DateTime.parse("20240219T120000"),
+ DateTime.parse("20240219T180000"))));
+ }
+
+
+ @Test
+ void testOneFiniteAndOneInfiniteDelegates() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215T180000")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215T120000"))),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240215T120000"),
+ DateTime.parse("20240215T180000"),
+ DateTime.parse("20240216T120000"),
+ DateTime.parse("20240216T180000"),
+ DateTime.parse("20240217T120000"),
+ DateTime.parse("20240217T180000"),
+ DateTime.parse("20240218T120000"),
+ DateTime.parse("20240218T180000"),
+ DateTime.parse("20240219T120000"),
+ DateTime.parse("20240219T180000"))));
+ }
+
+
+ @Test
+ void testOneInfiniteAndOneFiniteDelegates() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215T180000")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215T120000"))),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240215T120000"),
+ DateTime.parse("20240215T180000"),
+ DateTime.parse("20240216T120000"),
+ DateTime.parse("20240216T180000"),
+ DateTime.parse("20240217T120000"),
+ DateTime.parse("20240217T180000"),
+ DateTime.parse("20240218T120000"),
+ DateTime.parse("20240218T180000"),
+ DateTime.parse("20240219T120000"),
+ DateTime.parse("20240219T180000"))));
+ }
+
+
+ @Test
+ void testTwoInfiniteDelegates() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215T180000")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215T120000"))),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240215T120000"),
+ DateTime.parse("20240215T180000"),
+ DateTime.parse("20240216T120000"),
+ DateTime.parse("20240216T180000"),
+ DateTime.parse("20240217T120000"),
+ DateTime.parse("20240217T180000"),
+ DateTime.parse("20240218T120000"),
+ DateTime.parse("20240218T180000"),
+ DateTime.parse("20240219T120000"),
+ DateTime.parse("20240219T180000"))));
+ }
+
+ @Test
+ void testMultipleFinite() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new Merged(
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240216")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240214")),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215"))),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240214"),
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"),
+ DateTime.parse("20240220"))));
+ }
+
+ /**
+ * See Issue 61
+ */
+ @Test
+ void test_github_issue_61() throws InvalidRecurrenceRuleException
+ {
+ DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
+ assertThat(
+ new Merged(
+ new OfRule(new RecurrenceRule("FREQ=HOURLY;INTERVAL=5"), start),
+ new OfRule(new RecurrenceRule("FREQ=DAILY;INTERVAL=1"), start)
+ ),
+ allOf(
+ is(infinite()),
+ startsWith(
+ new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 1, 5, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 1, 10, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 1, 15, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 1, 20, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 1, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 6, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 11, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 16, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 2, 21, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0),
+ new DateTime(DateTime.UTC, 2019, 1, 3, 2, 0, 0)
+ )));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/OfListTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/OfListTest.java
new file mode 100644
index 0000000..ea85cb8
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/OfListTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.rfc5545.DateTime;
+import org.junit.jupiter.api.Test;
+
+import static java.util.TimeZone.getTimeZone;
+import static org.dmfs.rfc5545.confidence.Recur.emptyRecurrenceSet;
+import static org.dmfs.rfc5545.confidence.Recur.infinite;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class OfListTest
+{
+ @Test
+ void testEmptyList()
+ {
+ assertThat(new OfList(new DateTime[0]),
+ is(emptyRecurrenceSet()));
+ }
+
+ @Test
+ void testSingletonList()
+ {
+ assertThat(new OfList(DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501")),
+ allOf(
+ is(not(infinite())),
+ iterates(DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"))));
+ }
+
+ @Test
+ void testOrderedList()
+ {
+ assertThat(new OfList(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501"))));
+ }
+
+ @Test
+ void testUnorderedList()
+ {
+ assertThat(new OfList(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501"))));
+ }
+
+
+ @Test
+ void testStringList()
+ {
+ assertThat(new OfList(
+ getTimeZone("Europe/Berlin"), "20240216T162501,20240216T142501,20240216T182501"),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501"))));
+ }
+
+
+ @Test
+ void testStringLists()
+ {
+ assertThat(new OfList(
+ getTimeZone("Europe/Berlin"),
+ "20240216T162501,20240216T142501,20240216T182501",
+ "20240216T202501,20240216T232501,20240216T222501"),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T202501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T222501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T232501"))));
+ }
+
+
+ @Test
+ void testStrings()
+ {
+ assertThat(new OfList(
+ getTimeZone("Europe/Berlin"),
+ "20240216T162501",
+ "20240216T142501",
+ "20240216T182501",
+ "20240216T202501",
+ "20240216T232501",
+ "20240216T222501"),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T142501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T162501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T182501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T202501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T222501"),
+ DateTime.parse(getTimeZone("Europe/Berlin"), "20240216T232501"))));
+ }
+
+
+ @Test
+ void testFloatingStringList()
+ {
+ assertThat(new OfList(
+ "20240216T162501,20240216T142501,20240216T182501"),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240216T142501"),
+ DateTime.parse("20240216T162501"),
+ DateTime.parse("20240216T182501"))));
+ }
+
+
+ @Test
+ void testFloatingStringLists()
+ {
+ assertThat(new OfList(
+ "20240216T162501,20240216T142501,20240216T182501",
+ "20240216T202501,20240216T232501,20240216T222501"),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240216T142501"),
+ DateTime.parse("20240216T162501"),
+ DateTime.parse("20240216T182501"),
+ DateTime.parse("20240216T202501"),
+ DateTime.parse("20240216T222501"),
+ DateTime.parse("20240216T232501"))));
+ }
+
+
+ @Test
+ void testFloatingStrings()
+ {
+ assertThat(new OfList(
+ "20240216T162501",
+ "20240216T142501",
+ "20240216T182501",
+ "20240216T202501",
+ "20240216T232501",
+ "20240216T222501"),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240216T142501"),
+ DateTime.parse("20240216T162501"),
+ DateTime.parse("20240216T182501"),
+ DateTime.parse("20240216T202501"),
+ DateTime.parse("20240216T222501"),
+ DateTime.parse("20240216T232501"))));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirstTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirstTest.java
new file mode 100644
index 0000000..886b104
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleAndFirstTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.junit.jupiter.api.Test;
+
+import static org.dmfs.rfc5545.confidence.Recur.infinite;
+import static org.dmfs.rfc5545.confidence.Recur.startsWith;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class OfRuleAndFirstTest
+{
+ @Test
+ void testRuleWithCountSyncStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR;COUNT=3"), DateTime.parse("20240216")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240216"),
+ DateTime.parse("20240223"),
+ DateTime.parse("20240301"))));
+ }
+
+ @Test
+ void testRuleWithCountUnsyncedStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR;COUNT=3"), DateTime.parse("20240214")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240214"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240223"))));
+ }
+
+ @Test
+ void testRuleWithUntilSyncStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR;UNTIL=20240301"), DateTime.parse("20240216")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240216"),
+ DateTime.parse("20240223"),
+ DateTime.parse("20240301"))));
+ }
+
+
+ @Test
+ void testRuleWithUntilUnsyncedStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR;UNTIL=20240301"), DateTime.parse("20240214")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240214"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240223"),
+ DateTime.parse("20240301"))));
+ }
+
+ @Test
+ void testInfiniteRuleSyncStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR"), DateTime.parse("20240216")),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240216"),
+ DateTime.parse("20240223"),
+ DateTime.parse("20240301"))));
+ }
+
+
+ @Test
+ void testInfiniteRuleUnsyncedStart() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRuleAndFirst(new RecurrenceRule("FREQ=DAILY;BYDAY=FR"), DateTime.parse("20240214")),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240214"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240223"),
+ DateTime.parse("20240301"))));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleTest.java
new file mode 100644
index 0000000..7e0515f
--- /dev/null
+++ b/src/test/java/org/dmfs/rfc5545/recurrenceset/OfRuleTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2024 Marten Gajda
+ *
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.dmfs.rfc5545.recurrenceset;
+
+import org.dmfs.rfc5545.DateTime;
+import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
+import org.dmfs.rfc5545.recur.RecurrenceRule;
+import org.junit.jupiter.api.Test;
+
+import static org.dmfs.rfc5545.confidence.Recur.infinite;
+import static org.dmfs.rfc5545.confidence.Recur.startsWith;
+import static org.saynotobugs.confidence.Assertion.assertThat;
+import static org.saynotobugs.confidence.quality.Core.*;
+
+class OfRuleTest
+{
+ @Test
+ void testRuleWithCount() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRule(new RecurrenceRule("FREQ=DAILY;COUNT=5"), DateTime.parse("20240215")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+ @Test
+ void testRuleWithUntil() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRule(new RecurrenceRule("FREQ=DAILY;UNTIL=20240219"), DateTime.parse("20240215")),
+ allOf(
+ is(not(infinite())),
+ iterates(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+
+
+ @Test
+ void testInfiniteRule() throws InvalidRecurrenceRuleException
+ {
+ assertThat(new OfRule(new RecurrenceRule("FREQ=DAILY"), DateTime.parse("20240215")),
+ allOf(
+ is(infinite()),
+ startsWith(
+ DateTime.parse("20240215"),
+ DateTime.parse("20240216"),
+ DateTime.parse("20240217"),
+ DateTime.parse("20240218"),
+ DateTime.parse("20240219"))));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/RecurrenceRuleAdapterTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/RecurrenceRuleAdapterTest.java
deleted file mode 100644
index 7b063fd..0000000
--- a/src/test/java/org/dmfs/rfc5545/recurrenceset/RecurrenceRuleAdapterTest.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright 2017 Marten Gajda
- *
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.rfc5545.DateTime;
-import org.dmfs.rfc5545.recur.RecurrenceRule;
-import org.junit.jupiter.api.Test;
-
-import java.util.TimeZone;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-
-/**
- * @author marten
- */
-public class RecurrenceRuleAdapterTest
-{
- @Test
- public void testGetIteratorSyncedStartInfinite() throws Exception
- {
- AbstractRecurrenceAdapter.InstanceIterator iterator = new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY"))
- .getIterator(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp());
-
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170210T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170310T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- }
-
-
- @Test
- public void testGetIteratorSyncedStartWithCount() throws Exception
- {
- AbstractRecurrenceAdapter.InstanceIterator iterator = new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;COUNT=3"))
- .getIterator(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp());
-
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170210T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170310T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(false));
- assertThat(iterator.hasNext(), is(false));
- }
-
-
- @Test
- public void testGetIteratorSyncedStartWithUntil() throws Exception
- {
- AbstractRecurrenceAdapter.InstanceIterator iterator = new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;UNTIL=20170312T113012Z"))
- .getIterator(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp());
-
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170210T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170310T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(false));
- assertThat(iterator.hasNext(), is(false));
- }
-
-
- @Test
- public void testGetIteratorUnsyncedStartInfinite() throws Exception
- {
- AbstractRecurrenceAdapter.InstanceIterator iterator = new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;BYMONTHDAY=11"))
- .getIterator(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp());
-
- // note the unsynced start is not a result, it's added separately by `RecurrenceSet`
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170111T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170211T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170311T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- }
-
-
- @Test
- public void testGetIteratorUnsyncedStartWithCount() throws Exception
- {
- AbstractRecurrenceAdapter.InstanceIterator iterator = new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;COUNT=3;BYMONTHDAY=11"))
- .getIterator(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp());
-
- // note the unsynced start is not a result, it's added separately by `RecurrenceSet`
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170111T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170211T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(false));
- assertThat(iterator.hasNext(), is(false));
- }
-
-
- @Test
- public void testGetIteratorUnsyncedStartWithUntil() throws Exception
- {
- AbstractRecurrenceAdapter.InstanceIterator iterator = new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;UNTIL=20170312T113012Z;BYMONTHDAY=11"))
- .getIterator(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp());
-
- // note the unsynced start is not a result, it's added separately by `RecurrenceSet`
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170111T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170211T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.hasNext(), is(true));
- assertThat(iterator.next(), is(DateTime.parse("Europe/Berlin", "20170311T113012").getTimestamp()));
- assertThat(iterator.hasNext(), is(false));
- assertThat(iterator.hasNext(), is(false));
- }
-
-
- @Test
- public void testIsInfinite() throws Exception
- {
- assertThat(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY")).isInfinite(), is(true));
- assertThat(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;COUNT=10")).isInfinite(), is(false));
- assertThat(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;UNTIL=20171212")).isInfinite(), is(false));
- }
-
-
- @Test
- public void testGetLastInstance() throws Exception
- {
- assertThat(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;COUNT=10"))
- .getLastInstance(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp()),
- is(DateTime.parse("Europe/Berlin", "20171010T113012").getTimestamp()));
- assertThat(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=MONTHLY;UNTIL=20171212T101010Z"))
- .getLastInstance(TimeZone.getTimeZone("Europe/Berlin"), DateTime.parse("Europe/Berlin", "20170110T113012").getTimestamp()),
- is(DateTime.parse("Europe/Berlin", "20171210T113012").getTimestamp()));
- }
-}
\ No newline at end of file
diff --git a/src/test/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSetIteratorTest.java b/src/test/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSetIteratorTest.java
deleted file mode 100644
index 2425f91..0000000
--- a/src/test/java/org/dmfs/rfc5545/recurrenceset/RecurrenceSetIteratorTest.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/*
- * Copyright 2019 Marten Gajda
- *
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.dmfs.rfc5545.recurrenceset;
-
-import org.dmfs.jems2.iterator.BaseIterator;
-import org.dmfs.rfc5545.DateTime;
-import org.dmfs.rfc5545.Duration;
-import org.dmfs.rfc5545.recur.InvalidRecurrenceRuleException;
-import org.dmfs.rfc5545.recur.RecurrenceRule;
-import org.junit.jupiter.api.Test;
-
-import java.util.NoSuchElementException;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-
-import static java.util.Arrays.asList;
-import static org.dmfs.jems2.hamcrest.matchers.generatable.GeneratableMatcher.startsWith;
-import static org.dmfs.jems2.hamcrest.matchers.iterator.IteratorMatcher.iteratorOf;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-
-/**
- * Unit test for {@link RecurrenceSetIterator}.
- *
- * @author Marten Gajda
- */
-public class RecurrenceSetIteratorTest
-{
- /**
- * Test results if a single exception list has been provided.
- */
- @Test
- public void testExceptionsAllDay()
- {
- TimeZone testZone = TimeZone.getTimeZone("UTC");
- DateTime start = DateTime.parse("20180101");
- RecurrenceSetIterator recurrenceSetIterator = new RecurrenceSetIterator(
- asList(new RecurrenceList("20180101,20180102,20180103,20180104", testZone).getIterator(testZone, start.getTimestamp())),
- asList(new RecurrenceList("20180102,20180103", testZone).getIterator(testZone, start.getTimestamp())));
-
- // note we call hasNext twice to ensure it's idempotent
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.addDuration(new Duration(1, 3, 0)).getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- }
-
-
- /**
- * Test results if multiple exception lists have been provided.
- */
- @Test
- public void testMultipleExceptionsAllDay()
- {
- TimeZone testZone = TimeZone.getTimeZone("UTC");
- DateTime start = DateTime.parse("20180101");
- RecurrenceSetIterator recurrenceSetIterator = new RecurrenceSetIterator(
- asList(new RecurrenceList("20180101,20180102,20180103,20180104", testZone).getIterator(testZone, start.getTimestamp())),
- asList(new RecurrenceList("20180103", testZone).getIterator(testZone, start.getTimestamp()),
- new RecurrenceList("20180102", testZone).getIterator(testZone, start.getTimestamp())));
-
- // note we call hasNext twice to ensure it's idempotent
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.addDuration(new Duration(1, 3, 0)).getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- }
-
-
- /**
- * Test results if a single exception list has been provided.
- */
- @Test
- public void testExceptions()
- {
- TimeZone testZone = TimeZone.getTimeZone("UTC");
- DateTime start = DateTime.parse("20180101T120000");
- RecurrenceSetIterator recurrenceSetIterator = new RecurrenceSetIterator(
- asList(new RecurrenceList("20180101T120000,20180102T120000,20180103T120000,20180104T120000", testZone).getIterator(testZone,
- start.getTimestamp())),
- asList(new RecurrenceList("20180102T120000,20180103T120000", testZone).getIterator(testZone, start.getTimestamp())));
-
- // note we call hasNext twice to ensure it's idempotent
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.addDuration(new Duration(1, 3, 0)).getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- }
-
-
- /**
- * Test results if multiple exception lists have been provided.
- */
- @Test
- public void testMultipleExceptions()
- {
- TimeZone testZone = TimeZone.getTimeZone("UTC");
- DateTime start = DateTime.parse("20180101T120000");
- RecurrenceSetIterator recurrenceSetIterator = new RecurrenceSetIterator(
- asList(new RecurrenceList("20180101T120000,20180102T120000,20180103T120000,20180104T120000", testZone).getIterator(testZone,
- start.getTimestamp())),
- asList(new RecurrenceList("20180103T120000", testZone).getIterator(testZone, start.getTimestamp()),
- new RecurrenceList("20180102T120000", testZone).getIterator(testZone, start.getTimestamp())));
-
- // note we call hasNext twice to ensure it's idempotent
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.hasNext(), is(true));
- assertThat(recurrenceSetIterator.next(), is(start.addDuration(new Duration(1, 3, 0)).getTimestamp()));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- assertThat(recurrenceSetIterator.hasNext(), is(false));
- }
-
-
- /**
- * See https://github.com/dmfs/lib-recur/issues/61
- */
- @Test
- public void testMultipleRules() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
-
- // Combine all Recurrence Rules into a RecurrenceSet
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=HOURLY;INTERVAL=5")));
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- assertThat(() -> it::next, startsWith(
- new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 1, 5, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 1, 10, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 1, 15, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 1, 20, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 1, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 6, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 11, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 16, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 21, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 2, 0, 0).getTimestamp()
- ));
- }
-
-
- @Test
- public void testMultipleRulesWithSameValues() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(2019, 1, 1);
-
- // Combine all Recurrence Rules into a RecurrenceSet
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=MO,TU,WE")));
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=WE,TH,FR")));
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=WE,FR,SA")));
- ruleSet.addExceptions(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=MO,TH")));
- ruleSet.addExceptions(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=MO")));
- ruleSet.addExceptions(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=TH,FR")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- assertThat(() -> it::next, startsWith(
- new DateTime(2019, 1, 2).getTimestamp(), // SA
- new DateTime(2019, 1, 5).getTimestamp(), // TU
- new DateTime(2019, 1, 6).getTimestamp(), // WE
- new DateTime(2019, 1, 9).getTimestamp(), // SA
- new DateTime(2019, 1, 12).getTimestamp(), // TU
- new DateTime(2019, 1, 13).getTimestamp(), // WE
- new DateTime(2019, 1, 16).getTimestamp(), // SA
- new DateTime(2019, 1, 19).getTimestamp(), // TU
- new DateTime(2019, 1, 20).getTimestamp(), // WE
- new DateTime(2019, 1, 23).getTimestamp(), // SA
- new DateTime(2019, 1, 26).getTimestamp(), // TU
- new DateTime(2019, 1, 27).getTimestamp(), // WE
- new DateTime(2019, 2, 2).getTimestamp(), // SA
- new DateTime(2019, 2, 5).getTimestamp() // TU
- ));
- }
-
-
- /**
- * See https://github.com/dmfs/lib-recur/issues/93
- */
- @Test
- public void testGithubIssue93() throws InvalidRecurrenceRuleException
- {
- DateTime start = DateTime.parse("20200414T160000Z");
-
- // Combine all Recurrence Rules into a RecurrenceSet
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=WEEKLY;UNTIL=20200511T000000Z;BYDAY=TU")));
- ruleSet.addExceptions(new RecurrenceList("20200421T160000Z,20200505T160000Z", DateTime.UTC));
-
- // Create an iterator using the RecurrenceSet
- assertThat(() -> new RecurrenceAdapter(ruleSet.iterator(start.getTimeZone(), start.getTimestamp())),
- iteratorOf(
- DateTime.parse("20200414T160000Z").getTimestamp(),
- DateTime.parse("20200428T160000Z").getTimestamp()));
- }
-
-
- @Test
- public void testMultipleRulesWithSameValuesAndCount() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(2019, 1, 1);
-
- // Combine all Recurrence Rules into a RecurrenceSet
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=MO,TU,WE")));
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=WE,TH,FR;COUNT=10")));
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=WE,FR,SA;COUNT=5")));
- ruleSet.addExceptions(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=MO,TH;UNTIL=20190212")));
- ruleSet.addExceptions(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=MO;COUNT=4")));
- ruleSet.addExceptions(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;BYDAY=TH,FR")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- assertThat(() -> it::next, startsWith(
- new DateTime(2019, 1, 2).getTimestamp(), // SA
- new DateTime(2019, 1, 5).getTimestamp(), // TU
- new DateTime(2019, 1, 6).getTimestamp(), // WE
- new DateTime(2019, 1, 9).getTimestamp(), // SA
- new DateTime(2019, 1, 12).getTimestamp(), // TU
- new DateTime(2019, 1, 13).getTimestamp(), // WE
- //new DateTime(2019, 1, 16).getTimestamp(), // SA
- new DateTime(2019, 1, 19).getTimestamp(), // TU
- new DateTime(2019, 1, 20).getTimestamp(), // WE
- //new DateTime(2019, 1, 23).getTimestamp(), // SA
- new DateTime(2019, 1, 25).getTimestamp(), // MO
- new DateTime(2019, 1, 26).getTimestamp(), // TU
- new DateTime(2019, 1, 27).getTimestamp(), // WE
- //new DateTime(2019, 2, 2).getTimestamp(), // SA
- new DateTime(2019, 2, 4).getTimestamp(), // MO
- new DateTime(2019, 2, 5).getTimestamp() // TU
- ));
- }
-
-
- /**
- * See https://github.com/dmfs/lib-recur/issues/61
- */
- @Test
- public void testMultipleRulesWithFastForward() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
-
- // Combine all Recurrence Rules into a RecurrenceSet
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=HOURLY;INTERVAL=5")));
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- // Fast forward to the time of calculation (1/1/2019 at 10pm).
- it.fastForward(new DateTime(DateTime.UTC, 2019, 1, 1, 22, 0, 0).getTimestamp());
-
- assertThat(() -> it::next, startsWith(
- new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 1, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 6, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 11, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 16, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 21, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 2, 0, 0).getTimestamp()
- ));
- }
-
-
- /**
- * See https://github.com/dmfs/lib-recur/issues/85
- *
- * Fast forward to the start date (i.e. not fast forwarding at all)
- */
- @Test
- public void testFastForwardToStart() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
-
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- // "Fast forward" to start.
- it.fastForward(start.getTimestamp());
-
- assertThat(() -> it::next, startsWith(
- new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 4, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 5, 0, 0, 0).getTimestamp()
- ));
- }
-
-
- @Test
- public void testFastForwardToPast() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
-
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- // "Fast forward" to 100 days in the past.
- it.fastForward(start.getTimestamp() - TimeUnit.DAYS.toMillis(100));
-
- assertThat(() -> it::next, startsWith(
- new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 4, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 5, 0, 0, 0).getTimestamp()
- ));
- }
-
-
- @Test
- public void testFastForwardToNext() throws InvalidRecurrenceRuleException
- {
- DateTime start = new DateTime(DateTime.UTC, 2019, 1, 1, 0, 0, 0);
-
- RecurrenceSet ruleSet = new RecurrenceSet();
- ruleSet.addInstances(new RecurrenceRuleAdapter(new RecurrenceRule("FREQ=DAILY;INTERVAL=1")));
-
- // Create an iterator using the RecurrenceSet
- RecurrenceSetIterator it = ruleSet.iterator(start.getTimeZone(), start.getTimestamp());
-
- // "Fast forward" to 1 millisecond after start (skipping the first instance only)
- it.fastForward(start.getTimestamp() + 1);
-
- assertThat(() -> it::next, startsWith(
- new DateTime(DateTime.UTC, 2019, 1, 2, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 3, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 4, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 5, 0, 0, 0).getTimestamp(),
- new DateTime(DateTime.UTC, 2019, 1, 6, 0, 0, 0).getTimestamp()
- ));
- }
-
-
- private final static class RecurrenceAdapter extends BaseIterator
- {
-
- private final RecurrenceSetIterator mDelegate;
-
-
- private RecurrenceAdapter(RecurrenceSetIterator delegate)
- {
- mDelegate = delegate;
- }
-
-
- @Override
- public boolean hasNext()
- {
- return mDelegate.hasNext();
- }
-
-
- @Override
- public Long next()
- {
- if (!hasNext())
- {
- throw new NoSuchElementException();
- }
- return mDelegate.next();
- }
- }
-}
\ No newline at end of file