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

Add new APIs for abstracting the system clock and local time zone. #48681

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,16 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerTypeProxyAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebuggerVisualizerAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebugProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\ActualSystemClock.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\StackFrame.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\StackFrameExtensions.cs" Condition="'$(TargetsCoreRT)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\StackTrace.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\StackTraceHiddenAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\SymbolStore\ISymbolDocumentWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\TimeClock.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\TimeContext.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\VirtualClock.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DivideByZeroException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DllNotFoundException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Double.cs" />
Expand Down Expand Up @@ -1621,6 +1625,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Win32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\ActualSystemClock.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebugProvider.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\RuntimeEventSourceHelper.Windows.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
Expand Down Expand Up @@ -1823,6 +1828,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\AppDomain.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Buffer.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\DateTime.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\ActualSystemClock.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\DebugProvider.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Stopwatch.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Diagnostics\Tracing\RuntimeEventSourceHelper.Unix.cs" Condition="'$(FeaturePerfTracing)' == 'true'" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ public readonly partial struct DateTime
{
internal const bool s_systemSupportsLeapSeconds = false;

public static DateTime UtcNow
{
get
{
return new DateTime(((ulong)(Interop.Sys.GetSystemTimeAsTicks() + UnixEpochTicks)) | KindUtc);
}
}

private static DateTime FromFileTimeLeapSecondsAware(ulong fileTime) => default;
private static ulong ToFileTimeLeapSecondsAware(long ticks) => default;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System
{
public readonly partial struct DateTime
{
internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds();

public static unsafe DateTime UtcNow
{
get
{
ulong fileTime;
s_pfnGetSystemTimeAsFileTime(&fileTime);

if (s_systemSupportsLeapSeconds)
{
Interop.Kernel32.SYSTEMTIME time;
ulong hundredNanoSecond;

if (Interop.Kernel32.FileTimeToSystemTime(&fileTime, &time) != Interop.BOOL.FALSE)
{
// to keep the time precision
ulong tmp = fileTime; // temp. variable avoids double read from memory
hundredNanoSecond = tmp % TicksPerMillisecond;
}
else
{
Interop.Kernel32.GetSystemTime(&time);
hundredNanoSecond = 0;
}

return CreateDateTimeFromSystemTime(in time, hundredNanoSecond);
}
else
{
return new DateTime(fileTime + FileTimeOffset | KindUtc);
}
}
}

internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, DateTimeKind kind)
{
Interop.Kernel32.SYSTEMTIME time;
Expand Down Expand Up @@ -80,7 +45,9 @@ private static unsafe DateTime FromFileTimeLeapSecondsAware(ulong fileTime)
{
throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_DateTimeBadTicks);
}
return CreateDateTimeFromSystemTime(in time, fileTime % TicksPerMillisecond);

ulong ticks = GetTicksFromSystemTime(in time, fileTime % TicksPerMillisecond);
return new DateTime(ticks | KindUtc);
}

private static unsafe ulong ToFileTimeLeapSecondsAware(long ticks)
Expand Down Expand Up @@ -110,7 +77,7 @@ private static unsafe ulong ToFileTimeLeapSecondsAware(long ticks)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static DateTime CreateDateTimeFromSystemTime(in Interop.Kernel32.SYSTEMTIME time, ulong hundredNanoSecond)
internal static ulong GetTicksFromSystemTime(in Interop.Kernel32.SYSTEMTIME time, ulong hundredNanoSecond)
{
uint year = time.Year;
uint[] days = IsLeapYear((int)year) ? s_daysToMonth366 : s_daysToMonth365;
Expand All @@ -124,52 +91,12 @@ private static DateTime CreateDateTimeFromSystemTime(in Interop.Kernel32.SYSTEMT
if (second <= 59)
{
ulong tmp = second * (uint)TicksPerSecond + time.Milliseconds * (uint)TicksPerMillisecond + hundredNanoSecond;
return new DateTime(ticks + tmp | KindUtc);
return ticks + tmp;
}

// we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation.
// we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds
ticks += TicksPerMinute - 1 | KindUtc;
return new DateTime(ticks);
}

private static unsafe readonly delegate* unmanaged[SuppressGCTransition]<ulong*, void> s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr();

private static unsafe delegate* unmanaged[SuppressGCTransition]<ulong*, void> GetGetSystemTimeAsFileTimeFnPtr()
{
IntPtr kernel32Lib = Interop.Kernel32.LoadLibraryEx(Interop.Libraries.Kernel32, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32);
Debug.Assert(kernel32Lib != IntPtr.Zero);

IntPtr pfnGetSystemTime = NativeLibrary.GetExport(kernel32Lib, "GetSystemTimeAsFileTime");

if (NativeLibrary.TryGetExport(kernel32Lib, "GetSystemTimePreciseAsFileTime", out IntPtr pfnGetSystemTimePrecise))
{
// GetSystemTimePreciseAsFileTime exists and we'd like to use it. However, on
// misconfigured systems, it's possible for the "precise" time to be inaccurate:
// https://github.com/dotnet/runtime/issues/9014
// If it's inaccurate, though, we expect it to be wildly inaccurate, so as a
// workaround/heuristic, we get both the "normal" and "precise" times, and as
// long as they're close, we use the precise one. This workaround can be removed
// when we better understand what's causing the drift and the issue is no longer
// a problem or can be better worked around on all targeted OSes.

// Retry this check several times to reduce chance of false negatives due to thread being rescheduled
// at wrong time.
for (int i = 0; i < 10; i++)
{
long systemTimeResult, preciseSystemTimeResult;
((delegate* unmanaged[SuppressGCTransition]<long*, void>)pfnGetSystemTime)(&systemTimeResult);
((delegate* unmanaged[SuppressGCTransition]<long*, void>)pfnGetSystemTimePrecise)(&preciseSystemTimeResult);

if (Math.Abs(preciseSystemTimeResult - systemTimeResult) <= 100 * TicksPerMillisecond)
{
pfnGetSystemTime = pfnGetSystemTimePrecise; // use the precise version
break;
}
}
}

return (delegate* unmanaged[SuppressGCTransition]<ulong*, void>)pfnGetSystemTime;
return ticks + TicksPerMinute - 1;
}
}
}
12 changes: 7 additions & 5 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ namespace System
private const long MaxMillis = (long)DaysTo10000 * MillisPerDay;

internal const long UnixEpochTicks = DaysTo1970 * TicksPerDay;
private const long FileTimeOffset = DaysTo1601 * TicksPerDay;
internal const long FileTimeOffset = DaysTo1601 * TicksPerDay;
private const long DoubleDateOffset = DaysTo1899 * TicksPerDay;
// The minimum OA date is 0100/01/01 (Note it's year 100).
// The maximum OA date is 9999/12/31
Expand Down Expand Up @@ -112,7 +112,7 @@ namespace System
private const ulong FlagsMask = 0xC000000000000000;
private const long TicksCeiling = 0x4000000000000000;
private const ulong KindUnspecified = 0x0000000000000000;
private const ulong KindUtc = 0x4000000000000000;
internal const ulong KindUtc = 0x4000000000000000;
private const ulong KindLocal = 0x8000000000000000;
private const ulong KindLocalAmbiguousDst = 0xC000000000000000;
private const int KindShift = 62;
Expand Down Expand Up @@ -140,7 +140,7 @@ public DateTime(long ticks)
_dateData = (ulong)ticks;
}

private DateTime(ulong dateData)
internal DateTime(ulong dateData)
{
this._dateData = dateData;
}
Expand Down Expand Up @@ -743,7 +743,7 @@ public static DateTime FromFileTimeUtc(long fileTime)
throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid);
}

#pragma warning disable 162 // Unrechable code on Unix
#pragma warning disable 162 // Unreachable code on Unix
if (s_systemSupportsLeapSeconds)
{
return FromFileTimeLeapSecondsAware((ulong)fileTime);
Expand Down Expand Up @@ -1057,6 +1057,8 @@ public static DateTime Now
//
public static DateTime Today => Now.Date;

public static DateTime UtcNow => TimeContext.Current.Clock.GetCurrentUtcDateTime();

// Returns the year part of this DateTime. The returned value is an
// integer between 1 and 9999.
//
Expand Down Expand Up @@ -1203,7 +1205,7 @@ public long ToFileTimeUtc()
// Treats the input as universal if it is not specified
long ticks = ((_dateData & KindLocal) != 0) ? ToUniversalTime().Ticks : Ticks;

#pragma warning disable 162 // Unrechable code on Unix
#pragma warning disable 162 // Unreachable code on Unix
if (s_systemSupportsLeapSeconds)
{
return (long)ToFileTimeLeapSecondsAware(ticks);
Expand Down
15 changes: 2 additions & 13 deletions src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace System

// Constructors

private DateTimeOffset(short validOffsetMinutes, DateTime validDateTime)
internal DateTimeOffset(short validOffsetMinutes, DateTime validDateTime)
{
_dateTime = validDateTime;
_offsetMinutes = validOffsetMinutes;
Expand Down Expand Up @@ -176,18 +176,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se
// resolution of the returned value depends on the system timer.
public static DateTimeOffset Now => ToLocalTime(DateTime.UtcNow, true);

public static DateTimeOffset UtcNow
{
get
{
DateTime utcNow = DateTime.UtcNow;
var result = new DateTimeOffset(0, utcNow);

Debug.Assert(new DateTimeOffset(utcNow) == result); // ensure lack of verification does not break anything

return result;
}
}
public static DateTimeOffset UtcNow => TimeContext.Current.Clock.GetCurrentUtcDateTimeOffset();

public DateTime DateTime => ClockDateTime;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Diagnostics
{
public partial class ActualSystemClock
{
private static unsafe ulong GetTicks() => (ulong)(Interop.Sys.GetSystemTimeAsTicks() + DateTime.UnixEpochTicks);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;

namespace System.Diagnostics
{
public partial class ActualSystemClock
{
private static unsafe ulong GetTicks()
{
ulong fileTime;
s_pfnGetSystemTimeAsFileTime(&fileTime);

if (!DateTime.s_systemSupportsLeapSeconds)
{
return fileTime + DateTime.FileTimeOffset;
}

Interop.Kernel32.SYSTEMTIME time;
ulong hundredNanoSecond;

if (Interop.Kernel32.FileTimeToSystemTime(&fileTime, &time) != Interop.BOOL.FALSE)
{
// to keep the time precision
ulong tmp = fileTime; // temp. variable avoids double read from memory
hundredNanoSecond = tmp % TimeSpan.TicksPerMillisecond;
}
else
{
Interop.Kernel32.GetSystemTime(&time);
hundredNanoSecond = 0;
}

return DateTime.GetTicksFromSystemTime(in time, hundredNanoSecond);
}

private static readonly unsafe delegate* unmanaged[SuppressGCTransition]<ulong*, void> s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr();

private static unsafe delegate* unmanaged[SuppressGCTransition]<ulong*, void> GetGetSystemTimeAsFileTimeFnPtr()
{
IntPtr kernel32Lib = Interop.Kernel32.LoadLibraryEx(Interop.Libraries.Kernel32, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32);
Debug.Assert(kernel32Lib != IntPtr.Zero);

IntPtr pfnGetSystemTime = NativeLibrary.GetExport(kernel32Lib, "GetSystemTimeAsFileTime");

if (NativeLibrary.TryGetExport(kernel32Lib, "GetSystemTimePreciseAsFileTime", out IntPtr pfnGetSystemTimePrecise))
{
// GetSystemTimePreciseAsFileTime exists and we'd like to use it. However, on
// misconfigured systems, it's possible for the "precise" time to be inaccurate:
// https://github.com/dotnet/runtime/issues/9014
// If it's inaccurate, though, we expect it to be wildly inaccurate, so as a
// workaround/heuristic, we get both the "normal" and "precise" times, and as
// long as they're close, we use the precise one. This workaround can be removed
// when we better understand what's causing the drift and the issue is no longer
// a problem or can be better worked around on all targeted OSes.

// Retry this check several times to reduce chance of false negatives due to thread being rescheduled
// at wrong time.
for (int i = 0; i < 10; i++)
{
long systemTimeResult, preciseSystemTimeResult;
((delegate* unmanaged[SuppressGCTransition]<long*, void>)pfnGetSystemTime)(&systemTimeResult);
((delegate* unmanaged[SuppressGCTransition]<long*, void>)pfnGetSystemTimePrecise)(&preciseSystemTimeResult);

if (Math.Abs(preciseSystemTimeResult - systemTimeResult) <= 100 * TimeSpan.TicksPerMillisecond)
{
pfnGetSystemTime = pfnGetSystemTimePrecise; // use the precise version
break;
}
}
}

return (delegate* unmanaged[SuppressGCTransition]<ulong*, void>)pfnGetSystemTime;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Diagnostics
{
/// <summary>
/// Implements a <see cref="TimeClock"/> that retrieves the current time from
/// the actual system clock, as provided by the underlying operating system.
/// </summary>
public sealed partial class ActualSystemClock : TimeClock
{
private ActualSystemClock()
{
}

/// <summary>
/// Gets a singleton instance of the <see cref="ActualSystemClock"/>.
/// </summary>
public static ActualSystemClock Instance { get; } = new();

protected override DateTimeOffset GetCurrentUtcDateTimeOffsetImpl()
{
ulong ticks = GetTicks();
return new DateTimeOffset(0, new DateTime(ticks));
}

internal override DateTime GetCurrentUtcDateTimeImpl()
{
ulong ticks = GetTicks();
return new DateTime(ticks | DateTime.KindUtc);
}
}
}
Loading