Skip to content

Address the TimeProvider community feedback #84274

Description

@tarekgh

#36617

Original writing by @stephentoub

  • Base implementation. We make all the abstract members instead be virtual. We’d then make their base implementations be the system implementations. This makes it easier to customize the default behavior, only needing to override the things you care about (it shouldn’t impact mocking at all one way or the other). We can still keep the type abstract, in order to make it clear that there’s zero benefit to instantiating it directly, but we could remove the abstractness if there’s a good reason to. If we do make it non-abstract, the System singleton would just be an instance of the base type. If we leave it abstract, the internal SystemTimeProvider simply becomes private sealed class SystemTimeProvider : TimeProvider { }, as all of its implementation is the base implementation.
    Delete FromLocalTimeZone. This is such a niche use case, it’s not worth cluttering up the surface area of the type for or breeding choice when someone does TimeProvider. in an IDE, e.g. I don’t want someone accidentally thinking they should be doing TimeProvider.FromLocalTimeZone(TimeZoneInfo.Local) or something like that. For someone who does have this scenario, once the system implementation is the base, they can achieve it with their own simple derived type:
    sealed class LocalTimeProvider(TimeZoneInfo local) : TimeProvider
sealed class LocalTimeProvider(TimeZoneInfo local) : TimeProvider
{
    public override LocalTimeZone => local;
}
  • Parameterless ctor. Delete the existing (int) constructor and just make it parameterless, and either a) make TimestampFrequency virtual or b) add a protected virtual TimestampFrequencyCore (where TimestampFrequency would then invoke TimestampFrequencyCore once and store the result). We ended up with the ctor design so as to help ensure that frequency is guaranteed to be a constant, since we don’t envision or want to support a system where frequency changes (it would make it impossible to understand timestamps if that’s possible). We can achieve the same thing with FrequencyCore, or we can just state that an override should return the same value every time and leave it at that. And with the base implementation being the system implementation, the need for someone to override this should be practically never other than if they want to test their system’s correct handling of manual use of frequency values. As we cited as a possible issue when we were discussing the ctor, having no parameterless ctor does make mocking a tad bit more complicated, and as that's a primary scenario, removing any road blocks there is a good thing.
  • Methods. I could go either way on this one, whether keeping UtcNow and LocalNow properties or making them GetUtcNow() and GetLocalNow(). I lean towards methods, for consistency within the class itself with GetTimeStamp(), because if we were designing everything from scratch they would be methods, because we do expect their values to change automatically between some invocations, etc.
    What’s currently checked in is:

Change Proposal

namespace System
{
    public abstract class TimeProvider
    {
        public static TimeProvider System { get; }
        
+      protected TimeProvider();

-      public abstract DateTimeOffset UtcNow { get; }
+      public virtual DateTimeOffset GetUtcNow();

-      public DateTimeOffset LocalNow { get; }
+      public DateTimeOffset GetLocalNow();

-      public abstract TimeZoneInfo LocalTimeZone { get; }
+      public virtual TimeZoneInfo LocalTimeZone { get; }

-      public abstract long GetTimestamp();
+      public virtual long GetTimestamp();

-      protected TimeProvider(long timestampFrequency);
-      public static TimeProvider FromLocalTimeZone(TimeZoneInfo timeZone);

-      public long TimestampFrequency { get; }
+      public virtual long TimestampFrequency { get; } // or `public long TimestampFrequency { get; }` and `protected virtual long TimestampFrequencyCore { get; }`

-      public abstract ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period);
+      public virtual ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period);

       public TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp);
    }
}

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.DateTimeblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions