diff --git a/src/libraries/Common/src/System/ITimer.cs b/src/libraries/Common/src/System/Threading/ITimer.cs
similarity index 100%
rename from src/libraries/Common/src/System/ITimer.cs
rename to src/libraries/Common/src/System/Threading/ITimer.cs
diff --git a/src/libraries/Common/src/System/TimeProvider.cs b/src/libraries/Common/src/System/TimeProvider.cs
index 316cde1dc3768..b8d7fb9fa6aa2 100644
--- a/src/libraries/Common/src/System/TimeProvider.cs
+++ b/src/libraries/Common/src/System/TimeProvider.cs
@@ -29,7 +29,15 @@ public abstract class TimeProvider
/// Frequency of the values returned from method.
protected TimeProvider(long timestampFrequency)
{
+#if SYSTEM_PRIVATE_CORELIB
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(timestampFrequency);
+#else
+ if (timestampFrequency <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(timestampFrequency), timestampFrequency, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeNonNegativeNonZero, nameof(timestampFrequency)));
+ }
+#endif // SYSTEM_PRIVATE_CORELIB
+
TimestampFrequency = timestampFrequency;
_timeToTicksRatio = (double)TimeSpan.TicksPerSecond / TimestampFrequency;
}
@@ -41,6 +49,9 @@ protected TimeProvider(long timestampFrequency)
///
public abstract DateTimeOffset UtcNow { get; }
+ private static readonly long s_minDateTicks = DateTime.MinValue.Ticks;
+ private static readonly long s_maxDateTicks = DateTime.MaxValue.Ticks;
+
///
/// Gets a value that is set to the current date and time according to this 's
/// notion of time based on , with the offset set to the 's offset from Coordinated Universal Time (UTC).
@@ -53,9 +64,9 @@ public DateTimeOffset LocalNow
TimeSpan offset = LocalTimeZone.GetUtcOffset(utcDateTime);
long localTicks = utcDateTime.Ticks + offset.Ticks;
- if ((ulong)localTicks > DateTime.MaxTicks)
+ if ((ulong)localTicks > (ulong)s_maxDateTicks)
{
- localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks;
+ localTicks = localTicks < s_minDateTicks ? s_minDateTicks : s_maxDateTicks;
}
return new DateTimeOffset(localTicks, offset);
@@ -82,7 +93,15 @@ public DateTimeOffset LocalNow
/// is null.
public static TimeProvider FromLocalTimeZone(TimeZoneInfo timeZone)
{
+#if SYSTEM_PRIVATE_CORELIB
ArgumentNullException.ThrowIfNull(timeZone);
+#else
+ if (timeZone is null)
+ {
+ throw new ArgumentNullException(nameof(timeZone));
+ }
+#endif // SYSTEM_PRIVATE_CORELIB
+
return new SystemTimeProvider(timeZone);
}
@@ -155,7 +174,15 @@ private sealed class SystemTimeProvider : TimeProvider
///
public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period)
{
+#if SYSTEM_PRIVATE_CORELIB
ArgumentNullException.ThrowIfNull(callback);
+#else
+ if (callback is null)
+ {
+ throw new ArgumentNullException(nameof(callback));
+ }
+#endif // SYSTEM_PRIVATE_CORELIB
+
return new SystemTimeProviderTimer(dueTime, period, callback, state);
}
@@ -165,7 +192,7 @@ public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSp
///
public override DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
- /// Thin wrapper for a .
+ /// Thin wrapper for a .
///
/// We don't return a TimerQueueTimer directly as it implements IThreadPoolWorkItem and we don't
/// want it exposed in a way that user code could directly queue the timer to the thread pool.
@@ -174,33 +201,85 @@ public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSp
///
private sealed class SystemTimeProviderTimer : ITimer
{
+#if SYSTEM_PRIVATE_CORELIB
private readonly TimerQueueTimer _timer;
-
+#else
+ private readonly Timer _timer;
+#endif // SYSTEM_PRIVATE_CORELIB
public SystemTimeProviderTimer(TimeSpan dueTime, TimeSpan period, TimerCallback callback, object? state)
{
(uint duration, uint periodTime) = CheckAndGetValues(dueTime, period);
+#if SYSTEM_PRIVATE_CORELIB
_timer = new TimerQueueTimer(callback, state, duration, periodTime, flowExecutionContext: true);
+#else
+ // We want to ensure the timer we create will be tracked as long as it is scheduled.
+ // To do that, we call the constructor which track only the callback which will make the time to be tracked by the scheduler
+ // then we call Change on the timer to set the desired duration and period.
+ _timer = new Timer(_ => callback(state));
+ _timer.Change(duration, periodTime);
+#endif // SYSTEM_PRIVATE_CORELIB
}
public bool Change(TimeSpan dueTime, TimeSpan period)
{
(uint duration, uint periodTime) = CheckAndGetValues(dueTime, period);
- return _timer.Change(duration, periodTime);
+ try
+ {
+ return _timer.Change(duration, periodTime);
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
}
public void Dispose() => _timer.Dispose();
+
+#if SYSTEM_PRIVATE_CORELIB
public ValueTask DisposeAsync() => _timer.DisposeAsync();
+#else
+ public ValueTask DisposeAsync()
+ {
+ _timer.Dispose();
+ return default;
+ }
+#endif // SYSTEM_PRIVATE_CORELIB
private static (uint duration, uint periodTime) CheckAndGetValues(TimeSpan dueTime, TimeSpan periodTime)
{
long dueTm = (long)dueTime.TotalMilliseconds;
+ long periodTm = (long)periodTime.TotalMilliseconds;
+
+#if SYSTEM_PRIVATE_CORELIB
ArgumentOutOfRangeException.ThrowIfLessThan(dueTm, -1, nameof(dueTime));
ArgumentOutOfRangeException.ThrowIfGreaterThan(dueTm, Timer.MaxSupportedTimeout, nameof(dueTime));
- long periodTm = (long)periodTime.TotalMilliseconds;
ArgumentOutOfRangeException.ThrowIfLessThan(periodTm, -1, nameof(periodTime));
ArgumentOutOfRangeException.ThrowIfGreaterThan(periodTm, Timer.MaxSupportedTimeout, nameof(periodTime));
+#else
+ const uint MaxSupportedTimeout = 0xfffffffe;
+
+ if (dueTm < -1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dueTime), dueTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, nameof(dueTime), -1));
+ }
+
+ if (dueTm > MaxSupportedTimeout)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dueTime), dueTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeLessOrEqual, nameof(dueTime), MaxSupportedTimeout));
+ }
+
+ if (periodTm < -1)
+ {
+ throw new ArgumentOutOfRangeException(nameof(periodTm), periodTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeGreaterOrEqual, nameof(periodTm), -1));
+ }
+
+ if (periodTm > MaxSupportedTimeout)
+ {
+ throw new ArgumentOutOfRangeException(nameof(periodTm), periodTm, SR.Format(SR.ArgumentOutOfRange_Generic_MustBeLessOrEqual, nameof(periodTm), MaxSupportedTimeout));
+ }
+#endif // SYSTEM_PRIVATE_CORELIB
return ((uint)dueTm, (uint)periodTm);
}
diff --git a/src/libraries/Common/tests/Tests/System/TimeProviderTests.cs b/src/libraries/Common/tests/System/TimeProviderTests.cs
similarity index 71%
rename from src/libraries/Common/tests/Tests/System/TimeProviderTests.cs
rename to src/libraries/Common/tests/System/TimeProviderTests.cs
index 8b00756367d88..2f169512adabd 100644
--- a/src/libraries/Common/tests/Tests/System/TimeProviderTests.cs
+++ b/src/libraries/Common/tests/System/TimeProviderTests.cs
@@ -44,7 +44,11 @@ public void TestSystemProviderWithTimeZone()
{
Assert.Equal(TimeZoneInfo.Local.Id, TimeProvider.System.LocalTimeZone.Id);
+#if NETFRAMEWORK
+ TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
+#else
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(OperatingSystem.IsWindows() ? "Pacific Standard Time" : "America/Los_Angeles");
+#endif // NETFRAMEWORK
TimeProvider tp = TimeProvider.FromLocalTimeZone(tzi);
Assert.Equal(tzi.Id, tp.LocalTimeZone.Id);
@@ -57,6 +61,15 @@ public void TestSystemProviderWithTimeZone()
Assert.InRange(utcConvertedDto.Ticks, utcDto1.Ticks, utcDto2.Ticks);
}
+#if NETFRAMEWORK
+ public static double s_tickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+ public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) =>
+ new TimeSpan((long)((endingTimestamp - startingTimestamp) * s_tickFrequency));
+#else
+ public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) =>
+ Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp);
+#endif // NETFRAMEWORK
+
[Fact]
public void TestSystemTimestamp()
{
@@ -68,7 +81,7 @@ public void TestSystemTimestamp()
Assert.InRange(providerTimestamp1, timestamp1, timestamp2);
Assert.True(providerTimestamp2 > timestamp2);
- Assert.Equal(Stopwatch.GetElapsedTime(providerTimestamp1, providerTimestamp2), TimeProvider.System.GetElapsedTime(providerTimestamp1, providerTimestamp2));
+ Assert.Equal(GetElapsedTime(providerTimestamp1, providerTimestamp2), TimeProvider.System.GetElapsedTime(providerTimestamp1, providerTimestamp2));
Assert.Equal(Stopwatch.Frequency, TimeProvider.System.TimestampFrequency);
}
@@ -111,7 +124,7 @@ public void TestProviderTimer(TimeProvider provider, int MaxMilliseconds)
state,
TimeSpan.FromMilliseconds(state.Period), TimeSpan.FromMilliseconds(state.Period));
- state.TokenSource.Token.WaitHandle.WaitOne(30000);
+ state.TokenSource.Token.WaitHandle.WaitOne(60000);
state.TokenSource.Dispose();
Assert.Equal(4, state.Counter);
@@ -151,6 +164,32 @@ public static IEnumerable