diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs
index 03998218..fb163a0b 100644
--- a/Ical.Net.Tests/RecurrenceTests.cs
+++ b/Ical.Net.Tests/RecurrenceTests.cs
@@ -37,26 +37,30 @@ int eventIndex
.OrderBy(o => o.Period.StartTime)
.ToList();
- Assert.That(
- occurrences,
-Has.Count.EqualTo(dateTimes.Length),
- "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count);
-
- if (evt.RecurrenceRules.Count > 0)
+ Assert.Multiple(() =>
{
- Assert.That(evt.RecurrenceRules, Has.Count.EqualTo(1));
- }
+ Assert.That(
+ occurrences,
+ Has.Count.GreaterThanOrEqualTo(dateTimes.Length),
+ "There should have " + dateTimes.Length + " or more occurrences; there were " + occurrences.Count);
- for (var i = 0; i < dateTimes.Length; i++)
- {
- // Associate each incoming date/time with the calendar.
- dateTimes[i].AssociatedObject = cal;
+ if (evt.RecurrenceRules.Count > 0)
+ {
+ Assert.That(evt.RecurrenceRules, Has.Count.EqualTo(1));
+ }
+
+ for (var i = 0; i < dateTimes.Length; i++)
+ {
+ // Associate each incoming date/time with the calendar.
+ dateTimes[i].AssociatedObject = cal;
- var dt = dateTimes[i];
- Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur on " + dt);
- if (timeZones != null)
- Assert.That(dt.TimeZoneName, Is.EqualTo(timeZones[i]), "Event " + dt + " should occur in the " + timeZones[i] + " timezone");
- }
+ var dt = dateTimes[i];
+ Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur on " + dt);
+ if (timeZones != null)
+ Assert.That(dt.TimeZoneName, Is.EqualTo(timeZones[i]),
+ "Event " + dt + " should occur in the " + timeZones[i] + " timezone");
+ }
+ });
}
private void EventOccurrenceTest(
@@ -1868,27 +1872,26 @@ public void Bug1741093()
}
///
- /// Ensures that, by default, SECONDLY recurrence rules are not allowed.
+ /// Too many SECONDLY recurrences should time out.
///
[Test, Category("Recurrence")]
- public void Secondly1()
+ public void Secondly_HighNumberOfOccurrences_ShouldTimeout()
{
+ RecurrenceUtil.DefaultTimeout = 50; // reduce below default to ensure timeout
Assert.That(() =>
{
var iCal = Calendar.Load(IcsFiles.Secondly1);
_ = iCal.GetOccurrences(new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid), new CalDateTime(2007, 7, 21, 8, 0, 0, _tzid));
- }, Throws.Exception.TypeOf(), "Evaluation engine should have failed.");
+ }, Throws.Exception.TypeOf(), "Too many occurrences should timeout.");
}
///
- /// Ensures that the proper behavior occurs when the evaluation
- /// mode is set to adjust automatically for SECONDLY evaluation
+ /// At least a few SECONDLY occurrences are generated without TimeoutException.
///
[Test, Category("Recurrence")]
- public void Secondly1_1()
+ public void Secondly_LowNumberOfOccurrences_ShouldSucceed()
{
var iCal = Calendar.Load(IcsFiles.Secondly1);
- iCal.RecurrenceEvaluationMode = RecurrenceEvaluationModeType.AdjustAutomatically;
EventOccurrenceTest(
iCal,
@@ -1897,47 +1900,45 @@ public void Secondly1_1()
new[]
{
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 1, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 2, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 3, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 4, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 5, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 6, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 7, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 8, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 9, 0, _tzid),
- new CalDateTime(2007, 6, 21, 8, 10, 0, _tzid)
+ new CalDateTime(2007, 6, 21, 8, 0, 1, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 2, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 3, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 4, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 5, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 6, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 7, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 8, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 9, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 0, 10, _tzid)
},
null
);
}
///
- /// Ensures that if configured, MINUTELY recurrence rules are not allowed.
+ /// Too many MINUTELY occurrences time out.
///
[Test, Category("Recurrence")]
- public void Minutely1()
+ public void Minutely_HighNumberOfOccurrences_ShouldTimeout()
{
+ RecurrenceUtil.DefaultTimeout = 50; // reduce below default to ensure timeout
Assert.That(() =>
{
var iCal = Calendar.Load(IcsFiles.Minutely1);
- iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictMinutely;
- var occurrences = iCal.GetOccurrences(
+ var evt = iCal.Events.First();
+ var occurrences = evt.GetOccurrences(
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
new CalDateTime(2007, 7, 21, 8, 0, 0, _tzid));
- }, Throws.Exception.TypeOf());
+ }, Throws.Exception.TypeOf(), "Too many occurrences should timeout.");
}
///
- /// Ensures that the proper behavior occurs when the evaluation
- /// mode is set to adjust automatically for MINUTELY evaluation
+ /// At least a few MINUTELY occurrences are generated without TimeoutException.
///
[Test, Category("Recurrence")]
- public void Minutely1_1()
+ public void Minutely_LowNumberOfOccurrences_ShouldSucceed()
{
var iCal = Calendar.Load(IcsFiles.Minutely1);
- iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictMinutely;
- iCal.RecurrenceEvaluationMode = RecurrenceEvaluationModeType.AdjustAutomatically;
EventOccurrenceTest(
iCal,
@@ -1946,40 +1947,38 @@ public void Minutely1_1()
new[]
{
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
- new CalDateTime(2007, 6, 21, 9, 0, 0, _tzid),
- new CalDateTime(2007, 6, 21, 10, 0, 0, _tzid),
- new CalDateTime(2007, 6, 21, 11, 0, 0, _tzid),
- new CalDateTime(2007, 6, 21, 12, 0, 0, _tzid)
+ new CalDateTime(2007, 6, 21, 8, 1, 0, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 2, 0, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 3, 0, _tzid),
+ new CalDateTime(2007, 6, 21, 8, 4, 0, _tzid)
},
null
);
}
///
- /// Ensures that if configured, HOURLY recurrence rules are not allowed.
+ /// Too many HOURLY occurrences time out.
///
- [Test, Category("Recurrence")/*, ExpectedException(typeof(EvaluationEngineException))*/]
- public void Hourly1()
+ [Test, Category("Recurrence")]
+ public void Hourly_HighNumberOfOccurrences_ShouldTimeout()
{
+ RecurrenceUtil.DefaultTimeout = 50; // reduce below default to ensure timeout
Assert.That(() =>
- {
+ {
var iCal = Calendar.Load(IcsFiles.Hourly1);
- iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictHourly;
- _ = iCal.GetOccurrences(new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid), new CalDateTime(2007, 7, 21, 8, 0, 0, _tzid));
-
- }, Throws.Exception.TypeOf());
+ _ = iCal.GetOccurrences(new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
+ new CalDateTime(2013, 7, 21, 8, 0, 0, _tzid));
+ },
+ Throws.Exception.TypeOf(), "Too many occurrences should timeout.");
}
///
- /// Ensures that the proper behavior occurs when the evaluation
- /// mode is set to adjust automatically for HOURLY evaluation
+ /// At least a few HOURLY occurrences are generated without TimeoutException.
///
[Test, Category("Recurrence")]
- public void Hourly1_1()
+ public void Hourly_LowNumberOfOccurrences_ShouldSucceed()
{
var iCal = Calendar.Load(IcsFiles.Hourly1);
- iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictHourly;
- iCal.RecurrenceEvaluationMode = RecurrenceEvaluationModeType.AdjustAutomatically;
EventOccurrenceTest(
iCal,
@@ -1988,10 +1987,10 @@ public void Hourly1_1()
new[]
{
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
- new CalDateTime(2007, 6, 22, 8, 0, 0, _tzid),
- new CalDateTime(2007, 6, 23, 8, 0, 0, _tzid),
- new CalDateTime(2007, 6, 24, 8, 0, 0, _tzid),
- new CalDateTime(2007, 6, 25, 8, 0, 0, _tzid)
+ new CalDateTime(2007, 6, 21, 9, 0, 0, _tzid),
+ new CalDateTime(2007, 6, 21, 10, 0, 0, _tzid),
+ new CalDateTime(2007, 6, 21, 11, 0, 0, _tzid),
+ new CalDateTime(2007, 6, 21, 12, 0, 0, _tzid)
},
null
);
@@ -2038,7 +2037,7 @@ public void YearlyInterval1()
}
///
- /// Ensures that "off-day" calcuation works correctly
+ /// Ensures that "off-day" calculation works correctly
///
[Test, Category("Recurrence")]
public void DailyInterval1()
@@ -2841,10 +2840,7 @@ public void Evaluate1(string freq, int secsPerInterval, bool hasTime)
// This case (DTSTART of type DATE and FREQ=MINUTELY) is undefined in RFC 5545.
// ical.net handles the case by pretending DTSTART has the time set to midnight.
- evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5")
- {
- RestrictionType = RecurrenceRestrictionType.NoRestriction,
- });
+ evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5"));
var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(-1), CalDateTime.Today.AddDays(100))
.OrderBy(x => x)
@@ -2868,8 +2864,7 @@ public void RecurrencePattern1()
{
// NOTE: evaluators are not generally meant to be used directly like this.
// However, this does make a good test to ensure they behave as they should.
- RecurrencePattern pattern = new RecurrencePattern("FREQ=SECONDLY;INTERVAL=10");
- pattern.RestrictionType = RecurrenceRestrictionType.NoRestriction;
+ var pattern = new RecurrencePattern("FREQ=SECONDLY;INTERVAL=10");
var us = new CultureInfo("en-US");
@@ -3177,10 +3172,14 @@ public void OccurrenceMustBeCompletelyContainedWithinSearchRange()
Assert.That(occurrences.Last().StartTime.Equals(lastExpected), Is.True);
}
- [Test, Ignore("Turn on in v3")]
+ ///
+ /// Evaluate relevancy and validity of the request.
+ /// Find a solution for issue #120 or close forever
+ ///
+ [Test, Ignore("Turn on in v3", Until = "2024-12-31")]
public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet()
{
- //https://github.com/rianjs/ical.net/issues/120
+ //https://github.com/rianjs/ical.net/issues/120 dated Sep 5, 2016
const string ical =
@"BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
diff --git a/Ical.Net/Calendar.cs b/Ical.Net/Calendar.cs
index 47248aa6..cc38dafe 100644
--- a/Ical.Net/Calendar.cs
+++ b/Ical.Net/Calendar.cs
@@ -26,7 +26,7 @@ public static Calendar Load(Stream s)
=> CalendarCollection.Load(new StreamReader(s, Encoding.UTF8)).SingleOrDefault();
public static Calendar Load(TextReader tr)
- => CalendarCollection.Load(tr).OfType().SingleOrDefault();
+ => CalendarCollection.Load(tr)?.SingleOrDefault();
public static IList Load(Stream s, Encoding e)
=> Load(new StreamReader(s, e));
@@ -118,7 +118,7 @@ public override int GetHashCode()
public virtual IEnumerable RecurringItems => Children.OfType();
///
- /// A collection of components in the iCalendar.
+ /// A collection of components in the iCalendar.
///
public virtual IUniqueComponentList Events => _mEvents;
@@ -166,12 +166,14 @@ public virtual string Method
set => Properties.Set("METHOD", value);
}
+ [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)]
public virtual RecurrenceRestrictionType RecurrenceRestriction
{
get => Properties.Get("X-DDAY-ICAL-RECURRENCE-RESTRICTION");
set => Properties.Set("X-DDAY-ICAL-RECURRENCE-RESTRICTION", value);
}
+ [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)]
public virtual RecurrenceEvaluationModeType RecurrenceEvaluationMode
{
get => Properties.Get("X-DDAY-ICAL-RECURRENCE-EVALUATION-MODE");
@@ -190,35 +192,7 @@ public VTimeZone AddTimeZone(VTimeZone tz)
return tz;
}
- ///
- /// Evaluates component recurrences for the given range of time.
- ///
- /// For example, if you are displaying a month-view for January 2007,
- /// you would want to evaluate recurrences for Jan. 1, 2007 to Jan. 31, 2007
- /// to display relevant information for those dates.
- ///
- ///
- /// The beginning date/time of the range to test.
- /// The end date/time of the range to test.
- [Obsolete("This method is no longer supported. Use GetOccurrences() instead.")]
- public void Evaluate(IDateTime fromDate, IDateTime toDate)
- {
- throw new NotSupportedException("Evaluate() is no longer supported as a public method. Use GetOccurrences() instead.");
- }
-
- ///
- /// Evaluates component recurrences for the given range of time, for
- /// the type of recurring component specified.
- ///
- /// The type of component to be evaluated for recurrences.
- /// The beginning date/time of the range to test.
- /// The end date/time of the range to test.
- [Obsolete("This method is no longer supported. Use GetOccurrences() instead.")]
- public void Evaluate(IDateTime fromDate, IDateTime toDate)
- {
- throw new NotSupportedException("Evaluate() is no longer supported as a public method. Use GetOccurrences() instead.");
- }
-
+
///
/// Clears recurrence evaluations for recurring components.
///
@@ -273,6 +247,9 @@ public virtual HashSet GetOccurrences(IDateTime dt) where T : IRe
public virtual HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent
=> GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddTicks(-1)));
+ public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent
+ => GetOccurrences(new CalDateTime(startTime), new CalDateTime(endTime));
+
///
/// Returns all occurrences of components of type T that start within the date range provided.
/// All components occurring between and
@@ -282,24 +259,21 @@ public virtual HashSet GetOccurrences(DateTime dt) where T : IRec
/// The ending date range
public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent
{
- var occurrences = new HashSet(RecurringItems
- .OfType()
- .SelectMany(recurrable => recurrable.GetOccurrences(startTime, endTime)));
-
- var removeOccurrencesQuery = occurrences
- .Where(o => o.Source is UniqueComponent)
- .GroupBy(o => ((UniqueComponent)o.Source).Uid)
- .SelectMany(group => group
- .Where(o => o.Source.RecurrenceId != null)
- .SelectMany(occurrence => group.
- Where(o => o.Source.RecurrenceId == null && occurrence.Source.RecurrenceId.Date.Equals(o.Period.StartTime.Date))));
-
- occurrences.ExceptWith(removeOccurrencesQuery);
- return occurrences;
- }
-
- public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent
- => GetOccurrences(new CalDateTime(startTime), new CalDateTime(endTime));
+ var occurrences = new HashSet(RecurringItems
+ .OfType()
+ .SelectMany(recurrable => recurrable.GetOccurrences(startTime, endTime)));
+
+ var removeOccurrencesQuery = occurrences
+ .Where(o => o.Source is UniqueComponent)
+ .GroupBy(o => ((UniqueComponent)o.Source).Uid)
+ .SelectMany(group => group
+ .Where(o => o.Source.RecurrenceId != null)
+ .SelectMany(occurrence => group.
+ Where(o => o.Source.RecurrenceId == null && occurrence.Source.RecurrenceId.Date.Equals(o.Period.StartTime.Date))));
+
+ occurrences.ExceptWith(removeOccurrencesQuery);
+ return occurrences;
+ }
///
/// Creates a typed object that is a direct child of the iCalendar itself. Generally,
diff --git a/Ical.Net/Constants.cs b/Ical.Net/Constants.cs
index 062b27e8..0485f61e 100644
--- a/Ical.Net/Constants.cs
+++ b/Ical.Net/Constants.cs
@@ -219,6 +219,7 @@ public enum FrequencyOccurrence
Fifth = 5
}
+ [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)]
public enum RecurrenceRestrictionType
{
///
@@ -247,6 +248,7 @@ public enum RecurrenceRestrictionType
RestrictHourly
}
+ [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)]
public enum RecurrenceEvaluationModeType
{
///
diff --git a/Ical.Net/DataTypes/RecurrencePattern.cs b/Ical.Net/DataTypes/RecurrencePattern.cs
index bde5ebff..24d09305 100644
--- a/Ical.Net/DataTypes/RecurrencePattern.cs
+++ b/Ical.Net/DataTypes/RecurrencePattern.cs
@@ -84,6 +84,11 @@ public int Interval
public DayOfWeek FirstDayOfWeek { get; set; } = DayOfWeek.Monday;
+ ///
+ /// The type of restriction to apply to the evaluation of this recurrence pattern.
+ /// Returns if not set.
+ ///
+ [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)]
public RecurrenceRestrictionType RestrictionType
{
get
@@ -93,11 +98,12 @@ public RecurrenceRestrictionType RestrictionType
{
return _restrictionType.Value;
}
- return Calendar?.RecurrenceRestriction ?? RecurrenceRestrictionType.Default;
+ return RecurrenceRestrictionType.NoRestriction;
}
set => _restrictionType = value;
}
+ [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)]
public RecurrenceEvaluationModeType EvaluationMode
{
get
diff --git a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs
index f91a644f..43cbffb3 100644
--- a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs
+++ b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs
@@ -48,9 +48,6 @@ namespace Ical.Net.Evaluation
///
public class RecurrencePatternEvaluator : Evaluator
{
- // FIXME: in ical4j this is configurable.
- private const int _maxIncrementCount = 1000;
-
protected RecurrencePattern Pattern { get; set; }
public RecurrencePatternEvaluator(RecurrencePattern pattern)
@@ -232,14 +229,13 @@ private void EnforceEvaluationRestrictions(RecurrencePattern pattern)
}
}
- /**
- * Returns a list of start dates in the specified period represented by this recur. This method includes a base date
- * argument, which indicates the start of the fist occurrence of this recurrence. The base date is used to inject
- * default values to return a set of dates in the correct format. For example, if the search start date (start) is
- * Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, the start dates returned should all be at
- * 9:00AM, and not 12:19PM.
- */
-
+ ///
+ /// Returns a list of start dates in the specified period represented by this recurrence pattern.
+ /// This method includes a base date argument, which indicates the start of the first occurrence of this recurrence.
+ /// The base date is used to inject default values to return a set of dates in the correct format.
+ /// For example, if the search start date (start) is Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM,
+ /// the start dates returned should all be at 9:00AM, and not 12:19PM.
+ ///
private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTime periodEnd, int maxCount, RecurrencePattern pattern,
bool includeReferenceDateInResults)
{
@@ -261,7 +257,6 @@ private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTim
var expandBehavior = RecurrenceUtil.GetExpandBehaviorList(pattern);
- var noCandidateIncrementCount = 0;
var candidate = DateTime.MinValue;
while (maxCount < 0 || dates.Count < maxCount)
{
@@ -289,8 +284,6 @@ private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTim
var candidates = GetCandidates(seedCopy, pattern, expandBehavior);
if (candidates.Count > 0)
{
- noCandidateIncrementCount = 0;
-
foreach (var t in candidates.OrderBy(c => c).Where(t => t >= originalDate))
{
candidate = t;
@@ -316,14 +309,6 @@ private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTim
}
}
}
- else
- {
- noCandidateIncrementCount++;
- if (_maxIncrementCount > 0 && noCandidateIncrementCount > _maxIncrementCount)
- {
- break;
- }
- }
IncrementDate(ref seedCopy, pattern, pattern.Interval);
}
@@ -331,13 +316,13 @@ private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTim
return dates;
}
- /**
- * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed.
- * @param date the seed date
- * @param value the type of date list to return
- * @return a DateList
- */
-
+ ///
+ /// Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed.
+ ///
+ /// The seed date.
+ ///
+ ///
+ /// A list of possible dates.
private List GetCandidates(DateTime date, RecurrencePattern pattern, bool?[] expandBehaviors)
{
var dates = new List { date };
@@ -353,11 +338,12 @@ private List GetCandidates(DateTime date, RecurrencePattern pattern, b
return dates;
}
- /**
- * Applies BYSETPOS rules to dates
. Valid positions are from 1 to the size of the date list. Invalid
- * positions are ignored.
- * @param dates
- */
+ ///
+ /// Applies BYSETPOS rules to . Valid positions are from 1 to the size of the date list. Invalid
+ /// positions are ignored.
+ ///
+ /// The list of dates to which the BYSETPOS rules will be applied.
+ ///
private List ApplySetPosRules(List dates, RecurrencePattern pattern)
{
// return if no SETPOS rules specified..
@@ -379,12 +365,14 @@ private List ApplySetPosRules(List dates, RecurrencePattern
return setPosDates;
}
- /**
- * Applies BYMONTH rules specified in this Recur instance to the specified date list. If no BYMONTH rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
+ ///
+ /// Applies BYMONTH rules specified in this Recur instance to the specified date list.
+ /// If no BYMONTH rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYMONTH rules will be applied.
+ ///
+ ///
+ /// The modified list of dates after applying the BYMONTH rules.
private List GetMonthVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByMonth.Count == 0)
@@ -406,12 +394,12 @@ private List GetMonthVariants(List dates, RecurrencePattern
return dateSet.ToList();
}
- /**
- * Applies BYWEEKNO rules specified in this Recur instance to the specified date list. If no BYWEEKNO rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
+ ///
+ /// Applies BYWEEKNO rules specified in this Recur instance to the specified date list.
+ /// If no BYWEEKNO rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYWEEKNO rules will be applied.
+ /// The modified list of dates after applying the BYWEEKNO rules.
private List GetWeekNoVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByWeekNo.Count == 0)
@@ -462,13 +450,12 @@ private List GetWeekNoVariants(List dates, RecurrencePattern
return weekNoDates;
}
- /**
- * Applies BYYEARDAY rules specified in this Recur instance to the specified date list. If no BYYEARDAY rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
-
+ ///
+ /// Applies BYYEARDAY rules specified in this Recur instance to the specified date list.
+ /// If no BYYEARDAY rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYYEARDAY rules will be applied.
+ /// The modified list of dates after applying the BYYEARDAY rules.
private List GetYearDayVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByYearDay.Count == 0)
@@ -516,13 +503,12 @@ private List GetYearDayVariants(List dates, RecurrencePatter
return dates;
}
- /**
- * Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. If no BYMONTHDAY rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
-
+ ///
+ /// Applies BYMONTHDAY rules specified in this Recur instance to the specified date list.
+ /// If no BYMONTHDAY rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYMONTHDAY rules will be applied.
+ /// The modified list of dates after applying the BYMONTHDAY rules.
private List GetMonthDayVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByMonthDay.Count == 0)
@@ -571,20 +557,19 @@ select monthDay > 0
}
}
- Next:
+ Next:
dates.RemoveAt(i);
}
return dates;
}
- /**
- * Applies BYDAY rules specified in this Recur instance to the specified date list. If no BYDAY rules are specified
- * the date list is returned unmodified.
- * @param dates
- * @return
- */
-
+ ///
+ /// Applies BYDAY rules specified in this Recur instance to the specified date list.
+ /// If no BYDAY rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which BYDAY rules will be applied.
+ /// The modified list of dates after applying BYDAY rules, or the original list if no BYDAY rules are specified.
private List GetDayVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByDay.Count == 0)
@@ -632,14 +617,13 @@ private List GetDayVariants(List dates, RecurrencePattern pa
return dates;
}
- /**
- * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
- * specified by this recurrence rule.
- * @param date
- * @param weekDay
- * @return
- */
-
+ ///
+ /// Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency
+ /// specified by this recurrence rule.
+ ///
+ /// The date to start the evaluation from.
+ /// The week day to evaluate.
+ /// A list of applicable dates.
private List GetAbsWeekDays(DateTime date, WeekDay weekDay, RecurrencePattern pattern)
{
var days = new List();
@@ -723,15 +707,13 @@ private List GetAbsWeekDays(DateTime date, WeekDay weekDay, Recurrence
return GetOffsetDates(days, weekDay.Offset);
}
- /**
- * Returns a single-element sublist containing the element of list
at offset
. Valid
- * offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from list
- * are added to sublist
.
- * @param list
- * @param offset
- * @param sublist
- */
-
+ ///
+ /// Returns a single-element sublist containing the element of at .
+ /// Valid offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from
+ /// are added to result.
+ ///
+ /// The list from which to extract the element.
+ /// The position of the element to extract.
private List GetOffsetDates(List dates, int offset)
{
if (offset == int.MinValue)
@@ -752,13 +734,14 @@ private List GetOffsetDates(List dates, int offset)
return offsetDates;
}
- /**
- * Applies BYHOUR rules specified in this Recur instance to the specified date list. If no BYHOUR rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
-
+ ///
+ /// Applies BYHOUR rules specified in this Recur instance to the specified date list.
+ /// If no BYHOUR rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYHOUR rules will be applied.
+ ///
+ ///
+ /// The modified list of dates after applying the BYHOUR rules.
private List GetHourVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByHour.Count == 0)
@@ -802,13 +785,14 @@ private List GetHourVariants(List dates, RecurrencePattern p
return dates;
}
- /**
- * Applies BYMINUTE rules specified in this Recur instance to the specified date list. If no BYMINUTE rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
-
+ ///
+ /// Applies BYMINUTE rules specified in this Recur instance to the specified date list.
+ /// If no BYMINUTE rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYMINUTE rules will be applied.
+ ///
+ ///
+ /// The modified list of dates after applying the BYMINUTE rules.
private List GetMinuteVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.ByMinute.Count == 0)
@@ -852,13 +836,14 @@ private List GetMinuteVariants(List dates, RecurrencePattern
return dates;
}
- /**
- * Applies BYSECOND rules specified in this Recur instance to the specified date list. If no BYSECOND rules are
- * specified the date list is returned unmodified.
- * @param dates
- * @return
- */
-
+ ///
+ /// Applies BYSECOND rules specified in this Recur instance to the specified date list.
+ /// If no BYSECOND rules are specified, the date list is returned unmodified.
+ ///
+ /// The list of dates to which the BYSECOND rules will be applied.
+ ///
+ ///
+ /// The modified list of dates after applying the BYSECOND rules.
private List GetSecondVariants(List dates, RecurrencePattern pattern, bool? expand)
{
if (expand == null || pattern.BySecond.Count == 0)
@@ -927,7 +912,7 @@ private Period CreatePeriod(DateTime dt, IDateTime referenceDate)
///
public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults)
{
- if ((this.Pattern.Frequency != FrequencyType.None) && (this.Pattern.Frequency < FrequencyType.Daily) && !referenceDate.HasTime)
+ if (Pattern.Frequency != FrequencyType.None && Pattern.Frequency < FrequencyType.Daily && !referenceDate.HasTime)
{
// This case is not defined by RFC 5545. We handle it by evaluating the rule
// as if referenceDate had a time (i.e. set to midnight).
diff --git a/Ical.Net/Evaluation/RecurrenceUtil.cs b/Ical.Net/Evaluation/RecurrenceUtil.cs
index 7708e893..19ca3efd 100644
--- a/Ical.Net/Evaluation/RecurrenceUtil.cs
+++ b/Ical.Net/Evaluation/RecurrenceUtil.cs
@@ -1,13 +1,20 @@
using Ical.Net.CalendarComponents;
using Ical.Net.DataTypes;
using Ical.Net.Utility;
+using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
namespace Ical.Net.Evaluation
{
internal class RecurrenceUtil
{
+ // for v5 the timeout should be configurable (results in a breaking change)
+ // 500ms should be a good default value to cover most cases
+ internal static int DefaultTimeout = 500; // milliseconds
+
public static void ClearEvaluation(IRecurrable recurrable)
{
var evaluator = recurrable.GetService(typeof(IEvaluator)) as IEvaluator;
@@ -15,9 +22,10 @@ public static void ClearEvaluation(IRecurrable recurrable)
}
public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults) => GetOccurrences(recurrable,
- new CalDateTime(dt.AsSystemLocal.Date), new CalDateTime(dt.AsSystemLocal.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults);
+ new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults);
- public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime periodStart, IDateTime periodEnd, bool includeReferenceDateInResults)
+ public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime periodStart,
+ IDateTime periodEnd, bool includeReferenceDateInResults)
{
var evaluator = recurrable.GetService(typeof(IEvaluator)) as IEvaluator;
if (evaluator == null || recurrable.Start == null)
@@ -35,19 +43,41 @@ public static HashSet GetOccurrences(IRecurrable recurrable, IDateTi
periodStart.TzId = start.TzId;
periodEnd.TzId = start.TzId;
- var periods = evaluator.Evaluate(start, DateUtil.GetSimpleDateTimeData(periodStart), DateUtil.GetSimpleDateTimeData(periodEnd),
- includeReferenceDateInResults);
+ using var cancellationTokenSource = new CancellationTokenSource();
+ var task = Task.Run(() =>
+ {
+ var periods = evaluator.Evaluate(start, DateUtil.GetSimpleDateTimeData(periodStart),
+ DateUtil.GetSimpleDateTimeData(periodEnd),
+ includeReferenceDateInResults);
+
+ var otherOccurrences = from p in periods
+ let endTime = p.EndTime ?? p.StartTime
+ where
+ (endTime.GreaterThan(periodStart) && p.StartTime.LessThan(periodEnd) ||
+ (periodStart.Equals(periodEnd) && p.StartTime.LessThanOrEqual(periodStart) &&
+ endTime.GreaterThan(periodEnd))) || //A period that starts at the same time it ends
+ (p.StartTime.Equals(endTime) &&
+ periodStart.Equals(p.StartTime)) //An event that starts at the same time it ends
+ select new Occurrence(recurrable, p);
+
+ var occurrences = new HashSet(otherOccurrences);
+ return occurrences;
+ }, cancellationTokenSource.Token);
- var otherOccurrences = from p in periods
- let endTime = p.EndTime ?? p.StartTime
- where
- (endTime.GreaterThan(periodStart) && p.StartTime.LessThan(periodEnd) ||
- (periodStart.Equals(periodEnd) && p.StartTime.LessThanOrEqual(periodStart) && endTime.GreaterThan(periodEnd))) || //A period that starts at the same time it ends
- (p.StartTime.Equals(endTime) && periodStart.Equals(p.StartTime)) //An event that starts at the same time it ends
- select new Occurrence(recurrable, p);
+ if (task.Wait(TimeSpan.FromMilliseconds(DefaultTimeout)))
+ {
+ if (task.IsFaulted && task.Exception != null)
+ {
+ // maintain original exception details
+ throw task.Exception;
+ }
+
+ return task.Result;
+ }
- var occurrences = new HashSet(otherOccurrences);
- return occurrences;
+ cancellationTokenSource.Cancel();
+ // maintain any exception inside the task before timeout
+ throw new TimeoutException("Getting recurrences has timed out.", task.Exception);
}
public static bool?[] GetExpandBehaviorList(RecurrencePattern p)
diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs
index c435b4a6..1460f562 100644
--- a/Ical.Net/Evaluation/RecurringEvaluator.cs
+++ b/Ical.Net/Evaluation/RecurringEvaluator.cs
@@ -29,7 +29,7 @@ public RecurringEvaluator(IRecurrable obj)
}
///
- /// Evaulates the RRule component, and adds each specified Period to the Periods collection.
+ /// Evaluates the RRule component, and adds each specified Period to the Periods collection.
///
///
/// The beginning date of the range to evaluate.
@@ -62,7 +62,7 @@ protected HashSet EvaluateRRule(IDateTime referenceDate, DateTime period
return periods;
}
- /// Evalates the RDate component, and adds each specified DateTime or Period to the Periods collection.
+ /// Evaluates the RDate component, and adds each specified DateTime or Period to the Periods collection.
protected HashSet EvaluateRDate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd)
{
if (Recurrable.RecurrenceDates == null || !Recurrable.RecurrenceDates.Any())
@@ -75,7 +75,7 @@ protected HashSet EvaluateRDate(IDateTime referenceDate, DateTime period
}
///
- /// Evaulates the ExRule component, and excludes each specified DateTime from the Periods collection.
+ /// Evaluates the ExRule component, and excludes each specified DateTime from the Periods collection.
///
///
/// The beginning date of the range to evaluate.
@@ -102,7 +102,7 @@ protected HashSet EvaluateExRule(IDateTime referenceDate, DateTime perio
}
///
- /// Evalates the ExDate component, and excludes each specified DateTime or Period from the Periods collection.
+ /// Evaluates the ExDate component, and excludes each specified DateTime or Period from the Periods collection.
///
///
/// The beginning date of the range to evaluate.