Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to use DateOnly and TimeOnly for date/time handling #658

Merged
merged 12 commits into from
Dec 6, 2024

Conversation

axunonb
Copy link
Collaborator

@axunonb axunonb commented Nov 30, 2024

Increased CalDateTime immutability

CalDateTime:

  • Date is of type DateOnly (breaking vs. v4)
  • Make UtzTzId a public string
  • Remove DateOnlyValue
  • Remove HasDate (always true) (breaking vs. v4)
  • Remove Millisecond (breaking vs. v4)
  • Remove Ticks (breaking vs. v4)
  • Remove overload CalDateTime(int year, int month, int day, string? tzId) (breaking vs. v4)
  • HasTime only has a getter (breaking vs. v4)
  • Time is a getter of type TimeOnly. Values are always rounded to the nearest second (breaking vs. v4)
  • AddMilliseconds takes double as argument, like DateTime.AddMilliseconds (breaking vs. v4)
  • Remove TimeOnlyValue
  • Value only has a getter (breaking vs. v4)
  • TzId only has a getter (breaking vs. v4)
  • Remove AsSystemLocal (breaking vs. v4)
  • Remove AsDateTimeOffset (breaking vs. v4)
  • Remove AddMilliseconds (breaking vs. v4)
  • Remove AddTicks (breaking vs. v4)
  • Remove unused TimeSpan? operator -(CalDateTime? left, IDateTime? right) (breaking vs. v4)
  • Update Equality operators for comparing to floating date/time (breaking vs. v4)

IDateTime (breaking vs. v4):

  • Remove AsSystemLocal (breaking vs. v4)`
  • Remove AsDateTimeOffset (breaking vs. v4)`
  • Remove AddMilliseconds (breaking vs. v4)
  • Remove AddTicks (breaking vs. v4)
  • Remove Millisecond (breaking vs. v4)
  • Remove Ticks (breaking vs. v4)
  • Add explicit bool IsFloating (implicit TzId == null)
  • Date is a getter of type DateOnly
  • Time is a getter of type TimeOnly
  • HasTime only has a getter
  • Remove HasDate (always true) (breaking vs. v4)
  • Value only has a getter
  • TzId only has a getter
  • ToTimeZone(string?) argument is a NRT

Miscellaneous:

  • CTORs with DateTime arguments persist. Actually they are also comfortable, and now consistently processed
  • Keep IDateTime - maybe in another PR
  • Transitioned from DateTime to DateOnly and TimeOnly across the codebase for improved date and time handling.
  • Updated methods, properties, and tests to ensure compatibility with the new approach.
  • Updated xmldoc

Resolves #656
Resolves #662

@axunonb axunonb force-pushed the pr/refactor-idatetime branch 3 times, most recently from 39c43e4 to 7a72ba1 Compare November 30, 2024 22:56
Increased `CalDateTime` immutability

CalDateTime:
* `Date` is of type `DateOnly` (breaking vs. v4)
* Make `UtzTzId` a public string
* Remove `DateOnlyValue`
* Remove `HasDate` (always true) (breaking vs. v4)
* Remove overload CalDateTime(int year, int month, int day, string? tzId) (breaking vs. v4)
* `HasTime` only has a getter (breaking vs. v4)
* `Time` is a getter of type `TimeOnly`. Values are always rounded to the nearest second (breaking vs. v4)
* Remove `TimeOnlyValue`
* `Value` only has a getter (breaking vs. v4)
* `TzId` only has a getter (breaking vs. v4)
* Remove `AsSystemLocal` (breaking vs. v4)
* Remove unused `TimeSpan? operator -(CalDateTime? left, IDateTime? right)` (breaking vs. v4)
* Update Equality operators for comparing to floating date/time (breaking vs. v4)

IDateTime (breaking vs. v4):
* Remove `AsSystemLocal`
* Add explicit bool IsFloating (implicit TzId == null)
* `Date` is a getter of type `DateOnly`
* `Time` is a getter of type `TimeOnly`
* `HasTime` only has a getter
* Remove `HasDate`
* `Value` only has a getter
* `TzId` only has a getter
* `ToTimeZone(string?)` argument is a NRT

Miscellaneous:
* CTORs with `DateTime` arguments persist. Actually they are also comfortable, and now consistently processed
* Keep `IDateTime` - maybe in another PR
* Transitioned from DateTime to DateOnly and TimeOnly across the codebase for improved date and time handling.
* Updated methods, properties, and tests to ensure compatibility with the new approach.
* Updated xmldoc
@axunonb axunonb force-pushed the pr/refactor-idatetime branch from 7a72ba1 to 7463f67 Compare December 1, 2024 21:01
@axunonb axunonb force-pushed the pr/refactor-idatetime branch from ba7eb3e to 67cda7c Compare December 2, 2024 09:02
@axunonb axunonb force-pushed the pr/refactor-idatetime branch from 383e552 to dae01d7 Compare December 2, 2024 13:29
…anged

Remove fallback to system's local timezone for floating date/time
@axunonb axunonb force-pushed the pr/refactor-idatetime branch from dae01d7 to b8d5b8e Compare December 2, 2024 13:47
@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

@@ -988,6 +990,6 @@ private static IDateTime MatchTimeZone(IDateTime dt1, IDateTime dt2)

return dt1.IsUtc
? new CalDateTime(copy.AsUtc)
: new CalDateTime(copy.AsSystemLocal);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the code in MatchTimeZone(IDateTime dt1, IDateTime dt2) completely useless?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not the most elegant code, but it aims to adjust UNTIL, which usually is specified in UTC, to DTSTART's time zone, which usually isn't in UTC. This allows checking comparing the adjusted UNTIL with the generated recurrences without considering the time zone again. So the functionality is needed one way or the other.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I thought, but the way it is implemented looks like a noop. Then I removed the method invocation temporarily with no effect on unit tests. So this is just a hint re recurrences.

Copy link
Collaborator

@minichma minichma Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could easily be wrong, or relevant tests are missing. We'd need a test with UNTIL exactly at the last recurrence and then run the test with a TZ with positive offset and with one with a negative offset. In any case the last recurrence should be returned.

Copy link
Collaborator Author

@axunonb axunonb Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2 arguments to MatchTimeZone(IDateTime dt1, IDateTime dt2) always have the same timezone, so there is nothing to adjust? [Edit] The method in effect is r.Until = r.Until.

if (r.Until != DateTime.MinValue)
{
    r.Until = MatchTimeZone(referenceDate, new CalDateTime(r.Until, referenceDate.TzId)).Value;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are fully right, its a noop. But I strongly believe that something's wrong there, because the conversion needs to be done somewhere. Should be investigated separately. Maybe the translation between UTC and tzid happened in the constructor of CalDateTime and was lost in one of the previous PRs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #663 to track

@axunonb axunonb marked this pull request as ready for review December 2, 2024 14:39
@axunonb axunonb requested a review from minichma December 2, 2024 14:39
…t null

Reasoning: DATE cannot have a timezone
@minichma
Copy link
Collaborator

minichma commented Dec 2, 2024

Just noted, that I didn't submit my review comments from 2d ago, so please read my comments in the context of last Sat.

return false;
}

if (left.IsFloating || right.IsFloating)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest to add a check if IsFloating equals. I.e.

if (left.IsFloating != right.IsFloating)
    return false; 

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the first glance I thought the proposal worked, but it doesn't.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't add this check, we could consider instances as equal even if one is floating and the other has a TZ set, which seems wrong. What would be the reason for that behavior?

new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults);
public static HashSet<Occurrence> GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults)
{
var endTimeOnly = dt.Time?.Add(TimeSpan.FromSeconds(-1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful, Time could be 0:00 in which case we'd end up with a negative time.

Copy link
Collaborator Author

@axunonb axunonb Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InvalidOperationException: see below

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I understand. I think the problem still exists. If someone specifies a dt e.g. 20241203T000000, which is legit, then this code would lead to 20241203 T -000001.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, we would introduce a new bug here and should consider fixing it right away.

@@ -988,6 +990,6 @@ private static IDateTime MatchTimeZone(IDateTime dt1, IDateTime dt2)

return dt1.IsUtc
? new CalDateTime(copy.AsUtc)
: new CalDateTime(copy.AsSystemLocal);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not the most elegant code, but it aims to adjust UNTIL, which usually is specified in UTC, to DTSTART's time zone, which usually isn't in UTC. This allows checking comparing the adjusted UNTIL with the generated recurrences without considering the time zone again. So the functionality is needed one way or the other.

@minichma
Copy link
Collaborator

minichma commented Dec 2, 2024

This is a great improvement regarding readability and maintainability!

* Updated CalDateTime to truncate time to seconds instead of rounding.
* Removed AddMilliseconds and AddTicks methods and related test cases.
* Fix: minus and plus operators for cases where time parts are equal
* Adjusted EndOfDay method to use AddSeconds(-1) instead of AddTicks(-1)
* Removed GetOccurrenceShouldExcludeDtEndZoned test.
@axunonb axunonb requested a review from minichma December 3, 2024 12:53
@@ -205,11 +205,17 @@ public VTimeZone AddTimeZone(VTimeZone tz)
/// <param name="dt">The date for which to return occurrences. Time is ignored on this parameter.</param>
/// <returns>A list of occurrences that occur on the given date (<paramref name="dt"/>).</returns>
public virtual HashSet<Occurrence> GetOccurrences(IDateTime dt)
=> GetOccurrences<IRecurringComponent>(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)));
{
var endTimeOnly = dt.Time?.Add(TimeSpan.FromSeconds(-1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Time is 0:00, you'll end up with a negative time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Track in #662

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new issue introduced in this PR, so shouldn't we fix it right away? The behavior is also somewhat different from the previous one. In the previous version we just truncated the time, in the new one we keep the time, if set, and observe the period until the same time next day. Is this intentional?

@@ -152,15 +152,19 @@ public virtual bool IsAllDay
get => !Start.HasTime;
set
{
// Set whether or not the start date/time
// Set whether the start date/time
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this property should be editable. Whether the event is all-day should be specified via DTSTART.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just changed the wording of the comment :)

But yes, this one was easy. I removed the setter that was used only in 1 unit test. AllDay can simply be set with a CalDateTime without time.

_tzId = string.Equals(UtcTzId, tzId, StringComparison.OrdinalIgnoreCase) ? UtcTzId : tzId;
_tzId = tzId switch
{
_ when !timeOnly.HasValue && string.Equals(UtcTzId, tzId, StringComparison.OrdinalIgnoreCase) => UtcTzId,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does UTC receive special handling here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, that's redundant

return false;
}

if (left.IsFloating || right.IsFloating)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't add this check, we could consider instances as equal even if one is floating and the other has a TZ set, which seems wrong. What would be the reason for that behavior?

}
else
{
copy._dateOnly = DateOnly.FromDateTime(newValue);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we shouldn't silently convert a date-only value to date-time but rather throw an exception. This is tricky in multiple ways. In particular, if DST changes. Maybe related to #660. Do we need those operators at all? If yes, we might want to be more explicit, i.e. have a 'add/subtract nominal' vs 'add/subtract accurate' in correspondence with how RFC5545 distinguishes those cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #661 for tracking a separate PR

/// </summary>
int Second { get; }

/// <summary>
/// Gets the millisecond for this date/time value.
/// Gets the millisecond that applies to the <see cref="Value"/>.
/// </summary>
int Millisecond { get; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see below

/// </summary>
int Millisecond { get; }

/// <summary>
/// Gets the ticks for this date/time value.
/// Gets the ticks that applies to the <see cref="Value"/>.
/// </summary>
long Ticks { get; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

Copy link
Collaborator Author

@axunonb axunonb Dec 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question is applicable to other properties, too: Day, Month, Year...
All can be retrieved from the Value property. We could clean-up, when this would bring any advantage. But not in this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, my thought on Milliseconds and Ticks is just, that they are truncated starting with this PR, so providing getters for them could be confusing and considered inconsistent.

@@ -988,6 +990,6 @@ private static IDateTime MatchTimeZone(IDateTime dt1, IDateTime dt2)

return dt1.IsUtc
? new CalDateTime(copy.AsUtc)
: new CalDateTime(copy.AsSystemLocal);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are fully right, its a noop. But I strongly believe that something's wrong there, because the conversion needs to be done somewhere. Should be investigated separately. Maybe the translation between UTC and tzid happened in the constructor of CalDateTime and was lost in one of the previous PRs?

new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults);
public static HashSet<Occurrence> GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults)
{
var endTimeOnly = dt.Time?.Add(TimeSpan.FromSeconds(-1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, I understand. I think the problem still exists. If someone specifies a dt e.g. 20241203T000000, which is legit, then this code would lead to 20241203 T -000001.

@@ -19,7 +19,7 @@ public static IDateTime StartOfDay(IDateTime dt)
=> dt.AddHours(-dt.Hour).AddMinutes(-dt.Minute).AddSeconds(-dt.Second);

public static IDateTime EndOfDay(IDateTime dt)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably get rid of that. Calculations should be done precisely, adding -1 whatever shouldn't be necessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created issue #662 for a separate PR

@minichma
Copy link
Collaborator

minichma commented Dec 3, 2024

Sorry for the many comments. You're working on the big ones here, so many things to consider.

@axunonb
Copy link
Collaborator Author

axunonb commented Dec 3, 2024

No need for excuses :) - comments are all very welcome.
There is one more commit with 2 issues from your latest comments.
For other comments we have new issues to track them - so they'll not go into this PR, which is huge already.
One important topic is the discussion re comparing floating to zoned date/time, Let's resolve the topic there.

* Made `IsAllDay` in `CalendarEvent.cs` read-only,
* Updated `Issue432_AllDay` unit test in `RecurrenceTests.cs` to remove time component from `Start` property and eliminate setting the `IsAllDay` property. (The only place where the setter was used.)
* Simplified `CalDateTime.cs` initialization by removing condition related to `UtcTzId` when `timeOnly` is not provided.
@axunonb axunonb force-pushed the pr/refactor-idatetime branch from 3820342 to 5797236 Compare December 3, 2024 20:09
* `operator ==`: if (left.IsFloating != right.IsFloating) return false;
* Add unit test
@axunonb axunonb requested a review from minichma December 4, 2024 14:53
@axunonb
Copy link
Collaborator Author

axunonb commented Dec 4, 2024

@minichma PR comments should all all be resolved or addressed

@minichma
Copy link
Collaborator

minichma commented Dec 5, 2024

I'm on a business trip until Sat. Not sure, whether I'll find the time until then.

@@ -205,11 +205,17 @@ public VTimeZone AddTimeZone(VTimeZone tz)
/// <param name="dt">The date for which to return occurrences. Time is ignored on this parameter.</param>
/// <returns>A list of occurrences that occur on the given date (<paramref name="dt"/>).</returns>
public virtual HashSet<Occurrence> GetOccurrences(IDateTime dt)
=> GetOccurrences<IRecurringComponent>(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)));
{
var endTimeOnly = dt.Time?.Add(TimeSpan.FromSeconds(-1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new issue introduced in this PR, so shouldn't we fix it right away? The behavior is also somewhat different from the previous one. In the previous version we just truncated the time, in the new one we keep the time, if set, and observe the period until the same time next day. Is this intentional?

if (!dt.HasTime && hours % 24 > 0)
var copy = Copy<CalDateTime>();
var newValue = copy.Value.AddHours(hours);
if (!copy.HasTime && (hours % 24 == 0))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider (in a subsequent PR) to remove the special handling of multiples of 24. This is the kind of magic we had in v4 with midnight. A user wouldn't understand, why adding multiple of 24h behaves differently than adding other values. I suggest to either disallow adding hours to date-only instances altogether (preferred) , or, to always convert to date-time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issues #661 and #662 already exists since last review

new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults);
public static HashSet<Occurrence> GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults)
{
var endTimeOnly = dt.Time?.Add(TimeSpan.FromSeconds(-1));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, we would introduce a new bug here and should consider fixing it right away.

@axunonb
Copy link
Collaborator Author

axunonb commented Dec 5, 2024

introduce a new bug here and should consider fixing it right away

Agree about this should be fixed (#662) with another PR.
The fix was trivial, so it's done in 5956fb9

It's existing code logic though:

public static HashSet<Occurrence> GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults) => GetOccurrences(recurrable,
new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults);

[Edit]
The remaining time adjustments were only found in class Calendar.
The adjustments are redundant, because
public static HashSet<Occurrence> RecurrenceUtil.GetOccurrences(IRecurrable recurrable, IDateTime periodStart, IDateTime periodEnd, bool includeReferenceDateInResults)
uses precise "LessThan" for comparing end date/times
This is now also cleaned-up with 5956fb9

Removed the `endTimeOnly` variable and its usage in the `GetOccurrences` method. The code now directly creates the `CalDateTime` object for the end of the period without adjusting the time component. This change streamlines the code and avoids potential issues related to time adjustments.
@axunonb axunonb requested a review from minichma December 5, 2024 19:31
@axunonb
Copy link
Collaborator Author

axunonb commented Dec 5, 2024

Comments resolved or tracked by issues

The `GetOccurrences` methods in `Calendar.cs` have been simplified
by removing the code that adjusted the end time by subtracting one
second or one tick. The methods now directly use the start date
and the date one day after the start date without adjusting the
time component. This change simplifies the logic and removes
unnecessary time adjustments.

The adjustments are redudant, because
public static HashSet<Occurrence> RecurrenceUtil.GetOccurrences(IRecurrable recurrable, IDateTime periodStart, IDateTime periodEnd, bool includeReferenceDateInResults)
uses precise "LessThan" for comparing end date/times

Resolves  ical-org#662
Copy link

sonarqubecloud bot commented Dec 5, 2024

@axunonb
Copy link
Collaborator Author

axunonb commented Dec 5, 2024

The remaining time adjustments were only found in class Calendar.

The adjustments are redundant, because calls finally go to
public static HashSet<Occurrence> RecurrenceUtil.GetOccurrences(IRecurrable recurrable, IDateTime periodStart, IDateTime periodEnd, bool includeReferenceDateInResults)
which uses precise "LessThan" for comparing end date/times.

@minichma
Copy link
Collaborator

minichma commented Dec 6, 2024

The fix was trivial, so it's done in 5956fb9

It's existing code logic though:

Great, thank you! The difference to the previous code is, that the previous version calculated 23:59:59 the previous day, but the new code calculated -0:00:01 the same day. So now its certainly better.

Copy link
Collaborator

@minichma minichma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvement!

@axunonb axunonb merged commit 71a4831 into ical-org:main Dec 6, 2024
4 checks passed
@axunonb axunonb deleted the pr/refactor-idatetime branch December 6, 2024 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clean-up determining the end time of a period DTEND DATE value is not reckoned exclusively
2 participants