Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
78 changes: 75 additions & 3 deletions Ical.Net.Tests/CalDateTimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
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 +35,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 +92,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 +155,72 @@ 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 Simple_PropertyAndMethod_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), 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.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.ToString("dd.MM.yyyy"), Is.EqualTo("02.01.2025 Europe/Berlin"));
});
}
}
}
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
4 changes: 2 additions & 2 deletions Ical.Net.Tests/RecurrenceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2828,7 +2828,7 @@ public void UsHolidays()
[TestCase("SECONDLY", 1, true)]
[TestCase("MINUTELY", 60, true)]
[TestCase("HOURLY", 3600, true)]
[TestCase("DAILY", 24*3600, false)]
[TestCase("DAILY", 24*3600, true)]
public void Evaluate1(string freq, int secsPerInterval, bool hasTime)
{
Calendar cal = new Calendar();
Expand All @@ -2837,7 +2837,7 @@ public void Evaluate1(string freq, int secsPerInterval, bool hasTime)
evt.Summary = "Event summary";

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

// 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.
Expand Down
21 changes: 7 additions & 14 deletions Ical.Net.Tests/SimpleDeserializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,22 +451,15 @@ 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, Category("Deserialization")]
public void DateTime1()
public void DateTime1_Unrepresentable_DateTimeArgs_ShouldThrow()
{
var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.DateTime1)).Cast<Calendar>().Single();
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(() =>
{
_ = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.DateTime1))
.Cast<Calendar>()
.Single();
}, Throws.Exception.TypeOf<ArgumentOutOfRangeException>());
}

[Test, Category("Deserialization"), Ignore("Ignore until @thoemy commits the EventStatus.ics file")]
Expand Down
30 changes: 28 additions & 2 deletions Ical.Net/CalendarComponents/CalendarEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,37 @@ public virtual bool IsAllDay
// has a time value.
if (Start != null)
{
Start.HasTime = !value;
if (value)
{
// Ensure time part is not set
var dt = new CalDateTime(Start);
dt.SetValue(DateOnly.FromDateTime(Start.Value), null, Start.Value.Kind);
Start = dt;
}
else
{
// Ensure time part is set
var dt = new CalDateTime(Start);
dt.SetValue(DateOnly.FromDateTime(Start.Value), TimeOnly.FromDateTime(Start.Value), Start.Value.Kind);
Start = dt;
}
}
if (End != null)
{
End.HasTime = !value;
if (value)
{
// Ensure time part is not set
var dt = new CalDateTime(End);
dt.SetValue(DateOnly.FromDateTime(End.Value), null, End.Value.Kind);
End = dt;
}
else
{
// Ensure time part is set
var dt = new CalDateTime(End);
dt.SetValue(DateOnly.FromDateTime(End.Value), TimeOnly.FromDateTime(End.Value), End.Value.Kind);
End = dt;
}
}

if (value && Start != null && End != null && Equals(Start.Date, End.Date))
Expand Down
5 changes: 2 additions & 3 deletions Ical.Net/CalendarComponents/VTimeZone.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Ical.Net.DataTypes;
using Ical.Net.DataTypes;
using Ical.Net.Proxies;
using Ical.Net.Utility;
using NodaTime;
Expand Down Expand Up @@ -177,7 +177,7 @@ private static VTimeZoneInfo CreateTimeZoneInfo(List<ZoneInterval> matchedInterv
timeZoneInfo.TimeZoneName = oldestInterval.Name;

var start = oldestInterval.IsoLocalStart.ToDateTimeUnspecified() + delta;
timeZoneInfo.Start = new CalDateTime(start) { HasTime = true };
timeZoneInfo.Start = new CalDateTime(start);

if (isRRule)
{
Expand Down Expand Up @@ -244,7 +244,6 @@ private static void PopulateTimeZoneInfoRecurrenceDates(VTimeZoneInfo tzi, List<
continue;
}

date.HasTime = true;
periodList.Add(date);
tzi.RecurrenceDates.Add(periodList);
}
Expand Down
Loading
Loading