From f79f7c8b348e38378b2a4b2566fafe53172ae250 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 8 Nov 2018 11:44:03 -0800 Subject: [PATCH] Leap Seconds Support --- .../Microsoft.NETCore.Native.Windows.props | 13 +- .../NtDll/Interop.NtQuerySystemInformation.cs | 24 +++ .../kernel32/Interop.FileTimeToSystemTime.cs | 15 ++ .../kernel32/Interop.FullSystemTime.cs | 59 +++++++ .../kernel32/Interop.GetProcessInformation.cs | 17 ++ .../Windows/kernel32/Interop.GetSystemTime.cs | 15 ++ .../kernel32/Interop.SystemTimeToFileTime.cs | 15 ++ ...Interop.TzSpecificLocalTimeToSystemTime.cs | 15 ++ .../shared/System/DateTime.cs | 133 ++++++++++++--- .../shared/System/DateTimeOffset.cs | 60 ++++++- .../src/System.Private.CoreLib.csproj | 23 +++ .../src/System/DateTime.Unix.CoreRT.cs | 29 ++++ .../src/System/DateTime.Win32.cs | 19 +++ .../src/System/DateTime.WinRT.cs | 20 +++ .../src/System/DateTime.Windows.CoreRT.cs | 155 ++++++++++++++++++ 15 files changed, 572 insertions(+), 40 deletions(-) create mode 100644 src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs create mode 100644 src/Common/src/Interop/Windows/kernel32/Interop.FileTimeToSystemTime.cs create mode 100644 src/Common/src/Interop/Windows/kernel32/Interop.FullSystemTime.cs create mode 100644 src/Common/src/Interop/Windows/kernel32/Interop.GetProcessInformation.cs create mode 100644 src/Common/src/Interop/Windows/kernel32/Interop.GetSystemTime.cs create mode 100644 src/Common/src/Interop/Windows/kernel32/Interop.SystemTimeToFileTime.cs create mode 100644 src/Common/src/Interop/Windows/kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs create mode 100644 src/System.Private.CoreLib/src/System/DateTime.Win32.cs create mode 100644 src/System.Private.CoreLib/src/System/DateTime.WinRT.cs diff --git a/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props b/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props index b06165b8a84..aa458fcaecd 100644 --- a/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props +++ b/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props @@ -21,9 +21,9 @@ See the LICENSE file in the project root for more information. Runtime Runtime.ServerGC - + - + @@ -47,9 +47,10 @@ See the LICENSE file in the project root for more information. - + + @@ -62,7 +63,7 @@ See the LICENSE file in the project root for more information. - + @@ -71,11 +72,11 @@ See the LICENSE file in the project root for more information. - + - + diff --git a/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs b/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs new file mode 100644 index 00000000000..5f345fb012c --- /dev/null +++ b/src/Common/src/Interop/Windows/NtDll/Interop.NtQuerySystemInformation.cs @@ -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, SetLastError = true)] + internal static unsafe extern int NtQuerySystemInformation(int SystemInformationClass, void* SystemInformation, int SystemInformationLength, IntPtr ReturnLength); + + [StructLayout(LayoutKind.Sequential)] + internal struct SYSTEM_LEAP_SECOND_INFORMATION + { + public bool Enabled; + public uint Flags; + } + + internal const int SystemLeapSecondInformation = 206; + } +} diff --git a/src/Common/src/Interop/Windows/kernel32/Interop.FileTimeToSystemTime.cs b/src/Common/src/Interop/Windows/kernel32/Interop.FileTimeToSystemTime.cs new file mode 100644 index 00000000000..dc9cc44250b --- /dev/null +++ b/src/Common/src/Interop/Windows/kernel32/Interop.FileTimeToSystemTime.cs @@ -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 bool FileTimeToSystemTime(ref long lpFileTime, ref FullSystemTime lpSystemTime); + } +} diff --git a/src/Common/src/Interop/Windows/kernel32/Interop.FullSystemTime.cs b/src/Common/src/Interop/Windows/kernel32/Interop.FullSystemTime.cs new file mode 100644 index 00000000000..163265815bc --- /dev/null +++ b/src/Common/src/Interop/Windows/kernel32/Interop.FullSystemTime.cs @@ -0,0 +1,59 @@ +// 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 + { + // FullSystemTime struct matches Windows SYSTEMTIME struct, except we added the extra nanoSeconds field to store + // more precise time. + [StructLayout(LayoutKind.Sequential)] + internal struct FullSystemTime + { + internal FullSystemTime(int year, int month, DayOfWeek dayOfWeek, int day, int hour, int minute, int second) + { + wYear = (ushort) year; + wMonth = (ushort) month; + wDayOfWeek = (ushort) dayOfWeek; + wDay = (ushort) day; + wHour = (ushort) hour; + wMinute = (ushort) minute; + wSecond = (ushort) second; + wMillisecond = 0; + hundredNanoSecond = 0; + } + + internal FullSystemTime(long ticks) + { + DateTime dt = new DateTime(ticks); + + int year, month, day; + dt.GetDatePart(out year, out month, out day); + + wYear = (ushort) year; + wMonth = (ushort) month; + wDayOfWeek = (ushort) dt.DayOfWeek; + wDay = (ushort) day; + wHour = (ushort) dt.Hour; + wMinute = (ushort) dt.Minute; + wSecond = (ushort) dt.Second; + wMillisecond = (ushort) dt.Millisecond; + hundredNanoSecond = 0; + } + + internal ushort wYear; + internal ushort wMonth; + internal ushort wDayOfWeek; + internal ushort wDay; + internal ushort wHour; + internal ushort wMinute; + internal ushort wSecond; + internal ushort wMillisecond; + internal long hundredNanoSecond; + }; + } +} \ No newline at end of file diff --git a/src/Common/src/Interop/Windows/kernel32/Interop.GetProcessInformation.cs b/src/Common/src/Interop/Windows/kernel32/Interop.GetProcessInformation.cs new file mode 100644 index 00000000000..a4af7bd8684 --- /dev/null +++ b/src/Common/src/Interop/Windows/kernel32/Interop.GetProcessInformation.cs @@ -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, SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern bool GetProcessInformation(IntPtr hProcess, int ProcessInformationClass, ref long processInformation, int ProcessInformationSize); + } +} diff --git a/src/Common/src/Interop/Windows/kernel32/Interop.GetSystemTime.cs b/src/Common/src/Interop/Windows/kernel32/Interop.GetSystemTime.cs new file mode 100644 index 00000000000..58b9bcefacd --- /dev/null +++ b/src/Common/src/Interop/Windows/kernel32/Interop.GetSystemTime.cs @@ -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 FullSystemTime lpSystemTime); + } +} diff --git a/src/Common/src/Interop/Windows/kernel32/Interop.SystemTimeToFileTime.cs b/src/Common/src/Interop/Windows/kernel32/Interop.SystemTimeToFileTime.cs new file mode 100644 index 00000000000..c6ba39ba77c --- /dev/null +++ b/src/Common/src/Interop/Windows/kernel32/Interop.SystemTimeToFileTime.cs @@ -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.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + // api-ms-win-core-timezone-l1-1-0.dll + [DllImport(Libraries.Kernel32, SetLastError = true)] + internal static extern bool SystemTimeToFileTime(ref FullSystemTime lpSystemTime, out long lpFileTime); + } +} diff --git a/src/Common/src/Interop/Windows/kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs b/src/Common/src/Interop/Windows/kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs new file mode 100644 index 00000000000..a69f0b1751c --- /dev/null +++ b/src/Common/src/Interop/Windows/kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs @@ -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 extern bool TzSpecificLocalTimeToSystemTime(IntPtr lpTimeZoneInformation, ref FullSystemTime lpLocalTime, ref FullSystemTime lpUniversalTime); + } +} diff --git a/src/System.Private.CoreLib/shared/System/DateTime.cs b/src/System.Private.CoreLib/shared/System/DateTime.cs index 4cba9f0db43..b1bd0f04276 100644 --- a/src/System.Private.CoreLib/shared/System/DateTime.cs +++ b/src/System.Private.CoreLib/shared/System/DateTime.cs @@ -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_isLeapSecondsSupportedSystem && 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)); } @@ -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_isLeapSecondsSupportedSystem && 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)); } @@ -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_isLeapSecondsSupportedSystem) + { + // 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, @@ -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_isLeapSecondsSupportedSystem && 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) @@ -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_isLeapSecondsSupportedSystem && 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) @@ -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_isLeapSecondsSupportedSystem) + { + // 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) @@ -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_isLeapSecondsSupportedSystem) + { + // 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) @@ -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) @@ -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); @@ -1574,7 +1639,7 @@ 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; } @@ -1582,6 +1647,24 @@ internal static bool TryCreate(int year, int month, int day, int hour, int minut { return false; } + + if (second == 60) + { + if (s_isLeapSecondsSupportedSystem && 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; diff --git a/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs b/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs index e89cbc6caf8..a430d2dac6f 100644 --- a/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs +++ b/src/System.Private.CoreLib/shared/System/DateTimeOffset.cs @@ -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 @@ -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, IEquatable, ISerializable, IDeserializationCallback, ISpanFormattable { // Constants @@ -109,7 +109,21 @@ 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_isLeapSecondsSupportedSystem) + { + // 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, @@ -117,7 +131,21 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se 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_isLeapSecondsSupportedSystem) + { + // 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, @@ -125,7 +153,21 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se 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_isLeapSecondsSupportedSystem) + { + // 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 @@ -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); @@ -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); @@ -642,7 +684,7 @@ public static DateTimeOffset Parse(ReadOnlySpan 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); @@ -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)); @@ -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; diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 0dfa2952d1d..99b2322e182 100644 --- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -351,8 +351,31 @@ + + + + Interop\Windows\kernel32\Interop.FileTimeToSystemTime.cs + + + Interop\Windows\kernel32\Interop.FullSystemTime.cs + + + Interop\Windows\kernel32\Interop.GetSystemTime.cs + + + Interop\Windows\kernel32\Interop.SystemTimeToFileTime.cs + + + Interop\Windows\kernel32\Interop.TzSpecificLocalTimeToSystemTime.cs + + + Interop\Windows\NtDll\Interop.NtQuerySystemInformation.cs + + + Interop\Windows\kernel32\Interop.GetProcessInformation.cs + Interop\Windows\kernel32\Interop.ExpandEnvironmentStrings.cs diff --git a/src/System.Private.CoreLib/src/System/DateTime.Unix.CoreRT.cs b/src/System.Private.CoreLib/src/System/DateTime.Unix.CoreRT.cs index 6c1c4bacac4..05a50af382b 100644 --- a/src/System.Private.CoreLib/src/System/DateTime.Unix.CoreRT.cs +++ b/src/System.Private.CoreLib/src/System/DateTime.Unix.CoreRT.cs @@ -6,6 +6,8 @@ namespace System { public readonly partial struct DateTime { + internal static readonly bool s_isLeapSecondsSupportedSystem = false; + public static DateTime UtcNow { get @@ -14,5 +16,32 @@ public static DateTime UtcNow return new DateTime(((ulong)(Interop.Sys.GetSystemTimeAsTicks() + DateTime.UnixEpochTicks)) | KindUtc); } } + + 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); + } + + 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; + } + + // 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; } } diff --git a/src/System.Private.CoreLib/src/System/DateTime.Win32.cs b/src/System.Private.CoreLib/src/System/DateTime.Win32.cs new file mode 100644 index 00000000000..b1f7454d398 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/DateTime.Win32.cs @@ -0,0 +1,19 @@ +// 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. + +namespace System +{ + public readonly partial struct DateTime + { + private static unsafe bool IsLeapSecondsSupportedSystem() + { + Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION slsi = new Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION(); + return Interop.NtDll.NtQuerySystemInformation( + Interop.NtDll.SystemLeapSecondInformation, + (void *) &slsi, + sizeof(Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION), + IntPtr.Zero) == 0 && slsi.Enabled; + } + } +} diff --git a/src/System.Private.CoreLib/src/System/DateTime.WinRT.cs b/src/System.Private.CoreLib/src/System/DateTime.WinRT.cs new file mode 100644 index 00000000000..c677678eef3 --- /dev/null +++ b/src/System.Private.CoreLib/src/System/DateTime.WinRT.cs @@ -0,0 +1,20 @@ +// 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. + +namespace System +{ + public readonly partial struct DateTime + { + private static unsafe bool IsLeapSecondsSupportedSystem() + { + long l = 0; + + return Interop.Kernel32.GetProcessInformation( + Interop.mincore.GetCurrentProcess(), + Interop.Kernel32.ProcessLeapSecondInfo, + ref l, + sizeof(long)); + } + } +} diff --git a/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreRT.cs b/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreRT.cs index a8abb5fd97c..3a4422c5bcd 100644 --- a/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreRT.cs +++ b/src/System.Private.CoreLib/src/System/DateTime.Windows.CoreRT.cs @@ -2,19 +2,174 @@ // 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.CompilerServices; + namespace System { public readonly partial struct DateTime { + internal static readonly bool s_isLeapSecondsSupportedSystem = IsLeapSecondsSupportedSystem(); + public static unsafe DateTime UtcNow { get { long ticks; + + if (s_isLeapSecondsSupportedSystem) + { + Interop.Kernel32.FullSystemTime time = new Interop.Kernel32.FullSystemTime(); + GetSystemTimeWithLeapSecondsHandling(ref time); + return CreateDateTimeFromSystemTime(ref time); + } + Interop.mincore.GetSystemTimeAsFileTime(&ticks); // For performance, use a private constructor that does not validate arguments. return new DateTime(((ulong)(ticks + FileTimeOffset)) | KindUtc); } } + + public static DateTime FromFileTimeUtc(long fileTime) + { + if (fileTime < 0 || fileTime > MaxTicks - FileTimeOffset) + { + throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid); + } + + if (s_isLeapSecondsSupportedSystem) + { + return InternalFromFileTime(fileTime); + } + + // This is the ticks in Universal time for this fileTime. + long universalTicks = fileTime + FileTimeOffset; + return new DateTime(universalTicks, DateTimeKind.Utc); + } + + public long ToFileTimeUtc() { + // Treats the input as universal if it is not specified + long ticks = ((InternalKind & LocalMask) != 0) ? ToUniversalTime().InternalTicks : this.InternalTicks; + + if (s_isLeapSecondsSupportedSystem) + { + return InternalToFileTime(ticks); + } + + ticks -= FileTimeOffset; + if (ticks < 0) { + throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid); + } + + return ticks; + } + + internal static bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) + { + DateTime dt = new DateTime(year, month, day); + Interop.Kernel32.FullSystemTime time = new Interop.Kernel32.FullSystemTime(year, month, dt.DayOfWeek, day, hour, minute, second); + + switch (kind) + { + case DateTimeKind.Local: return ValidateSystemTime(ref time, localTime: true); + case DateTimeKind.Utc: return ValidateSystemTime(ref time, localTime: false); + default: + return ValidateSystemTime(ref time, localTime: true) || ValidateSystemTime(ref time, localTime: false); + } + } + + internal static DateTime InternalFromFileTime(long fileTime) + { + Interop.Kernel32.FullSystemTime time = new Interop.Kernel32.FullSystemTime(); + if (SystemFileTimeToSystemTime(fileTime, ref time)) + { + time.hundredNanoSecond = fileTime % TicksPerMillisecond; + return CreateDateTimeFromSystemTime(ref time); + } + + throw new ArgumentOutOfRangeException("fileTime", SR.ArgumentOutOfRange_DateTimeBadTicks); + } + + internal static long InternalToFileTime(long ticks) + { + long fileTime = 0; + Interop.Kernel32.FullSystemTime time = new Interop.Kernel32.FullSystemTime(ticks); + if (Interop.Kernel32.SystemTimeToFileTime(ref time, out fileTime)) + { + return fileTime + ticks % TicksPerMillisecond; + } + + throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static DateTime CreateDateTimeFromSystemTime(ref Interop.Kernel32.FullSystemTime time) + { + long ticks = DateToTicks(time.wYear, time.wMonth, time.wDay); + ticks += TimeToTicks(time.wHour, time.wMinute, time.wSecond); + ticks += time.wMillisecond * TicksPerMillisecond; + ticks += time.hundredNanoSecond; + return new DateTime( ((UInt64)(ticks)) | KindUtc); + } + + private static unsafe bool ValidateSystemTime(ref Interop.Kernel32.FullSystemTime time, bool localTime) + { + if (localTime) + { + Interop.Kernel32.FullSystemTime st = new Interop.Kernel32.FullSystemTime(); + return Interop.Kernel32.TzSpecificLocalTimeToSystemTime(IntPtr.Zero, ref time, ref st); + } + else + { + long timestamp; + return Interop.Kernel32.SystemTimeToFileTime(ref time, out timestamp); + } + } + + private static bool SystemFileTimeToSystemTime(long fileTime, ref Interop.Kernel32.FullSystemTime time) + { + long timestamp = 0; + + if (Interop.Kernel32.FileTimeToSystemTime(ref fileTime, ref time)) + { + // to keep the time precision + time.hundredNanoSecond = timestamp % TicksPerMillisecond; + if (time.wSecond > 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.wSecond = 59; + time.wMillisecond = 999; + time.hundredNanoSecond = 9999; + } + return true; + } + return false; + } + + private static unsafe void GetSystemTimeWithLeapSecondsHandling(ref Interop.Kernel32.FullSystemTime time) + { + long timestamp; + Interop.mincore.GetSystemTimeAsFileTime(×tamp); + + if (Interop.Kernel32.FileTimeToSystemTime(ref timestamp, ref time)) + { + // to keep the time precision + time.hundredNanoSecond = timestamp % TicksPerMillisecond; + } + else + { + Interop.Kernel32.GetSystemTime(ref time); + time.hundredNanoSecond = 0; + } + + if (time.wSecond > 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.wSecond = 59; + time.wMillisecond = 999; + time.hundredNanoSecond = 9999; + } + } } }