Skip to content

Commit

Permalink
Add abstraction layer to create EXDATE and RDATE
Browse files Browse the repository at this point in the history
- Introduced `ExceptionDateCollection`, `RecurrencePeriodCollection` and PeriodCollectionBase` classes.
- Updated serializers to handle the modified `PeriodList` implementation.

These classes are an abstraction layer for creating `List<PeriodList>` objects.
`Period`s and `CalDateTime`s are accepted without having to care for RFC 5545 rules regarding serialization:

`ToRecurrenceDates` and `ToRecurrenceDates` methods aggregate and convert the exception or recurrence `CalDateTime` and `Period` objects into a list of `PeriodList` objects. Periods are grouped by their timezone IDs and period kinds in a way, that each `PeriodList` contains only distinct periods ready to serialize.
  • Loading branch information
axunonb committed Jan 2, 2025
1 parent 8532029 commit 9eef103
Show file tree
Hide file tree
Showing 11 changed files with 582 additions and 127 deletions.
153 changes: 153 additions & 0 deletions Ical.Net.Tests/PeriodCollectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
//
// Copyright ical.net project maintainers and contributors.
// Licensed under the MIT license.
//

#nullable enable
using Ical.Net.Collections;
using Ical.Net.DataTypes;
using NUnit.Framework;

namespace Ical.Net.Tests;

[TestFixture]
public class PeriodCollectionTests
{
[Test]
public void RemovePeriod_ShouldDecreaseCount()
{
// Arrange
var recCollection = new RecurrencePeriodCollection();
var period = new Period(new CalDateTime(2023, 1, 1, 0, 0, 0), Duration.FromHours(1));
recCollection.Add(period);

// Act
recCollection.Remove(period);

// Assert
Assert.That(recCollection, Has.Count.EqualTo(0));
}

[Test]
public void GetSet_Period_ShouldReturnCorrectPeriod()
{
// Arrange
var recCollection = new RecurrencePeriodCollection();
var period1 = new Period(new CalDateTime(2025, 1, 1, 0, 0, 0), Duration.FromHours(1));
var period2 = new Period(new CalDateTime(2025, 2, 1, 0, 0, 0), Duration.FromHours(1));

recCollection.Add(period1);
recCollection.Add(period1);

// Act
var retrievedPeriod = recCollection[0];
recCollection[1] = period2;

// Assert
Assert.Multiple(() =>
{
Assert.That(period1, Is.EqualTo(retrievedPeriod));
Assert.That(recCollection.Contains(period1), Is.True);
});
}

[Test]
public void Clear_ShouldRemoveAll_PeriodsAdded()
{
// Arrange
var recCollection = new RecurrencePeriodCollection();
var pl = new PeriodList
{
new CalDateTime(2025, 1, 2),
new CalDateTime(2025, 1, 3)
};

recCollection.AddRange( [pl] );
recCollection.AddRange(
[
new CalDateTime(2025, 10, 1),
new CalDateTime(2025, 10, 2)
]);

var count = recCollection.Count;

// Act
recCollection.Clear();

// Assert
Assert.Multiple(() =>
{
Assert.That(count, Is.EqualTo(4));
Assert.That(recCollection, Has.Count.EqualTo(0));
Assert.That(recCollection.IsReadOnly, Is.False);
});
}

[Test]
public void CopyToPeriod_ShouldCopyPeriodsCorrectly()
{
// Arrange
var recCollection = new RecurrencePeriodCollection([new CalDateTime(2025, 1, 2),
new CalDateTime(2025, 1, 3)]);

var array = new Period[2];

// Act
recCollection.CopyTo(array, 0);

// Assert
Assert.Multiple(() =>
{
Assert.That(array[0], Is.EqualTo(recCollection[0]));
Assert.That(array[1], Is.EqualTo(recCollection[1]));
});
}

[Test]
public void Create_RecurrencePeriodCollection_With_DateTime()
{
var recCollection = new RecurrencePeriodCollection(new CalDateTime(2025, 1, 2));

Assert.Multiple(() =>
{
Assert.That(recCollection, Has.Count.EqualTo(1));
});
}

[Test]
public void Create_RecurrencePeriodCollection_With_PeriodEnumerable()
{
var recCollection = new RecurrencePeriodCollection([new Period(new CalDateTime(2025, 1, 2), Duration.FromDays(1))]);

Assert.Multiple(() =>
{
Assert.That(recCollection, Has.Count.EqualTo(1));
Assert.That(recCollection[0].Duration, Is.EqualTo(Duration.FromDays(1)));
});
}

[Test]
public void Create_ExceptionDateCollection_With_DateTimeEnumerable()
{
var exDateCollection = new ExceptionDateCollection([new CalDateTime(2025, 1, 2),
new CalDateTime(2025, 1, 3)]);

Assert.Multiple(() =>
{
Assert.That(exDateCollection, Has.Count.EqualTo(2));
});
}

[Test]
public void Create_ExceptionDateCollection_With_Period()
{
var exDateCollection = new ExceptionDateCollection();
exDateCollection.Add(new Period(new CalDateTime(2025, 1, 2), Duration.FromHours(1)));

Assert.Multiple(() =>
{
Assert.That(exDateCollection, Has.Count.EqualTo(1));
Assert.That(exDateCollection[0].EffectiveDuration, Is.Null);
});
}
}
22 changes: 15 additions & 7 deletions Ical.Net.Tests/PeriodListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.IO;
using Ical.Net.DataTypes;
using Ical.Net.Utility;
using NUnit.Framework;

namespace Ical.Net.Tests;
Expand Down Expand Up @@ -37,7 +38,8 @@ public void GetSet_Period_ShouldReturnCorrectPeriod()
var period1 = new Period(new CalDateTime(2025, 1, 1, 0, 0, 0), Duration.FromHours(1));
var period2 = new Period(new CalDateTime(2025, 2, 1, 0, 0, 0), Duration.FromHours(1));

periodList.AddPeriod(period1).AddPeriod(period1);
periodList.Add(period1);
periodList.Add(period1);

// Act
var retrievedPeriod = periodList[0];
Expand All @@ -57,11 +59,14 @@ public void Clear_ShouldRemoveAllPeriods()
{
// Arrange
var periodList = new PeriodList();
var pl = PeriodList
.FromDateTime(new CalDateTime(2025, 1, 2))
.Add(new CalDateTime(2025, 1, 3));
var pl = new PeriodList
{
new CalDateTime(2025, 1, 2),
new CalDateTime(2025, 1, 3)
};

var count = pl.Count;
periodList.AddRange(pl);
var count = periodList.Count;

// Act
periodList.Clear();
Expand Down Expand Up @@ -104,7 +109,8 @@ public void InsertAt_ShouldInsertPeriodAtCorrectPosition()
var period1 = new Period(new CalDateTime(2025, 1, 1, 0, 0, 0), Duration.FromHours(1));
var period2 = new Period(new CalDateTime(2025, 1, 2, 0, 0, 0), Duration.FromHours(1));
var period3 = new Period(new CalDateTime(2025, 1, 3, 0, 0, 0), Duration.FromHours(1));
periodList.AddPeriod(period1).AddPeriod(period3);
periodList.Add(period1);
periodList.Add(period3);

// Act
periodList.Insert(1, period2);
Expand All @@ -125,7 +131,9 @@ public void RemoveAt_ShouldRemovePeriodAtCorrectPosition()
var period1 = new Period(new CalDateTime(2025, 1, 1, 0, 0, 0), Duration.FromHours(1));
var period2 = new Period(new CalDateTime(2025, 1, 2, 0, 0, 0), Duration.FromHours(1));
var period3 = new Period(new CalDateTime(2025, 1, 3, 0, 0, 0), Duration.FromHours(1));
periodList.AddPeriod(period1).AddPeriod(period2).AddPeriod(period3);
periodList.Add(period1);
periodList.Add(period2);
periodList.Add(period3);

// Act
periodList.RemoveAt(1);
Expand Down
4 changes: 2 additions & 2 deletions Ical.Net.Tests/RecurrenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3201,10 +3201,10 @@ public void OccurrenceMustBeCompletelyContainedWithinSearchRange()
/// Evaluate relevancy and validity of the request.
/// Find a solution for issue #120 or close forever
/// </summary>
[Test, Ignore("No solution for issue #120 yet", Until = "2024-12-31")]
[Test, Ignore("No solution for issue #120 yet", Until = "2025-02-28")]
public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet()
{
//https://github.com/rianjs/ical.net/issues/120 dated Sep 5, 2016
//https://github.com/ical-org/ical.net/issues/120 dated Sep 5, 2016
const string ical =
@"BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
Expand Down
6 changes: 5 additions & 1 deletion Ical.Net.Tests/RecurrenceWithExDateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System;
using System.Linq;
using Ical.Net.CalendarComponents;
using Ical.Net.Collections;
using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using NUnit.Framework;
Expand All @@ -29,10 +30,13 @@ public void ShouldNotOccurOnLocalExceptionDate(bool useExDateWithTime)
const string timeZoneId = "Europe/London"; // IANA Time Zone ID
var start = new CalDateTime(2024, 10, 19, 18, 0, 0, timeZoneId);
var end = new CalDateTime(2024, 10, 19, 19, 0, 0, timeZoneId);

var exceptionDate = useExDateWithTime
? new CalDateTime(2024, 10, 19, 21, 0, 0, timeZoneId)
: new CalDateTime(2024, 10, 19);

var exDateCollection = new ExceptionDateCollection(exceptionDate);

var recurrencePattern = new RecurrencePattern(FrequencyType.Hourly)
{
Count = 2,
Expand All @@ -47,7 +51,7 @@ public void ShouldNotOccurOnLocalExceptionDate(bool useExDateWithTime)
End = end
};
recurringEvent.RecurrenceRules.Add(recurrencePattern);
recurringEvent.ExceptionDates.Add(PeriodList.FromDateTime(exceptionDate));
recurringEvent.ExceptionDates = exDateCollection.ToExceptionDates();

var calendar = new Calendar();
calendar.Events.Add(recurringEvent);
Expand Down
Loading

0 comments on commit 9eef103

Please sign in to comment.