Skip to content

Commit

Permalink
Leap Seconds Support
Browse files Browse the repository at this point in the history
  • Loading branch information
tarekgh committed Dec 8, 2018
1 parent 43840e0 commit 389615a
Show file tree
Hide file tree
Showing 15 changed files with 488 additions and 20 deletions.
13 changes: 7 additions & 6 deletions src/BuildIntegration/Microsoft.NETCore.Native.Windows.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ See the LICENSE file in the project root for more information.
<FullRuntimeName>Runtime</FullRuntimeName>
<FullRuntimeName Condition="'$(ServerGarbageCollection)' != ''">Runtime.ServerGC</FullRuntimeName>
</PropertyGroup>

<!-- Part of workaround for lack of secondary build artifact import - https://github.com/Microsoft/msbuild/issues/2807 -->
<!-- Ensure that runtime-specific paths have already been set -->
<!-- Ensure that runtime-specific paths have already been set -->
<Target Name="SetupOSSpecificProps" DependsOnTargets="$(IlcDynamicBuildPropertyDependencies)">
<ItemGroup>
<CppCompilerAndLinkerArg Include="/I$(IlcPath)\inc" />
Expand All @@ -47,9 +47,10 @@ See the LICENSE file in the project root for more information.
<NativeLibrary Condition="$(NativeCodeGen) == 'wasm'" Include="$(IlcPath)\sdk\PortableRuntime.lib" />
<NativeLibrary Condition="'$(IlcMultiModule)' == 'true' and $(NativeCodeGen) == ''" Include="$(SharedLibrary)" />
</ItemGroup>

<ItemGroup>
<NativeLibrary Include="kernel32.lib" />
<NativeLibrary Include="ntdll.lib" />
<NativeLibrary Include="user32.lib" />
<NativeLibrary Include="gdi32.lib" />
<NativeLibrary Include="winspool.lib" />
Expand All @@ -62,7 +63,7 @@ See the LICENSE file in the project root for more information.
<NativeLibrary Include="bcrypt.lib" />
<NativeLibrary Include="normaliz.lib" />
</ItemGroup>

<ItemGroup>
<LinkerArg Condition="$(NativeLib) == 'Shared'" Include="/DLL" />
<LinkerArg Include="@(NativeLibrary->'&quot;%(Identity)&quot;')" />
Expand All @@ -71,11 +72,11 @@ See the LICENSE file in the project root for more information.
<LinkerArg Include="/INCREMENTAL:NO" />
<LinkerArg Condition="'$(OutputType)' == 'WinExe'" Include="/SUBSYSTEM:WINDOWS /ENTRY:wmainCRTStartup" />
</ItemGroup>

<ItemGroup>
<!-- TODO <LinkerArg Include="/MACHINE:X64" /> -->
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' != 'Debug'">
<LinkerArg Include="/OPT:REF" />
<LinkerArg Include="/OPT:ICF" />
Expand Down
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,17 @@
// 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
{
internal const int ProcessLeapSecondInfo = 8;

[DllImport(Libraries.Kernel32)]
internal static extern bool GetProcessInformation(IntPtr hProcess, int ProcessInformationClass, ref long processInformation, int ProcessInformationSize);
}
}
15 changes: 15 additions & 0 deletions src/Common/src/Interop/Windows/kernel32/Interop.GetSystemTime.cs
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,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
@@ -0,0 +1,24 @@
// 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 NtDll
{
[DllImport(Libraries.NtDll)]
internal static unsafe extern int NtQuerySystemInformation(int SystemInformationClass, void* SystemInformation, int SystemInformationLength, uint* ReturnLength);

[StructLayout(LayoutKind.Sequential)]
internal struct SYSTEM_LEAP_SECOND_INFORMATION
{
public bool Enabled;
public uint Flags;
}

internal const int SystemLeapSecondInformation = 206;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Kernel32\Interop.WriteFile_SafeHandle_NativeOverlapped.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Normaliz\Interop.Idna.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Normaliz\Interop.Normalization.cs" Condition="'$(EnableDummyGlobalizationImplementation)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\NtDll\Interop.NtQuerySystemInformation.cs" Condition="'$(EnableWinRT)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\Ole32\Interop.CoCreateGuid.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysAllocStringLen.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Windows\OleAut32\Interop.SysFreeString.cs" />
Expand Down
123 changes: 120 additions & 3 deletions src/System.Private.CoreLib/shared/System/DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ public DateTime(int year, int month, int day, Calendar calendar)
//
public DateTime(int year, int month, int day, int hour, int minute, int second)
{
if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, DateTimeKind.Unspecified))
{
// if we have leap second (second = 60) then we'll need to check if it is valid time.
// if it is valid, then we adjust the second to 59 so DateTime will consider this second is last second
// in the specified minute.
// if it is not valid time, we'll eventually throw.
second = 59;
}
_dateData = (ulong)(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second));
}

Expand All @@ -205,6 +213,16 @@ public DateTime(int year, int month, int day, int hour, int minute, int second,
{
throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
}

if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, kind))
{
// if we have leap second (second = 60) then we'll need to check if it is valid time.
// if it is valid, then we adjust the second to 59 so DateTime will consider this second is last second
// in the specified minute.
// if it is not valid time, we'll eventually throw.
second = 59;
}

long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
_dateData = ((ulong)ticks | ((ulong)kind << KindShift));
}
Expand All @@ -216,7 +234,24 @@ public DateTime(int year, int month, int day, int hour, int minute, int second,
{
if (calendar == null)
throw new ArgumentNullException(nameof(calendar));

int originalSecond = second;
if (second == 60 && s_systemSupportsLeapSeconds)
{
// Reset the second value now and then we'll validate it later when we get the final Gregorian date.
second = 59;
}

_dateData = (ulong)calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks;

if (originalSecond == 60)
{
DateTime dt = new DateTime(_dateData);
if (!IsValidTimeWithLeapSeconds(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 60, DateTimeKind.Unspecified))
{
throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
}
}
}

// Constructs a DateTime from a given year, month, day, hour,
Expand All @@ -228,6 +263,16 @@ public DateTime(int year, int month, int day, int hour, int minute, int second,
{
throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1));
}

if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, DateTimeKind.Unspecified))
{
// if we have leap second (second = 60) then we'll need to check if it is valid time.
// if it is valid, then we adjust the second to 59 so DateTime will consider this second is last second
// in the specified minute.
// if it is not valid time, we'll eventually throw.
second = 59;
}

long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
ticks += millisecond * TicksPerMillisecond;
if (ticks < MinTicks || ticks > MaxTicks)
Expand All @@ -245,6 +290,16 @@ public DateTime(int year, int month, int day, int hour, int minute, int second,
{
throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
}

if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, kind))
{
// if we have leap second (second = 60) then we'll need to check if it is valid time.
// if it is valid, then we adjust the second to 59 so DateTime will consider this second is last second
// in the specified minute.
// if it is not valid time, we'll eventually throw.
second = 59;
}

long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);
ticks += millisecond * TicksPerMillisecond;
if (ticks < MinTicks || ticks > MaxTicks)
Expand All @@ -263,11 +318,28 @@ public DateTime(int year, int month, int day, int hour, int minute, int second,
{
throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1));
}

int originalSecond = second;
if (second == 60 && s_systemSupportsLeapSeconds)
{
// Reset the second value now and then we'll validate it later when we get the final Gregorian date.
second = 59;
}

long ticks = calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks;
ticks += millisecond * TicksPerMillisecond;
if (ticks < MinTicks || ticks > MaxTicks)
throw new ArgumentException(SR.Arg_DateTimeRange);
_dateData = (ulong)ticks;

if (originalSecond == 60)
{
DateTime dt = new DateTime(_dateData);
if (!IsValidTimeWithLeapSeconds(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 60, DateTimeKind.Unspecified))
{
throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
}
}
}

public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, DateTimeKind kind)
Expand All @@ -282,11 +354,28 @@ public DateTime(int year, int month, int day, int hour, int minute, int second,
{
throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind));
}

int originalSecond = second;
if (second == 60 && s_systemSupportsLeapSeconds)
{
// Reset the second value now and then we'll validate it later when we get the final Gregorian date.
second = 59;
}

long ticks = calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks;
ticks += millisecond * TicksPerMillisecond;
if (ticks < MinTicks || ticks > MaxTicks)
throw new ArgumentException(SR.Arg_DateTimeRange);
_dateData = ((ulong)ticks | ((ulong)kind << KindShift));

if (originalSecond == 60)
{
DateTime dt = new DateTime(_dateData);
if (!IsValidTimeWithLeapSeconds(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 60, kind))
{
throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond);
}
}
}

private DateTime(SerializationInfo info, StreamingContext context)
Expand Down Expand Up @@ -338,8 +427,6 @@ private DateTime(SerializationInfo info, StreamingContext context)
}
}



internal long InternalTicks
{
get
Expand Down Expand Up @@ -722,6 +809,11 @@ public static DateTime FromFileTimeUtc(long fileTime)
throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid);
}

if (s_systemSupportsLeapSeconds)
{
return FromFileTimeLeapSecondsAware(fileTime);
}

// This is the ticks in Universal time for this fileTime.
long universalTicks = fileTime + FileTimeOffset;
return new DateTime(universalTicks, DateTimeKind.Utc);
Expand Down Expand Up @@ -1223,11 +1315,18 @@ public long ToFileTimeUtc()
{
// Treats the input as universal if it is not specified
long ticks = ((InternalKind & LocalMask) != 0) ? ToUniversalTime().InternalTicks : this.InternalTicks;

if (s_systemSupportsLeapSeconds)
{
return ToFileTimeLeapSecondsAware(ticks);
}

ticks -= FileTimeOffset;
if (ticks < 0)
{
throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid);
}

return ticks;
}

Expand Down Expand Up @@ -1574,14 +1673,32 @@ internal static bool TryCreate(int year, int month, int day, int hour, int minut
{
return false;
}
if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60)
if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second > 60)
{
return false;
}
if (millisecond < 0 || millisecond >= MillisPerSecond)
{
return false;
}

if (second == 60)
{
if (s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, DateTimeKind.Unspecified))
{
// if we have leap second (second = 60) then we'll need to check if it is valid time.
// if it is valid, then we adjust the second to 59 so DateTime will consider this second is last second
// of this minute.
// if it is not valid time, we'll eventually throw.
// although this is unspecified datetime kind, we'll assume the passed time is UTC to check the leap seconds.
second = 59;
}
else
{
return false;
}
}

long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second);

ticks += millisecond * TicksPerMillisecond;
Expand Down
Loading

0 comments on commit 389615a

Please sign in to comment.