-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Add Microseconds and Nanoseconds to TimeStamp, DateTime, DateTimeOffset, and TimeOnly #23799
Comments
Is the common use case really to have separate values for seconds, milliseconds, microseconds and nanoseconds? Wouldn't it be better to instead expose a floating point number that's the number of seconds, including the fractional part? I realize this would be inconsistent (seconds and milliseconds are already separate), but I think usefulness beats consistency. Though some of the proposed members (like (Also, I'm not sure what exactly would be the right API to expose that.) |
If the caller has the values of seconds, milliseconds, microseconds, nanoseconds, would need to do some calculations to convert these values into the second fraction to be able to call the API. I think the request here is to help users not do the calculations themselves. users can achieve the result today by calculating the ticks from the second fractions. so I am seeing the proposed APIs just a helper methods. |
@ChristopherHaws what is the need for the To/From Unix APIs? could you please clarify how these exactly get used? |
@tarekgh Correct, there is no functional changes. It is just additional properties and methods for making it easier to work with microseconds and nanoseconds, which already exists in the I wasnt sure if unix has nanoseconds, but they do have the concept of microseconds, which is stored as a 64-bit unsigned integer. Here is a thread for the GO language where they are discussing use cases: golang/go#18935 (comment). One of the example mentioned (golang/go#18935 (comment)) is that there are many system calls in unix that now support microseconds and the standard protobuf timestamp uses nanoseconds. Specificly |
I get that. My question is: how often does that happen? As an example, what's more likely, that the input is "3 minutes 14 seconds 159 milliseconds" or "3 minutes 14.159 seconds"? I think it's the latter and the current API does not handle that case well and the API proposed here does not improve it much either. |
@svick I think that both ways are valid for different reasons. The way I proposed in the issue are simply extending the existing API's standard. If namespace System {
public struct DateTime {
public double GetPart(DateTimePart.Seconds, DateTimePartOptions.IncludeHigherPrecision);
}
} |
please let me know what you think. |
Um, but what about #23747 ? |
@Clockwork-Muse for #23747, I am seeing a lot of people is using new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) this why #23747 would have a value. but for the proposed Unix APIs here, I am not seeing many people using it. |
Removed from Proposal namespace System {
public struct DateTime {
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, int nanoseconds);
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, int nanoseconds, System.DateTimeKind kind);
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, int nanoseconds, System.Globalization.Calendar calendar);
public System.DateTime AddNanoseconds(int nanoseconds);
}
public struct DateTimeOffset {
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, int nanoseconds, System.TimeSpan offset);
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, int nanoseconds, System.TimeSpan offset, System.Globalization.Calendar calendar);
public static System.DateTimeOffset FromUnixTimeMicroseconds(long microseconds);
public static System.DateTimeOffset FromUnixTimeNanoseconds(long nanoseconds);
public System.DateTimeOffset AddNanoseconds(int nanoseconds);
public static long ToUnixTimeMicroseconds();
public static long ToUnixTimeNanoseconds();
}
public struct TimeSpan {
public static System.TimeSpan FromNanoseconds(double value);
public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds, int microseconds, int nanoseconds);
}
} |
@ChristopherHaws thanks for your updates. I'll try to look more get back to you if I have any other comment. could you please move the allowed values of the millisecond, Microseconds...etc. to the proposal, section to be clear about how the APIs will work and when we throw? |
@tarekgh Added in the form of xml docs to the proposal. Thanks! |
@iamcarbon I am seeing your reaction to the proposal. do you have any specific feedback to understand why you don't like the proposal? Thanks. |
@tarekgh DateTime is only accurate to 100 nanoseconds. It's confusing to have a property that cannot represent its full range. |
I am not sure I fully understand this but DateTime has Ticks property that can reflect the ticks (in hundreds of nanoseconds). Also, it has MinValue and MaxValue properties which can tell the min and max of the supported range. if you are referring to a specific property in the proposal, please let me know so we can evaluate your feedback. |
@tarekgh I believe he is referring to the fact that the I personally don't have any issue with this, as the alternative is that anyone who needs to know the amount of nanoseconds would have to calculate it from the ticks, but it should definitely be pointed out in the documentation. IMO, since |
This is a limitation of DateTime. even if we were supporting nanoseconds we could get cases asking for femtoseconds :-) At any case, we have to have a minimum unit we can support considering we want to keep DateTime size not exceeding long size for perf reasons. |
@tarekgh I agree. I think he was just pointing out that the signature states that it returns an int between 0 and 900, but the values are not contiguous which might confuse some people that don't realize that DateTime's precision is 100ns. I think asking users to convert ticks manually is much more confusing and potentially error prone. |
@ChristopherHaws of course a hypothetical future runtime/platform could support a finer resolution with the same API, and I don't believe we would consider that a breaking change. |
@danmosemsft True. That's another good reason for these helper methods/properties. If someone is making an assumption about the resolution of ticks, their code would break if that resolution changed from underneath them. With these helper methods, consumers can be sure that |
@joperezr this proposal looks good to me. if you agree, please mark the issue as ready for design review. Thanks. |
Be very careful with this. Exposing micros and nanos directly can easily lead to overflow exceptions or floating point errors. For example, (long) (TimeSpan.MaxValue.TotalSeconds * 1000 * 1000 * 1000) // -9223372036854775808 There's also a loss of precision with such extreme floating point values - which is counter-intuitive when working with APIs that promise nanoseconds. I advise caution. We don't want people to think |
@mj1856 Agreed. I was thinking about this as well. From what I can see, TotalMicroseconds shouldn't have any issue, but TotalNanoseconds could have issues when you start approaching |
@tarekgh Do you mean |
@ChristopherHaws ignore my comment. I was looking at different place :-( |
@mj1856 I was wrong, there is a potential for loss of precision with void Main()
{
TimeSpan.MaxValue.TotalMicroseconds(); // 9.22337203685478E+17
TimeSpan.MaxValue.TotalMicrosecondsAsDecimal(); // 922337203685477580.7
TimeSpan.MaxValue.TotalNanoseconds(); // 9.22337203685478E+20
TimeSpan.MaxValue.TotalNanosecondsAsDecimal(); // 922337203685477580700
}
public static class TimeSpanEx
{
public const Int64 TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;
public const Double TicksPerNanosecond = TicksPerMicrosecond / 1000d;
public static Double TotalMicroseconds(this TimeSpan self) => self.Ticks * (1d / TicksPerMicrosecond);
public static Decimal TotalMicrosecondsAsDecimal(this TimeSpan self) => self.Ticks * (1m / TicksPerMicrosecond);
public static Double TotalNanoseconds(this TimeSpan self) => self.Ticks * (1d / TicksPerNanosecond);
public static Decimal TotalNanosecondsAsDecimal(this TimeSpan self) => self.Ticks * (1m / (Decimal)TicksPerNanosecond);
} |
Well, the fact is that all three types in question already have their range locked in, and over that range one cannot represent nanoseconds in 64 bits without losing some of them. Floating point type allow for loss of precision, but one needs I'm not saying don't do this, I'm just saying check that the APIs can handle everything at all boundaries, especially with nanoseconds. |
Given the above concerns about overflow and precision, let's discuss a bit more on what would the right return type should be. |
My take here:
|
@tarekgh Here are the results using G17 formatting: void Main()
{
TimeSpan.MaxValue.TotalMicroseconds().ToString("G17", CultureInfo.InvariantCulture); // 9.2233720368547763E+17
TimeSpan.MaxValue.TotalMicrosecondsAsDecimal().ToString("G17", CultureInfo.InvariantCulture); // 9.2233720368547758E+17
TimeSpan.MaxValue.TotalNanoseconds().ToString("G17", CultureInfo.InvariantCulture); // 9.2233720368547758E+20
TimeSpan.MaxValue.TotalNanosecondsAsDecimal().ToString("G17", CultureInfo.InvariantCulture); // 9.2233720368547758E+20
}
public static class TimeSpanEx
{
public const Int64 TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000;
public const Double TicksPerNanosecond = TicksPerMicrosecond / 1000d;
public static Double TotalMicroseconds(this TimeSpan self) => self.Ticks * (1d / TicksPerMicrosecond);
public static Decimal TotalMicrosecondsAsDecimal(this TimeSpan self) => self.Ticks * (1m / TicksPerMicrosecond);
public static Double TotalNanoseconds(this TimeSpan self) => self.Ticks * (1d / TicksPerNanosecond);
public static Decimal TotalNanosecondsAsDecimal(this TimeSpan self) => self.Ticks * (1m / (Decimal)TicksPerNanosecond);
} |
@billknye good catch. I'll follow up to change the parameter name to microseconds. I'll need to review all Add methods in all types to ensure we are consistent at least inside the same type.
We appreciate that. Do you think you can get this soon? I am asking because I would like to include these APIs in our next preview to have a chance of getting early feedback. |
Also noting that users shouldn't even really try to rely on |
It's not about precision of the hardware but precision of the data we get from the system. To be honest I just get the time in nanosec from firebase database. Which manage their timestamp data in that precision Arguably firebase also not really that precise on their time, they also always convert their |
I think this week should still be feasible. |
.NET has a GC and likely isn't a good choice for things that require nanosecond level accuracy in direct measurement. A GC pause could end up causing all sorts of issues. Processing data with nanosecond level precision is different, but likewise needs special considerations (including in overflow) If you're requiring nanosecond level precision, you are likely working on incredibly specialized hardware and without the overhead of a proper OS. There is little need to go and invent something new and break existing code/assumptions (and it is breaking since |
As I said it didn't meant it need to do something in realtime nanosec. But I mean the data that the app will process might have come with nanosec precision. The hardware might be anything in anywhere. It just that the data was recorded and come to us in that format. Such as public research data provided from scientist and we just get it as SQL record Also I don't suggest that we need to change any existing code to support nanosec. I mean maybe we should add |
It would need to be an independent proposal. My own view is that its not worth exposing such domain specific functionality and that's better relegated to a community provide NuGet package or domain specific code solution. You should feel free to open an API proposal and note that others may have their own view/opinion. |
The Calendar-constructors using This is based on an abstract method which is Public API. With the current API, our only option is to ignore the I would propose: public virtual DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, int era); See runtime/src/libraries/System.Private.CoreLib/src/System/Globalization/Calendar.cs Line 616 in 16b2cf5
|
Let's not block on that. We can just use |
@bartonjs The other public struct DateTimeOffset : IComparable, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>, IFormattable
{
public DateTimeOffset AddMilliseconds(double milliseconds);
} |
@ChristopherHaws this is already noted here #23799 (comment) and I am already following up. Let's go with |
@tarekgh Perfect. Would you like me to update my original proposal with the changes? Thanks! |
Sure, I appreciate that. Keeping the old one is good too for the record. I mean you can have 2 sections, one titled with |
@tarekgh Also, in the review meeting, you talked about the overflow exception on public struct TimeSpan {
/// <returns>The total number of nanoseconds represented by this instance.</returns>
/// <exception cref="OverflowException">When value starts to approach TimeSpan.MinValue or TimeSpan.MaxValue.</exception>
public double TotalNanoseconds { get; }
} BTW, thanks for all your work on this issue. It's been 5 years in the making! I'm excited to get my hands on this in the next preview (hopefully) :) |
Yes, please remove it. We'll have the triple slashes docs on the new APIs reviewed to match the final behavior.
Thanks to you too and all community members contributed to this issue. I am excited too to see these new APIs in the product. |
I have basically implemented the proposal. Only a few small things like tests and the reference assembly are missing. These will come tomorrow. But if I may give you a little foretaste: My branch |
@deeprobin cool! I'll wait for the official PR. Take your time for adding the tests and doing any polishing there. Thanks for your help! |
@deeprobin I added 2 comments to the commit I could see based on a very quick look through. I too will wait for the PR before doing an in depth review :) Thanks! |
@deeprobin thanks for your comment dotnet/core#7378 (comment), that issue intended to list the preview 4 release notes and not for discussions. Could you move this comment here instead? |
@tarekgh done 👍🏼
|
Currently, the value of a single
Tick
in .NET is 100ns, however there is no API surface for accessing microseconds or nanoseconds other than usingTicks
to manually calculate these numbers. In order to allow for working with these higher precision times, I am proposing the following new APIs:Approved API
Original API Proposal
Example Usage
Notes
TimeSpan.TotalMicroseconds
andTimeSpan.TotalNanoseconds
return double still since that is what the otherTotal
properties return. This could be long but I kept it as double for consistency.TimeSpan.FromMicroseconds
takes a double to keep the API consistent with the otherFrom
methods.The text was updated successfully, but these errors were encountered: