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

Introduce DateOnly and TimeOnly types #50980

Merged
merged 12 commits into from
Apr 14, 2021
Merged

Conversation

tarekgh
Copy link
Member

@tarekgh tarekgh commented Apr 9, 2021

Fixes #49036

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@tarekgh
Copy link
Member Author

tarekgh commented Apr 9, 2021

CC @mattjohnsonpint

@tarekgh
Copy link
Member Author

tarekgh commented Apr 9, 2021

CC @safern

Copy link
Contributor

@gewarren gewarren left a comment

Choose a reason for hiding this comment

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

There is a lot of /// to review - here is some initial feedback.

Added a temorary logging info to the exception for CI failure diagnostics
@tarekgh
Copy link
Member Author

tarekgh commented Apr 12, 2021

Thanks @stephentoub & @danmoseley for all valuable feedback. I believe I have addressed all of it. Please let me know if you have any more comments or we are good to go.

@danmoseley
Copy link
Member

I have no other comments but I did not review it all so I defer to @stephentoub

@tarekgh
Copy link
Member Author

tarekgh commented Apr 13, 2021

@safern thanks for the feedback. do you have any more comments?

Copy link
Member

@safern safern left a comment

Choose a reason for hiding this comment

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

LGTM

@tarekgh tarekgh merged commit 94c13d0 into dotnet:main Apr 14, 2021
@tarekgh tarekgh deleted the DateAndTimeOnly branch April 14, 2021 15:34
ParseFailure,
WrongParts,
BadFormatSpecifier
}
Copy link
Member

Choose a reason for hiding this comment

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

We can't share the same enum that's used with DateTime?

Also, indentation is off here. And if we can't share it, it should probably be a nested enum under DateOnly rather than being System.ParseOperationResult.

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried to avoid sharing that with DateTimeParse.ParseFailureKind enum because they include different things specific to DateTime parsing and I wanted to avoid merging more values there that are not related to DateTime. I didn't make this enum nested inside DateOnly because I used it in TimeOnly too. we can still move it and reference it with the DateOnly but I thought this will be ugly.

For the rest of the comments, I'll try to address this in other PR later.

Copy link
Member

Choose a reason for hiding this comment

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

I wanted to avoid merging more values there that are not related to DateTime

I think it's fine to add more. DateTime, DateTimeOffset, DateOnly, and TimeOnly are all logically related and part of the same system, sharing many similar errors, share format and styles, etc.


return new DateOnly(newDayNumber);

static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue);
Copy link
Member

Choose a reason for hiding this comment

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

This can't be on ThrowHelper rather than its own method?

default:
Debug.Assert(result == ParseOperationResult.WrongParts);
throw new FormatException(SR.Format(SR.Format_DateTimeOnlyContainsNoneDateParts, s.ToString(), nameof(DateOnly)));
}
Copy link
Member

Choose a reason for hiding this comment

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

This block of exception handling code is duplicated in multiple places. It should instead be a helper method, just as with DateTime. That not only saves on duplication, it makes the call site more streamlined, with rarely used code separated out, makes it more likely to be inlineable, etc.

/// <returns>true if the s parameter was converted successfully; otherwise, false.</returns>
public static bool TryParse(string s, IFormatProvider? provider, DateTimeStyles style, out DateOnly result)
{
result = default;
Copy link
Member

Choose a reason for hiding this comment

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

This result = default; should be moved into the if block below.

bool b = DateTimeFormat.TryFormatDateOnlyO(value.Year, value.Month, value.Day, destination);
Debug.Assert(b);
});
}
Copy link
Member

Choose a reason for hiding this comment

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

Nit: these braces shouldn't be necessary... same for the 'r' case below

ParseOperationResult result = TryParseInternal(s, provider, style, out TimeOnly timeOnly);
if (result != ParseOperationResult.Success)
{
switch (result)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as on DateOnly

{
case 'o':
case 'O':
{
Copy link
Member

Choose a reason for hiding this comment

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

Ditto

@truegoodwill
Copy link

truegoodwill commented Apr 17, 2021

https://github.com/FastFinTech/FFT.TimeStamps

I pasted this link in case anyone's interested in code I wrote for my personal workaround for some of the the issues solved by this PR, plus a few more.

It has a DateOnly object, named as a DateStamp, for example, and a MonthStamp object as well. (Those objects are not used in any hotpaths, so instead of writing them from first principles, I wrapped DateTime objects with them, but then provided an interface that represents the intention of the objects.) It also has a TimeOfWeek object that represents ideas like "10am on Monday".

The main emphasis of this library has a couple of features that are not addressed by this PR:

  1. Comparison of times in multiple timezones is extremely efficient due to resolving all times back to a utc-based TimeStamp object. It's like converting everything in your code to DateTimeKind.Utc but without the ambiguity and without giving user code the opportunity to accidentally put or deserialize a DateTimeKind.Local time where DateTimeKind.Utc was expected.
  2. Conversion of timezones is a lot faster than the conversion utilities provided by TimeZoneInfo. See the TimeZoneCalculator class. Conversion iterators for sequential timestamps is even faster by magnitudes.

Whilst DateOnly and TimeOnly ideas do solve some problems and are very much needed in the framework, an object that is unquestionably UTC time and quickly/easily converted between timezones is also needed (at least, by me). The library linked above provides this. For example, the TimeStamp object can add an absolute amount of time, or you can pass in a TimeZoneInfo object to the method and it will add the given amount of time accounting for timezone offset changes in the given timezone.

Thanks for reading!

@mattjohnsonpint, updated to help out with the confused face :) Thanks to my .net heros for your amazing work!

@ghost ghost locked as resolved and limited conversation to collaborators May 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce Date and Time only structs