Skip to content

Commit 383e552

Browse files
committed
Floating date/time can convert to any timezone ID, keeping Value unchanged
Remove fallback to system's local timezone for floating date/time
1 parent 67cda7c commit 383e552

File tree

4 files changed

+45
-19
lines changed

4 files changed

+45
-19
lines changed

Ical.Net.Tests/CalDateTimeTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Collections;
1111
using System.Collections.Generic;
1212
using System.Globalization;
13+
using System.Linq;
1314

1415
namespace Ical.Net.Tests;
1516

Ical.Net.Tests/RecurrenceTests.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3786,5 +3786,28 @@ private static DateTime SimpleDateTimeToMatch(IDateTime dt, IDateTime toMatch)
37863786
}
37873787
return dt.Value;
37883788
}
3789-
}
37903789

3790+
[Test]
3791+
public void GetOccurrenceShouldExcludeDtEnd()
3792+
{
3793+
var ical = """
3794+
BEGIN:VCALENDAR
3795+
VERSION:2.0
3796+
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 5.0//EN
3797+
BEGIN:VEVENT
3798+
UID:123456
3799+
DTSTAMP:20240630T000000Z
3800+
DTSTART;VALUE=DATE:20241001
3801+
DTEND;VALUE=DATE:20241202
3802+
SUMMARY:Don't include the end date of this event
3803+
END:VEVENT
3804+
END:VCALENDAR
3805+
""";
3806+
3807+
var calendar = Calendar.Load(ical);
3808+
// Set start date for occurrences to search to the end date of the event
3809+
var occurrences = calendar.GetOccurrences(new CalDateTime(2024, 12, 2));
3810+
3811+
Assert.That(occurrences, Is.Empty);
3812+
}
3813+
}

Ical.Net/DataTypes/CalDateTime.cs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ namespace Ical.Net.DataTypes;
1717
/// The iCalendar equivalent of the .NET <see cref="DateTime"/> class.
1818
/// <remarks>
1919
/// In addition to the features of the <see cref="DateTime"/> class, the <see cref="CalDateTime"/>
20-
/// class handles timezones, and integrates seamlessly into the iCalendar framework.
20+
/// class handles timezones, floating date/times and integrates seamlessly into the iCalendar framework.
2121
/// <para/>
2222
/// Any <see cref="Time"/> values are always rounded to the nearest second.
23-
/// This is because RFC 5545, Section 3.3.5 does not allow for fractional seconds.
23+
/// This is because RFC 5545, Section 3.3.5, does not allow for fractional seconds.
2424
/// </remarks>
2525
/// </summary>
2626
public sealed class CalDateTime : EncodableDataType, IDateTime
@@ -357,18 +357,10 @@ public override int GetHashCode()
357357
/// </summary>
358358
public static implicit operator CalDateTime(DateTime left) => new CalDateTime(left);
359359

360-
/// <summary>
361-
/// Returns a representation of the <see cref="DateTime"/> in UTC.
362-
/// </summary>
363-
public DateTime AsUtc => ToTimeZone(UtcTzId).Value;
360+
/// <inheritdoc/>
361+
public DateTime AsUtc => DateTime.SpecifyKind(ToTimeZone(UtcTzId).Value, DateTimeKind.Utc);
364362

365-
/// <summary>
366-
/// Gets the date and time value in the ISO calendar as a <see cref="DateTime"/> type with <see cref="DateTimeKind.Unspecified"/>.
367-
/// The value has no associated timezone.
368-
/// The precision of the time part is up to seconds.
369-
/// <para/>
370-
/// The value is equivalent to <seealso cref="NodaTime.LocalDateTime"/>.
371-
/// </summary>
363+
/// <inheritdoc/>
372364
public DateTime Value
373365
{
374366
get
@@ -477,9 +469,10 @@ public DateTime Value
477469
}
478470

479471
/// <inheritdoc/>
480-
/// <remarks>If <see paramref="otherTzId"/> is not a well-known timezone ID, the system's local timezone will be used.</remarks>
481472
public IDateTime ToTimeZone(string otherTzId)
482473
{
474+
if (IsFloating) return new CalDateTime(_dateOnly, _timeOnly, otherTzId);
475+
483476
var zonedOriginal = DateUtil.ToZonedDateTimeLeniently(Value, TzId);
484477
var converted = zonedOriginal.WithZone(DateUtil.GetZone(otherTzId));
485478

Ical.Net/DataTypes/IDateTime.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public interface IDateTime : IEncodableDataType, IComparable<IDateTime>, IFormat
1212
{
1313
/// <summary>
1414
/// Converts the date/time to UTC (Coordinated Universal Time)
15+
/// If <see cref="IsFloating"/>==<see langword="true"/>
16+
/// it means that the <see cref="Value"/> is considered as local time for every timezone:
17+
/// The returned <see cref="Value"/> is unchanged, but with <see cref="DateTimeKind.Utc"/>.
1518
/// </summary>
1619
DateTime AsUtc { get; }
1720

@@ -27,10 +30,12 @@ public interface IDateTime : IEncodableDataType, IComparable<IDateTime>, IFormat
2730
string? TimeZoneName { get; }
2831

2932
/// <summary>
30-
/// Gets the underlying DateTime value. This should always
31-
/// use DateTimeKind.Utc, regardless of its actual representation.
32-
/// Use IsUtc along with the TZID to control how this
33-
/// date/time is handled.
33+
/// Gets the date and time value in the ISO calendar as a <see cref="DateTime"/> type with <see cref="DateTimeKind.Unspecified"/>.
34+
/// The value has no associated timezone.<br/>
35+
/// The precision of the time part is up to seconds.
36+
/// <para/>
37+
/// Use <see cref="IsUtc"/> along with <see cref="TzId"/> and <see cref="IsFloating"/>
38+
/// to control how this date/time is handled.
3439
/// </summary>
3540
DateTime Value { get; }
3641

@@ -114,6 +119,10 @@ public interface IDateTime : IEncodableDataType, IComparable<IDateTime>, IFormat
114119
/// <summary>
115120
/// Converts the <see cref="Value"/> to a date/time
116121
/// within the specified <see paramref="otherTzId"/> timezone.
122+
/// <para/>
123+
/// If <see cref="IsFloating"/>==<see langword="true"/>
124+
/// it means that the <see cref="Value"/> is considered as local time for every timezone:
125+
/// The returned <see cref="Value"/> is unchanged and the <see paramref="otherTzId"/> is set as <see cref="TzId"/>.
117126
/// </summary>
118127
IDateTime ToTimeZone(string otherTzId);
119128
IDateTime Add(TimeSpan ts);

0 commit comments

Comments
 (0)