Repository navigation
Add Microseconds and Nanoseconds to TimeStamp, DateTime, DateTimeOffset, and TimeOnly #23799
Copy link
Copy link
Closed
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.DateTime
Milestone
Description
Currently, the value of a single Tick in .NET is 100ns, however there is no API surface for accessing microseconds or nanoseconds other than using Ticks 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
namespace System {
public struct DateTime {
/// <exception cref="ArgumentOutOfRangeException">When microsecond is not between 0 and 999.</exception>
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);
/// <exception cref="ArgumentOutOfRangeException">When microsecond is not between 0 and 999.</exception>
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.DateTimeKind kind);
/// <exception cref="ArgumentOutOfRangeException">When microsecond is not between 0 and 999.</exception>
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.Globalization.Calendar calendar);
/// <returns>The microseconds component, expressed as a value between 0 and 999.</returns>
public int Microsecond { get; }
/// <returns>The nanoseconds component, expressed as a value between 0 and 900.</returns>
public int Nanosecond { get; }
/// <exception cref="ArgumentOutOfRangeException">The resulting DateTime is less than DateTime.MinValue or greater than DateTime.MaxValue.</exception>
public DateTime AddMicroseconds(double value);
}
public struct DateTimeOffset {
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.TimeSpan offset);
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, System.TimeSpan offset, System.Globalization.Calendar calendar);
/// <returns>The microseconds component, expressed as a value between 0 and 999.</returns>
public int Microsecond { get; }
/// <returns>The nanoseconds component, expressed as a value between 0 and 900.</returns>
public int Nanosecond { get; }
/// <exception cref="ArgumentOutOfRangeException">The resulting DateTimeOffset is less than DateTimeOffset.MinValue or greater than DateTimeOffset.MaxValue.</exception>
public DateTimeOffset AddMicroseconds(double value);
}
public struct TimeSpan {
public const long TicksPerMicrosecond = 10L;
public const long NanosecondsPerTick = 100L;
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds, int microseconds);
/// <returns>The microseconds component, expressed as a value between 0 and 999.</returns>
public int Microseconds { get; }
/// <returns>The nanoseconds component, expressed as a value between 0 and 900.</returns>
public int Nanoseconds { get; }
/// <returns>The total number of microseconds represented by this instance.</returns>
public double TotalMicroseconds { get; }
/// <returns>The total number of nanoseconds represented by this instance.</returns>
public double TotalNanoseconds { get; }
/// <exception cref="OverflowException">When value is less than TimeSpan.MinValue or greater than TimeSpan.MaxValue.</exception>
public static TimeSpan FromMicroseconds(double microseconds);
}
public struct TimeOnly {
/// <param name="microsecond">The microsecond (0 through 999).</param>
/// <exception cref="ArgumentOutOfRangeException">When microsecond is not between 0 and 999.</exception>
public TimeOnly(int day, int hour, int minute, int second, int millisecond, int microsecond);
/// <returns>The microsecond component of the time represented by this instant, expressed as a value between 0 and 999.</returns>
public int Microsecond { get; }
/// <returns>The nanosecond component of the time represented by this instant, expressed as a value between 0 and 900.</returns>
public int Nanosecond { get; }
}
}Original API Proposal
namespace System {
public struct DateTime {
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds);
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, System.DateTimeKind kind);
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, System.Globalization.Calendar calendar);
/// <returns>The microseconds component, expressed as a value between 0 and 999.</returns>
public int Microsecond { get; }
/// <returns>The nanoseconds component, expressed as a value between 0 and 900.</returns>
public int Nanosecond { get; }
/// <exception cref="ArgumentOutOfRangeException">The resulting DateTime is less than DateTime.MinValue or greater than DateTime.MaxValue.</exception>
public DateTime AddMicroseconds(int microseconds);
}
public struct DateTimeOffset {
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, System.TimeSpan offset);
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds, System.TimeSpan offset, System.Globalization.Calendar calendar);
/// <returns>The microseconds component, expressed as a value between 0 and 999.</returns>
public int Microsecond { get; }
/// <returns>The nanoseconds component, expressed as a value between 0 and 900.</returns>
public int Nanosecond { get; }
/// <exception cref="ArgumentOutOfRangeException">The resulting DateTimeOffset is less than DateTimeOffset.MinValue or greater than DateTimeOffset.MaxValue.</exception>
public DateTimeOffset AddMicroseconds(int microseconds);
}
public struct TimeSpan {
public const long TicksPerMicrosecond = 10L;
public const double TicksPerNanosecond = 0.01D;
/// <exception cref="ArgumentOutOfRangeException">When microseconds is not between 0 and 999.</exception>
public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds, int microseconds);
/// <returns>The microseconds component, expressed as a value between 0 and 999.</returns>
public int Microseconds { get; }
/// <returns>The nanoseconds component, expressed as a value between 0 and 900.</returns>
public int Nanoseconds { get; }
/// <returns>The total number of microseconds represented by this instance.</returns>
public double TotalMicroseconds { get; }
/// <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; }
/// <exception cref="OverflowException">When value is less than TimeSpan.MinValue or greater than TimeSpan.MaxValue.</exception>
public static TimeSpan FromMicroseconds(double value);
}
public struct TimeOnly {
/// <param name="microsecond">The microsecond (0 through 999).</param>
/// <exception cref="ArgumentOutOfRangeException">When microsecond is not between 0 and 999.</exception>
public TimeOnly(int day, int hour, int minute, int second, int millisecond, int microsecond);
/// <returns>The microsecond component of the time represented by this instant, expressed as a value between 0 and 999.</returns>
public int Microsecond { get; }
/// <returns>The nanosecond component of the time represented by this instant, expressed as a value between 0 and 900.</returns>
public int Nanosecond { get; }
}
}Example Usage
public class Samples
{
public void DateTimeSamples()
{
new DateTime(0001, 01, 01, 00, 00, 00, 00, 999).Ticks; // 9990
DateTime.Parse("0001-01-01 00:00:00.0009990").Microsecond; // 999
DateTime.Parse("0001-01-01 00:00:00.0000009").Nanosecond; // 900
DateTime.Zero.AddMicroseconds(999).Ticks; // 9990
}
public void DateTimeOffsetSamples()
{
new DateTimeOffset(0001, 01, 01, 00, 00, 00, 00, 999, TimeSpan.FromHours(-7)).Ticks; // 9990
DateTimeOffset.Parse("0001-01-01 00:00:00.0009990 -7").Microsecond; // 999
DateTimeOffset.Parse("0001-01-01 00:00:00.0000009 -7").Nanosecond; // 900
new DateTimeOffset().AddMicroseconds(999).Ticks; // 9990
}
public void TimeSpanSamples()
{
new TimeSpan(999 * TimeSpan.TicksPerMicrosecond).Ticks; // 9990
new TimeSpan((long)(900 * TimeSpan.TicksPerNanosecond)).Ticks; // 9
new TimeSpan(0001, 01, 01, 00, 00, 00, 00, 999).Ticks; // 9990
TimeSpan.Parse("0001-01-01 00:00:00.0009990").Microsecond; // 999
TimeSpan.Parse("0001-01-01 00:00:00.0000009").Nanosecond; // 900
TimeSpan.Zero.AddMicroseconds(999).Ticks; // 9990
}
}Notes
TimeSpan.TotalMicrosecondsandTimeSpan.TotalNanosecondsreturn double still since that is what the otherTotalproperties return. This could be long but I kept it as double for consistency.TimeSpan.FromMicrosecondstakes a double to keep the API consistent with the otherFrommethods.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.DateTime
Activity
svick commentedon Oct 10, 2017
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
AddMicrosecondsorTotalMicroseconds) would still make sense.(Also, I'm not sure what exactly would be the right API to expose that.)
tarekgh commentedon Oct 10, 2017
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.
tarekgh commentedon Oct 10, 2017
@ChristopherHaws what is the need for the To/From Unix APIs? could you please clarify how these exactly get used?
ChristopherHaws commentedon Oct 10, 2017
@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
Ticks. Here is a recent PR where I made some helper methods for doing just this: https://github.com/fluentassertions/fluentassertions/pull/669/filesI 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
TimeSpanConversionExtensions.csandFluentDateTimeExtensions.cssvick commentedon Oct 11, 2017
@tarekgh
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.
ChristopherHaws commentedon Oct 11, 2017
@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
DateTime.Secondshad been a method instead, I would say there should be an overload that allows you to decide if it includes the higher precision values or not. I cant think of a good name, but something like might work better for what you are talking about:tarekgh commentedon Oct 11, 2017
@ChristopherHaws
please let me know what you think.
Clockwork-Muse commentedon Oct 11, 2017
Um, but what about #23747 ?
tarekgh commentedon Oct 11, 2017
@Clockwork-Muse for #23747, I am seeing a lot of people is using
this why #23747 would have a value. but for the proposed Unix APIs here, I am not seeing many people using it.
ChristopherHaws commentedon Oct 11, 2017
@tarekgh
Nanoseconds { get; }but removeFromNanoseconds(double value);and the constructors containing nanoseconds. I would be ok with this, but I still don't like that as a consumer ofDateTime, if I want to add nanoseconds, I need to know how to convert between ticks and nanoseconds. For this reason, I would recommend we keep theTicksPerNanosecondconst.DateTime.MicrosecondandDateTime.Nanosecondreturn int now since the values will always be between 0 and 999.DateTime.AddMicrosecondstakes an int now since the value must be between 0 and 999.DateTimeOffset.MicrosecondandDateTimeOffset.Nanosecondreturn int now since the values will always be between 0 and 999.DateTimeOffset.AddMicrosecondstakes an int now since the value must be between 0 and 999.TimeSpan.MicrosecondsandTimeSpan.Nanosecondsreturn int now since the values will always be between 0 and 999.TimeSpan.TotalMicrosecondsandTimeSpan.TotalNanosecondsreturn double still since that is what the other total properties return. This could be long but I kept it as double for consistency. Let me know if I should change this.TimeSpan.FromMicrosecondstakes a double still to keep the api consistent with the otherFrommethods.TimeSpan.TicksPerNanosecondis a double since it is equal to0.01d.Removed from Proposal
tarekgh commentedon Oct 11, 2017
@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?
ChristopherHaws commentedon Oct 11, 2017
@tarekgh Added in the form of xml docs to the proposal. Thanks!
tarekgh commentedon Oct 11, 2017
@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.
iamcarbon commentedon Oct 11, 2017
@tarekgh DateTime is only accurate to 100 nanoseconds. It's confusing to have a property that cannot represent its full range.
tarekgh commentedon Oct 11, 2017
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.
74 remaining items
ChristopherHaws commentedon Apr 5, 2022
@tarekgh Perfect. Would you like me to update my original proposal with the changes? Thanks!
tarekgh commentedon Apr 5, 2022
Sure, I appreciate that. Keeping the old one is good too for the record. I mean you can have 2 sections, one titled with
Approved Proposaland the second titled withOriginal Proposal.ChristopherHaws commentedon Apr 5, 2022
@tarekgh Also, in the review meeting, you talked about the overflow exception on
TotalNanosecondsbeing wrong. Should this exception be removed as well?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) :)
tarekgh commentedon Apr 5, 2022
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.
deeprobin commentedon Apr 5, 2022
@tarekgh
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
tarekgh commentedon Apr 5, 2022
@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!
ChristopherHaws commentedon Apr 5, 2022
@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!
tarekgh commentedon Apr 14, 2022
@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?
deeprobin commentedon Apr 14, 2022
@tarekgh done 👍🏼