Skip to content

Commit

Permalink
Leap Seconds Support (dotnet/coreclr#21420)
Browse files Browse the repository at this point in the history
* Leap Seconds Support

* Address Feedback

* More feedback addressing

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
  • Loading branch information
tarekgh authored and dotnet-bot committed Dec 8, 2018
1 parent 5ce17d9 commit 75707e7
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 12 deletions.
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/Common/src/CoreLib/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
60 changes: 51 additions & 9 deletions src/Common/src/CoreLib/System/DateTimeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

namespace System
{
// DateTimeOffset is a value type that consists of a DateTime and a time zone offset,
// DateTimeOffset is a value type that consists of a DateTime and a time zone offset,
// ie. how far away the time is from GMT. The DateTime is stored whole, and the offset
// is stored as an Int16 internally to save space, but presented as a TimeSpan.
// is stored as an Int16 internally to save space, but presented as a TimeSpan.
//
// The range is constrained so that both the represented clock time and the represented
// UTC time fit within the boundaries of MaxValue. This gives it the same range as DateTime
// for actual UTC times, and a slightly constrained range on one end when an offset is
// present.
// present.
//
// This class should be substitutable for date time in most cases; so most operations
// effectively work on the clock time. However, the underlying UTC time is what counts
Expand All @@ -30,7 +30,7 @@ namespace System

[StructLayout(LayoutKind.Auto)]
[Serializable]
[System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
[System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
public readonly struct DateTimeOffset : IComparable, IFormattable, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>, ISerializable, IDeserializationCallback, ISpanFormattable
{
// Constants
Expand Down Expand Up @@ -109,23 +109,65 @@ public DateTimeOffset(DateTime dateTime, TimeSpan offset)
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, TimeSpan offset)
{
_offsetMinutes = ValidateOffset(offset);

int originalSecond = second;
if (second == 60 && DateTime.s_systemSupportsLeapSeconds)
{
// Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time.
second = 59;
}

_dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second), offset);

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

// Constructs a DateTimeOffset from a given year, month, day, hour,
// minute, second, millsecond and offset
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset)
{
_offsetMinutes = ValidateOffset(offset);

int originalSecond = second;
if (second == 60 && DateTime.s_systemSupportsLeapSeconds)
{
// Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time.
second = 59;
}

_dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond), offset);

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

// Constructs a DateTimeOffset from a given year, month, day, hour,
// minute, second, millsecond, Calendar and offset.
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset)
{
_offsetMinutes = ValidateOffset(offset);

int originalSecond = second;
if (second == 60 && DateTime.s_systemSupportsLeapSeconds)
{
// Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time.
second = 59;
}

_dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond, calendar), offset);

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

// Returns a DateTimeOffset representing the current date and time. The
Expand Down Expand Up @@ -596,7 +638,7 @@ public override int GetHashCode()
// Constructs a DateTimeOffset from a string. The string must specify a
// date and optionally a time in a culture-specific or universal format.
// Leading and trailing whitespace characters are allowed.
//
//
public static DateTimeOffset Parse(string input)
{
if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
Expand All @@ -612,7 +654,7 @@ public static DateTimeOffset Parse(string input)
// Constructs a DateTimeOffset from a string. The string must specify a
// date and optionally a time in a culture-specific or universal format.
// Leading and trailing whitespace characters are allowed.
//
//
public static DateTimeOffset Parse(string input, IFormatProvider formatProvider)
{
if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
Expand Down Expand Up @@ -642,7 +684,7 @@ public static DateTimeOffset Parse(ReadOnlySpan<char> input, IFormatProvider for
// Constructs a DateTimeOffset from a string. The string must specify a
// date and optionally a time in a culture-specific or universal format.
// Leading and trailing whitespace characters are allowed.
//
//
public static DateTimeOffset ParseExact(string input, string format, IFormatProvider formatProvider)
{
if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
Expand All @@ -653,7 +695,7 @@ public static DateTimeOffset ParseExact(string input, string format, IFormatProv
// Constructs a DateTimeOffset from a string. The string must specify a
// date and optionally a time in a culture-specific or universal format.
// Leading and trailing whitespace characters are allowed.
//
//
public static DateTimeOffset ParseExact(string input, string format, IFormatProvider formatProvider, DateTimeStyles styles)
{
styles = ValidateStyles(styles, nameof(styles));
Expand Down Expand Up @@ -943,7 +985,7 @@ private static DateTimeStyles ValidateStyles(DateTimeStyles style, string parame
// RoundtripKind does not make sense for DateTimeOffset; ignore this flag for backward compatibility with DateTime
style &= ~DateTimeStyles.RoundtripKind;

// AssumeLocal is also ignored as that is what we do by default with DateTimeOffset.Parse
// AssumeLocal is also ignored as that is what we do by default with DateTimeOffset.Parse
style &= ~DateTimeStyles.AssumeLocal;

return style;
Expand Down

0 comments on commit 75707e7

Please sign in to comment.