Skip to content

Commit

Permalink
Enhance Refactor CalDateTime and DateTimeSerializer
Browse files Browse the repository at this point in the history
* Introduced new methods and test cases in `CalDateTimeTests.cs` to ensure correct behavior of `CalDateTime` properties and methods.
* Updated various tests in `DeserializationTests.cs` and `RecurrenceTests.cs` to handle `CalDateTime` without time components and improve clarity.
* Refined `IsAllDay` property in `CalendarEvent.cs` and updated methods in `UniqueComponent.cs` and `VTimeZone.cs` to use `CalDateTime` with time components.
* Removed outdated comments and improved code formatting.
* Enabled nullable reference types and updated `IDateTime` interface.
* Added new methods and properties to `CalDateTime.cs` for better date and time management.
* Refactored methods in `Period`, `RecurrencePatternEvaluator`, and `RecurringEvaluator` classes to handle `HasTime` property correctly.
* Improved `DateTimeSerializer` class for better performance and handling of nullable types.
* Added XML documentation and marked obsolete methods.

Resolves ical-org#630
Resolves ical-org#633
Resolves ical-org#635
Resolves ical-org#636
Resolves ical-org#637
  • Loading branch information
axunonb committed Nov 9, 2024
1 parent 533aeeb commit 5ec194e
Show file tree
Hide file tree
Showing 18 changed files with 721 additions and 343 deletions.
2 changes: 1 addition & 1 deletion Ical.Net.Tests/AlarmTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Ical.Net.DataTypes;
using Ical.Net.DataTypes;
using NUnit.Framework;
using System;
using System.Collections.Generic;
Expand Down
170 changes: 167 additions & 3 deletions Ical.Net.Tests/CalDateTimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;

namespace Ical.Net.Tests
{
public class CalDateTimeTests
{
private static readonly DateTime _now = DateTime.Now;
private static readonly DateTime _later = _now.AddHours(1);

private static CalendarEvent GetEventWithRecurrenceRules(string tzId)
{
var dailyForFiveDays = new RecurrencePattern(FrequencyType.Daily, 1)
Expand All @@ -32,7 +34,7 @@ private static CalendarEvent GetEventWithRecurrenceRules(string tzId)
public void ToTimeZoneTests(CalendarEvent calendarEvent, string targetTimeZone)
{
var startAsUtc = calendarEvent.Start.AsUtc;

var convertedStart = calendarEvent.Start.ToTimeZone(targetTimeZone);
var convertedAsUtc = convertedStart.AsUtc;

Expand Down Expand Up @@ -89,13 +91,15 @@ public static IEnumerable AsDateTimeOffsetTestCases()
var convertedToNySummer = new CalDateTime(summerDate, "UTC");
convertedToNySummer.TzId = nyTzId;
yield return new TestCaseData(convertedToNySummer)
.SetName("Summer UTC DateTime converted to NY time zone by setting TzId returns a DateTimeOffset with UTC-4")
.SetName(
"Summer UTC DateTime converted to NY time zone by setting TzId returns a DateTimeOffset with UTC-4")
.Returns(new DateTimeOffset(summerDate, nySummerOffset));

var noTz = new CalDateTime(summerDate);
var currentSystemOffset = TimeZoneInfo.Local.GetUtcOffset(summerDate);
yield return new TestCaseData(noTz)
.SetName($"Summer DateTime with no time zone information returns the system-local's UTC offset ({currentSystemOffset})")
.SetName(
$"Summer DateTime with no time zone information returns the system-local's UTC offset ({currentSystemOffset})")
.Returns(new DateTimeOffset(summerDate, currentSystemOffset));
}

Expand Down Expand Up @@ -150,5 +154,165 @@ public static IEnumerable DateTimeKindOverrideTestCases()
.Returns(DateTimeKind.Unspecified)
.SetName("DateTime with Kind = Unspecified and null tzid returns DateTimeKind.Unspecified");
}

[Test, TestCaseSource(nameof(ToStringTestCases))]
public string ToStringTests(CalDateTime calDateTime, string format, IFormatProvider formatProvider)
=> calDateTime.ToString(format, formatProvider);

public static IEnumerable ToStringTestCases()
{
yield return new TestCaseData(new CalDateTime(2024, 8, 30, 10, 30, 0, tzId: "Pacific/Auckland"), "O", null)
.Returns("2024-08-30T10:30:00.0000000+12:00 Pacific/Auckland")
.SetName("Date and time with 'O' format arg, default culture");

yield return new TestCaseData(new CalDateTime(2024, 8, 30, tzId: "Pacific/Auckland"), "O", null)
.Returns("08/30/2024 Pacific/Auckland")
.SetName("Date only with 'O' format arg, default culture");

yield return new TestCaseData(new CalDateTime(2024, 8, 30, 10, 30, 0, tzId: "Pacific/Auckland"), "O",
CultureInfo.GetCultureInfo("fr-FR"))
.Returns("2024-08-30T10:30:00.0000000+12:00 Pacific/Auckland")
.SetName("Date and time with 'O' format arg, French culture");

yield return new TestCaseData(new CalDateTime(2024, 8, 30, 10, 30, 0, tzId: "Pacific/Auckland"),
"yyyy-MM-dd", CultureInfo.InvariantCulture)
.Returns("2024-08-30 Pacific/Auckland")
.SetName("Date and time with custom format, default culture");

yield return new TestCaseData(new CalDateTime(2024, 8, 30, 10, 30, 0, tzId: "Pacific/Auckland"),
"MM/dd/yyyy HH:mm:ss", CultureInfo.GetCultureInfo("FR"))
.Returns("08/30/2024 10:30:00 Pacific/Auckland")
.SetName("Date and time with format and 'FR' CultureInfo");

yield return new TestCaseData(new CalDateTime(2024, 8, 30, tzId: "Pacific/Auckland"), null,
CultureInfo.GetCultureInfo("IT"))
.Returns("30/08/2024 Pacific/Auckland")
.SetName("Date only with 'IT' CultureInfo and default format arg");
}

[Test]
public void SetValue_AppliesSameRulesAsWith_CTOR()
{
var dateTime = new DateTime(2024, 8, 30, 10, 30, 0, DateTimeKind.Unspecified);
var tzId = "Europe/Berlin";

var dt1 = new CalDateTime(dateTime, tzId);
var dt2 = new CalDateTime(DateTime.Now, tzId);
dt2.Value = dateTime;

Assert.Multiple(() =>
{
// TzId changes the DateTimeKind to Local
Assert.That(dt1.Value.Kind, Is.Not.EqualTo(dateTime.Kind));
Assert.That(dt1.Value.Kind, Is.EqualTo(dt2.Value.Kind));
Assert.That(dt1.TzId, Is.EqualTo(dt2.TzId));
});
}

[Test]
public void SetValue_LeavesExistingPropertiesUnchanged()
{
var cal = new Calendar();
var dateTime = new DateTime(2024, 8, 30, 10, 30, 0, DateTimeKind.Unspecified);
var tzId = "Europe/Berlin";

var dt = new CalDateTime(dateTime, tzId, false)
{
AssociatedObject = cal
};
var hasTimeInitial = dt.HasTime;

dt.Value = DateTime.MinValue;

// Properties should remain unchanged
Assert.Multiple(() =>
{
Assert.That(dt.HasTime, Is.EqualTo(hasTimeInitial));
Assert.That(dt.TzId, Is.EqualTo(tzId));
Assert.That(dt.Calendar, Is.SameAs(cal));
});
}

[Test]
public void Simple_PropertyAndMethod_HasTime_Tests()
{
var dt = new DateTime(2025, 1, 2, 10, 20, 30, DateTimeKind.Utc);
var c = new CalDateTime(dt, tzId: "Europe/Berlin");

var c2 = new CalDateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, c.TzId, null);
var c3 = new CalDateTime(new DateOnly(dt.Year, dt.Month, dt.Day),
new TimeOnly(dt.Hour, dt.Minute, dt.Second), dt.Kind, c.TzId);

Assert.Multiple(() =>
{
Assert.That(c2.Ticks, Is.EqualTo(c3.Ticks));
Assert.That(c2.TzId, Is.EqualTo(c3.TzId));
Assert.That(CalDateTime.UtcNow.Value.Kind, Is.EqualTo(DateTimeKind.Utc));
Assert.That(c.Millisecond, Is.EqualTo(0));
Assert.That(c.Ticks, Is.EqualTo(dt.Ticks));
Assert.That(c.DayOfYear, Is.EqualTo(dt.DayOfYear));
Assert.That(c.TimeOfDay, Is.EqualTo(dt.TimeOfDay));
Assert.That(c.Subtract(TimeSpan.FromSeconds(dt.Second)).Value.Second, Is.EqualTo(0));
Assert.That(c.AddYears(1).Value, Is.EqualTo(dt.AddYears(1)));
Assert.That(c.AddMonths(1).Value, Is.EqualTo(dt.AddMonths(1)));
Assert.That(c.AddDays(1).Value, Is.EqualTo(dt.AddDays(1)));
Assert.That(c.AddHours(1).Value, Is.EqualTo(dt.AddHours(1)));
Assert.That(c.AddMinutes(1).Value, Is.EqualTo(dt.AddMinutes(1)));
Assert.That(c.AddSeconds(15).Value, Is.EqualTo(dt.AddSeconds(15)));
Assert.That(c.AddMilliseconds(100).Value, Is.EqualTo(dt.AddMilliseconds(0))); // truncated
Assert.That(c.AddMilliseconds(1000).Value, Is.EqualTo(dt.AddMilliseconds(1000)));
Assert.That(c.AddTicks(1).Value, Is.EqualTo(dt.AddTicks(0))); // truncated
Assert.That(c.AddTicks(TimeSpan.FromMinutes(1).Ticks).Value, Is.EqualTo(dt.AddTicks(TimeSpan.FromMinutes(1).Ticks)));
Assert.That(c.DateOnlyValue, Is.EqualTo(new DateOnly(dt.Year, dt.Month, dt.Day)));
Assert.That(c.TimeOnlyValue, Is.EqualTo(new TimeOnly(dt.Hour, dt.Minute, dt.Second)));
Assert.That(c.ToString("dd.MM.yyyy"), Is.EqualTo("02.01.2025 Europe/Berlin"));
});
}

[Test]
public void Simple_PropertyAndMethod_NotHasTime_Tests()
{
var dt = new DateTime(2025, 1, 2, 0, 0, 0, DateTimeKind.Utc);
var c = new CalDateTime(dt, tzId: "Europe/Berlin", hasTime: false);

// Adding time to a date-only value should not change the HasTime property
Assert.Multiple(() =>
{
var result = c.AddHours(1);
Assert.That(result.HasTime, Is.EqualTo(true));
result = c.AddMinutes(1);
Assert.That(result.HasTime, Is.EqualTo(true));
result = c.AddSeconds(1);
Assert.That(result.HasTime, Is.EqualTo(true));
result = c.AddMilliseconds(1000);
Assert.That(result.HasTime, Is.EqualTo(true));
result = c.AddTicks(TimeSpan.FromMinutes(1).Ticks);
Assert.That(result.HasTime, Is.EqualTo(true));
});
}

[Test]
public void Toggling_HasDate_ShouldSucceed()
{
var dateTime = new DateTime(2025, 1, 2, 10, 20, 30, DateTimeKind.Utc);
var dt = new CalDateTime(dateTime);
Assert.Multiple(() =>
{
Assert.That(dt.HasTime, Is.True);
Assert.That(dt.HasDate, Is.True);
dt.HasDate = false;
Assert.That(dt.HasDate, Is.False);
Assert.That(dt.DateOnlyValue.HasValue, Is.False);
Assert.That(() => dt.Value, Throws.InstanceOf<InvalidOperationException>());
dt.HasDate = true;
Assert.That(dt.HasDate, Is.True);
});
}
}
}
19 changes: 5 additions & 14 deletions Ical.Net.Tests/DeserializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -448,22 +448,13 @@ public void Transparency2()
Assert.That(evt.Transparency, Is.EqualTo(TransparencyType.Transparent));
}

/// <summary>
/// Tests that DateTime values that are out-of-range are still parsed correctly
/// and set to the closest representable date/time in .NET.
/// </summary>
[Test]
public void DateTime1()
public void DateTime1_Unrepresentable_DateTimeArgs_ShouldThrow()
{
var iCal = Calendar.Load(IcsFiles.DateTime1);
Assert.That(iCal.Events, Has.Count.EqualTo(6));

var evt = iCal.Events["[email protected]"];
Assert.That(evt, Is.Not.Null);

// The "Created" date is out-of-bounds. It should be coerced to the
// closest representable date/time.
Assert.That(evt.Created.Value, Is.EqualTo(DateTime.MinValue));
Assert.That(() =>
{
_ = Calendar.Load(IcsFiles.DateTime1);
}, Throws.Exception.TypeOf<ArgumentOutOfRangeException>());
}

[Test]
Expand Down
Loading

0 comments on commit 5ec194e

Please sign in to comment.