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

Leap Seconds Support #21420

Merged
merged 3 commits into from
Dec 8, 2018
Merged
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
3 changes: 2 additions & 1 deletion src/System.Private.CoreLib/System.Private.CoreLib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@
<Compile Include="$(BclSourcesRoot)\System\Collections\Generic\EqualityComparer.cs" />
<Compile Include="$(BclSourcesRoot)\System\Collections\ObjectModel\ReadOnlyDictionary.cs" />
<Compile Include="$(BclSourcesRoot)\System\Currency.cs" />
<Compile Include="$(BclSourcesRoot)\System\DateTime.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\DefaultBinder.CanConvert.cs" />
<Compile Include="$(BclSourcesRoot)\System\Delegate.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\Contracts\Contracts.cs" />
Expand Down Expand Up @@ -425,12 +424,14 @@
<Compile Include="$(BclSourcesRoot)\System\Variant.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<Compile Include="$(BclSourcesRoot)\System\DateTime.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\Interop\Unix\Interop.Libraries.cs" />
<Compile Include="$(BclSourcesRoot)\System\Globalization\CultureInfo.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\Globalization\GlobalizationMode.Unix.cs" />
<Compile Include="$(BclSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
<Compile Include="$(BclSourcesRoot)\System\DateTime.Windows.cs" />
<Compile Include="$(BclSourcesRoot)\Interop\Windows\Kernel32\Interop.GetSystemDirectoryW.cs" />
<Compile Include="$(BclSourcesRoot)\System\ApplicationModel.Windows.cs" />
<Compile Include="$(BclSourcesRoot)\System\Diagnostics\DebugProvider.Windows.cs" />
Expand Down
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