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 2 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
4 changes: 3 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,15 @@
<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="shared/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the project in the shared partition

<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;
}
}
133 changes: 108 additions & 25 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 @@ -715,18 +804,6 @@ public static DateTime FromFileTime(long fileTime)
return FromFileTimeUtc(fileTime).ToLocalTime();
}

public static DateTime FromFileTimeUtc(long fileTime)
{
if (fileTime < 0 || fileTime > MaxTicks - FileTimeOffset)
{
throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid);
}

// This is the ticks in Universal time for this fileTime.
long universalTicks = fileTime + FileTimeOffset;
return new DateTime(universalTicks, DateTimeKind.Utc);
}

// Creates a DateTime from an OLE Automation Date.
//
public static DateTime FromOADate(double d)
Expand Down Expand Up @@ -1219,18 +1296,6 @@ public long ToFileTime()
return ToUniversalTime().ToFileTimeUtc();
}

public long ToFileTimeUtc()
{
// Treats the input as universal if it is not specified
long ticks = ((InternalKind & LocalMask) != 0) ? ToUniversalTime().InternalTicks : this.InternalTicks;
ticks -= FileTimeOffset;
if (ticks < 0)
{
throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid);
}
return ticks;
}

public DateTime ToLocalTime()
{
return ToLocalTime(false);
Expand Down Expand Up @@ -1574,14 +1639,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