Skip to content

Commit

Permalink
Merge pull request #224 from rianjs/GetOccurrenceCaching
Browse files Browse the repository at this point in the history
GetOccurrences shouldn't cache the result of a previous computation, …
  • Loading branch information
rianjs authored Jan 16, 2017
2 parents db10b6e + a759d3d commit de1f045
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 75 deletions.
1 change: 1 addition & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ A listing of what each [Nuget package](https://www.nuget.org/packages/Ical.Net)

### v2

* 2.2.29: Calling `GetOccurrences()` on a recurrable component should recompute the recurrence set. Specifying `EXDATE` values that don't have a `TimeOfDay` component should "black out" that day from a recurring component's `StartTime`.[#223](https://github.com/rianjs/ical.net/issues/223)
* 2.2.28: Working with `Resources` on `Event`s didn't allow you to do normal set operations: `Add`, `Remove`, `UnionWith`, `ExceptWith`, etc. [#189](https://github.com/rianjs/ical.net/issues/189)
* 2.2.27: N/A -- unpublished, no downloads
* 2.2.26: Unpublished due to data duplication bug
Expand Down
2 changes: 1 addition & 1 deletion v2/Ical.Net.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Ical.Net</id>
<version>2.2.28</version>
<version>2.2.29</version>
<title>Ical.Net</title>
<authors>Rian Stockbower, Douglas Day, M. David Peterson</authors>
<owners>Rian Stockbower</owners>
Expand Down
43 changes: 43 additions & 0 deletions v2/ical.NET.UnitTests/RecurrenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3087,5 +3087,48 @@ public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet()
Assert.AreEqual(expectedSept3Start, orderedOccurrences[5].StartTime);
Assert.AreEqual(expectedSept3End, orderedOccurrences[5].EndTime);
}

[Test]
public void AddExDateToEventAfterGetOccurrencesShouldRecomputeResult()
{
var searchStart = _now.AddDays(-1);
var searchEnd = _now.AddDays(7);
var e = GetEventWithRecurrenceRules();
var occurrences = e.GetOccurrences(searchStart, searchEnd);
Assert.IsTrue(occurrences.Count == 5);

var exDate = _now.AddDays(1);
var period = new Period(new CalDateTime(exDate));
var periodList = new PeriodList {period};
e.ExceptionDates.Add(periodList);
occurrences = e.GetOccurrences(searchStart, searchEnd);
Assert.IsTrue(occurrences.Count == 4);

//Specifying just a date should "black out" that date
var excludeTwoDaysFromNow = _now.AddDays(2).Date;
period = new Period(new CalDateTime(excludeTwoDaysFromNow));
periodList.Add(period);
occurrences = e.GetOccurrences(searchStart, searchEnd);
Assert.IsTrue(occurrences.Count == 3);
}

private static readonly DateTime _now = DateTime.Now;
private static readonly DateTime _later = _now.AddHours(1);
private static Event GetEventWithRecurrenceRules()
{
var dailyForFiveDays = new RecurrencePattern(FrequencyType.Daily, 1)
{
Count = 5,
};

var calendarEvent = new Event
{
Start = new CalDateTime(_now),
End = new CalDateTime(_later),
RecurrenceRules = new List<IRecurrencePattern> { dailyForFiveDays },
Resources = new HashSet<string>(new[] {"Foo", "Bar", "Baz"}),
};
return calendarEvent;
}
}
}
70 changes: 33 additions & 37 deletions v2/ical.NET/Evaluation/RecurringEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,49 +113,45 @@ protected HashSet<IPeriod> EvaluateExDate(IDateTime referenceDate, DateTime peri

public override HashSet<IPeriod> Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults)
{
// Evaluate extra time periods, without re-evaluating ones that were already evaluated
if ((EvaluationStartBounds == DateTime.MaxValue && EvaluationEndBounds == DateTime.MinValue) || periodEnd.Equals(EvaluationStartBounds) ||
periodStart.Equals(EvaluationEndBounds))
Periods.Clear();

var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults);
if (includeReferenceDateInResults)
{
rruleOccurrences.UnionWith(new[] { new Period(referenceDate), });
}

var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd);

var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd);
var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd);

//Exclusions trump inclusions
Periods.UnionWith(rruleOccurrences);
Periods.UnionWith(rdateOccurrences);
Periods.ExceptWith(exRuleExclusions);
Periods.ExceptWith(exDateExclusions);

var dateOverlaps = FindDateOverlaps(exDateExclusions);
Periods.ExceptWith(dateOverlaps);

if (EvaluationStartBounds == DateTime.MaxValue || EvaluationStartBounds > periodStart)
{
//Exclusions take precedence over inclusions, so build the master set, then subtract the exclusions from it
var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults);
if (includeReferenceDateInResults)
{
rruleOccurrences.UnionWith(new [] {new Period(referenceDate), });
}

var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd);

var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd);
var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd);

Periods.UnionWith(rruleOccurrences);
Periods.UnionWith(rdateOccurrences);
Periods.ExceptWith(exRuleExclusions);
Periods.ExceptWith(exDateExclusions);

if (EvaluationStartBounds == DateTime.MaxValue || EvaluationStartBounds > periodStart)
{
EvaluationStartBounds = periodStart;
}
if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < periodEnd)
{
EvaluationEndBounds = periodEnd;
}
EvaluationStartBounds = periodStart;
}
else
if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < periodEnd)
{
if (EvaluationStartBounds != DateTime.MaxValue && periodStart < EvaluationStartBounds)
{
Evaluate(referenceDate, periodStart, EvaluationStartBounds, includeReferenceDateInResults);
}
if (EvaluationEndBounds != DateTime.MinValue && periodEnd > EvaluationEndBounds)
{
Evaluate(referenceDate, EvaluationEndBounds, periodEnd, includeReferenceDateInResults);
}
EvaluationEndBounds = periodEnd;
}

return Periods;
}

private HashSet<IPeriod> FindDateOverlaps(HashSet<IPeriod> dates)
{
var datesWithoutTimes = new HashSet<DateTime>(dates.Where(d => d.StartTime.Value.TimeOfDay == TimeSpan.Zero).Select(d => d.StartTime.Value));
var overlaps = new HashSet<IPeriod>(Periods.Where(p => datesWithoutTimes.Contains(p.StartTime.Value.Date)));
return overlaps;
}
}
}
43 changes: 43 additions & 0 deletions v3/ical.NET.UnitTests/RecurrenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3085,5 +3085,48 @@ public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet()
Assert.AreEqual(expectedSept3Start, orderedOccurrences[5].StartTime);
Assert.AreEqual(expectedSept3End, orderedOccurrences[5].EndTime);
}

[Test]
public void AddExDateToEventAfterGetOccurrencesShouldRecomputeResult()
{
var searchStart = _now.AddDays(-1);
var searchEnd = _now.AddDays(7);
var e = GetEventWithRecurrenceRules();
var occurrences = e.GetOccurrences(searchStart, searchEnd);
Assert.IsTrue(occurrences.Count == 5);

var exDate = _now.AddDays(1);
var period = new Period(new CalDateTime(exDate));
var periodList = new PeriodList { period };
e.ExceptionDates.Add(periodList);
occurrences = e.GetOccurrences(searchStart, searchEnd);
Assert.IsTrue(occurrences.Count == 4);

//Specifying just a date should "black out" that date
var excludeTwoDaysFromNow = _now.AddDays(2).Date;
period = new Period(new CalDateTime(excludeTwoDaysFromNow));
periodList.Add(period);
occurrences = e.GetOccurrences(searchStart, searchEnd);
Assert.IsTrue(occurrences.Count == 3);
}

private static readonly DateTime _now = DateTime.Now;
private static readonly DateTime _later = _now.AddHours(1);
private static CalendarEvent GetEventWithRecurrenceRules()
{
var dailyForFiveDays = new RecurrencePattern(FrequencyType.Daily, 1)
{
Count = 5,
};

var calendarEvent = new CalendarEvent
{
Start = new CalDateTime(_now),
End = new CalDateTime(_later),
RecurrenceRules = new List<RecurrencePattern> { dailyForFiveDays },
Resources = new HashSet<string>(new[] { "Foo", "Bar", "Baz" }),
};
return calendarEvent;
}
}
}
70 changes: 33 additions & 37 deletions v3/ical.NET/Evaluation/RecurringEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,49 +113,45 @@ protected HashSet<Period> EvaluateExDate(IDateTime referenceDate, DateTime perio

public override HashSet<Period> Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults)
{
// Evaluate extra time periods, without re-evaluating ones that were already evaluated
if ((EvaluationStartBounds == DateTime.MaxValue && EvaluationEndBounds == DateTime.MinValue) || periodEnd.Equals(EvaluationStartBounds) ||
periodStart.Equals(EvaluationEndBounds))
Periods.Clear();

var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults);
if (includeReferenceDateInResults)
{
rruleOccurrences.UnionWith(new[] { new Period(referenceDate), });
}

var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd);

var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd);
var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd);

//Exclusions trump inclusions
Periods.UnionWith(rruleOccurrences);
Periods.UnionWith(rdateOccurrences);
Periods.ExceptWith(exRuleExclusions);
Periods.ExceptWith(exDateExclusions);

var dateOverlaps = FindDateOverlaps(exDateExclusions);
Periods.ExceptWith(dateOverlaps);

if (EvaluationStartBounds == DateTime.MaxValue || EvaluationStartBounds > periodStart)
{
//Exclusions take precedence over inclusions, so build the master set, then subtract the exclusions from it
var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults);
if (includeReferenceDateInResults)
{
rruleOccurrences.UnionWith(new [] {new Period(referenceDate), });
}

var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd);

var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd);
var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd);

Periods.UnionWith(rruleOccurrences);
Periods.UnionWith(rdateOccurrences);
Periods.ExceptWith(exRuleExclusions);
Periods.ExceptWith(exDateExclusions);

if (EvaluationStartBounds == DateTime.MaxValue || EvaluationStartBounds > periodStart)
{
EvaluationStartBounds = periodStart;
}
if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < periodEnd)
{
EvaluationEndBounds = periodEnd;
}
EvaluationStartBounds = periodStart;
}
else
if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < periodEnd)
{
if (EvaluationStartBounds != DateTime.MaxValue && periodStart < EvaluationStartBounds)
{
Evaluate(referenceDate, periodStart, EvaluationStartBounds, includeReferenceDateInResults);
}
if (EvaluationEndBounds != DateTime.MinValue && periodEnd > EvaluationEndBounds)
{
Evaluate(referenceDate, EvaluationEndBounds, periodEnd, includeReferenceDateInResults);
}
EvaluationEndBounds = periodEnd;
}

return Periods;
}

private HashSet<Period> FindDateOverlaps(HashSet<Period> dates)
{
var datesWithoutTimes = new HashSet<DateTime>(dates.Where(d => d.StartTime.Value.TimeOfDay == TimeSpan.Zero).Select(d => d.StartTime.Value));
var overlaps = new HashSet<Period>(Periods.Where(p => datesWithoutTimes.Contains(p.StartTime.Value.Date)));
return overlaps;
}
}
}

0 comments on commit de1f045

Please sign in to comment.