From e31f845d1f12e07a76afcd534f7878dc0c87ff30 Mon Sep 17 00:00:00 2001 From: Pent Ploompuu Date: Tue, 24 Nov 2020 04:33:49 +0200 Subject: [PATCH] Optimize System.DateTime --- src/coreclr/classlibnative/bcltype/system.h | 6 - .../Kernel32/Interop.FileTimeToSystemTime.cs | 2 +- .../Kernel32/Interop.SystemTimeToFileTime.cs | 2 +- .../src/Resources/Strings.resx | 3 - .../src/System/DateTime.Unix.cs | 6 +- .../src/System/DateTime.Windows.cs | 70 +- .../src/System/DateTime.cs | 782 ++++++++---------- .../src/System/DateTimeOffset.cs | 6 +- .../Globalization/DateTimeFormatInfo.cs | 23 +- .../EastAsianLunisolarCalendar.cs | 4 +- .../System/Globalization/GregorianCalendar.cs | 42 +- .../Globalization/GregorianCalendarHelper.cs | 2 +- .../System/Globalization/HebrewCalendar.cs | 2 +- .../src/System/Globalization/HijriCalendar.cs | 2 +- .../System/Globalization/JulianCalendar.cs | 2 +- .../System/Globalization/PersianCalendar.cs | 2 +- .../System/Globalization/UmAlQuraCalendar.cs | 2 +- .../Serialization/SerializationInfo.cs | 3 +- .../src/System/ThrowHelper.cs | 6 + .../tests/System/DateTimeOffsetTests.cs | 4 +- .../tests/System/DateTimeTests.cs | 57 +- 21 files changed, 421 insertions(+), 607 deletions(-) diff --git a/src/coreclr/classlibnative/bcltype/system.h b/src/coreclr/classlibnative/bcltype/system.h index fe8b1e476a160..61f2deae89435 100644 --- a/src/coreclr/classlibnative/bcltype/system.h +++ b/src/coreclr/classlibnative/bcltype/system.h @@ -14,12 +14,6 @@ #include "fcall.h" #include "qcall.h" -struct FullSystemTime -{ - SYSTEMTIME systemTime; - INT64 hundredNanoSecond; -}; - class SystemNative { friend class DebugStackTrace; diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.cs index 3a219035f6197..b22ab21c065b6 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.cs @@ -9,6 +9,6 @@ internal static partial class Kernel32 { [DllImport(Libraries.Kernel32)] [SuppressGCTransition] - internal static extern unsafe Interop.BOOL FileTimeToSystemTime(long* lpFileTime, Interop.Kernel32.SYSTEMTIME* lpSystemTime); + internal static extern unsafe Interop.BOOL FileTimeToSystemTime(ulong* lpFileTime, Interop.Kernel32.SYSTEMTIME* lpSystemTime); } } diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs index 69a4a4622d336..41d4b068a7325 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs @@ -9,6 +9,6 @@ internal static partial class Kernel32 { [DllImport(Libraries.Kernel32)] [SuppressGCTransition] - internal static extern unsafe Interop.BOOL SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* lpSystemTime, long* lpFileTime); + internal static extern unsafe Interop.BOOL SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* lpSystemTime, ulong* lpFileTime); } } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 8f1b7359b99be..7186e5b4f63a3 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -259,9 +259,6 @@ A datatype misalignment was detected in a load or store instruction. - - Combination of arguments to the DateTime constructor is out of the legal range. - Decimal constructor requires an array or span of four valid decimal bytes. diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs index 7c1df12cbdf76..2fd3e7368e5f4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs @@ -15,10 +15,10 @@ public static DateTime UtcNow } } - private static DateTime FromFileTimeLeapSecondsAware(long fileTime) => default; - private static long ToFileTimeLeapSecondsAware(long ticks) => default; + private static DateTime FromFileTimeLeapSecondsAware(ulong fileTime) => default; + private static ulong ToFileTimeLeapSecondsAware(long ticks) => default; // 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; + internal static bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, DateTimeKind kind) => false; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs index 9f2bf534d2d13..e43013b673f5c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs @@ -15,7 +15,7 @@ public static unsafe DateTime UtcNow { get { - long fileTime; + ulong fileTime; s_pfnGetSystemTimeAsFileTime(&fileTime); if (s_systemSupportsLeapSeconds) @@ -25,7 +25,7 @@ public static unsafe DateTime UtcNow if (Interop.Kernel32.FileTimeToSystemTime(&fileTime, &time.systemTime) != Interop.BOOL.FALSE) { // to keep the time precision - time.hundredNanoSecond = fileTime % 10000; // 10000 is the number of 100-nano seconds per Millisecond + time.hundredNanoSecond = (uint)(fileTime % 10000); // 10000 is the number of 100-nano seconds per Millisecond } else { @@ -33,28 +33,19 @@ public static unsafe DateTime UtcNow time.hundredNanoSecond = 0; } - if (time.systemTime.Second > 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.systemTime.Second = 59; - time.systemTime.Milliseconds = 999; - time.hundredNanoSecond = 9999; - } - return CreateDateTimeFromSystemTime(in time); } else { - return new DateTime(((ulong)(fileTime + FileTimeOffset)) | KindUtc); + return new DateTime(fileTime + FileTimeOffset | KindUtc); } } } - internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) + internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, DateTimeKind kind) { DateTime dt = new DateTime(year, month, day); - FullSystemTime time = new FullSystemTime(year, month, dt.DayOfWeek, day, hour, minute, second); + FullSystemTime time = new FullSystemTime(year, month, dt.DayOfWeek, day, hour, minute, 60); if (kind != DateTimeKind.Utc) { @@ -65,7 +56,7 @@ internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int if (kind != DateTimeKind.Local) { - long ft; + ulong ft; if (Interop.Kernel32.SystemTimeToFileTime(&time.systemTime, &ft) != Interop.BOOL.FALSE) return true; } @@ -73,7 +64,7 @@ internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int return false; } - private static unsafe DateTime FromFileTimeLeapSecondsAware(long fileTime) + private static unsafe DateTime FromFileTimeLeapSecondsAware(ulong fileTime) { FullSystemTime time; if (Interop.Kernel32.FileTimeToSystemTime(&fileTime, &time.systemTime) == Interop.BOOL.FALSE) @@ -82,39 +73,44 @@ private static unsafe DateTime FromFileTimeLeapSecondsAware(long fileTime) } // to keep the time precision - time.hundredNanoSecond = fileTime % TicksPerMillisecond; - if (time.systemTime.Second > 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.systemTime.Second = 59; - time.systemTime.Milliseconds = 999; - time.hundredNanoSecond = 9999; - } + time.hundredNanoSecond = (uint)(fileTime % TicksPerMillisecond); return CreateDateTimeFromSystemTime(in time); } - private static unsafe long ToFileTimeLeapSecondsAware(long ticks) + private static unsafe ulong ToFileTimeLeapSecondsAware(long ticks) { FullSystemTime time = new FullSystemTime(ticks); - long fileTime; + ulong fileTime; if (Interop.Kernel32.SystemTimeToFileTime(&time.systemTime, &fileTime) == Interop.BOOL.FALSE) { throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid); } - return fileTime + ticks % TicksPerMillisecond; + return fileTime + (ulong)ticks % TicksPerMillisecond; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static DateTime CreateDateTimeFromSystemTime(in FullSystemTime time) { - long ticks = DateToTicks(time.systemTime.Year, time.systemTime.Month, time.systemTime.Day); - ticks += TimeToTicks(time.systemTime.Hour, time.systemTime.Minute, time.systemTime.Second); - ticks += time.systemTime.Milliseconds * TicksPerMillisecond; - ticks += time.hundredNanoSecond; - return new DateTime(((ulong)(ticks)) | KindUtc); + uint year = time.systemTime.Year; + uint[] days = IsLeapYear((int)year) ? s_daysToMonth366 : s_daysToMonth365; + uint n = DaysToYear(year) + days[time.systemTime.Month - 1] + time.systemTime.Day - 1; + ulong ticks = n * (ulong)TicksPerDay; + + ticks += time.systemTime.Hour * (ulong)TicksPerHour; + ticks += time.systemTime.Minute * (ulong)TicksPerMinute; + uint second = time.systemTime.Second; + if (second <= 59) + { + ulong tmp = second * (uint)TicksPerSecond + time.systemTime.Milliseconds * (uint)TicksPerMillisecond + time.hundredNanoSecond; + return new DateTime(ticks + tmp | KindUtc); + } + + // 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 + ticks += TicksPerMinute - 1 | KindUtc; + return new DateTime(ticks); } // FullSystemTime struct is the SYSTEMTIME struct with extra hundredNanoSecond field to store more precise time. @@ -122,7 +118,7 @@ private static DateTime CreateDateTimeFromSystemTime(in FullSystemTime time) private struct FullSystemTime { internal Interop.Kernel32.SYSTEMTIME systemTime; - internal long hundredNanoSecond; + internal uint hundredNanoSecond; internal FullSystemTime(int year, int month, DayOfWeek dayOfWeek, int day, int hour, int minute, int second) { @@ -156,9 +152,9 @@ internal FullSystemTime(long ticks) } } - private static unsafe readonly delegate* unmanaged[SuppressGCTransition] s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr(); + private static unsafe readonly delegate* unmanaged[SuppressGCTransition] s_pfnGetSystemTimeAsFileTime = GetGetSystemTimeAsFileTimeFnPtr(); - private static unsafe delegate* unmanaged[SuppressGCTransition] GetGetSystemTimeAsFileTimeFnPtr() + private static unsafe delegate* unmanaged[SuppressGCTransition] GetGetSystemTimeAsFileTimeFnPtr() { IntPtr kernel32Lib = Interop.Kernel32.LoadLibraryEx(Interop.Libraries.Kernel32, IntPtr.Zero, Interop.Kernel32.LOAD_LIBRARY_SEARCH_SYSTEM32); Debug.Assert(kernel32Lib != IntPtr.Zero); @@ -192,7 +188,7 @@ internal FullSystemTime(long ticks) } } - return (delegate* unmanaged[SuppressGCTransition])pfnGetSystemTime; + return (delegate* unmanaged[SuppressGCTransition])pfnGetSystemTime; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index f45333cbad1cf..5a1ec7e687f13 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -4,11 +4,9 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Serialization; -using CultureInfo = System.Globalization.CultureInfo; -using Calendar = System.Globalization.Calendar; namespace System { @@ -98,12 +96,15 @@ namespace System private const int DatePartMonth = 2; private const int DatePartDay = 3; - private static readonly int[] s_daysToMonth365 = { + private static readonly uint[] s_daysToMonth365 = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; - private static readonly int[] s_daysToMonth366 = { + private static readonly uint[] s_daysToMonth366 = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }; - public static readonly DateTime MinValue = new DateTime(MinTicks, DateTimeKind.Unspecified); + private static ReadOnlySpan DaysInMonth365 => new byte[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + private static ReadOnlySpan DaysInMonth366 => new byte[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + public static readonly DateTime MinValue; public static readonly DateTime MaxValue = new DateTime(MaxTicks, DateTimeKind.Unspecified); public static readonly DateTime UnixEpoch = new DateTime(UnixEpochTicks, DateTimeKind.Utc); @@ -136,8 +137,7 @@ namespace System // public DateTime(long ticks) { - if (ticks < MinTicks || ticks > MaxTicks) - throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_DateTimeBadTicks); + if ((ulong)ticks > MaxTicks) ThrowTicksOutOfRange(); _dateData = (ulong)ticks; } @@ -148,33 +148,33 @@ private DateTime(ulong dateData) public DateTime(long ticks, DateTimeKind kind) { - if (ticks < MinTicks || ticks > MaxTicks) - { - throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_DateTimeBadTicks); - } - if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local) - { - throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind)); - } - _dateData = ((ulong)ticks | ((ulong)kind << KindShift)); + if ((ulong)ticks > MaxTicks) ThrowTicksOutOfRange(); + if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); + _dateData = (ulong)ticks | ((ulong)(uint)kind << KindShift); } internal DateTime(long ticks, DateTimeKind kind, bool isAmbiguousDst) { - if (ticks < MinTicks || ticks > MaxTicks) - { - throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_DateTimeBadTicks); - } + if ((ulong)ticks > MaxTicks) ThrowTicksOutOfRange(); Debug.Assert(kind == DateTimeKind.Local, "Internal Constructor is for local times only"); _dateData = ((ulong)ticks | (isAmbiguousDst ? KindLocalAmbiguousDst : KindLocal)); } + private static void ThrowTicksOutOfRange() => throw new ArgumentOutOfRangeException("ticks", SR.ArgumentOutOfRange_DateTimeBadTicks); + private static void ThrowInvalidKind() => throw new ArgumentException(SR.Argument_InvalidDateTimeKind, "kind"); + private static void ThrowMillisecondOutOfRange() => throw new ArgumentOutOfRangeException("millisecond", SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1)); + + private static void ThrowDateArithmetic(int param) + { + throw new ArgumentOutOfRangeException(param switch { 0 => "value", 1 => "t", _ => "months" }, SR.ArgumentOutOfRange_DateArithmetic); + } + // Constructs a DateTime from a given year, month, and day. The // time-of-day of the resulting DateTime is always midnight. // public DateTime(int year, int month, int day) { - _dateData = (ulong)DateToTicks(year, month, day); + _dateData = DateToTicks(year, month, day); } // Constructs a DateTime from a given year, month, and day for @@ -191,35 +191,43 @@ 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 (second != 60 || !s_systemSupportsLeapSeconds) { - // 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 = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); + } + else + { + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute) + { + t = new DateTime(year, month, day, hour, minute, 59); + t.ValidateLeapSecond(); + } } - _dateData = (ulong)(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second)); } public DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) { - if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local) + if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); + + if (second != 60 || !s_systemSupportsLeapSeconds) { - throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind)); + ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); + _dateData = ticks | ((ulong)kind << KindShift); } - - if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, kind)) + else { - // 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; + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute, kind); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute, DateTimeKind kind) + { + t = new DateTime(year, month, day, hour, minute, 59, kind); + t.ValidateLeapSecond(); + } } - - long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); - _dateData = ((ulong)ticks | ((ulong)kind << KindShift)); } // Constructs a DateTime from a given year, month, day, hour, @@ -230,21 +238,19 @@ 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) + 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 = calendar.ToDateTime(year, month, day, hour, minute, second, 0).UTicks; } - - _dateData = (ulong)calendar.ToDateTime(year, month, day, hour, minute, second, 0).Ticks; - - if (originalSecond == 60) + else { - DateTime dt = new DateTime(_dateData); - if (!IsValidTimeWithLeapSeconds(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 60, DateTimeKind.Unspecified)) + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute, calendar); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute, Calendar calendar) { - throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); + t = new DateTime(year, month, day, hour, minute, 59, calendar); + t.ValidateLeapSecond(); } } } @@ -254,52 +260,51 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, // public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond) { - if (millisecond < 0 || millisecond >= MillisPerSecond) + if ((uint)millisecond >= MillisPerSecond) ThrowMillisecondOutOfRange(); + + if (second != 60 || !s_systemSupportsLeapSeconds) { - throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1)); + ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); + ticks += (uint)millisecond * (uint)TicksPerMillisecond; + Debug.Assert(ticks <= MaxTicks, "Input parameters validated already"); + _dateData = ticks; } - - if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, DateTimeKind.Unspecified)) + else { - // 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; + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute) + { + t = new DateTime(year, month, day, hour, minute, 59, 999); + t.ValidateLeapSecond(); + } } - - long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); - ticks += millisecond * TicksPerMillisecond; - if (ticks < MinTicks || ticks > MaxTicks) - throw new ArgumentException(SR.Arg_DateTimeRange); - _dateData = (ulong)ticks; } public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind) { - if (millisecond < 0 || millisecond >= MillisPerSecond) - { - throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1)); - } - if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local) + if ((uint)millisecond >= MillisPerSecond) ThrowMillisecondOutOfRange(); + if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); + + if (second != 60 || !s_systemSupportsLeapSeconds) { - throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind)); + ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); + ticks += (uint)millisecond * (uint)TicksPerMillisecond; + Debug.Assert(ticks <= MaxTicks, "Input parameters validated already"); + _dateData = ticks | ((ulong)kind << KindShift); } - - if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, second, kind)) + else { - // 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; + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute, kind); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute, DateTimeKind kind) + { + t = new DateTime(year, month, day, hour, minute, 59, 999, kind); + t.ValidateLeapSecond(); + } } - - long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); - ticks += millisecond * TicksPerMillisecond; - if (ticks < MinTicks || ticks > MaxTicks) - throw new ArgumentException(SR.Arg_DateTimeRange); - _dateData = ((ulong)ticks | ((ulong)kind << KindShift)); } // Constructs a DateTime from a given year, month, day, hour, @@ -309,30 +314,20 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, { if (calendar == null) throw new ArgumentNullException(nameof(calendar)); - if (millisecond < 0 || millisecond >= MillisPerSecond) - { - throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1)); - } - int originalSecond = second; - if (second == 60 && s_systemSupportsLeapSeconds) + 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 = calendar.ToDateTime(year, month, day, hour, minute, second, millisecond).UTicks; } - - 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) + else { - DateTime dt = new DateTime(_dateData); - if (!IsValidTimeWithLeapSeconds(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 60, DateTimeKind.Unspecified)) + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute, calendar); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute, Calendar calendar) { - throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); + t = new DateTime(year, month, day, hour, minute, 59, 999, calendar); + t.ValidateLeapSecond(); } } } @@ -341,47 +336,40 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, { if (calendar == null) throw new ArgumentNullException(nameof(calendar)); - if (millisecond < 0 || millisecond >= MillisPerSecond) - { - throw new ArgumentOutOfRangeException(nameof(millisecond), SR.Format(SR.ArgumentOutOfRange_Range, 0, MillisPerSecond - 1)); - } - if (kind < DateTimeKind.Unspecified || kind > DateTimeKind.Local) - { - throw new ArgumentException(SR.Argument_InvalidDateTimeKind, nameof(kind)); - } + if ((uint)millisecond >= MillisPerSecond) ThrowMillisecondOutOfRange(); + if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); - int originalSecond = second; - if (second == 60 && s_systemSupportsLeapSeconds) + 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; + ulong ticks = calendar.ToDateTime(year, month, day, hour, minute, second, millisecond).UTicks; + _dateData = ticks | ((ulong)kind << KindShift); } - - 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) + else { - DateTime dt = new DateTime(_dateData); - if (!IsValidTimeWithLeapSeconds(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 60, kind)) + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + WithLeapSecond(out this, year, month, day, hour, minute, calendar, kind); + [MethodImpl(MethodImplOptions.NoInlining)] + static void WithLeapSecond(out DateTime t, int year, int month, int day, int hour, int minute, Calendar calendar, DateTimeKind kind) { - throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); + t = new DateTime(year, month, day, hour, minute, 59, 999, calendar, kind); + t.ValidateLeapSecond(); } } } + private void ValidateLeapSecond() + { + if (!IsValidTimeWithLeapSeconds(Year, Month, Day, Hour, Minute, Kind)) + { + ThrowHelper.ThrowArgumentOutOfRange_BadHourMinuteSecond(); + } + } + private DateTime(SerializationInfo info, StreamingContext context) { - if (info == null) - throw new ArgumentNullException(nameof(info)); + if (info == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); bool foundTicks = false; - bool foundDateData = false; - long serializedTicks = 0; - ulong serializedDateData = 0; // Get the data SerializationInfoEnumerator enumerator = info.GetEnumerator(); @@ -390,38 +378,27 @@ private DateTime(SerializationInfo info, StreamingContext context) switch (enumerator.Name) { case TicksField: - serializedTicks = Convert.ToInt64(enumerator.Value, CultureInfo.InvariantCulture); + _dateData = (ulong)Convert.ToInt64(enumerator.Value, CultureInfo.InvariantCulture); foundTicks = true; - break; + continue; case DateDataField: - serializedDateData = Convert.ToUInt64(enumerator.Value, CultureInfo.InvariantCulture); - foundDateData = true; - break; - default: - // Ignore other fields for forward compatibility. - break; + _dateData = Convert.ToUInt64(enumerator.Value, CultureInfo.InvariantCulture); + goto foundData; } } - if (foundDateData) - { - _dateData = serializedDateData; - } - else if (foundTicks) - { - _dateData = (ulong)serializedTicks; - } - else + if (!foundTicks) { throw new SerializationException(SR.Serialization_MissingDateTimeData); } - long ticks = InternalTicks; - if (ticks < MinTicks || ticks > MaxTicks) + foundData: + if (UTicks > MaxTicks) { throw new SerializationException(SR.Serialization_DateTimeTicksOutOfRange); } } internal long InternalTicks => (long)(_dateData & TicksMask); + private ulong UTicks => _dateData & TicksMask; private ulong InternalKind => _dateData & FlagsMask; @@ -437,12 +414,11 @@ public DateTime Add(TimeSpan value) // time units to this DateTime. private DateTime Add(double value, int scale) { - double millis_double = value * (double)scale + (value >= 0 ? 0.5 : -0.5); - - if (millis_double <= (double)-MaxMillis || millis_double >= (double)MaxMillis) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue); - + double millis_double = value * scale + (value >= 0 ? 0.5 : -0.5); + if (millis_double <= -MaxMillis || millis_double >= MaxMillis) ThrowOutOfRange(); return AddTicks((long)millis_double * TicksPerMillisecond); + + static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_AddValue); } // Returns the DateTime resulting from adding a fractional number of @@ -509,25 +485,27 @@ public DateTime AddMinutes(double value) public DateTime AddMonths(int months) { if (months < -120000 || months > 120000) throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateTimeBadMonths); - GetDate(out int y, out int m, out int d); - int i = m - 1 + months; - if (i >= 0) + GetDate(out int year, out int month, out int day); + int y = year, d = day; + int m = month + months; + if (m > 0) { - m = i % 12 + 1; - y += i / 12; + int q = (int)((uint)(m - 1) / 12); + y += q; + m -= q * 12; } else { - m = 12 + (i + 1) % 12; - y += (i - 11) / 12; + y += m / 12 - 1; + m = 12 + m % 12; } - if (y < 1 || y > 9999) - { - throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateArithmetic); - } - int days = DaysInMonth(y, m); + if (y < 1 || y > 9999) ThrowDateArithmetic(2); + uint[] daysTo = IsLeapYear(y) ? s_daysToMonth366 : s_daysToMonth365; + uint daysToMonth = daysTo[m - 1]; + int days = (int)(daysTo[m] - daysToMonth); if (d > days) d = days; - return new DateTime((ulong)(DateToTicks(y, m, d) + InternalTicks % TicksPerDay) | InternalKind); + uint n = DaysToYear((uint)y) + daysToMonth + (uint)d - 1; + return new DateTime(n * (ulong)TicksPerDay + UTicks % TicksPerDay | InternalKind); } // Returns the DateTime resulting from adding a fractional number of @@ -547,24 +525,21 @@ public DateTime AddSeconds(double value) // public DateTime AddTicks(long value) { - long ticks = InternalTicks; - if (value > MaxTicks - ticks || value < MinTicks - ticks) - { - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_DateArithmetic); - } - return new DateTime((ulong)(ticks + value) | InternalKind); + ulong ticks = (ulong)(Ticks + value); + if (ticks > MaxTicks) ThrowDateArithmetic(0); + return new DateTime(ticks | InternalKind); } // TryAddTicks is exact as AddTicks except it doesn't throw internal bool TryAddTicks(long value, out DateTime result) { - long ticks = InternalTicks; - if (value > MaxTicks - ticks || value < MinTicks - ticks) + ulong ticks = (ulong)(Ticks + value); + if (ticks > MaxTicks) { result = default; return false; } - result = new DateTime((ulong)(ticks + value) | InternalKind); + result = new DateTime(ticks | InternalKind); return true; } @@ -582,7 +557,23 @@ public DateTime AddYears(int value) { throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_DateTimeBadYears); } - return AddMonths(value * 12); + GetDate(out int year, out int month, out int day); + int y = year + value; + if (y < 1 || y > 9999) ThrowDateArithmetic(0); + uint n = DaysToYear((uint)y); + + int m = month - 1, d = day - 1; + if (IsLeapYear(y)) + { + n += s_daysToMonth366[m]; + } + else + { + if (d == 28 && m == 1) d--; + n += s_daysToMonth365[m]; + } + n += (uint)d; + return new DateTime(n * (ulong)TicksPerDay + UTicks % TicksPerDay | InternalKind); } // Compares two DateTime values, returning an integer that indicates @@ -622,37 +613,43 @@ public int CompareTo(DateTime value) // Returns the tick count corresponding to the given year, month, and day. // Will check the if the parameters are valid. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long DateToTicks(int year, int month, int day) + private static ulong DateToTicks(int year, int month, int day) { if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1) { ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay(); } - int[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365; - if (day > days[month] - days[month - 1]) + uint[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365; + if ((uint)day > days[month] - days[month - 1]) { ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay(); } - int y = year - 1; - int n = y * 365 + y / 4 - y / 100 + y / 400 + days[month - 1] + day - 1; - return n * TicksPerDay; + uint n = DaysToYear((uint)year) + days[month - 1] + (uint)day - 1; + return n * (ulong)TicksPerDay; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint DaysToYear(uint year) + { + uint y = year - 1; + uint cent = y / 100; + return y * (365 * 4 + 1) / 4 - cent + cent / 4; } // Return the tick count corresponding to the given hour, minute, second. // Will check the if the parameters are valid. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long TimeToTicks(int hour, int minute, int second) + private static ulong TimeToTicks(int hour, int minute, int second) { - // TimeSpan.TimeToTicks is a family access function which does no error checking, so - // we need to put some error checking out here. if ((uint)hour >= 24 || (uint)minute >= 60 || (uint)second >= 60) { ThrowHelper.ThrowArgumentOutOfRange_BadHourMinuteSecond(); } - return TimeSpan.TimeToTicks(hour, minute, second); + int totalSeconds = hour * 3600 + minute * 60 + second; + return (uint)totalSeconds * (ulong)TicksPerSecond; } // Returns the number of days in the month given by the year and @@ -660,10 +657,9 @@ private static long TimeToTicks(int hour, int minute, int second) // public static int DaysInMonth(int year, int month) { - if (month < 1 || month > 12) throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); + if (month < 1 || month > 12) ThrowHelper.ThrowArgumentOutOfRange_Month(month); // IsLeapYear checks the year argument - int[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365; - return days[month] - days[month - 1]; + return (IsLeapYear(year) ? DaysInMonth366 : DaysInMonth365)[month - 1]; } // Converts an OLE Date to a tick count. @@ -736,20 +732,20 @@ public static DateTime FromBinary(long dateData) // the UTC offset from MinValue and MaxValue to be consistent with Parse. bool isAmbiguousLocalDst = false; long offsetTicks; - if (ticks < MinTicks) + if (ticks < 0) { - offsetTicks = TimeZoneInfo.GetLocalUtcOffset(DateTime.MinValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; + offsetTicks = TimeZoneInfo.GetLocalUtcOffset(MinValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; } else if (ticks > MaxTicks) { - offsetTicks = TimeZoneInfo.GetLocalUtcOffset(DateTime.MaxValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; + offsetTicks = TimeZoneInfo.GetLocalUtcOffset(MaxValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; } else { // Because the ticks conversion between UTC and local is lossy, we need to capture whether the // time is in a repeated hour so that it can be passed to the DateTime constructor. DateTime utcDt = new DateTime(ticks, DateTimeKind.Utc); - offsetTicks = TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out bool isDaylightSavings, out isAmbiguousLocalDst).Ticks; + offsetTicks = TimeZoneInfo.GetUtcOffsetFromUtc(utcDt, TimeZoneInfo.Local, out _, out isAmbiguousLocalDst).Ticks; } ticks += offsetTicks; // Another behaviour of parsing is to cause small times to wrap around, so that they can be used @@ -758,7 +754,7 @@ public static DateTime FromBinary(long dateData) { ticks += TicksPerDay; } - if (ticks < MinTicks || ticks > MaxTicks) + if ((ulong)ticks > MaxTicks) { throw new ArgumentException(SR.Argument_DateTimeBadBinaryData, nameof(dateData)); } @@ -766,20 +762,12 @@ public static DateTime FromBinary(long dateData) } else { - return DateTime.FromBinaryRaw(dateData); + if (((ulong)dateData & TicksMask) > MaxTicks) + throw new ArgumentException(SR.Argument_DateTimeBadBinaryData, nameof(dateData)); + return new DateTime((ulong)dateData); } } - // A version of ToBinary that uses the real representation and does not adjust local times. This is needed for - // scenarios where the serialized data must maintain compatibility - internal static DateTime FromBinaryRaw(long dateData) - { - long ticks = dateData & (long)TicksMask; - if (ticks < MinTicks || ticks > MaxTicks) - throw new ArgumentException(SR.Argument_DateTimeBadBinaryData, nameof(dateData)); - return new DateTime((ulong)dateData); - } - // Creates a DateTime from a Windows filetime. A Windows filetime is // a long representing the date and time as the number of // 100-nanosecond intervals that have elapsed since 1/1/1601 12:00am. @@ -791,7 +779,7 @@ public static DateTime FromFileTime(long fileTime) public static DateTime FromFileTimeUtc(long fileTime) { - if (fileTime < 0 || fileTime > MaxTicks - FileTimeOffset) + if ((ulong)fileTime > MaxTicks - FileTimeOffset) { throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_FileTimeInvalid); } @@ -799,13 +787,13 @@ public static DateTime FromFileTimeUtc(long fileTime) #pragma warning disable 162 // Unrechable code on Unix if (s_systemSupportsLeapSeconds) { - return FromFileTimeLeapSecondsAware(fileTime); + return FromFileTimeLeapSecondsAware((ulong)fileTime); } #pragma warning restore 162 // This is the ticks in Universal time for this fileTime. - long universalTicks = fileTime + FileTimeOffset; - return new DateTime(universalTicks, DateTimeKind.Utc); + ulong universalTicks = (ulong)fileTime + FileTimeOffset; + return new DateTime(universalTicks | KindUtc); } // Creates a DateTime from an OLE Automation Date. @@ -817,10 +805,7 @@ public static DateTime FromOADate(double d) void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { - if (info == null) - { - throw new ArgumentNullException(nameof(info)); - } + if (info == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); // Serialize both the old and the new format info.AddValue(TicksField, InternalTicks); @@ -829,7 +814,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex public bool IsDaylightSavingTime() { - if (Kind == DateTimeKind.Utc) + if (InternalKind == KindUtc) { return false; } @@ -838,12 +823,13 @@ public bool IsDaylightSavingTime() public static DateTime SpecifyKind(DateTime value, DateTimeKind kind) { - return new DateTime(value.InternalTicks, kind); + if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); + return new DateTime(value.UTicks | ((ulong)kind << KindShift)); } public long ToBinary() { - if (Kind == DateTimeKind.Local) + if ((_dateData & LocalMask) != 0) { // Local times need to be adjusted as you move from one time zone to another, // just as they are when serializing in text. As such the format for local times @@ -878,8 +864,8 @@ public DateTime Date { get { - long ticks = InternalTicks; - return new DateTime((ulong)(ticks - ticks % TicksPerDay) | InternalKind); + ulong ticks = UTicks; + return new DateTime((ticks - ticks % TicksPerDay) | InternalKind); } } @@ -887,49 +873,47 @@ public DateTime Date // to compute the year, day-of-year, month, or day part. private int GetDatePart(int part) { - long ticks = InternalTicks; // n = number of days since 1/1/0001 - int n = (int)(ticks / TicksPerDay); + uint n = (uint)(UTicks / TicksPerDay); // y400 = number of whole 400-year periods since 1/1/0001 - int y400 = n / DaysPer400Years; + uint y400 = n / DaysPer400Years; // n = day number within 400-year period n -= y400 * DaysPer400Years; // y100 = number of whole 100-year periods within 400-year period - int y100 = n / DaysPer100Years; + uint y100 = n / DaysPer100Years; // Last 100-year period has an extra day, so decrement result if 4 if (y100 == 4) y100 = 3; // n = day number within 100-year period n -= y100 * DaysPer100Years; // y4 = number of whole 4-year periods within 100-year period - int y4 = n / DaysPer4Years; + uint y4 = n / DaysPer4Years; // n = day number within 4-year period n -= y4 * DaysPer4Years; // y1 = number of whole years within 4-year period - int y1 = n / DaysPerYear; + uint y1 = n / DaysPerYear; // Last year has an extra day, so decrement result if 4 if (y1 == 4) y1 = 3; // If year was requested, compute and return it if (part == DatePartYear) { - return y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1; + return (int)(y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1); } // n = day number within year n -= y1 * DaysPerYear; // If day-of-year was requested, return it - if (part == DatePartDayOfYear) return n + 1; + if (part == DatePartDayOfYear) return (int)n + 1; // Leap year calculation looks different from IsLeapYear since y1, y4, // and y100 are relative to year 1, not year 0 - bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3); - int[] days = leapYear ? s_daysToMonth366 : s_daysToMonth365; + uint[] days = y1 == 3 && (y4 != 24 || y100 == 3) ? s_daysToMonth366 : s_daysToMonth365; // All months have less than 32 days, so n >> 5 is a good conservative // estimate for the month - int m = (n >> 5) + 1; + uint m = (n >> 5) + 1; // m = 1-based month number while (n >= days[m]) m++; // If month was requested, return it - if (part == DatePartMonth) return m; + if (part == DatePartMonth) return (int)m; // Return 1-based day-of-month - return n - days[m - 1] + 1; + return (int)(n - days[m - 1] + 1); } // Exactly the same as GetDatePart, except computing all of @@ -937,80 +921,79 @@ private int GetDatePart(int part) // are needed rather than redoing the computations for each. internal void GetDate(out int year, out int month, out int day) { - long ticks = InternalTicks; // n = number of days since 1/1/0001 - int n = (int)(ticks / TicksPerDay); + uint n = (uint)(UTicks / TicksPerDay); // y400 = number of whole 400-year periods since 1/1/0001 - int y400 = n / DaysPer400Years; + uint y400 = n / DaysPer400Years; // n = day number within 400-year period n -= y400 * DaysPer400Years; // y100 = number of whole 100-year periods within 400-year period - int y100 = n / DaysPer100Years; + uint y100 = n / DaysPer100Years; // Last 100-year period has an extra day, so decrement result if 4 if (y100 == 4) y100 = 3; // n = day number within 100-year period n -= y100 * DaysPer100Years; // y4 = number of whole 4-year periods within 100-year period - int y4 = n / DaysPer4Years; + uint y4 = n / DaysPer4Years; // n = day number within 4-year period n -= y4 * DaysPer4Years; // y1 = number of whole years within 4-year period - int y1 = n / DaysPerYear; + uint y1 = n / DaysPerYear; // Last year has an extra day, so decrement result if 4 if (y1 == 4) y1 = 3; // compute year - year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1; + year = (int)(y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1); // n = day number within year n -= y1 * DaysPerYear; // dayOfYear = n + 1; // Leap year calculation looks different from IsLeapYear since y1, y4, // and y100 are relative to year 1, not year 0 - bool leapYear = y1 == 3 && (y4 != 24 || y100 == 3); - int[] days = leapYear ? s_daysToMonth366 : s_daysToMonth365; + uint[] days = y1 == 3 && (y4 != 24 || y100 == 3) ? s_daysToMonth366 : s_daysToMonth365; // All months have less than 32 days, so n >> 5 is a good conservative // estimate for the month - int m = (n >> 5) + 1; + uint m = (n >> 5) + 1; // m = 1-based month number while (n >= days[m]) m++; // compute month and day - month = m; - day = n - days[m - 1] + 1; + month = (int)m; + day = (int)(n - days[m - 1] + 1); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void GetTime(out int hour, out int minute, out int second) { - long n = InternalTicks / TicksPerSecond; - n = Math.DivRem(n, 60, out long m); - second = (int)m; - n = Math.DivRem(n, 60, out m); - minute = (int)m; - hour = (int)(n % 24); + ulong seconds = UTicks / TicksPerSecond; + ulong minutes = seconds / 60; + second = (int)(seconds - (minutes * 60)); + ulong hours = minutes / 60; + minute = (int)(minutes - (hours * 60)); + hour = (int)((uint)hours % 24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void GetTime(out int hour, out int minute, out int second, out int millisecond) { - long n = InternalTicks / TicksPerMillisecond; - n = Math.DivRem(n, 1000, out long m); - millisecond = (int)m; - n = Math.DivRem(n, 60, out m); - second = (int)m; - n = Math.DivRem(n, 60, out m); - minute = (int)m; - hour = (int)(n % 24); + ulong milliseconds = UTicks / TicksPerMillisecond; + ulong seconds = milliseconds / 1000; + millisecond = (int)(milliseconds - (seconds * 1000)); + ulong minutes = seconds / 60; + second = (int)(seconds - (minutes * 60)); + ulong hours = minutes / 60; + minute = (int)(minutes - (hours * 60)); + hour = (int)((uint)hours % 24); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void GetTimePrecise(out int hour, out int minute, out int second, out int tick) { - long n = Math.DivRem(InternalTicks, TicksPerSecond, out long m); - tick = (int)m; - n = Math.DivRem(n, 60, out m); - second = (int)m; - n = Math.DivRem(n, 60, out m); - minute = (int)m; - hour = (int)(n % 24); + ulong ticks = UTicks; + ulong seconds = ticks / TicksPerSecond; + tick = (int)(ticks - (seconds * TicksPerSecond)); + ulong minutes = seconds / 60; + second = (int)(seconds - (minutes * 60)); + ulong hours = minutes / 60; + minute = (int)(minutes - (hours * 60)); + hour = (int)((uint)hours % 24); } // Returns the day-of-month part of this DateTime. The returned @@ -1023,7 +1006,7 @@ internal void GetTimePrecise(out int hour, out int minute, out int second, out i // Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates // Thursday, 5 indicates Friday, and 6 indicates Saturday. // - public DayOfWeek DayOfWeek => (DayOfWeek)((InternalTicks / TicksPerDay + 1) % 7); + public DayOfWeek DayOfWeek => (DayOfWeek)(((uint)(UTicks / TicksPerDay) + 1) % 7); // Returns the day-of-year part of this DateTime. The returned value // is an integer between 1 and 366. @@ -1041,7 +1024,7 @@ public override int GetHashCode() // Returns the hour part of this DateTime. The returned value is an // integer between 0 and 23. // - public int Hour => (int)((InternalTicks / TicksPerHour) % 24); + public int Hour => (int)((uint)(UTicks / TicksPerHour) % 24); internal bool IsAmbiguousDaylightSavingTime() => InternalKind == KindLocalAmbiguousDst; @@ -1060,12 +1043,12 @@ public DateTimeKind Kind // Returns the millisecond part of this DateTime. The returned value // is an integer between 0 and 999. // - public int Millisecond => (int)((InternalTicks / TicksPerMillisecond) % 1000); + public int Millisecond => (int)((UTicks / TicksPerMillisecond) % 1000); // Returns the minute part of this DateTime. The returned value is // an integer between 0 and 59. // - public int Minute => (int)((InternalTicks / TicksPerMinute) % 60); + public int Minute => (int)((UTicks / TicksPerMinute) % 60); // Returns the month part of this DateTime. The returned value is an // integer between 1 and 12. @@ -1081,39 +1064,39 @@ public static DateTime Now DateTime utc = UtcNow; long offset = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utc, out bool isAmbiguousLocalDst).Ticks; long tick = utc.Ticks + offset; - if (tick > DateTime.MaxTicks) - { - return new DateTime(DateTime.MaxTicks, DateTimeKind.Local); - } - if (tick < DateTime.MinTicks) + if ((ulong)tick <= MaxTicks) { - return new DateTime(DateTime.MinTicks, DateTimeKind.Local); + if (!isAmbiguousLocalDst) + { + return new DateTime((ulong)tick | KindLocal); + } + return new DateTime((ulong)tick | KindLocalAmbiguousDst); } - return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst); + return new DateTime(tick < 0 ? KindLocal : MaxTicks | KindLocal); } } // Returns the second part of this DateTime. The returned value is // an integer between 0 and 59. // - public int Second => (int)((InternalTicks / TicksPerSecond) % 60); + public int Second => (int)((UTicks / TicksPerSecond) % 60); // Returns the tick count for this DateTime. The returned value is // the number of 100-nanosecond intervals that have elapsed since 1/1/0001 // 12:00am. // - public long Ticks => InternalTicks; + public long Ticks => (long)(_dateData & TicksMask); // Returns the time-of-day part of this DateTime. The returned value // is a TimeSpan that indicates the time elapsed since midnight. // - public TimeSpan TimeOfDay => new TimeSpan(InternalTicks % TicksPerDay); + public TimeSpan TimeOfDay => new TimeSpan((long)(UTicks % TicksPerDay)); // Returns a DateTime representing the current date. The date part // of the returned value is the current date, and the time-of-day part of // the returned value is zero (midnight). // - public static DateTime Today => DateTime.Now.Date; + public static DateTime Today => Now.Date; // Returns the year part of this DateTime. The returned value is an // integer between 1 and 9999. @@ -1130,7 +1113,10 @@ public static bool IsLeapYear(int year) { ThrowHelper.ThrowArgumentOutOfRange_Year(); } - return (year & 3) == 0 && ((year & 15) == 0 || (year % 25) != 0); + if ((year & 3) != 0) return false; + if ((year & 15) == 0) return true; + // return true/false not the test result https://github.com/dotnet/runtime/issues/4207 + return (uint)year % 25 != 0 ? true : false; } // Constructs a DateTime from a string. The string must specify a @@ -1155,14 +1141,14 @@ public static DateTime Parse(string s, IFormatProvider? provider) public static DateTime Parse(string s, IFormatProvider? provider, DateTimeStyles styles) { - DateTimeFormatInfo.ValidateStyles(styles, nameof(styles)); + DateTimeFormatInfo.ValidateStyles(styles, styles: true); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); return DateTimeParse.Parse(s, DateTimeFormatInfo.GetInstance(provider), styles); } public static DateTime Parse(ReadOnlySpan s, IFormatProvider? provider = null, DateTimeStyles styles = DateTimeStyles.None) { - DateTimeFormatInfo.ValidateStyles(styles, nameof(styles)); + DateTimeFormatInfo.ValidateStyles(styles, styles: true); return DateTimeParse.Parse(s, DateTimeFormatInfo.GetInstance(provider), styles); } @@ -1183,7 +1169,7 @@ public static DateTime ParseExact(string s, string format, IFormatProvider? prov // public static DateTime ParseExact(string s, string format, IFormatProvider? provider, DateTimeStyles style) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format); return DateTimeParse.ParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style); @@ -1191,20 +1177,20 @@ public static DateTime ParseExact(string s, string format, IFormatProvider? prov public static DateTime ParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); return DateTimeParse.ParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style); } public static DateTime ParseExact(string s, string[] formats, IFormatProvider? provider, DateTimeStyles style) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); return DateTimeParse.ParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style); } public static DateTime ParseExact(ReadOnlySpan s, string[] formats, IFormatProvider? provider, DateTimeStyles style = DateTimeStyles.None) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); return DateTimeParse.ParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style); } @@ -1215,13 +1201,9 @@ public TimeSpan Subtract(DateTime value) public DateTime Subtract(TimeSpan value) { - long ticks = InternalTicks; - long valueTicks = value._ticks; - if (ticks - MinTicks < valueTicks || ticks - MaxTicks > valueTicks) - { - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_DateArithmetic); - } - return new DateTime((ulong)(ticks - valueTicks) | InternalKind); + ulong ticks = (ulong)(Ticks - value._ticks); + if (ticks > MaxTicks) ThrowDateArithmetic(0); + return new DateTime(ticks | InternalKind); } // This function is duplicated in COMDateTime.cpp @@ -1260,12 +1242,12 @@ public long ToFileTime() public long ToFileTimeUtc() { // Treats the input as universal if it is not specified - long ticks = ((InternalKind & LocalMask) != 0) ? ToUniversalTime().InternalTicks : this.InternalTicks; + long ticks = ((_dateData & LocalMask) != 0) ? ToUniversalTime().InternalTicks : this.InternalTicks; #pragma warning disable 162 // Unrechable code on Unix if (s_systemSupportsLeapSeconds) { - return ToFileTimeLeapSecondsAware(ticks); + return (long)ToFileTimeLeapSecondsAware(ticks); } #pragma warning restore 162 @@ -1280,32 +1262,21 @@ public long ToFileTimeUtc() public DateTime ToLocalTime() { - return ToLocalTime(false); - } - - internal DateTime ToLocalTime(bool throwOnOverflow) - { - if (Kind == DateTimeKind.Local) + if ((_dateData & LocalMask) != 0) { return this; } - long offset = TimeZoneInfo.GetUtcOffsetFromUtc(this, TimeZoneInfo.Local, out bool isDaylightSavings, out bool isAmbiguousLocalDst).Ticks; + long offset = TimeZoneInfo.GetUtcOffsetFromUtc(this, TimeZoneInfo.Local, out _, out bool isAmbiguousLocalDst).Ticks; long tick = Ticks + offset; - if (tick > DateTime.MaxTicks) - { - if (throwOnOverflow) - throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); - else - return new DateTime(DateTime.MaxTicks, DateTimeKind.Local); - } - if (tick < DateTime.MinTicks) + if ((ulong)tick <= MaxTicks) { - if (throwOnOverflow) - throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); - else - return new DateTime(DateTime.MinTicks, DateTimeKind.Local); + if (!isAmbiguousLocalDst) + { + return new DateTime((ulong)tick | KindLocal); + } + return new DateTime((ulong)tick | KindLocalAmbiguousDst); } - return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst); + return new DateTime(tick < 0 ? KindLocal : MaxTicks | KindLocal); } public string ToLongDateString() @@ -1373,7 +1344,7 @@ public static bool TryParse(ReadOnlySpan s, out DateTime result) public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, DateTimeStyles styles, out DateTime result) { - DateTimeFormatInfo.ValidateStyles(styles, nameof(styles)); + DateTimeFormatInfo.ValidateStyles(styles, styles: true); if (s == null) { @@ -1386,13 +1357,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? prov public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, DateTimeStyles styles, out DateTime result) { - DateTimeFormatInfo.ValidateStyles(styles, nameof(styles)); + DateTimeFormatInfo.ValidateStyles(styles, styles: true); return DateTimeParse.TryParse(s, DateTimeFormatInfo.GetInstance(provider), styles, out result); } public static bool TryParseExact([NotNullWhen(true)] string? s, [NotNullWhen(true)] string? format, IFormatProvider? provider, DateTimeStyles style, out DateTime result) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); if (s == null || format == null) { @@ -1405,13 +1376,13 @@ public static bool TryParseExact([NotNullWhen(true)] string? s, [NotNullWhen(tru public static bool TryParseExact(ReadOnlySpan s, ReadOnlySpan format, IFormatProvider? provider, DateTimeStyles style, out DateTime result) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); return DateTimeParse.TryParseExact(s, format, DateTimeFormatInfo.GetInstance(provider), style, out result); } public static bool TryParseExact([NotNullWhen(true)] string? s, [NotNullWhen(true)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateTime result) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); if (s == null) { @@ -1424,30 +1395,22 @@ public static bool TryParseExact([NotNullWhen(true)] string? s, [NotNullWhen(tru public static bool TryParseExact(ReadOnlySpan s, [NotNullWhen(true)] string?[]? formats, IFormatProvider? provider, DateTimeStyles style, out DateTime result) { - DateTimeFormatInfo.ValidateStyles(style, nameof(style)); + DateTimeFormatInfo.ValidateStyles(style); return DateTimeParse.TryParseExactMultiple(s, formats, DateTimeFormatInfo.GetInstance(provider), style, out result); } public static DateTime operator +(DateTime d, TimeSpan t) { - long ticks = d.InternalTicks; - long valueTicks = t._ticks; - if (valueTicks > MaxTicks - ticks || valueTicks < MinTicks - ticks) - { - throw new ArgumentOutOfRangeException(nameof(t), SR.ArgumentOutOfRange_DateArithmetic); - } - return new DateTime((ulong)(ticks + valueTicks) | d.InternalKind); + ulong ticks = (ulong)(d.Ticks + t._ticks); + if (ticks > MaxTicks) ThrowDateArithmetic(1); + return new DateTime(ticks | d.InternalKind); } public static DateTime operator -(DateTime d, TimeSpan t) { - long ticks = d.InternalTicks; - long valueTicks = t._ticks; - if (ticks - MinTicks < valueTicks || ticks - MaxTicks > valueTicks) - { - throw new ArgumentOutOfRangeException(nameof(t), SR.ArgumentOutOfRange_DateArithmetic); - } - return new DateTime((ulong)(ticks - valueTicks) | d.InternalKind); + ulong ticks = (ulong)(d.Ticks - t._ticks); + if (ticks > MaxTicks) ThrowDateArithmetic(1); + return new DateTime(ticks | d.InternalKind); } public static TimeSpan operator -(DateTime d1, DateTime d2) => new TimeSpan(d1.InternalTicks - d2.InternalTicks); @@ -1500,135 +1463,70 @@ public string[] GetDateTimeFormats(char format, IFormatProvider? provider) // IConvertible implementation // - public TypeCode GetTypeCode() - { - return TypeCode.DateTime; - } - - bool IConvertible.ToBoolean(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Boolean")); - } - - char IConvertible.ToChar(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Char")); - } - - sbyte IConvertible.ToSByte(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "SByte")); - } - - byte IConvertible.ToByte(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Byte")); - } - - short IConvertible.ToInt16(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Int16")); - } - - ushort IConvertible.ToUInt16(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "UInt16")); - } - - int IConvertible.ToInt32(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Int32")); - } - - uint IConvertible.ToUInt32(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "UInt32")); - } + public TypeCode GetTypeCode() => TypeCode.DateTime; - long IConvertible.ToInt64(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Int64")); - } - - ulong IConvertible.ToUInt64(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "UInt64")); - } - - float IConvertible.ToSingle(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Single")); - } - - double IConvertible.ToDouble(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Double")); - } + bool IConvertible.ToBoolean(IFormatProvider? provider) => throw InvalidCast(nameof(Boolean)); + char IConvertible.ToChar(IFormatProvider? provider) => throw InvalidCast(nameof(Char)); + sbyte IConvertible.ToSByte(IFormatProvider? provider) => throw InvalidCast(nameof(SByte)); + byte IConvertible.ToByte(IFormatProvider? provider) => throw InvalidCast(nameof(Byte)); + short IConvertible.ToInt16(IFormatProvider? provider) => throw InvalidCast(nameof(Int16)); + ushort IConvertible.ToUInt16(IFormatProvider? provider) => throw InvalidCast(nameof(UInt16)); + int IConvertible.ToInt32(IFormatProvider? provider) => throw InvalidCast(nameof(Int32)); + uint IConvertible.ToUInt32(IFormatProvider? provider) => throw InvalidCast(nameof(UInt32)); + long IConvertible.ToInt64(IFormatProvider? provider) => throw InvalidCast(nameof(Int64)); + ulong IConvertible.ToUInt64(IFormatProvider? provider) => throw InvalidCast(nameof(UInt64)); + float IConvertible.ToSingle(IFormatProvider? provider) => throw InvalidCast(nameof(Single)); + double IConvertible.ToDouble(IFormatProvider? provider) => throw InvalidCast(nameof(Double)); + decimal IConvertible.ToDecimal(IFormatProvider? provider) => throw InvalidCast(nameof(Decimal)); - decimal IConvertible.ToDecimal(IFormatProvider? provider) - { - throw new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, "DateTime", "Decimal")); - } + private static Exception InvalidCast(string to) => new InvalidCastException(SR.Format(SR.InvalidCast_FromTo, nameof(DateTime), to)); - DateTime IConvertible.ToDateTime(IFormatProvider? provider) - { - return this; - } + DateTime IConvertible.ToDateTime(IFormatProvider? provider) => this; - object IConvertible.ToType(Type type, IFormatProvider? provider) - { - return Convert.DefaultToType((IConvertible)this, type, provider); - } + object IConvertible.ToType(Type type, IFormatProvider? provider) => Convert.DefaultToType(this, type, provider); // Tries to construct a DateTime from a given year, month, day, hour, // minute, second and millisecond. // internal static bool TryCreate(int year, int month, int day, int hour, int minute, int second, int millisecond, out DateTime result) { - result = DateTime.MinValue; - if (year < 1 || year > 9999 || month < 1 || month > 12) + result = default; + if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1) { return false; } - int[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365; - if (day < 1 || day > days[month] - days[month - 1]) + if ((uint)hour >= 24 || (uint)minute >= 60 || (uint)millisecond >= MillisPerSecond) { return false; } - if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second > 60) + + uint[] days = IsLeapYear(year) ? s_daysToMonth366 : s_daysToMonth365; + if ((uint)day > days[month] - days[month - 1]) { return false; } - if (millisecond < 0 || millisecond >= MillisPerSecond) + ulong ticks = (DaysToYear((uint)year) + days[month - 1] + (uint)day - 1) * (ulong)TicksPerDay; + + if ((uint)second < 60) { - return false; + ticks += TimeToTicks(hour, minute, second) + (uint)millisecond * (uint)TicksPerMillisecond; } - - if (second == 60) + else if (second == 60 && s_systemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, DateTimeKind.Unspecified)) { - 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; - } + // 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. + ticks += TimeToTicks(hour, minute, 59) + 999 * TicksPerMillisecond; } - - long ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); - - ticks += millisecond * TicksPerMillisecond; - if (ticks < MinTicks || ticks > MaxTicks) + else { return false; } - result = new DateTime(ticks, DateTimeKind.Unspecified); + + Debug.Assert(ticks <= MaxTicks, "Input parameters validated already"); + result = new DateTime(ticks); return true; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index 0ba7aaac6a4f1..d0479371dea7f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -122,7 +122,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _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)) + !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, DateTimeKind.Utc)) { throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); } @@ -144,7 +144,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _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)) + !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, DateTimeKind.Utc)) { throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); } @@ -166,7 +166,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se _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)) + !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, DateTimeKind.Utc)) { throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs index a6c20e7c22dcc..03ca28680d243 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs @@ -1826,20 +1826,23 @@ public string[] MonthGenitiveNames | DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.RoundtripKind); - internal static void ValidateStyles(DateTimeStyles style, string parameterName) + internal static void ValidateStyles(DateTimeStyles style, bool styles = false) { - if ((style & InvalidDateTimeStyles) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName); - } - if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) + const DateTimeStyles localUniversal = DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal; + + if ((style & InvalidDateTimeStyles) != 0 || + (style & localUniversal) == localUniversal || + (style & (DateTimeStyles.RoundtripKind | localUniversal | DateTimeStyles.AdjustToUniversal)) > DateTimeStyles.RoundtripKind) { - throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName); + ThrowInvalid(style, styles); } - if (((style & DateTimeStyles.RoundtripKind) != 0) - && ((style & (DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal)) != 0)) + + static void ThrowInvalid(DateTimeStyles style, bool styles) { - throw new ArgumentException(SR.Argument_ConflictingDateTimeRoundtripStyles, parameterName); + string message = (style & InvalidDateTimeStyles) != 0 ? SR.Argument_InvalidDateTimeStyles + : (style & localUniversal) == localUniversal ? SR.Argument_ConflictingDateTimeStyles + : SR.Argument_ConflictingDateTimeRoundtripStyles; + throw new ArgumentException(message, styles ? nameof(styles) : nameof(style)); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/EastAsianLunisolarCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/EastAsianLunisolarCalendar.cs index 4d7cba3f7abfc..4e14dfdc384f1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/EastAsianLunisolarCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/EastAsianLunisolarCalendar.cs @@ -188,13 +188,13 @@ internal int CheckYearMonthRange(int year, int month, int era) // Reject if there is no leap month this year if (GetYearInfo(year, LeapMonth) == 0) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } } if (month < 1 || month > 13) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } return year; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs index 4f7bf1c3184da..1b0c1aa9a23dd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendar.cs @@ -196,10 +196,7 @@ public override DateTime AddYears(DateTime time, int years) /// Monday, 2 indicates Tuesday, 3 indicates Wednesday, 4 indicates /// Thursday, 5 indicates Friday, and 6 indicates Saturday. /// - public override DayOfWeek GetDayOfWeek(DateTime time) - { - return (DayOfWeek)((int)(time.Ticks / TicksPerDay + 1) % 7); - } + public override DayOfWeek GetDayOfWeek(DateTime time) => time.DayOfWeek; /// /// Returns the day-of-year part of the specified DateTime. The returned value @@ -217,21 +214,7 @@ public override int GetDaysInMonth(int year, int month, int era) { throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue); } - - if (year < 1 || year > MaxYear) - { - throw new ArgumentOutOfRangeException( - nameof(year), - year, - SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxYear)); - } - if (month < 1 || month > 12) - { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); - } - - int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); - return days[month] - days[month - 1]; + return DateTime.DaysInMonth(year, month); } /// @@ -244,16 +227,7 @@ public override int GetDaysInYear(int year, int era) { throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue); } - - if (year < 1 || year > MaxYear) - { - throw new ArgumentOutOfRangeException( - nameof(year), - year, - SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxYear)); - } - - return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? 366 : 365; + return DateTime.IsLeapYear(year) ? 366 : 365; } public override int GetEra(DateTime time) => ADEra; @@ -404,15 +378,7 @@ public override bool IsLeapYear(int year, int era) { throw new ArgumentOutOfRangeException(nameof(era), era, SR.ArgumentOutOfRange_InvalidEraValue); } - if (year < 1 || year > MaxYear) - { - throw new ArgumentOutOfRangeException( - nameof(year), - year, - SR.Format(SR.ArgumentOutOfRange_Range, 1, MaxYear)); - } - - return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + return DateTime.IsLeapYear(year); } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.cs index 6e8df3e0244a6..e1dd518d4174b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.cs @@ -443,7 +443,7 @@ public int GetDaysInMonth(int year, int month, int era) year = GetGregorianYear(year, era); if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException(nameof(month), SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } int[] days = ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ? DaysToMonth366 : DaysToMonth365); return days[month] - days[month - 1]; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs index 1e919f1788b63..b11fa9b32e22c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/HebrewCalendar.cs @@ -664,7 +664,7 @@ public override int GetDaysInMonth(int year, int month, int era) int monthDays = LunarMonthLen[hebrewYearType * MaxMonthPlusOne + month]; if (monthDays == 0) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } return monthDays; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/HijriCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/HijriCalendar.cs index 4e382b3bd02d7..c4452992cbc23 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/HijriCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/HijriCalendar.cs @@ -182,7 +182,7 @@ internal static void CheckYearMonthRange(int year, int month, int era) if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/JulianCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/JulianCalendar.cs index d109c32b7e2d6..764b8f948d2cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/JulianCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/JulianCalendar.cs @@ -83,7 +83,7 @@ internal static void CheckMonthRange(int month) { if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/PersianCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/PersianCalendar.cs index 752d6b9b1a9f5..061ac38ceaddb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/PersianCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/PersianCalendar.cs @@ -117,7 +117,7 @@ internal static void CheckYearMonthRange(int year, int month, int era) if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/UmAlQuraCalendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/UmAlQuraCalendar.cs index 940d9b38c5b68..f5cd02775bf97 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/UmAlQuraCalendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/UmAlQuraCalendar.cs @@ -330,7 +330,7 @@ internal static void CheckYearMonthRange(int year, int month, int era) CheckYearRange(year, era); if (month < 1 || month > 12) { - throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + ThrowHelper.ThrowArgumentOutOfRange_Month(month); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs index b49250a36db23..6e315a97b2b8c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Serialization/SerializationInfo.cs @@ -246,11 +246,10 @@ public void AddValue(string name, DateTime value) internal void AddValueInternal(string name, object? value, Type type) { - if (_nameToIndex.ContainsKey(name)) + if (!_nameToIndex.TryAdd(name, _count)) { throw new SerializationException(SR.Serialization_SameNameTwice); } - _nameToIndex.Add(name, _count); // If we need to expand the arrays, do so. if (_count >= _names.Length) diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 271028cdb6b6b..8667c198913bc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -149,6 +149,12 @@ internal static void ThrowArgumentOutOfRange_Year() ExceptionResource.ArgumentOutOfRange_Year); } + [DoesNotReturn] + internal static void ThrowArgumentOutOfRange_Month(int month) + { + throw new ArgumentOutOfRangeException(nameof(month), month, SR.ArgumentOutOfRange_Month); + } + [DoesNotReturn] internal static void ThrowArgumentOutOfRange_BadYearMonthDay() { diff --git a/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs b/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs index 940704fa9a044..67c3f9a678b29 100644 --- a/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs +++ b/src/libraries/System.Runtime/tests/System/DateTimeOffsetTests.cs @@ -359,8 +359,8 @@ public static void AddYears_NewDateOutOfRange_ThrowsArgumentOutOfRangeException( AssertExtensions.Throws("value", () => DateTimeOffset.Now.AddYears(10001)); AssertExtensions.Throws("value", () => DateTimeOffset.Now.AddYears(-10001)); - AssertExtensions.Throws("months", () => DateTimeOffset.MaxValue.AddYears(1)); - AssertExtensions.Throws("months", () => DateTimeOffset.MinValue.AddYears(-1)); + AssertExtensions.Throws("value", () => DateTimeOffset.MaxValue.AddYears(1)); + AssertExtensions.Throws("value", () => DateTimeOffset.MinValue.AddYears(-1)); } public static IEnumerable AddMonths_TestData() diff --git a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs index 9a222860a3fd4..f0da337f157b2 100644 --- a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs +++ b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs @@ -250,13 +250,6 @@ public void Ctor_NullCalendar_ThrowsArgumentNullException() AssertExtensions.Throws("calendar", () => new DateTime(1, 1, 1, 1, 1, 1, 1, null, DateTimeKind.Local)); } - [Fact] - public void Ctor_OverflowingCalendar_ThrowsArgumentException() - { - AssertExtensions.Throws(null, () => new DateTime(1, 1, 1, 1, 1, 1, 1, new DateMaxCalendar())); - AssertExtensions.Throws(null, () => new DateTime(1, 1, 1, 1, 1, 1, 1, new DateMaxCalendar(), DateTimeKind.Local)); - } - [Theory] [InlineData(2004, 1, 31)] [InlineData(2004, 2, 29)] @@ -382,17 +375,17 @@ public void AddYears_Invoke_ReturnsExpected(DateTime dateTime, int years, DateTi public static IEnumerable AddYears_OutOfRange_TestData() { - yield return new object[] { DateTime.Now, 10001, "value" }; - yield return new object[] { DateTime.Now, -10001, "value" }; - yield return new object[] { DateTime.MaxValue, 1, "months" }; - yield return new object[] { DateTime.MinValue, -1, "months" }; + yield return new object[] { DateTime.Now, 10001 }; + yield return new object[] { DateTime.Now, -10001 }; + yield return new object[] { DateTime.MaxValue, 1 }; + yield return new object[] { DateTime.MinValue, -1 }; } [Theory] [MemberData(nameof(AddYears_OutOfRange_TestData))] - public static void AddYears_NewDateOutOfRange_ThrowsArgumentOutOfRangeException(DateTime date, int years, string paramName) + public static void AddYears_NewDateOutOfRange_ThrowsArgumentOutOfRangeException(DateTime date, int years) { - AssertExtensions.Throws(paramName, () => date.AddYears(years)); + AssertExtensions.Throws("value", () => date.AddYears(years)); } public static IEnumerable AddMonths_TestData() @@ -2270,44 +2263,6 @@ internal struct SYSTEMTIME internal ushort wMillisecond; } - private class DateMaxCalendar : Calendar - { - public override int[] Eras => throw new NotImplementedException(); - - public override DateTime AddMonths(DateTime time, int months) => time; - - public override DateTime AddYears(DateTime time, int years) => time; - - public override int GetDayOfMonth(DateTime time) => 0; - - public override DayOfWeek GetDayOfWeek(DateTime time) => DayOfWeek.Monday; - - public override int GetDayOfYear(DateTime time) => 0; - - public override int GetDaysInMonth(int year, int month, int era) => 0; - - public override int GetDaysInYear(int year, int era) => 0; - - public override int GetEra(DateTime time) => 0; - - public override int GetMonth(DateTime time) => 0; - - public override int GetMonthsInYear(int year, int era) => 0; - - public override int GetYear(DateTime time) => 0; - - public override bool IsLeapDay(int year, int month, int day, int era) => false; - - public override bool IsLeapMonth(int year, int month, int era) => false; - - public override bool IsLeapYear(int year, int era) => false; - - public override DateTime ToDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int era) - { - return DateTime.MaxValue; - } - } - [Theory] [MemberData(nameof(StandardFormatSpecifiers))] public static void TryFormat_MatchesToString(string format)