-
Notifications
You must be signed in to change notification settings - Fork 231
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
Enhance CalDateTime and DateTimeSerializer #638
Conversation
01de82a
to
0743c68
Compare
0743c68
to
a4cd5c0
Compare
08c7ff1
to
28df6fd
Compare
69f6508
to
a5b1bf5
Compare
Ical.Net/DataTypes/CalDateTime.cs
Outdated
{ | ||
Initialize(_value, _timeOnly.HasValue, value, Calendar); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the TzId now initializes with immediate effect
7ff8e90
to
edf6103
Compare
Ical.Net/DataTypes/CalDateTime.cs
Outdated
/// </remarks> | ||
/// </summary> | ||
public sealed class CalDateTime : EncodableDataType, IDateTime | ||
{ | ||
// The date and time parts that were used to initialize the instance | ||
// or by the Value setter. | ||
private DateTime _value; |
There was a problem hiding this comment.
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 maintain both, the DateTime value and Date-/TimeOnly. I suggest to remove _value
altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the backing value
we lose the HasDate
and HasTime
"toggle". The Date-/TimOnly fields are just the replacement for the backing _hasDate
and _hasTime
booleans. This implementation is mainly because HasDate
and HasTime
require a setter from the IDateTime
interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought it would be an option to calculate the DateTime value from the other two just in time, when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I get your point: HasDate
and HasTime
wouldn't have backing fields, they would just become pure boolean getters/setters. Then _value
can disappear. Yep, that's better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be updated as proposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Ical.Net/DataTypes/CalDateTime.cs
Outdated
private void Initialize(DateTime value, string tzId, Calendar cal) | ||
{ | ||
if (!string.IsNullOrWhiteSpace(tzId) && !tzId.Equals("UTC", StringComparison.OrdinalIgnoreCase)) | ||
if ((tzId != null && !string.IsNullOrWhiteSpace(tzId) && !tzId.Equals("UTC", StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, it's legacy (so nothing to necessarily deal with in this PR), but I think that we shouldn't have special handling for white space, or empty strings. Either the value is set (!= null) or not (== null). Input sanitization is not something this class should be responsible for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be updated as proposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Ical.Net/DataTypes/CalDateTime.cs
Outdated
TzId = tzId; | ||
_tzId = tzId; | ||
|
||
initialValue = DateTime.SpecifyKind(dateTime, DateTimeKind.Local); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also legacy, but IMHO important: I think we should avoid DateTimeKind.Local
wherever we can, because dealing with the system's local time really is a special case. If no tzid is specified, this would usually mean that the instance represents floating time (which btw cannot be converted to a different time zone). Floating time should rather be represented by DateTimeKind.Unspecified
than .Local
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will be updated as proposed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@axunonb I only had a very brief look at the new CalDateTime class. While I think that this PR brings some improvements, I feel that the whole design of the CalDateTime class deserves some more attention and improvements. Just a few thoughts:
jm2c |
Todo in this PR: Use of
|
* 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
1d15f23
to
54ee355
Compare
Depending on the time offset from local time to UTC the tests failed. Changed all date kinds to UTC.
54ee355
to
e7f772f
Compare
Affected classes: * `CalDateTime` * `DateUtil` * `DateTimeSerializer`
…alizer` `CalDateTime.TzId` does no more read or write `CalDateTime.Parameters`
…me.Value` * Depending on `HasTime`, `Value` contains only the `DateTime.Date`, or the value including `DateTime.TimeOfDay`. * The timezone dermines whether `DateTimeKind.Utc`or `DateTimeKind.Unspecified`will be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the simplification that comes with removing the ambiguous DateTime
member field and with removing DateTimeKind.Local
! Didn't do a full review, only CalDateTime
for now, but I think that's the core part anyways. Left a few comments with my thoughts, maybe not all relevant for this PR but for following ones.
Ical.Net/DataTypes/CalDateTime.cs
Outdated
public static IDateTime operator -(CalDateTime left, TimeSpan right) | ||
{ | ||
var copy = left.Copy<IDateTime>(); | ||
var copy = left.Copy<CalDateTime>(); | ||
if (Math.Abs(right.TotalDays % 1) > AlmostZeroEpsilon) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe (right.Ticks % TimeSpan.TicksPerDay) != 0
? This way we could avoid dealing with FP precision issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
excellent, done
Ical.Net/Utility/DateUtil.cs
Outdated
@@ -22,7 +22,7 @@ public static IDateTime EndOfDay(IDateTime dt) | |||
=> StartOfDay(dt).AddDays(1).AddTicks(-1); | |||
|
|||
public static DateTime GetSimpleDateTimeData(IDateTime dt) | |||
=> DateTime.SpecifyKind(dt.Value, dt.IsUtc ? DateTimeKind.Utc : DateTimeKind.Local); | |||
=> DateTime.SpecifyKind(dt.Value, dt.IsUtc ? DateTimeKind.Utc : DateTimeKind.Unspecified); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the improved CalDateTime, this would be as simple as => dt.Value
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right - done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, there are a few more places to deal with local date in this class.
- The behavior of
SimpleDateTimeToMatch
is not easy to understand. Within the sln its only used for testing, so I'd suggest to move it to a test project, where the specific behavior might be helpful. MatchTimeZone
is only used inRecurrencePatternEvaluator
. I'm pretty sure, that the way it deals with floating time is not the most natural one either (see GetOccurrences should not assume the system local date time #197). Probably we want to deal with that in a separate PR. Maybe move this method intoRecurrencePatternEvaluator
for now and/or add a comment that we should reconsider/fix how it deals with local time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done:
- Move
DateUtil.SimpleDateTimeToMatch
as private toRecurrenceTests
- Move
DateUtil.MatchTimeZone
as private toRecurrencePatternEvaluator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DateUtil
is internal, so this shouldn't be breaking.
/// the system's local timezone. | ||
/// </param> | ||
/// <param name="hasTime">Set to <see langword="true"/> (default), if the <see cref="DateTime.TimeOfDay"/> must be included.</param> | ||
public CalDateTime(DateTime value, string? tzId, bool hasTime = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having both, the Kind
in the DateTime
value, as well as the tzId
, causes redundancy, that's not easy to understand. What, if we remove this constructor altogether? The user could easily call new CalDateTime(value).ToTimeZone(tzId)
, with way less ambiguity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be back on the agenda when we implement floating date/time. See #647
public DateTime Date => Value.Date; | ||
|
||
/// <inheritdoc cref="DateTime.TimeOfDay"/> | ||
public TimeSpan TimeOfDay => Value.TimeOfDay; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this obsolete, now that we have the TimeOnlyValue
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see task in #646
public int DayOfYear => Value.DayOfYear; | ||
|
||
/// <inheritdoc cref="DateTime.Date"/> | ||
public DateTime Date => Value.Date; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obsolete with DateOnlyValue
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see task in #646
/// <summary> | ||
/// Gets the underlying <see cref="DateOnlyValue"/> of <see cref="Value"/>. | ||
/// </summary> | ||
public DateOnly? DateOnlyValue => HasDate ? _dateOnly : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just name it Date
, in replacement of the existing, now obsolete Date
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see task in #646
/// <summary> | ||
/// Gets the underlying <see cref="TimeOnlyValue"/> of <see cref="Value"/>. | ||
/// </summary> | ||
public TimeOnly? TimeOnlyValue => HasTime ? _timeOnly : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just Time
or TimeOfDay
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see task in #646
* `if (Math.Abs(right.TotalDays % 1) > AlmostZeroEpsilon)` is now `if ((right.Ticks % TimeSpan.TicksPerDay) != 0)` * `DateUtil.DateTime GetSimpleDateTimeData(IDateTime dt)` returns `dt.Value` * Move internal `DateUtil.SimpleDateTimeToMatch` as private to `RecurrenceTests` * Move internal `DateUtil.MatchTimeZone` as private to `RecurrencePatternEvaluator` * Create tasks in ical-org#646 and ical-org#647 for new PRs
Also refactor for SonarCloud complaints
b8a64e7
to
7bf9c3b
Compare
Quality Gate passedIssues Measures |
@minichma Merged as you already reviewed twice :) |
Sorry for not being able to review this any earlier. Anyhow, this is a significant improvement, congrats! |
Breaking Changes
Calendar
argument to constructor overload ofCalDateTime
CalDateTilme
only usesDateTilmeKind.Utc
andDateTimeKind.Unspecified
.DateTimeKind.Local
has been dropped.null
for the timezone will apply the systems local time with
AsDateTimeOffset`.DateTimeKind
of aDateTime
value. This is also the case, if a UTC date and a timezone (not null) is used.CalDateTime.TzId
: Will not read or writeCalDateTime.Parameters
. The functionality has been moved to `DateTimeSerializer.Deserialize(TextReader)DateTimeSerializer.Deserialize(TextReader)
will now throw for unrepresentable date/time values.CalDateTime.TzId
can benull
or a valid timezone ID. Whitespace is not allowed.CalDateTime
DateOnly
andTimeOnly
in order to return correct values forCalDateTime.HasTime
andCalDateTime.HasDate
, even if hour, minutes and seconds are zero.Value
orTzId
will re-initialize the instance in the same way as with CTOR overloads.ToString
overloads to convert the date and time to a string representation. Thanks to @nzbart for his initial PR.DateTimeSerializer
SerializeToString
method to ensure the TZID parameter is correctly handled based on whether the date-time is in UTC.DateOnly
andTimeOnly
parts accordingly.Other
IDateTime
Portable.System.DateTimeOnly
for netstandard2.0 and netstandard2.1 for compatibility with net6.0 and later.Resolves #630
Resolves #633
Resolves #635
Resolves #636
Resolves #637
Resolves #197