Skip to content

Commit 533aeeb

Browse files
authored
Improve recurrence handling, remove restrictions (#627)
* Modified Constants.cs: - Deprecated `RecurrenceRestrictionType` and `RecurrenceEvaluationModeType` enums. Updated RecurrencePattern.cs: - Deprecated `RestrictionType` and `EvaluationMode` properties. - Set default for `RestrictionType`. Enhanced RecurrenceUtil: - Added new using directives. - Introduced `DefaultTimeout`. - Updated `GetOccurrences` with timeout logic. Refined RecurrencePatternEvaluator: - Removed `_maxIncrementCount` with connected logic in favor of `RecurrenceUtil.DefaultTimeout` - Improved XML documentation comments. Updated Calendar.cs: - Simplified `Load` method. - Deprecated certain properties and methods associated with `RestrictionType` and `EvaluationMode` - Removed obsolete `Evaluate` method. Enhanced RecurrenceTests.cs with more robust test cases: - Replaced `Assert.That` with `Assert.Multiple`. - Changed assertion for occurrences count. - Renamed test methods for clarity. - Added timeout settings and updated expected exceptions. - Adjusted test data and added comments. Corrected typos and improved formatting for better readability. Fixes #622 * Don't convert dt to `SystsemLocal` in `Calendar.GetOccurrences(IDateTime dt)` * Remove timeout handling and adjust related recurrence tests - Removed `SetupBeforeEachTest` and timeout settings from tests. - Re-introduced `_maxIncrementCount` in `RecurrencePatternEvaluator`. - Modified `GetDates` to use `_maxIncrementCount` for loop control. - Removed timeout handling from `RecurrenceUtil` class. * Refactor test names and update assertion message - Updated test method names to reflect checking for at least a defined number of occurrences. - Removed redundant summary comments to test methods for clarity. - Remove remaining mentions of "Timeout" * Change tests to check for exact number of occurrences * Secondly_DefinedNumberOfOccurrences_ShouldSucceed(), Minutely_DefinedNumberOfOccurrences_ShouldSucceed(), Hourly_DefinedNumberOfOccurrences_ShouldSucceed() assert exact number of occurences. * Updated test methods to dynamically generate expected occurrences using loops * Implemented changes from the code review
1 parent 52b34e2 commit 533aeeb

File tree

9 files changed

+206
-279
lines changed

9 files changed

+206
-279
lines changed

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- name: Build
3434
run: dotnet build --no-restore --configuration Release -p:nowarn=1591
3535
- name: Test
36-
run: dotnet test --no-build --configuration Release --verbosity quiet /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCoverStrongNameKey="../IcalNetStrongnameKey.snk" /p:AltCoverAssemblyExcludeFilter="Ical.Net.Tests|NUnit3.TestAdapter|AltCover" /p:AltCoverAttributeFilter="ExcludeFromCodeCoverage" /p:AltCoverLineCover="false"
36+
run: dotnet test --no-build --configuration Release --verbosity normal /p:AltCover=true /p:AltCoverXmlReport="coverage.xml" /p:AltCoverStrongNameKey="../IcalNetStrongnameKey.snk" /p:AltCoverAssemblyExcludeFilter="Ical.Net.Tests|NUnit3.TestAdapter|AltCover" /p:AltCoverAttributeFilter="ExcludeFromCodeCoverage" /p:AltCoverLineCover="false"
3737

3838
- name: Store coverage report as artifact
3939
uses: actions/upload-artifact@v4

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ jobs:
3131
- name: Build
3232
run: dotnet build --no-restore --configuration Release -p:nowarn=1591
3333
- name: Test
34-
run: dotnet test --no-build --configuration Release --verbosity quiet
34+
run: dotnet test --no-build --configuration Release --verbosity normal

Ical.Net.Tests/RecurrenceTests.cs

Lines changed: 71 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,41 @@ int eventIndex
3030
)
3131
{
3232
var evt = cal.Events.Skip(eventIndex).First();
33+
var rule = evt.RecurrenceRules.FirstOrDefault();
34+
#pragma warning disable 0618
35+
if (rule != null) rule.RestrictionType = RecurrenceRestrictionType.NoRestriction;
36+
#pragma warning restore 0618
3337
fromDate.AssociatedObject = cal;
3438
toDate.AssociatedObject = cal;
3539

3640
var occurrences = evt.GetOccurrences(fromDate, toDate)
3741
.OrderBy(o => o.Period.StartTime)
3842
.ToList();
3943

40-
Assert.That(
41-
occurrences,
42-
Has.Count.EqualTo(dateTimes.Length),
43-
"There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count);
44-
45-
if (evt.RecurrenceRules.Count > 0)
44+
Assert.Multiple(() =>
4645
{
47-
Assert.That(evt.RecurrenceRules, Has.Count.EqualTo(1));
48-
}
46+
Assert.That(
47+
occurrences,
48+
Has.Count.EqualTo(dateTimes.Length),
49+
"There should have been " + dateTimes.Length + " occurrences; there were " + occurrences.Count);
4950

50-
for (var i = 0; i < dateTimes.Length; i++)
51-
{
52-
// Associate each incoming date/time with the calendar.
53-
dateTimes[i].AssociatedObject = cal;
51+
if (evt.RecurrenceRules.Count > 0)
52+
{
53+
Assert.That(evt.RecurrenceRules, Has.Count.EqualTo(1));
54+
}
55+
56+
for (var i = 0; i < dateTimes.Length; i++)
57+
{
58+
// Associate each incoming date/time with the calendar.
59+
dateTimes[i].AssociatedObject = cal;
5460

55-
var dt = dateTimes[i];
56-
Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur on " + dt);
57-
if (timeZones != null)
58-
Assert.That(dt.TimeZoneName, Is.EqualTo(timeZones[i]), "Event " + dt + " should occur in the " + timeZones[i] + " timezone");
59-
}
61+
var dt = dateTimes[i];
62+
Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur on " + dt);
63+
if (timeZones != null)
64+
Assert.That(dt.TimeZoneName, Is.EqualTo(timeZones[i]),
65+
"Event " + dt + " should occur in the " + timeZones[i] + " timezone");
66+
}
67+
});
6068
}
6169

6270
private void EventOccurrenceTest(
@@ -1867,134 +1875,55 @@ public void Bug1741093()
18671875
);
18681876
}
18691877

1870-
/// <summary>
1871-
/// Ensures that, by default, SECONDLY recurrence rules are not allowed.
1872-
/// </summary>
1873-
[Test, Category("Recurrence")]
1874-
public void Secondly1()
1875-
{
1876-
Assert.That(() =>
1877-
{
1878-
var iCal = Calendar.Load(IcsFiles.Secondly1);
1879-
_ = iCal.GetOccurrences(new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid), new CalDateTime(2007, 7, 21, 8, 0, 0, _tzid));
1880-
}, Throws.Exception.TypeOf<ArgumentException>(), "Evaluation engine should have failed.");
1881-
}
1882-
1883-
/// <summary>
1884-
/// Ensures that the proper behavior occurs when the evaluation
1885-
/// mode is set to adjust automatically for SECONDLY evaluation
1886-
/// </summary>
18871878
[Test, Category("Recurrence")]
1888-
public void Secondly1_1()
1879+
public void Secondly_DefinedNumberOfOccurrences_ShouldSucceed()
18891880
{
18901881
var iCal = Calendar.Load(IcsFiles.Secondly1);
1891-
iCal.RecurrenceEvaluationMode = RecurrenceEvaluationModeType.AdjustAutomatically;
18921882

1893-
EventOccurrenceTest(
1894-
iCal,
1895-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1896-
new CalDateTime(2007, 6, 21, 8, 10, 1, _tzid), // End period is exclusive, not inclusive.
1897-
new[]
1898-
{
1899-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1900-
new CalDateTime(2007, 6, 21, 8, 1, 0, _tzid),
1901-
new CalDateTime(2007, 6, 21, 8, 2, 0, _tzid),
1902-
new CalDateTime(2007, 6, 21, 8, 3, 0, _tzid),
1903-
new CalDateTime(2007, 6, 21, 8, 4, 0, _tzid),
1904-
new CalDateTime(2007, 6, 21, 8, 5, 0, _tzid),
1905-
new CalDateTime(2007, 6, 21, 8, 6, 0, _tzid),
1906-
new CalDateTime(2007, 6, 21, 8, 7, 0, _tzid),
1907-
new CalDateTime(2007, 6, 21, 8, 8, 0, _tzid),
1908-
new CalDateTime(2007, 6, 21, 8, 9, 0, _tzid),
1909-
new CalDateTime(2007, 6, 21, 8, 10, 0, _tzid)
1910-
},
1911-
null
1912-
);
1913-
}
1883+
var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid);
1884+
var end = new CalDateTime(2007, 6, 21, 8, 1, 0, _tzid); // End period is exclusive, not inclusive.
19141885

1915-
/// <summary>
1916-
/// Ensures that if configured, MINUTELY recurrence rules are not allowed.
1917-
/// </summary>
1918-
[Test, Category("Recurrence")]
1919-
public void Minutely1()
1920-
{
1921-
Assert.That(() =>
1886+
var dateTimes = new List<IDateTime>();
1887+
for (var dt = start; dt.LessThan(end); dt = (CalDateTime) dt.AddSeconds(1))
19221888
{
1923-
var iCal = Calendar.Load(IcsFiles.Minutely1);
1924-
iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictMinutely;
1925-
var occurrences = iCal.GetOccurrences(
1926-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1927-
new CalDateTime(2007, 7, 21, 8, 0, 0, _tzid));
1928-
}, Throws.Exception.TypeOf<ArgumentException>());
1889+
dateTimes.Add(new CalDateTime(dt));
1890+
}
1891+
1892+
EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null);
19291893
}
19301894

1931-
/// <summary>
1932-
/// Ensures that the proper behavior occurs when the evaluation
1933-
/// mode is set to adjust automatically for MINUTELY evaluation
1934-
/// </summary>
19351895
[Test, Category("Recurrence")]
1936-
public void Minutely1_1()
1896+
public void Minutely_DefinedNumberOfOccurrences_ShouldSucceed()
19371897
{
19381898
var iCal = Calendar.Load(IcsFiles.Minutely1);
1939-
iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictMinutely;
1940-
iCal.RecurrenceEvaluationMode = RecurrenceEvaluationModeType.AdjustAutomatically;
19411899

1942-
EventOccurrenceTest(
1943-
iCal,
1944-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1945-
new CalDateTime(2007, 6, 21, 12, 0, 1, _tzid), // End period is exclusive, not inclusive.
1946-
new[]
1947-
{
1948-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1949-
new CalDateTime(2007, 6, 21, 9, 0, 0, _tzid),
1950-
new CalDateTime(2007, 6, 21, 10, 0, 0, _tzid),
1951-
new CalDateTime(2007, 6, 21, 11, 0, 0, _tzid),
1952-
new CalDateTime(2007, 6, 21, 12, 0, 0, _tzid)
1953-
},
1954-
null
1955-
);
1956-
}
1900+
var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid);
1901+
var end = new CalDateTime(2007, 6, 21, 12, 0, 1, _tzid); // End period is exclusive, not inclusive.
19571902

1958-
/// <summary>
1959-
/// Ensures that if configured, HOURLY recurrence rules are not allowed.
1960-
/// </summary>
1961-
[Test, Category("Recurrence")/*, ExpectedException(typeof(EvaluationEngineException))*/]
1962-
public void Hourly1()
1963-
{
1964-
Assert.That(() =>
1903+
var dateTimes = new List<IDateTime>();
1904+
for (var dt = start; dt.LessThan(end); dt = (CalDateTime)dt.AddMinutes(1))
19651905
{
1966-
var iCal = Calendar.Load(IcsFiles.Hourly1);
1967-
iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictHourly;
1968-
_ = iCal.GetOccurrences(new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid), new CalDateTime(2007, 7, 21, 8, 0, 0, _tzid));
1906+
dateTimes.Add(new CalDateTime(dt));
1907+
}
19691908

1970-
}, Throws.Exception.TypeOf<ArgumentException>());
1909+
EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null);
19711910
}
19721911

1973-
/// <summary>
1974-
/// Ensures that the proper behavior occurs when the evaluation
1975-
/// mode is set to adjust automatically for HOURLY evaluation
1976-
/// </summary>
19771912
[Test, Category("Recurrence")]
1978-
public void Hourly1_1()
1913+
public void Hourly_DefinedNumberOfOccurrences_ShouldSucceed()
19791914
{
19801915
var iCal = Calendar.Load(IcsFiles.Hourly1);
1981-
iCal.RecurrenceRestriction = RecurrenceRestrictionType.RestrictHourly;
1982-
iCal.RecurrenceEvaluationMode = RecurrenceEvaluationModeType.AdjustAutomatically;
19831916

1984-
EventOccurrenceTest(
1985-
iCal,
1986-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1987-
new CalDateTime(2007, 6, 25, 8, 0, 1, _tzid), // End period is exclusive, not inclusive.
1988-
new[]
1989-
{
1990-
new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid),
1991-
new CalDateTime(2007, 6, 22, 8, 0, 0, _tzid),
1992-
new CalDateTime(2007, 6, 23, 8, 0, 0, _tzid),
1993-
new CalDateTime(2007, 6, 24, 8, 0, 0, _tzid),
1994-
new CalDateTime(2007, 6, 25, 8, 0, 0, _tzid)
1995-
},
1996-
null
1997-
);
1917+
var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid);
1918+
var end = new CalDateTime(2007, 6, 25, 8, 0, 1, _tzid); // End period is exclusive, not inclusive.
1919+
1920+
var dateTimes = new List<IDateTime>();
1921+
for (var dt = start; dt.LessThan(end); dt = (CalDateTime)dt.AddHours(1))
1922+
{
1923+
dateTimes.Add(new CalDateTime(dt));
1924+
}
1925+
1926+
EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null);
19981927
}
19991928

20001929
/// <summary>
@@ -2038,7 +1967,7 @@ public void YearlyInterval1()
20381967
}
20391968

20401969
/// <summary>
2041-
/// Ensures that "off-day" calcuation works correctly
1970+
/// Ensures that "off-day" calculation works correctly
20421971
/// </summary>
20431972
[Test, Category("Recurrence")]
20441973
public void DailyInterval1()
@@ -2841,10 +2770,10 @@ public void Evaluate1(string freq, int secsPerInterval, bool hasTime)
28412770

28422771
// This case (DTSTART of type DATE and FREQ=MINUTELY) is undefined in RFC 5545.
28432772
// ical.net handles the case by pretending DTSTART has the time set to midnight.
2844-
evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5")
2845-
{
2846-
RestrictionType = RecurrenceRestrictionType.NoRestriction,
2847-
});
2773+
evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5"));
2774+
#pragma warning disable 0618
2775+
evt.RecurrenceRules[0].RestrictionType = RecurrenceRestrictionType.NoRestriction;
2776+
#pragma warning restore 0618
28482777

28492778
var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(-1), CalDateTime.Today.AddDays(100))
28502779
.OrderBy(x => x)
@@ -2868,8 +2797,10 @@ public void RecurrencePattern1()
28682797
{
28692798
// NOTE: evaluators are not generally meant to be used directly like this.
28702799
// However, this does make a good test to ensure they behave as they should.
2871-
RecurrencePattern pattern = new RecurrencePattern("FREQ=SECONDLY;INTERVAL=10");
2800+
var pattern = new RecurrencePattern("FREQ=SECONDLY;INTERVAL=10");
2801+
#pragma warning disable 0618
28722802
pattern.RestrictionType = RecurrenceRestrictionType.NoRestriction;
2803+
#pragma warning restore 0618
28732804

28742805
var us = new CultureInfo("en-US");
28752806

@@ -3177,10 +3108,14 @@ public void OccurrenceMustBeCompletelyContainedWithinSearchRange()
31773108
Assert.That(occurrences.Last().StartTime.Equals(lastExpected), Is.True);
31783109
}
31793110

3180-
[Test, Ignore("Turn on in v3")]
3111+
/// <summary>
3112+
/// Evaluate relevancy and validity of the request.
3113+
/// Find a solution for issue #120 or close forever
3114+
/// </summary>
3115+
[Test, Ignore("Turn on in v3", Until = "2024-12-31")]
31813116
public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet()
31823117
{
3183-
//https://github.com/rianjs/ical.net/issues/120
3118+
//https://github.com/rianjs/ical.net/issues/120 dated Sep 5, 2016
31843119
const string ical =
31853120
@"BEGIN:VCALENDAR
31863121
PRODID:-//Google Inc//Google Calendar 70.9054//EN
@@ -3757,7 +3692,9 @@ private static IEnumerable<RecurrenceTestCase> TestLibicalTestCasesSource
37573692

37583693
[TestCaseSource(nameof(TestLibicalTestCasesSource))]
37593694
public void TestLibicalTestCases(RecurrenceTestCase testCase)
3760-
=> ExecuteRecurrenceTestCase(testCase);
3695+
{
3696+
ExecuteRecurrenceTestCase(testCase);
3697+
}
37613698

37623699
private static IEnumerable<RecurrenceTestCase> TestFileBasedRecurrenceTestCaseSource
37633700
=> ParseTestCaseFile(IcsFiles.RecurrrenceTestCases);
@@ -3775,10 +3712,7 @@ public void ExecuteRecurrenceTestCase(RecurrenceTestCase testCase)
37753712

37763713
// Start at midnight, UTC time
37773714
evt.Start = testCase.DtStart;
3778-
evt.RecurrenceRules.Add(new RecurrencePattern(testCase.RRule)
3779-
{
3780-
RestrictionType = RecurrenceRestrictionType.NoRestriction,
3781-
});
3715+
evt.RecurrenceRules.Add(new RecurrencePattern(testCase.RRule));
37823716

37833717
var occurrences = evt.GetOccurrences(testCase.StartAt?.Value ?? DateTime.MinValue, DateTime.MaxValue)
37843718
.OrderBy(x => x)

0 commit comments

Comments
 (0)