Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

WIP: Move DateTime native code into managed #21848

Closed
wants to merge 4 commits into from
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
@@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

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

internal static partial class Interop
{
internal unsafe partial class Sys
{
[DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetSystemTimeAsTicks")]
internal static extern long GetSystemTimeAsTicks();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal partial class Interop
{
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static unsafe extern bool FileTimeToSystemTime(in long lpFileTime, out Interop.Kernel32.SYSTEMTIME lpSystemTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal partial class Interop
{
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32, SetLastError = true)]
internal static unsafe extern void GetSystemTime(ref Interop.Kernel32.SYSTEMTIME lpSystemTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal partial class Interop
{
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static unsafe extern void GetSystemTimeAsFileTime(long* lpSystemTimeAsFileTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal partial class Interop
{
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static unsafe extern void GetSystemTimePreciseAsFileTime(long* lpSystemTimeAsFileTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.InteropServices;

internal partial class Interop
{
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static extern bool SystemTimeToFileTime(in Interop.Kernel32.SYSTEMTIME lpSystemTime, out long lpFileTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.InteropServices;

internal partial class Interop
{
internal partial class Kernel32
{
[DllImport(Libraries.Kernel32)]
internal static extern bool TzSpecificLocalTimeToSystemTime(
IntPtr lpTimeZoneInformation,
in Interop.Kernel32.SYSTEMTIME lpLocalTime,
out Interop.Kernel32.SYSTEMTIME lpUniversalTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.CancelIoEx.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileAttributes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FILE_INFO_BY_HANDLE_CLASS.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileTimeToSystemTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FileTypes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FindClose.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.FindFirstFileEx.cs" />
Expand All @@ -861,6 +862,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFileType_SafeHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetFullPathNameW.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetLongPathNameW.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemTimeAsFileTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetSystemTimePreciseAsFileTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempFileNameW.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.GetTempPathW.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.Globalization.cs" />
Expand All @@ -877,7 +881,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetThreadErrorMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SetFilePointerEx.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.SystemTimeToFileTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.TimeZone.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.TzSpecificLocalTimeToSystemTime.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WideCharToMultiByte.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_IntPtr.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_NativeOverlapped.cs" />
Expand Down Expand Up @@ -980,6 +986,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.FTruncate.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetCwd.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetRandomBytes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.GetSystemTimeAsTicks.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.LockFileRegion.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.LSeek.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Unix\System.Native\Interop.MksTemps.cs" />
Expand Down
5 changes: 1 addition & 4 deletions src/System.Private.CoreLib/src/System/DateTime.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static DateTime UtcNow
{
get
{
return new DateTime(((ulong)(GetSystemTimeAsFileTime() + FileTimeOffset)) | KindUtc);
return new DateTime(((ulong)(Interop.Sys.GetSystemTimeAsTicks() + DateTime.UnixEpochTicks)) | KindUtc);
}
}

Expand All @@ -23,8 +23,5 @@ public static DateTime UtcNow

// IsValidTimeWithLeapSeconds is not expected to be called at all for now on non-Windows platforms
internal static bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) => false;

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern long GetSystemTimeAsFileTime();
}
}
112 changes: 92 additions & 20 deletions src/System.Private.CoreLib/src/System/DateTime.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,37 @@

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();
internal static readonly bool s_systemSupportsPreciseSystemTime = SystemSupportsPreciseSystemTime();

public static DateTime UtcNow
public static unsafe DateTime UtcNow
{
get
{
long timestamp;

if (s_systemSupportsPreciseSystemTime)
{
Interop.Kernel32.GetSystemTimePreciseAsFileTime(&timestamp);
}
else
{
Interop.Kernel32.GetSystemTimeAsFileTime(&timestamp);
}

if (s_systemSupportsLeapSeconds)
{
GetSystemTimeWithLeapSecondsHandling(out FullSystemTime time);
GetSystemTimeWithLeapSecondsHandling(timestamp, out FullSystemTime time);
return CreateDateTimeFromSystemTime(in time);
}

return new DateTime(((ulong)(GetSystemTimeAsFileTime() + FileTimeOffset)) | KindUtc);
return new DateTime(((ulong)(timestamp + FileTimeOffset)) | KindUtc);
}
}

Expand Down Expand Up @@ -52,7 +65,7 @@ internal static DateTime FromFileTimeLeapSecondsAware(long fileTime)
internal static long ToFileTimeLeapSecondsAware(long ticks)
{
FullSystemTime time = new FullSystemTime(ticks);
if (SystemTimeToFileTime(in time.systemTime, out long fileTime))
if (Interop.Kernel32.SystemTimeToFileTime(in time.systemTime, out long fileTime))
{
return fileTime + ticks % TicksPerMillisecond;
}
Expand Down Expand Up @@ -81,6 +94,80 @@ private static unsafe bool SystemSupportsLeapSeconds()
null) == 0 && slsi.Enabled;
}

private static unsafe bool ValidateSystemTime(in Interop.Kernel32.SYSTEMTIME time, bool localTime)
{
if (localTime)
{
return Interop.Kernel32.TzSpecificLocalTimeToSystemTime(IntPtr.Zero, in time, out Interop.Kernel32.SYSTEMTIME st);
}
else
{
return Interop.Kernel32.SystemTimeToFileTime(in time, out long timestamp);
}
}

private static bool FileTimeToSystemTime(long fileTime, out FullSystemTime time)
{
time = new FullSystemTime();
if (Interop.Kernel32.FileTimeToSystemTime(in fileTime, out time.systemTime))
{
// to keep the time precision
time.hundredNanoSecond = fileTime % TicksPerMillisecond;
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;
}
return true;
}
return false;
}

private static unsafe void GetSystemTimeWithLeapSecondsHandling(long timestamp, out FullSystemTime time)
{
if (!FileTimeToSystemTime(timestamp, out time))
{
Interop.Kernel32.GetSystemTime(ref 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 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/coreclr/issues/14187
// 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;
}

// FullSystemTime struct is the SYSTEMTIME struct with extra hundredNanoSecond field to store more precise time.
[StructLayout(LayoutKind.Sequential)]
internal struct FullSystemTime
Expand Down Expand Up @@ -118,21 +205,6 @@ internal FullSystemTime(long ticks)
systemTime.Milliseconds = (ushort) dt.Millisecond;
hundredNanoSecond = 0;
}
};

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern bool ValidateSystemTime(in Interop.Kernel32.SYSTEMTIME time, bool localTime);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern bool FileTimeToSystemTime(long fileTime, out FullSystemTime time);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern void GetSystemTimeWithLeapSecondsHandling(out FullSystemTime time);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern bool SystemTimeToFileTime(in Interop.Kernel32.SYSTEMTIME time, out long fileTime);

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern long GetSystemTimeAsFileTime();
}
}
}
Loading