Skip to content

Commit

Permalink
Define behavior for time-based FREQ and date-only DTSTART (#616)
Browse files Browse the repository at this point in the history
* Test: Revitalize test Evaluate1, which didn't actually test anything. Now, after repairing it, the test fails.

* RecurrencePatternEvaluator: Implement case that FREQ refers to a time component (sec,min,hour) while DTSTART is date-only. This is undefined by RFC 5545 but we handle it by pretending DTSTART had a time set to midnight.
  • Loading branch information
minichma authored Oct 20, 2024
1 parent 9070096 commit 564aaa0
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 7 deletions.
36 changes: 29 additions & 7 deletions Ical.Net.Tests/RecurrenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2823,21 +2823,43 @@ public void UsHolidays()
/// HasTime set to true if the beginning time had HasTime set
/// to false.
/// </summary>
[Test, Category("Recurrence")]
public void Evaluate1()
[Category("Recurrence")]
[TestCase("SECONDLY", 1, true)]
[TestCase("MINUTELY", 60, true)]
[TestCase("HOURLY", 3600, true)]
[TestCase("DAILY", 24*3600, false)]
public void Evaluate1(string freq, int secsPerInterval, bool hasTime)
{
Calendar cal = new Calendar();

CalendarEvent evt = cal.Create<CalendarEvent>();
evt.Summary = "Event summary";

// Start at midnight, UTC time
evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc));
evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)) { HasTime = false };

evt.RecurrenceRules.Add(new RecurrencePattern("FREQ=MINUTELY;INTERVAL=10;COUNT=5"));
var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(1), CalDateTime.Today.AddDays(2));
// 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,
});

foreach (var o in occurrences)
Assert.That(o.Period.StartTime.HasTime, Is.True, "All recurrences of this event should have a time set.");
var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(-1), CalDateTime.Today.AddDays(100))
.OrderBy(x => x)
.ToList();

var startDates = occurrences.Select(x => x.Period.StartTime.Value).ToList();

var expectedStartDates = Enumerable.Range(0, 5)
.Select(i => DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc).AddSeconds(i * secsPerInterval * 10))
.ToList();

Assert.Multiple(() =>
{
Assert.That(occurrences.Select(x => x.Period.StartTime.HasTime == hasTime), Is.All.True);
Assert.That(startDates, Is.EqualTo(expectedStartDates));
});
}

[Test, Category("Recurrence")]
Expand Down
9 changes: 9 additions & 0 deletions Ical.Net/Evaluation/RecurrencePatternEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,15 @@ private Period CreatePeriod(DateTime dt, IDateTime referenceDate)

public override HashSet<Period> Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults)
{
if ((this.Pattern.Frequency != FrequencyType.None) && (this.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).

referenceDate = referenceDate.Copy<IDateTime>();
referenceDate.HasTime = true;
}

// Create a recurrence pattern suitable for use during evaluation.
var pattern = ProcessRecurrencePattern(referenceDate);

Expand Down

0 comments on commit 564aaa0

Please sign in to comment.