diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
index 1b0204588a2f8a..2a6c8528372c3a 100644
--- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -291,7 +291,6 @@
-
Common\Interop\Windows\OleAut32\Interop.VariantClear.cs
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreCLR.cs
deleted file mode 100644
index 1f77180f47870f..00000000000000
--- a/src/coreclr/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreCLR.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.CompilerServices;
-
-namespace System
-{
- public readonly partial struct DateTime
- {
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern unsafe bool ValidateSystemTime(Interop.Kernel32.SYSTEMTIME* time, bool localTime);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern unsafe bool FileTimeToSystemTime(long fileTime, FullSystemTime* time);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern unsafe void GetSystemTimeWithLeapSecondsHandling(FullSystemTime* time);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern unsafe bool SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* time, long* fileTime);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern unsafe long GetSystemTimeAsFileTime();
- }
-}
diff --git a/src/coreclr/src/classlibnative/bcltype/system.cpp b/src/coreclr/src/classlibnative/bcltype/system.cpp
index 3660c14963df99..bf688e1da6ce2d 100644
--- a/src/coreclr/src/classlibnative/bcltype/system.cpp
+++ b/src/coreclr/src/classlibnative/bcltype/system.cpp
@@ -30,69 +30,12 @@
#include "array.h"
#include "eepolicy.h"
-#ifndef TARGET_UNIX
-typedef void(WINAPI *pfnGetSystemTimeAsFileTime)(LPFILETIME lpSystemTimeAsFileTime);
-extern pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime;
-
-void WINAPI InitializeGetSystemTimeAsFileTime(LPFILETIME lpSystemTimeAsFileTime)
-{
- pfnGetSystemTimeAsFileTime func = NULL;
-
- HMODULE hKernel32 = WszLoadLibrary(W("kernel32.dll"));
- if (hKernel32 != NULL)
- {
- func = (pfnGetSystemTimeAsFileTime)GetProcAddress(hKernel32, "GetSystemTimePreciseAsFileTime");
- if (func != NULL)
- {
- // 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.
-
- FILETIME systemTimeResult;
- ::GetSystemTimeAsFileTime(&systemTimeResult);
-
- FILETIME preciseSystemTimeResult;
- func(&preciseSystemTimeResult);
-
- LONG64 systemTimeLong100ns = (LONG64)((((ULONG64)systemTimeResult.dwHighDateTime) << 32) | (ULONG64)systemTimeResult.dwLowDateTime);
- LONG64 preciseSystemTimeLong100ns = (LONG64)((((ULONG64)preciseSystemTimeResult.dwHighDateTime) << 32) | (ULONG64)preciseSystemTimeResult.dwLowDateTime);
-
- const INT32 THRESHOLD_100NS = 1000000; // 100ms
- if (abs(preciseSystemTimeLong100ns - systemTimeLong100ns) > THRESHOLD_100NS)
- {
- // Too much difference. Don't use GetSystemTimePreciseAsFileTime.
- func = NULL;
- }
- }
- }
- if (func == NULL)
- {
- func = &::GetSystemTimeAsFileTime;
- }
-
- InterlockedCompareExchangeT(&g_pfnGetSystemTimeAsFileTime, func, &InitializeGetSystemTimeAsFileTime);
-
- g_pfnGetSystemTimeAsFileTime(lpSystemTimeAsFileTime);
-}
-
-pfnGetSystemTimeAsFileTime g_pfnGetSystemTimeAsFileTime = &InitializeGetSystemTimeAsFileTime;
-#endif // TARGET_UNIX
-
+#ifdef TARGET_UNIX
FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime)
{
FCALL_CONTRACT;
- INT64 timestamp;
-#ifndef TARGET_UNIX
- g_pfnGetSystemTimeAsFileTime((FILETIME*)×tamp);
-#else
GetSystemTimeAsFileTime((FILETIME*)×tamp);
-#endif
#if BIGENDIAN
timestamp = (INT64)(((UINT64)timestamp >> 32) | ((UINT64)timestamp << 32));
@@ -101,88 +44,8 @@ FCIMPL0(INT64, SystemNative::__GetSystemTimeAsFileTime)
return timestamp;
}
FCIMPLEND;
-
-
-#ifndef TARGET_UNIX
-
-FCIMPL1(VOID, SystemNative::GetSystemTimeWithLeapSecondsHandling, FullSystemTime *time)
-{
- FCALL_CONTRACT;
- INT64 timestamp;
-
- g_pfnGetSystemTimeAsFileTime((FILETIME*)×tamp);
-
- if (::FileTimeToSystemTime((FILETIME*)×tamp, &(time->systemTime)))
- {
- // to keep the time precision
- time->hundredNanoSecond = timestamp % 10000; // 10000 is the number of 100-nano seconds per Millisecond
- }
- else
- {
- ::GetSystemTime(&(time->systemTime));
- time->hundredNanoSecond = 0;
- }
-
- if (time->systemTime.wSecond > 59)
- {
- // 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
- time->systemTime.wSecond = 59;
- time->systemTime.wMilliseconds = 999;
- time->hundredNanoSecond = 9999;
- }
-}
-FCIMPLEND;
-
-FCIMPL2(FC_BOOL_RET, SystemNative::FileTimeToSystemTime, INT64 fileTime, FullSystemTime *time)
-{
- FCALL_CONTRACT;
- if (::FileTimeToSystemTime((FILETIME*)&fileTime, (LPSYSTEMTIME) time))
- {
- // to keep the time precision
- time->hundredNanoSecond = fileTime % 10000; // 10000 is the number of 100-nano seconds per Millisecond
- if (time->systemTime.wSecond > 59)
- {
- // 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
- time->systemTime.wSecond = 59;
- time->systemTime.wMilliseconds = 999;
- time->hundredNanoSecond = 9999;
- }
- FC_RETURN_BOOL(TRUE);
- }
- FC_RETURN_BOOL(FALSE);
-}
-FCIMPLEND;
-
-FCIMPL2(FC_BOOL_RET, SystemNative::ValidateSystemTime, SYSTEMTIME *time, CLR_BOOL localTime)
-{
- FCALL_CONTRACT;
-
- if (localTime)
- {
- SYSTEMTIME st;
- FC_RETURN_BOOL(::TzSpecificLocalTimeToSystemTime(NULL, time, &st));
- }
- else
- {
- FILETIME timestamp;
- FC_RETURN_BOOL(::SystemTimeToFileTime(time, ×tamp));
- }
-}
-FCIMPLEND;
-
-FCIMPL2(FC_BOOL_RET, SystemNative::SystemTimeToFileTime, SYSTEMTIME *time, INT64 *pFileTime)
-{
- FCALL_CONTRACT;
-
- BOOL ret = ::SystemTimeToFileTime(time, (LPFILETIME) pFileTime);
- FC_RETURN_BOOL(ret);
-}
-FCIMPLEND;
#endif // TARGET_UNIX
-
FCIMPL0(UINT32, SystemNative::GetTickCount)
{
FCALL_CONTRACT;
diff --git a/src/coreclr/src/classlibnative/bcltype/system.h b/src/coreclr/src/classlibnative/bcltype/system.h
index 5bbf73d4a1048d..1315f0bdcb512e 100644
--- a/src/coreclr/src/classlibnative/bcltype/system.h
+++ b/src/coreclr/src/classlibnative/bcltype/system.h
@@ -43,13 +43,9 @@ class SystemNative
public:
// Functions on the System.Environment class
-#ifndef TARGET_UNIX
- static FCDECL1(VOID, GetSystemTimeWithLeapSecondsHandling, FullSystemTime *time);
- static FCDECL2(FC_BOOL_RET, ValidateSystemTime, SYSTEMTIME *time, CLR_BOOL localTime);
- static FCDECL2(FC_BOOL_RET, FileTimeToSystemTime, INT64 fileTime, FullSystemTime *time);
- static FCDECL2(FC_BOOL_RET, SystemTimeToFileTime, SYSTEMTIME *time, INT64 *pFileTime);
-#endif // TARGET_UNIX
+#ifdef TARGET_UNIX
static FCDECL0(INT64, __GetSystemTimeAsFileTime);
+#endif // TARGET_UNIX
static FCDECL0(UINT32, GetTickCount);
static FCDECL0(UINT64, GetTickCount64);
diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h
index 87389732f8d2e0..a4e7debdf25662 100644
--- a/src/coreclr/src/vm/ecalllist.h
+++ b/src/coreclr/src/vm/ecalllist.h
@@ -132,13 +132,9 @@ FCFuncStart(gDiagnosticsStackTrace)
FCFuncEnd()
FCFuncStart(gDateTimeFuncs)
-#if !defined(TARGET_UNIX)
- FCFuncElement("GetSystemTimeWithLeapSecondsHandling", SystemNative::GetSystemTimeWithLeapSecondsHandling)
- FCFuncElement("ValidateSystemTime", SystemNative::ValidateSystemTime)
- FCFuncElement("FileTimeToSystemTime", SystemNative::FileTimeToSystemTime)
- FCFuncElement("SystemTimeToFileTime", SystemNative::SystemTimeToFileTime)
-#endif // TARGET_UNIX
+#if defined(TARGET_UNIX)
FCFuncElement("GetSystemTimeAsFileTime", SystemNative::__GetSystemTimeAsFileTime)
+#endif // TARGET_UNIX
FCFuncEnd()
FCFuncStart(gEnvironmentFuncs)
diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs
index ac4e1fe6be1168..6c5266ae34f144 100644
--- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs
@@ -1,30 +1,172 @@
// 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;
+using System.Threading;
namespace System
{
public readonly partial struct DateTime
{
- internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds();
+ private static LeapSecondCache s_leapSecondCache = new LeapSecondCache(); // dummy value, will be invalidated on first call
+
+ private static unsafe delegate* unmanaged[Stdcall] s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr();
+
+ private static unsafe delegate* unmanaged[Stdcall] GetGetSystemTimeAsFileTimeFnPtr()
+ {
+ IntPtr kernel32Lib = NativeLibrary.Load("kernel32.dll", typeof(DateTime).Assembly, DllImportSearchPath.System32);
+ IntPtr pfnGetSystemTime = NativeLibrary.GetExport(kernel32Lib, "GetSystemTimeAsFileTime"); // will never fail
+
+ if (NativeLibrary.TryGetExport(kernel32Lib, "GetSystemTimePreciseAsFileTime", out IntPtr pfnGetSystemTimePrecise))
+ {
+ // If GetSystemTimePreciseAsFileTime exists, 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.
+
+ // TODO: Once https://github.com/dotnet/runtime/issues/38134 is resolved,
+ // the calls below should suppress the GC transition.
+
+ ulong filetimeStd, filetimePrecise;
+ ((delegate* unmanaged[Stdcall])pfnGetSystemTime)(&filetimeStd);
+ ((delegate* unmanaged[Stdcall])pfnGetSystemTimePrecise)(&filetimePrecise);
+
+ if (Math.Abs((long)(filetimeStd - filetimePrecise)) <= 100 * TicksPerMillisecond)
+ {
+ pfnGetSystemTime = pfnGetSystemTimePrecise; // use the precise version
+ }
+ }
+
+ return (delegate* unmanaged[Stdcall])pfnGetSystemTime;
+ }
public static unsafe DateTime UtcNow
{
get
{
- if (s_systemSupportsLeapSeconds)
+ // The OS tick count and .NET's tick count are slightly different. The OS tick
+ // count is the *absolute* number of 100-ns intervals which have elapsed since
+ // January 1, 1601 (UTC). Due to leap second handling, the number of ticks per
+ // day is variable. Dec. 30, 2016 had 864,000,000,000 ticks (a standard 24-hour
+ // day), but Dec. 31, 2016 had 864,010,000,000 ticks due to leap second insertion.
+ // In .NET, *every* day is assumed to have exactly 24 hours (864,000,000,000 ticks).
+ // This means that per the OS, midnight Dec. 31, 2016 + 864 bn ticks = Dec. 31, 2016 23:59:60,
+ // but per .NET, midnight Dec. 31, 2016 + 864 bn ticks = Jan. 1, 2017 00:00:00.
+ //
+ // We can query the OS and have it deconstruct the tick count into (yyyy-mm-dd hh:mm:ss),
+ // constructing a new DateTime object from these components, but this is slow.
+ // So instead we'll rely on the fact that leap seconds only ever adjust the day
+ // by +1 or -1 second, and only at the very end of the day. That is, time rolls
+ // 23:59:58 -> 00:00:00 (negative leap second) or 23:59:59 -> 23:59:60 (positive leap
+ // second). Thus we assume that each day has at least 23 hr 59 min 59 sec, or
+ // 863,990,000,000 ticks.
+ //
+ // We take advantage of this by caching what the OS believes the tick count is at
+ // the beginning of the month vs. what .NET believes the tick count is at the beginning
+ // of the month. When the OS returns a tick count to us, if it's before 23:59:59 on the
+ // last day of the month, then we know that there's no way for a leap second to have been
+ // inserted or removed, and we can short-circuit the leap second handling logic by
+ // performing a quick addition and returning immediately.
+ //
+ // This relies on:
+ // a) Leap seconds only ever taking place on the last day of the month
+ // (historically these are Jun. & Dec., but we support any month);
+ // b) Leap seconds only ever occurring at 23:59:59;
+ // c) At most a single leap second being added or removed per month; and
+ // d) Windows never inserting a historical leap second (into the past) once the
+ // process is up and running (future insertions are ok).
+
+ // TODO: Once https://github.com/dotnet/runtime/issues/38134 is resolved,
+ // the call below should suppress the GC transition.
+
+ ulong osTicks;
+ s_pfnGetSystemTimeAsFileTime(&osTicks);
+
+ // If the OS doesn't support leap second handling, short-circuit everything.
+
+ if (!s_systemSupportsLeapSeconds)
+ {
+ return new DateTime((osTicks + FileTimeOffset) | KindUtc);
+ }
+
+ return GetUtcNowFromOSTicksLeapSecondsAwareCached(osTicks);
+ }
+ }
+
+ // This method is extracted into its own helper so that unit tests
+ // can call into it via reflection.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static DateTime GetUtcNowFromOSTicksLeapSecondsAwareCached(ulong osTicks)
+ {
+ Debug.Assert(s_systemSupportsLeapSeconds, "Shouldn't call this helper if OS doesn't support leap seconds.");
+
+ // If our cache has been populated and we're within its validity period, rely
+ // solely on the cache. We use unsigned arithmetic below so that if the system
+ // clock is set backward, the resulting value will integer underflow and cause
+ // a cache miss, forcing us down the slow path.
+
+ LeapSecondCache cache = s_leapSecondCache;
+ ulong deltaTicksFromStartOfMonth = osTicks - cache.OSFileTimeTicksAtStartOfMonth;
+ if (deltaTicksFromStartOfMonth < cache.CacheValidityPeriodInTicks)
+ {
+ return new DateTime(deltaTicksFromStartOfMonth + cache.DotNetDateDataAtStartOfMonth); // includes UTC marker
+ }
+
+ // Move uncommon fallback pack into its own subroutine so that register
+ // allocation in get_UtcNow can stay as optimized as possible.
+
+ return Fallback(osTicks);
+ static DateTime Fallback(ulong osTicks)
+ {
+ // If we reached this point, one of the following is true:
+ // a) the cache hasn't yet been initialized; or
+ // b) the month has changed since the last call to UtcNow; or
+ // c) the current time is 23:59:59 or 23:59:60 on the last day of the month.
+ //
+ // In cases (a) and (b), we'll update the cache. In case (c), we
+ // pessimistically assume we might be inside a leap second, so we
+ // won't update the cache.
+
+ DateTime utcNow = FromFileTimeLeapSecondsAware((long)osTicks);
+ DateTime oneSecondBeforeMonthEnd = new DateTime(utcNow.Year, utcNow.Month, DaysInMonth(utcNow.Year, utcNow.Month), 23, 59, 59, DateTimeKind.Utc);
+
+ if (utcNow < oneSecondBeforeMonthEnd)
{
- FullSystemTime time;
- GetSystemTimeWithLeapSecondsHandling(&time);
- return CreateDateTimeFromSystemTime(in time);
+ // It's not yet 23:59:59 on the last day of the month, so update the cache.
+ // It's ok for multiple threads to do this concurrently as long as the write
+ // to the static field is published *after* the cache's instance fields have
+ // been populated.
+
+ DateTime startOfMonth = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc);
+ Debug.Assert(startOfMonth <= utcNow);
+
+ Volatile.Write(ref s_leapSecondCache, new LeapSecondCache
+ {
+ OSFileTimeTicksAtStartOfMonth = osTicks - (ulong)(utcNow.Ticks - startOfMonth.Ticks),
+ DotNetDateDataAtStartOfMonth = startOfMonth._dateData,
+ CacheValidityPeriodInTicks = (ulong)(oneSecondBeforeMonthEnd.Ticks - startOfMonth.Ticks)
+ });
}
- return new DateTime(((ulong)(GetSystemTimeAsFileTime() + FileTimeOffset)) | KindUtc);
+ return utcNow;
}
}
+ private sealed class LeapSecondCache
+ {
+ internal ulong OSFileTimeTicksAtStartOfMonth; // Windows FILETIME value
+ internal ulong DotNetDateDataAtStartOfMonth; // DateTime._dateData
+ internal ulong CacheValidityPeriodInTicks; // 100-ns intervals from StartOfMonth (inclusive) to cache expiration (exclusive)
+ }
+
+ internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds();
+
internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
{
DateTime dt = new DateTime(year, month, day);
@@ -111,34 +253,6 @@ internal FullSystemTime(long ticks)
}
}
-#if !CORECLR
- internal static readonly bool s_systemSupportsPreciseSystemTime = SystemSupportsPreciseSystemTime();
-
- private static unsafe bool SystemSupportsPreciseSystemTime()
- {
- if (Environment.IsWindows8OrAbove)
- {
- // 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.
-
- long systemTimeResult;
- Interop.Kernel32.GetSystemTimeAsFileTime(&systemTimeResult);
-
- long preciseSystemTimeResult;
- Interop.Kernel32.GetSystemTimePreciseAsFileTime(&preciseSystemTimeResult);
-
- return Math.Abs(preciseSystemTimeResult - systemTimeResult) <= 100 * TicksPerMillisecond;
- }
-
- return false;
- }
-
private static unsafe bool ValidateSystemTime(Interop.Kernel32.SYSTEMTIME* time, bool localTime)
{
if (localTime)
@@ -172,43 +286,9 @@ private static unsafe bool FileTimeToSystemTime(long fileTime, FullSystemTime* t
return false;
}
- private static unsafe void GetSystemTimeWithLeapSecondsHandling(FullSystemTime* time)
- {
- if (!FileTimeToSystemTime(GetSystemTimeAsFileTime(), time))
- {
- Interop.Kernel32.GetSystemTime(&time->systemTime);
- time->hundredNanoSecond = 0;
- if (time->systemTime.Second > 59)
- {
- // 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
- time->systemTime.Second = 59;
- time->systemTime.Milliseconds = 999;
- time->hundredNanoSecond = 9999;
- }
- }
- }
-
private static unsafe bool SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* time, long* fileTime)
{
return Interop.Kernel32.SystemTimeToFileTime(time, fileTime) != Interop.BOOL.FALSE;
}
-
- private static unsafe long GetSystemTimeAsFileTime()
- {
- long timestamp;
-
- if (s_systemSupportsPreciseSystemTime)
- {
- Interop.Kernel32.GetSystemTimePreciseAsFileTime(×tamp);
- }
- else
- {
- Interop.Kernel32.GetSystemTimeAsFileTime(×tamp);
- }
-
- return timestamp;
- }
-#endif
}
}