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 8dd87e3105d0b..5bbf718e96be9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Unix.cs @@ -24,7 +24,7 @@ public static DateTime UtcNow 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, DateTimeKind kind) => false; + internal static bool IsValidTimeWithLeapSeconds(DateTime value) => false; #pragma warning restore IDE0060 } 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 1c0ac9210d2f9..64a41edfe9f7a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.Windows.cs @@ -41,26 +41,28 @@ public static unsafe DateTime UtcNow } [MethodImpl(MethodImplOptions.NoInlining)] - internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, DateTimeKind kind) + private static unsafe bool IsValidTimeWithLeapSeconds(DateTime value) { Interop.Kernel32.SYSTEMTIME time; + value.GetDate(out int year, out int month, out int day); time.Year = (ushort)year; time.Month = (ushort)month; time.DayOfWeek = 0; // ignored by TzSpecificLocalTimeToSystemTime/SystemTimeToFileTime time.Day = (ushort)day; + value.GetTime(out int hour, out int minute, out _); time.Hour = (ushort)hour; time.Minute = (ushort)minute; time.Second = 60; time.Milliseconds = 0; - if (kind != DateTimeKind.Utc) + if (value.Kind != DateTimeKind.Utc) { Interop.Kernel32.SYSTEMTIME st; if (Interop.Kernel32.TzSpecificLocalTimeToSystemTime(IntPtr.Zero, &time, &st) != Interop.BOOL.FALSE) return true; } - if (kind != DateTimeKind.Local) + if (value.Kind != DateTimeKind.Local) { ulong ft; if (Interop.Kernel32.SystemTimeToFileTime(&time, &ft) != Interop.BOOL.FALSE) @@ -82,7 +84,7 @@ private static unsafe DateTime FromFileTimeLeapSecondsAware(ulong fileTime) private static unsafe ulong ToFileTimeLeapSecondsAware(long ticks) { - DateTime dt = new(ticks); + DateTime dt = new((ulong)ticks); Interop.Kernel32.SYSTEMTIME time; dt.GetDate(out int year, out int month, out int day); diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index 691786ca18d43..dd13190b043ec 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -118,8 +118,7 @@ public readonly partial struct DateTime private const ulong TicksMask = 0x3FFFFFFFFFFFFFFF; private const ulong FlagsMask = 0xC000000000000000; private const long TicksCeiling = 0x4000000000000000; - private const ulong KindUnspecified = 0x0000000000000000; - private const ulong KindUtc = 0x4000000000000000; + internal const ulong KindUtc = 0x4000000000000000; private const ulong KindLocal = 0x8000000000000000; private const ulong KindLocalAmbiguousDst = 0xC000000000000000; private const int KindShift = 62; @@ -135,7 +134,7 @@ public readonly partial struct DateTime // savings time hour and it is in daylight savings time. This allows distinction of these // otherwise ambiguous local times and prevents data loss when round tripping from Local to // UTC time. - private readonly ulong _dateData; + internal readonly ulong _dateData; // Constructs a DateTime from a tick count. The ticks // argument specifies the date as the number of 100-nanosecond intervals @@ -204,8 +203,8 @@ internal DateTime(long ticks, DateTimeKind kind, bool isAmbiguousDst) 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, TimeSpan.MillisecondsPerSecond - 1)); - private static void ThrowMicrosecondOutOfRange() => throw new ArgumentOutOfRangeException("microsecond", SR.Format(SR.ArgumentOutOfRange_Range, 0, TimeSpan.MicrosecondsPerMillisecond - 1)); + internal static void ThrowMillisecondOutOfRange() => throw new ArgumentOutOfRangeException("millisecond", SR.Format(SR.ArgumentOutOfRange_Range, 0, TimeSpan.MillisecondsPerSecond - 1)); + internal static void ThrowMicrosecondOutOfRange() => throw new ArgumentOutOfRangeException("microsecond", SR.Format(SR.ArgumentOutOfRange_Range, 0, TimeSpan.MicrosecondsPerMillisecond - 1)); private static void ThrowDateArithmetic(int param) => throw new ArgumentOutOfRangeException(param switch { 0 => "value", 1 => "t", _ => "months" }, SR.ArgumentOutOfRange_DateArithmetic); private static void ThrowAddOutOfRange() => throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_AddValue); @@ -293,48 +292,57 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, if (second != 60 || !SystemSupportsLeapSeconds) { ulong ticks = calendar.ToDateTime(year, month, day, hour, minute, second, millisecond).UTicks; - _dateData = ticks | ((ulong)kind << KindShift); + _dateData = ticks | ((ulong)(uint)kind << KindShift); } 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. - this = new DateTime(year, month, day, hour, minute, 59, millisecond, calendar, kind); - ValidateLeapSecond(); + _dateData = WithLeapSecond(calendar, year, month, day, hour, minute, millisecond, kind); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static ulong WithLeapSecond(Calendar calendar, int year, int month, int day, int hour, int minute, int millisecond, DateTimeKind 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. + return ValidateLeapSecond(new DateTime(year, month, day, hour, minute, 59, millisecond, calendar, kind)); + } + // Constructs a DateTime from a given year, month, day, hour, // minute, and second. // public DateTime(int year, int month, int day, int hour, int minute, int second) { + ulong ticks = DateToTicks(year, month, day); if (second != 60 || !SystemSupportsLeapSeconds) { - _dateData = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); + _dateData = ticks + 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. - // codeql[cs/leap-year/unsafe-date-construction-from-two-elements] - DateTime is constructed using the user specified values, not a combination of different sources. It would be intentional to throw if an invalid combination occurred. - this = new DateTime(year, month, day, hour, minute, 59); - ValidateLeapSecond(); + _dateData = WithLeapSecond(ticks, hour, minute); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static ulong WithLeapSecond(ulong ticks, int hour, int minute) + { + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + // codeql[cs/leap-year/unsafe-date-construction-from-two-elements] - DateTime is constructed using the user specified values, not a combination of different sources. It would be intentional to throw if an invalid combination occurred. + return ValidateLeapSecond(new DateTime(ticks + TimeToTicks(hour, minute, 59))); + } + public DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) { if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); + ulong ticks = DateToTicks(year, month, day) | ((ulong)(uint)kind << KindShift); if (second != 60 || !SystemSupportsLeapSeconds) { - ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); - _dateData = ticks | ((ulong)kind << KindShift); + _dateData = ticks + 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. - this = new DateTime(year, month, day, hour, minute, 59, kind); - ValidateLeapSecond(); + _dateData = WithLeapSecond(ticks, hour, minute); } } @@ -351,12 +359,17 @@ public DateTime(int year, int month, int day, int hour, int minute, int 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. - this = new DateTime(year, month, day, hour, minute, 59, calendar); - ValidateLeapSecond(); + _dateData = WithLeapSecond(calendar, year, month, day, hour, minute); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static ulong WithLeapSecond(Calendar calendar, int year, int month, int day, int hour, int minute) + { + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + return ValidateLeapSecond(new DateTime(year, month, day, hour, minute, 59, calendar)); + } + /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, /// millisecond, and Coordinated Universal Time (UTC) or local time for the specified calendar. @@ -405,8 +418,12 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, /// For applications in which portability of date and time data or a limited degree of time zone awareness is important, /// you can use the corresponding constructor. /// - public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond) => - _dateData = Init(year, month, day, hour, minute, second, millisecond); + public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond) + : this(year, month, day, hour, minute, second) + { + if ((uint)millisecond >= TimeSpan.MillisecondsPerSecond) ThrowMillisecondOutOfRange(); + _dateData += (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond; + } /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, @@ -461,8 +478,12 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, /// For applications in which portability of date and time data or a limited degree of time zone awareness is important, /// you can use the corresponding constructor. /// - public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind) => - _dateData = Init(year, month, day, hour, minute, second, millisecond, kind); + public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind) + : this(year, month, day, hour, minute, second, kind) + { + if ((uint)millisecond >= TimeSpan.MillisecondsPerSecond) ThrowMillisecondOutOfRange(); + _dateData += (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond; + } /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, @@ -524,12 +545,17 @@ public DateTime(int year, int month, int day, int hour, int minute, int 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. - this = new DateTime(year, month, day, hour, minute, 59, millisecond, calendar); - ValidateLeapSecond(); + _dateData = WithLeapSecond(calendar, year, month, day, hour, minute, millisecond); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static ulong WithLeapSecond(Calendar calendar, int year, int month, int day, int hour, int minute, int millisecond) + { + // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. + return ValidateLeapSecond(new DateTime(year, month, day, hour, minute, 59, millisecond, calendar)); + } + /// /// Initializes a new instance of the structure to the specified year, month, day, hour, minute, second, /// millisecond, and Coordinated Universal Time (UTC) or local time for the specified calendar. @@ -647,14 +673,10 @@ public DateTime(int year, int month, int day, int hour, int minute, int second, /// you can use the corresponding constructor. /// public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, DateTimeKind kind) + : this(year, month, day, hour, minute, second, millisecond, kind) { - ulong ticks = Init(year, month, day, hour, minute, second, millisecond, kind); if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) ThrowMicrosecondOutOfRange(); - - ulong newTicks = (ticks & TicksMask) + (ulong)(microsecond * TimeSpan.TicksPerMicrosecond); - - Debug.Assert(newTicks <= MaxTicks); - _dateData = newTicks | (ticks & FlagsMask); + _dateData += (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond; } /// @@ -782,44 +804,17 @@ 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, int microsecond, Calendar calendar, DateTimeKind kind) : this(year, month, day, hour, minute, second, millisecond, calendar, kind) { - if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) - { - ThrowMicrosecondOutOfRange(); - } - _dateData = new DateTime(_dateData).AddMicroseconds(microsecond)._dateData; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Init(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind = DateTimeKind.Unspecified) - { - if ((uint)millisecond >= TimeSpan.MillisecondsPerSecond) ThrowMillisecondOutOfRange(); - if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); - - if (second != 60 || !SystemSupportsLeapSeconds) - { - ulong ticks = DateToTicks(year, month, day) + TimeToTicks(hour, minute, second); - ticks += (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond; - Debug.Assert(ticks <= MaxTicks, "Input parameters validated already"); - return ticks | ((ulong)kind << KindShift); - } - - // if we have a leap second, then we adjust it to 59 so that DateTime will consider it the last in the specified minute. - DateTime dt = new(year, month, day, hour, minute, 59, millisecond, kind); - - if (!IsValidTimeWithLeapSeconds(year, month, day, hour, 59, kind)) - { - ThrowHelper.ThrowArgumentOutOfRange_BadHourMinuteSecond(); - } - - return dt._dateData; + if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) ThrowMicrosecondOutOfRange(); + _dateData += (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond; } - private void ValidateLeapSecond() + internal static ulong ValidateLeapSecond(DateTime value) { - if (!IsValidTimeWithLeapSeconds(Year, Month, Day, Hour, Minute, Kind)) + if (!IsValidTimeWithLeapSeconds(value)) { ThrowHelper.ThrowArgumentOutOfRange_BadHourMinuteSecond(); } + return value._dateData; } private DateTime(SerializationInfo info, StreamingContext context) @@ -961,10 +956,11 @@ private DateTime AddUnits(double value, long maxUnitCount, long ticksPerUnit) // or equal to d that denotes a valid day in month m1 of year // y1. // - public DateTime AddMonths(int months) + public DateTime AddMonths(int months) => AddMonths(this, months); + private static DateTime AddMonths(DateTime date, int months) { if (months < -120000 || months > 120000) throw new ArgumentOutOfRangeException(nameof(months), SR.ArgumentOutOfRange_DateTimeBadMonths); - GetDate(out int year, out int month, out int day); + date.GetDate(out int year, out int month, out int day); int y = year, d = day; int m = month + months; int q = m > 0 ? (int)((uint)(m - 1) / 12) : m / 12 - 1; @@ -976,7 +972,7 @@ public DateTime AddMonths(int months) int days = (int)(daysTo[m] - daysToMonth); if (d > days) d = days; uint n = DaysToYear((uint)y) + daysToMonth + (uint)d - 1; - return new DateTime(n * (ulong)TimeSpan.TicksPerDay + UTicks % TimeSpan.TicksPerDay | InternalKind); + return new DateTime(n * (ulong)TimeSpan.TicksPerDay + date.UTicks % TimeSpan.TicksPerDay | date.InternalKind); } /// @@ -1020,13 +1016,14 @@ internal bool TryAddTicks(long value, out DateTime result) // DateTime becomes 2/28. Otherwise, the month, day, and time-of-day // parts of the result are the same as those of this DateTime. // - public DateTime AddYears(int value) + public DateTime AddYears(int value) => AddYears(this, value); + private static DateTime AddYears(DateTime date, int value) { if (value < -10000 || value > 10000) { throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_DateTimeBadYears); } - GetDate(out int year, out int month, out int day); + date.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); @@ -1042,7 +1039,7 @@ public DateTime AddYears(int value) n += DaysToMonth365[m]; } n += (uint)d; - return new DateTime(n * (ulong)TimeSpan.TicksPerDay + UTicks % TimeSpan.TicksPerDay | InternalKind); + return new DateTime(n * (ulong)TimeSpan.TicksPerDay + date.UTicks % TimeSpan.TicksPerDay | date.InternalKind); } // Compares two DateTime values, returning an integer that indicates @@ -1223,13 +1220,9 @@ 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) - { - offsetTicks = TimeZoneInfo.GetLocalUtcOffset(MinValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; - } - else if (ticks > MaxTicks) + if ((ulong)ticks > MaxTicks) { - offsetTicks = TimeZoneInfo.GetLocalUtcOffset(MaxValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; + offsetTicks = TimeZoneInfo.GetLocalUtcOffset(ticks < MinTicks ? MinValue : MaxValue, TimeZoneInfoOptions.NoThrowOnInvalidTime).Ticks; } else { @@ -1303,7 +1296,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex public bool IsDaylightSavingTime() { - if (InternalKind == KindUtc) + if (_dateData >> KindShift == (int)DateTimeKind.Utc) { return false; } @@ -1313,7 +1306,7 @@ public bool IsDaylightSavingTime() public static DateTime SpecifyKind(DateTime value, DateTimeKind kind) { if ((uint)kind > (uint)DateTimeKind.Local) ThrowInvalidKind(); - return new DateTime(value.UTicks | ((ulong)kind << KindShift)); + return new DateTime(value.UTicks | ((ulong)(uint)kind << KindShift)); } public long ToBinary() @@ -1349,14 +1342,7 @@ public long ToBinary() // corresponds to this DateTime with the time-of-day part set to // zero (midnight). // - public DateTime Date - { - get - { - ulong ticks = UTicks; - return new DateTime((ticks - ticks % TimeSpan.TicksPerDay) | InternalKind); - } - } + public DateTime Date => new((UTicks / TimeSpan.TicksPerDay * TimeSpan.TicksPerDay) | InternalKind); // Exactly the same as Year, Month, Day properties, except computing all of // year/month/day rather than just one of them. Used when all three @@ -1364,17 +1350,18 @@ public DateTime Date // // Implementation based on article https://arxiv.org/pdf/2102.06959.pdf // Cassio Neri, Lorenz Schneider - Euclidean Affine Functions and Applications to Calendar Algorithms - 2021 - internal void GetDate(out int year, out int month, out int day) + internal void GetDate(out int year, out int month, out int day) => GetDate(_dateData, out year, out month, out day); + private static void GetDate(ulong dateData, out int year, out int month, out int day) { // y100 = number of whole 100-year periods since 3/1/0000 // r1 = (day number within 100-year period) * 4 - (uint y100, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years); + (uint y100, uint r1) = Math.DivRem(((uint)((dateData & TicksMask) / TicksPer6Hours) | 3U) + 1224, DaysPer400Years); ulong u2 = Math.BigMul(EafMultiplier, r1 | 3U); - ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider); - int n3 = 2141 * daySinceMarch1 + 197913; + uint daySinceMarch1 = (uint)u2 / EafDivider; + uint n3 = 2141 * daySinceMarch1 + 197913; year = (int)(100 * y100 + (uint)(u2 >> 32)); // compute month and day - month = (ushort)(n3 >> 16); + month = (int)(n3 >> 16); day = (ushort)n3 / 2141 + 1; // rollover December 31 @@ -1465,18 +1452,17 @@ public override int GetHashCode() // public int Hour => (int)((uint)(UTicks / TimeSpan.TicksPerHour) % 24); - internal bool IsAmbiguousDaylightSavingTime() => - InternalKind == KindLocalAmbiguousDst; + internal bool IsAmbiguousDaylightSavingTime() => _dateData >= KindLocalAmbiguousDst; public DateTimeKind Kind { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => InternalKind switch + get { - KindUnspecified => DateTimeKind.Unspecified, - KindUtc => DateTimeKind.Utc, - _ => DateTimeKind.Local, - }; + uint kind = (uint)(_dateData >> KindShift); + // values 0-2 map directly to DateTimeKind, 3 (LocalAmbiguousDst) needs to be mapped to 2 (Local) using bit0 NAND bit1 + return (DateTimeKind)(kind & ~(kind >> 1)); + } } // Returns the millisecond part of this DateTime. The returned value @@ -1561,15 +1547,13 @@ public static DateTime Now // Returns the year part of this DateTime. The returned value is an // integer between 1 and 9999. // - public int Year + public int Year => GetYear(_dateData); + private static int GetYear(ulong dateData) { - get - { - // y100 = number of whole 100-year periods since 1/1/0001 - // r1 = (day number within 100-year period) * 4 - (uint y100, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U), DaysPer400Years); - return 1 + (int)(100 * y100 + (r1 | 3) / DaysPer4Years); - } + // y100 = number of whole 100-year periods since 1/1/0001 + // r1 = (day number within 100-year period) * 4 + (uint y100, uint r1) = Math.DivRem(((uint)((dateData & TicksMask) / TicksPer6Hours) | 3U), DaysPer400Years); + return 1 + (int)(100 * y100 + (r1 | 3) / DaysPer4Years); } // Checks whether a given year is a leap year. This method returns true if @@ -1793,9 +1777,7 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS DateTimeFormat.TryFormat(this, utf8Destination, out bytesWritten, format, provider); public DateTime ToUniversalTime() - { - return TimeZoneInfo.ConvertTimeToUtc(this, TimeZoneInfoOptions.NoThrowOnInvalidTime); - } + => _dateData >> KindShift == (int)DateTimeKind.Utc ? this : TimeZoneInfo.ConvertTimeToUtc(this, TimeZoneInfoOptions.NoThrowOnInvalidTime); public static bool TryParse([NotNullWhen(true)] string? s, out DateTime result) { @@ -2019,7 +2001,7 @@ internal static bool TryCreate(int year, int month, int day, int hour, int minut { ticks += TimeToTicks(hour, minute, second) + (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond; } - else if (second == 60 && SystemSupportsLeapSeconds && IsValidTimeWithLeapSeconds(year, month, day, hour, minute, DateTimeKind.Unspecified)) + else if (second == 60 && 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 @@ -2027,6 +2009,9 @@ internal static bool TryCreate(int year, int month, int day, int hour, int minut // 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 * TimeSpan.TicksPerMillisecond; + + if (!IsValidTimeWithLeapSeconds(new DateTime(ticks))) + return false; } else { diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index 73fe858885e9b..811eafc1ab4ee 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -46,7 +46,9 @@ public readonly partial struct DateTimeOffset IUtf8SpanFormattable { // Constants - internal const long MaxOffset = TimeSpan.TicksPerHour * 14; + private const int MaxOffsetMinutes = 14 * 60; + private const int MinOffsetMinutes = -MaxOffsetMinutes; + internal const long MaxOffset = MaxOffsetMinutes * TimeSpan.TicksPerMinute; internal const long MinOffset = -MaxOffset; private const long UnixEpochSeconds = DateTime.UnixEpochTicks / TimeSpan.TicksPerSecond; // 62,135,596,800 @@ -56,18 +58,21 @@ public readonly partial struct DateTimeOffset internal const long UnixMaxSeconds = DateTime.MaxTicks / TimeSpan.TicksPerSecond - UnixEpochSeconds; // Static Fields - public static readonly DateTimeOffset MinValue = new DateTimeOffset(DateTime.MinTicks, TimeSpan.Zero); - public static readonly DateTimeOffset MaxValue = new DateTimeOffset(DateTime.MaxTicks, TimeSpan.Zero); - public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(DateTime.UnixEpochTicks, TimeSpan.Zero); + public static readonly DateTimeOffset MinValue; + public static readonly DateTimeOffset MaxValue = new DateTimeOffset(0, DateTime.UnsafeCreate(DateTime.MaxTicks)); + public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(0, DateTime.UnsafeCreate(DateTime.UnixEpochTicks)); // Instance Fields private readonly DateTime _dateTime; - private readonly short _offsetMinutes; + private readonly int _offsetMinutes; // Constructors - private DateTimeOffset(short validOffsetMinutes, DateTime validDateTime) + private DateTimeOffset(int validOffsetMinutes, DateTime validDateTime) { + Debug.Assert(validOffsetMinutes is >= MinOffsetMinutes and <= MaxOffsetMinutes); + Debug.Assert(validDateTime.Kind == DateTimeKind.Unspecified); + Debug.Assert((ulong)(validDateTime.Ticks + validOffsetMinutes * TimeSpan.TicksPerMinute) <= DateTime.MaxTicks); _dateTime = validDateTime; _offsetMinutes = validOffsetMinutes; } @@ -77,22 +82,24 @@ public DateTimeOffset(long ticks, TimeSpan offset) : this(ValidateOffset(offset) { } + private static DateTimeOffset CreateValidateOffset(DateTime dateTime, TimeSpan offset) => new DateTimeOffset(ValidateOffset(offset), ValidateDate(dateTime, offset)); + // Constructs a DateTimeOffset from a DateTime. For Local and Unspecified kinds, // extracts the local offset. For UTC, creates a UTC instance with a zero offset. public DateTimeOffset(DateTime dateTime) { - TimeSpan offset; if (dateTime.Kind != DateTimeKind.Utc) { // Local and Unspecified are both treated as Local - offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); + TimeSpan offset = TimeZoneInfo.GetLocalUtcOffset(dateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); + _offsetMinutes = ValidateOffset(offset); + _dateTime = ValidateDate(dateTime, offset); } else { - offset = new TimeSpan(0); + _offsetMinutes = 0; + _dateTime = DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified); } - _offsetMinutes = ValidateOffset(offset); - _dateTime = ValidateDate(dateTime, offset); } // Constructs a DateTimeOffset from a DateTime. And an offset. Always makes the clock time @@ -109,7 +116,7 @@ public DateTimeOffset(DateTime dateTime, TimeSpan offset) } else if (dateTime.Kind == DateTimeKind.Utc) { - if (offset != TimeSpan.Zero) + if (offset.Ticks != 0) { throw new ArgumentException(SR.Argument_OffsetUtcMismatch, nameof(offset)); } @@ -135,66 +142,60 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se { _offsetMinutes = ValidateOffset(offset); - int originalSecond = second; - if (second == 60 && DateTime.SystemSupportsLeapSeconds) + if (second != 60 || !DateTime.SystemSupportsLeapSeconds) { - // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. - second = 59; + _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second), offset); } - - _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, DateTimeKind.Utc)) + else { - throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); + _dateTime = WithLeapSecond(year, month, day, hour, minute, offset); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static DateTime WithLeapSecond(int year, int month, int day, int hour, int minute, TimeSpan offset) + { + // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. + DateTimeOffset value = new(year, month, day, hour, minute, 59, offset); + DateTime.ValidateLeapSecond(value.UtcDateTime); + return value._dateTime; + } + // Constructs a DateTimeOffset from a given year, month, day, hour, // minute, second, millisecond and offset public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset) + : this(year, month, day, hour, minute, second, offset) { - _offsetMinutes = ValidateOffset(offset); - - int originalSecond = second; - if (second == 60 && DateTime.SystemSupportsLeapSeconds) - { - // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. - second = 59; - } - - _dateTime = ValidateDate(new DateTime(year, month, day, hour, minute, second, millisecond), offset); - - if (originalSecond == 60 && - !DateTime.IsValidTimeWithLeapSeconds(_dateTime.Year, _dateTime.Month, _dateTime.Day, _dateTime.Hour, _dateTime.Minute, DateTimeKind.Utc)) - { - throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); - } + if ((uint)millisecond >= TimeSpan.MillisecondsPerSecond) DateTime.ThrowMillisecondOutOfRange(); + _dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond); } // Constructs a DateTimeOffset from a given year, month, day, hour, // minute, second, millisecond, Calendar and offset. public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset) { + ArgumentNullException.ThrowIfNull(calendar); _offsetMinutes = ValidateOffset(offset); - int originalSecond = second; - if (second == 60 && DateTime.SystemSupportsLeapSeconds) + if (second != 60 || !DateTime.SystemSupportsLeapSeconds) { - // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. - second = 59; + _dateTime = ValidateDate(calendar.ToDateTime(year, month, day, hour, minute, second, millisecond), offset); } - - _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, DateTimeKind.Utc)) + else { - throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_BadHourMinuteSecond); + _dateTime = WithLeapSecond(calendar, year, month, day, hour, minute, millisecond, offset); } } + [MethodImpl(MethodImplOptions.NoInlining)] + private static DateTime WithLeapSecond(Calendar calendar, int year, int month, int day, int hour, int minute, int millisecond, TimeSpan offset) + { + // Reset the leap second to 59 for now and then we'll validate it after getting the final UTC time. + DateTimeOffset value = new DateTimeOffset(year, month, day, hour, minute, 59, millisecond, calendar, offset); + DateTime.ValidateLeapSecond(value.UtcDateTime); + return value._dateTime; + } + /// /// Initializes a new instance of the structure using the /// specified , , , , , @@ -250,11 +251,8 @@ 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, int microsecond, TimeSpan offset) : this(year, month, day, hour, minute, second, millisecond, offset) { - if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) - { - throw new ArgumentOutOfRangeException(nameof(microsecond), SR.ArgumentOutOfRange_BadHourMinuteSecond); - } - _dateTime = _dateTime.AddMicroseconds(microsecond); + if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) DateTime.ThrowMicrosecondOutOfRange(); + _dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond); } /// @@ -325,45 +323,30 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se /// The property is earlier than or later than . /// public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond, Calendar calendar, TimeSpan offset) - : this(year, month, day, hour, minute, second, millisecond, calendar, offset) + : this(year, month, day, hour, minute, second, millisecond, calendar, offset) { - if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) - { - throw new ArgumentOutOfRangeException(nameof(microsecond), SR.ArgumentOutOfRange_BadHourMinuteSecond); - } - _dateTime = _dateTime.AddMicroseconds(microsecond); + if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) DateTime.ThrowMicrosecondOutOfRange(); + _dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond); } - public static DateTimeOffset UtcNow - { - get - { - DateTime utcNow = DateTime.UtcNow; - var result = new DateTimeOffset(0, utcNow); - - Debug.Assert(new DateTimeOffset(utcNow) == result); // ensure lack of verification does not break anything - - return result; - } - } + public static DateTimeOffset UtcNow => new DateTimeOffset(0, DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified)); public DateTime DateTime => ClockDateTime; - public DateTime UtcDateTime => DateTime.SpecifyKind(_dateTime, DateTimeKind.Utc); + public DateTime UtcDateTime => DateTime.UnsafeCreate((long)(_dateTime._dateData | DateTime.KindUtc)); public DateTime LocalDateTime => UtcDateTime.ToLocalTime(); // Adjust to a given offset with the same UTC time. Can throw ArgumentException // - public DateTimeOffset ToOffset(TimeSpan offset) => - new DateTimeOffset((_dateTime + offset).Ticks, offset); + public DateTimeOffset ToOffset(TimeSpan offset) => CreateValidateOffset(_dateTime + offset, offset); // Instance Properties // The clock or visible time represented. This is just a wrapper around the internal date because this is // the chosen storage mechanism. Going through this helper is good for readability and maintainability. // This should be used for display but not identity. - private DateTime ClockDateTime => new DateTime((_dateTime + Offset).Ticks, DateTimeKind.Unspecified); + private DateTime ClockDateTime => DateTime.UnsafeCreate(UtcTicks + _offsetMinutes * TimeSpan.TicksPerMinute); // Returns the date part of this DateTimeOffset. The resulting value // corresponds to this DateTimeOffset with the time-of-day part set to @@ -396,7 +379,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => // Returns the millisecond part of this DateTimeOffset. The returned value // is an integer between 0 and 999. // - public int Millisecond => ClockDateTime.Millisecond; + public int Millisecond => UtcDateTime.Millisecond; /// /// Gets the microsecond component of the time represented by the current object. @@ -406,7 +389,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => /// the precision of the time's microseconds component depends on the resolution of the system clock. /// On Windows NT 3.5 and later, and Windows Vista operating systems, the clock's resolution is approximately 10000-15000 microseconds. /// - public int Microsecond => ClockDateTime.Microsecond; + public int Microsecond => UtcDateTime.Microsecond; /// /// Gets the nanosecond component of the time represented by the current object. @@ -416,7 +399,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => /// the precision of the time's nanosecond component depends on the resolution of the system clock. /// On Windows NT 3.5 and later, and Windows Vista operating systems, the clock's resolution is approximately 10000000-15000000 nanoseconds. /// - public int Nanosecond => ClockDateTime.Nanosecond; + public int Nanosecond => UtcDateTime.Nanosecond; // Returns the minute part of this DateTimeOffset. The returned value is // an integer between 0 and 59. @@ -428,7 +411,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => // public int Month => ClockDateTime.Month; - public TimeSpan Offset => new TimeSpan(0, _offsetMinutes, 0); + public TimeSpan Offset => new TimeSpan(_offsetMinutes * TimeSpan.TicksPerMinute); /// /// Gets the total number of minutes representing the time's offset from Coordinated Universal Time (UTC). @@ -438,7 +421,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => // Returns the second part of this DateTimeOffset. The returned value is // an integer between 0 and 59. // - public int Second => ClockDateTime.Second; + public int Second => UtcDateTime.Second; // Returns the tick count for this DateTimeOffset. The returned value is // the number of 100-nanosecond intervals that have elapsed since 1/1/0001 @@ -446,7 +429,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => // public long Ticks => ClockDateTime.Ticks; - public long UtcTicks => UtcDateTime.Ticks; + public long UtcTicks => (long)_dateTime._dateData; // Returns the time-of-day part of this DateTimeOffset. The returned value // is a TimeSpan that indicates the time elapsed since midnight. @@ -461,8 +444,7 @@ public DateTimeOffset ToOffset(TimeSpan offset) => // Returns the DateTimeOffset resulting from adding the given // TimeSpan to this DateTimeOffset. // - public DateTimeOffset Add(TimeSpan timeSpan) => - new DateTimeOffset(ClockDateTime.Add(timeSpan), Offset); + public DateTimeOffset Add(TimeSpan timeSpan) => Add(ClockDateTime.Add(timeSpan)); // Returns the DateTimeOffset resulting from adding a fractional number of // days to this DateTimeOffset. The result is computed by rounding the @@ -470,8 +452,7 @@ public DateTimeOffset Add(TimeSpan timeSpan) => // millisecond, and adding that interval to this DateTimeOffset. The // value argument is permitted to be negative. // - public DateTimeOffset AddDays(double days) => - new DateTimeOffset(ClockDateTime.AddDays(days), Offset); + public DateTimeOffset AddDays(double days) => Add(ClockDateTime.AddDays(days)); // Returns the DateTimeOffset resulting from adding a fractional number of // hours to this DateTimeOffset. The result is computed by rounding the @@ -479,8 +460,7 @@ public DateTimeOffset AddDays(double days) => // millisecond, and adding that interval to this DateTimeOffset. The // value argument is permitted to be negative. // - public DateTimeOffset AddHours(double hours) => - new DateTimeOffset(ClockDateTime.AddHours(hours), Offset); + public DateTimeOffset AddHours(double hours) => Add(ClockDateTime.AddHours(hours)); // Returns the DateTimeOffset resulting from the given number of // milliseconds to this DateTimeOffset. The result is computed by rounding @@ -488,8 +468,7 @@ public DateTimeOffset AddHours(double hours) => // and adding that interval to this DateTimeOffset. The value // argument is permitted to be negative. // - public DateTimeOffset AddMilliseconds(double milliseconds) => - new DateTimeOffset(ClockDateTime.AddMilliseconds(milliseconds), Offset); + public DateTimeOffset AddMilliseconds(double milliseconds) => Add(ClockDateTime.AddMilliseconds(milliseconds)); /// /// Returns a new object that adds a specified number of microseconds to the value of this instance. @@ -515,8 +494,7 @@ public DateTimeOffset AddMilliseconds(double milliseconds) => /// /// The resulting value is greater than /// - public DateTimeOffset AddMicroseconds(double microseconds) => - new DateTimeOffset(ClockDateTime.AddMicroseconds(microseconds), Offset); + public DateTimeOffset AddMicroseconds(double microseconds) => Add(ClockDateTime.AddMicroseconds(microseconds)); // Returns the DateTimeOffset resulting from adding a fractional number of // minutes to this DateTimeOffset. The result is computed by rounding the @@ -524,11 +502,9 @@ public DateTimeOffset AddMicroseconds(double microseconds) => // millisecond, and adding that interval to this DateTimeOffset. The // value argument is permitted to be negative. // - public DateTimeOffset AddMinutes(double minutes) => - new DateTimeOffset(ClockDateTime.AddMinutes(minutes), Offset); + public DateTimeOffset AddMinutes(double minutes) => Add(ClockDateTime.AddMinutes(minutes)); - public DateTimeOffset AddMonths(int months) => - new DateTimeOffset(ClockDateTime.AddMonths(months), Offset); + public DateTimeOffset AddMonths(int months) => Add(ClockDateTime.AddMonths(months)); // Returns the DateTimeOffset resulting from adding a fractional number of // seconds to this DateTimeOffset. The result is computed by rounding the @@ -536,15 +512,13 @@ public DateTimeOffset AddMonths(int months) => // millisecond, and adding that interval to this DateTimeOffset. The // value argument is permitted to be negative. // - public DateTimeOffset AddSeconds(double seconds) => - new DateTimeOffset(ClockDateTime.AddSeconds(seconds), Offset); + public DateTimeOffset AddSeconds(double seconds) => Add(ClockDateTime.AddSeconds(seconds)); // Returns the DateTimeOffset resulting from adding the given number of // 100-nanosecond ticks to this DateTimeOffset. The value argument // is permitted to be negative. // - public DateTimeOffset AddTicks(long ticks) => - new DateTimeOffset(ClockDateTime.AddTicks(ticks), Offset); + public DateTimeOffset AddTicks(long ticks) => Add(ClockDateTime.AddTicks(ticks)); // Returns the DateTimeOffset resulting from adding the given number of // years to this DateTimeOffset. The result is computed by incrementing @@ -554,14 +528,15 @@ public DateTimeOffset AddTicks(long ticks) => // DateTimeOffset becomes 2/28. Otherwise, the month, day, and time-of-day // parts of the result are the same as those of this DateTimeOffset. // - public DateTimeOffset AddYears(int years) => - new DateTimeOffset(ClockDateTime.AddYears(years), Offset); + public DateTimeOffset AddYears(int years) => Add(ClockDateTime.AddYears(years)); + + private DateTimeOffset Add(DateTime dateTime) => new DateTimeOffset(_offsetMinutes, ValidateDate(dateTime, Offset)); // Compares two DateTimeOffset values, returning an integer that indicates // their relationship. // public static int Compare(DateTimeOffset first, DateTimeOffset second) => - DateTime.Compare(first.UtcDateTime, second.UtcDateTime); + first.UtcTicks.CompareTo(second.UtcTicks); // Compares this DateTimeOffset to a given object. This method provides an // implementation of the IComparable interface. The object @@ -576,11 +551,11 @@ int IComparable.CompareTo(object? obj) throw new ArgumentException(SR.Arg_MustBeDateTimeOffset); } - return DateTime.Compare(UtcDateTime, other.UtcDateTime); + return UtcTicks.CompareTo(other.UtcTicks); } public int CompareTo(DateTimeOffset other) => - DateTime.Compare(UtcDateTime, other.UtcDateTime); + UtcTicks.CompareTo(other.UtcTicks); // Checks if this DateTimeOffset is equal to a given object. Returns // true if the given object is a boxed DateTimeOffset and its value @@ -588,26 +563,18 @@ public int CompareTo(DateTimeOffset other) => // otherwise. // public override bool Equals([NotNullWhen(true)] object? obj) => - obj is DateTimeOffset && UtcDateTime.Equals(((DateTimeOffset)obj).UtcDateTime); + obj is DateTimeOffset && UtcTicks == ((DateTimeOffset)obj).UtcTicks; - public bool Equals(DateTimeOffset other) => - UtcDateTime.Equals(other.UtcDateTime); + public bool Equals(DateTimeOffset other) => UtcTicks == other.UtcTicks; - public bool EqualsExact(DateTimeOffset other) => - // - // returns true when the ClockDateTime, Kind, and Offset match - // - // currently the Kind should always be Unspecified, but there is always the possibility that a future version - // of DateTimeOffset overloads the Kind field - // - ClockDateTime == other.ClockDateTime && Offset == other.Offset && ClockDateTime.Kind == other.ClockDateTime.Kind; + // returns true when the ClockDateTime, Kind, and Offset match + public bool EqualsExact(DateTimeOffset other) => UtcTicks == other.UtcTicks && _offsetMinutes == other._offsetMinutes; // Compares two DateTimeOffset values for equality. Returns true if // the two DateTimeOffset values are equal, or false if they are // not equal. // - public static bool Equals(DateTimeOffset first, DateTimeOffset second) => - DateTime.Equals(first.UtcDateTime, second.UtcDateTime); + public static bool Equals(DateTimeOffset first, DateTimeOffset second) => first.UtcTicks == second.UtcTicks; // Creates a DateTimeOffset from a Windows filetime. A Windows filetime is // a long representing the date and time as the number of @@ -625,7 +592,7 @@ public static DateTimeOffset FromUnixTimeSeconds(long seconds) } long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks; - return new DateTimeOffset(ticks, TimeSpan.Zero); + return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks)); } public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds) @@ -640,7 +607,7 @@ public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds) } long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks; - return new DateTimeOffset(ticks, TimeSpan.Zero); + return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks)); } // ----- SECTION: private serialization instance methods ----------------* @@ -663,7 +630,7 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex ArgumentNullException.ThrowIfNull(info); info.AddValue("DateTime", _dateTime); // Do not rename (binary serialization) - info.AddValue("OffsetMinutes", _offsetMinutes); // Do not rename (binary serialization) + info.AddValue("OffsetMinutes", (short)_offsetMinutes); // Do not rename (binary serialization) } private DateTimeOffset(SerializationInfo info, StreamingContext context) @@ -676,7 +643,7 @@ private DateTimeOffset(SerializationInfo info, StreamingContext context) // Returns the hash code for this DateTimeOffset. // - public override int GetHashCode() => UtcDateTime.GetHashCode(); + public override int GetHashCode() => UtcTicks.GetHashCode(); // Constructs a DateTimeOffset from a string. The string must specify a // date and optionally a time in a culture-specific or universal format. @@ -690,7 +657,7 @@ public static DateTimeOffset Parse(string input) DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } // Constructs a DateTimeOffset from a string. The string must specify a @@ -698,28 +665,25 @@ public static DateTimeOffset Parse(string input) // Leading and trailing whitespace characters are allowed. // public static DateTimeOffset Parse(string input, IFormatProvider? formatProvider) - { - if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); - return Parse(input, formatProvider, DateTimeStyles.None); - } + => Parse(input, formatProvider, DateTimeStyles.None); public static DateTimeOffset Parse(string input, IFormatProvider? formatProvider, DateTimeStyles styles) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); DateTime dateResult = DateTimeParse.Parse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } public static DateTimeOffset Parse(ReadOnlySpan input, IFormatProvider? formatProvider = null, DateTimeStyles styles = DateTimeStyles.None) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); DateTime dateResult = DateTimeParse.Parse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } // Constructs a DateTimeOffset from a string. The string must specify a @@ -727,11 +691,7 @@ public static DateTimeOffset Parse(ReadOnlySpan input, IFormatProvider? fo // Leading and trailing whitespace characters are allowed. // public static DateTimeOffset ParseExact(string input, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, IFormatProvider? formatProvider) - { - if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); - if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format); - return ParseExact(input, format, formatProvider, DateTimeStyles.None); - } + => ParseExact(input, format, formatProvider, DateTimeStyles.None); // Constructs a DateTimeOffset from a string. The string must specify a // date and optionally a time in a culture-specific or universal format. @@ -739,7 +699,7 @@ public static DateTimeOffset ParseExact(string input, [StringSyntax(StringSyntax // public static DateTimeOffset ParseExact(string input, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string format, IFormatProvider? formatProvider, DateTimeStyles styles) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); if (format == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.format); @@ -748,19 +708,19 @@ public static DateTimeOffset ParseExact(string input, [StringSyntax(StringSyntax DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } public static DateTimeOffset ParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, DateTimeStyles styles = DateTimeStyles.None) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); DateTime dateResult = DateTimeParse.ParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } public static DateTimeOffset ParseExact(string input, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string[] formats, IFormatProvider? formatProvider, DateTimeStyles styles) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input); DateTime dateResult = DateTimeParse.ParseExactMultiple(input, @@ -768,23 +728,21 @@ public static DateTimeOffset ParseExact(string input, [StringSyntax(StringSyntax DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } public static DateTimeOffset ParseExact(ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string[] formats, IFormatProvider? formatProvider, DateTimeStyles styles = DateTimeStyles.None) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); DateTime dateResult = DateTimeParse.ParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out TimeSpan offset); - return new DateTimeOffset(dateResult.Ticks, offset); + return CreateValidateOffset(dateResult, offset); } - public TimeSpan Subtract(DateTimeOffset value) => - UtcDateTime.Subtract(value.UtcDateTime); + public TimeSpan Subtract(DateTimeOffset value) => new TimeSpan(UtcTicks - value.UtcTicks); - public DateTimeOffset Subtract(TimeSpan value) => - new DateTimeOffset(ClockDateTime.Subtract(value), Offset); + public DateTimeOffset Subtract(TimeSpan value) => Add(ClockDateTime.Subtract(value)); - public long ToFileTime() => UtcDateTime.ToFileTime(); + public long ToFileTime() => UtcDateTime.ToFileTimeUtc(); public long ToUnixTimeSeconds() { @@ -804,7 +762,7 @@ public long ToUnixTimeSeconds() // // In other words, we want to consistently round toward the time 1/1/0001 00:00:00, // rather than toward the Unix Epoch (1/1/1970 00:00:00). - long seconds = UtcDateTime.Ticks / TimeSpan.TicksPerSecond; + long seconds = (long)((ulong)UtcTicks / TimeSpan.TicksPerSecond); return seconds - UnixEpochSeconds; } @@ -812,21 +770,17 @@ public long ToUnixTimeMilliseconds() { // Truncate sub-millisecond precision before offsetting by the Unix Epoch to avoid // the last digit being off by one for dates that result in negative Unix times - long milliseconds = UtcDateTime.Ticks / TimeSpan.TicksPerMillisecond; + long milliseconds = (long)((ulong)UtcTicks / TimeSpan.TicksPerMillisecond); return milliseconds - UnixEpochMilliseconds; } - public DateTimeOffset ToLocalTime() => - ToLocalTime(false); - - internal DateTimeOffset ToLocalTime(bool throwOnOverflow) => - ToLocalTime(UtcDateTime, throwOnOverflow); + public DateTimeOffset ToLocalTime() => ToLocalTime(UtcDateTime, false); private static DateTimeOffset ToLocalTime(DateTime utcDateTime, bool throwOnOverflow) { TimeSpan offset = TimeZoneInfo.GetLocalUtcOffset(utcDateTime, TimeZoneInfoOptions.NoThrowOnInvalidTime); long localTicks = utcDateTime.Ticks + offset.Ticks; - if (localTicks < DateTime.MinTicks || localTicks > DateTime.MaxTicks) + if ((ulong)localTicks > DateTime.MaxTicks) { if (throwOnOverflow) throw new ArgumentException(SR.Arg_ArgumentOutOfRangeException); @@ -834,7 +788,7 @@ private static DateTimeOffset ToLocalTime(DateTime utcDateTime, bool throwOnOver localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks; } - return new DateTimeOffset(localTicks, offset); + return CreateValidateOffset(DateTime.UnsafeCreate(localTicks), offset); } public override string ToString() => @@ -856,8 +810,7 @@ public bool TryFormat(Span destination, out int charsWritten, [StringSynta public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] ReadOnlySpan format = default, IFormatProvider? formatProvider = null) => DateTimeFormat.TryFormat(ClockDateTime, utf8Destination, out bytesWritten, format, formatProvider, Offset); - public DateTimeOffset ToUniversalTime() => - new DateTimeOffset(UtcDateTime); + public DateTimeOffset ToUniversalTime() => new DateTimeOffset(0, _dateTime); public static bool TryParse([NotNullWhen(true)] string? input, out DateTimeOffset result) { @@ -866,20 +819,20 @@ public static bool TryParse([NotNullWhen(true)] string? input, out DateTimeOffse DateTimeStyles.None, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParse(ReadOnlySpan input, out DateTimeOffset result) { bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.None, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParse([NotNullWhen(true)] string? input, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); if (input == null) { result = default; @@ -891,22 +844,22 @@ public static bool TryParse([NotNullWhen(true)] string? input, IFormatProvider? styles, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParse(ReadOnlySpan input, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); bool parsed = DateTimeParse.TryParse(input, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string? format, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); if (input == null || format == null) { result = default; @@ -919,23 +872,23 @@ public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen styles, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParseExact( ReadOnlySpan input, [StringSyntax(StringSyntaxAttribute.DateTimeFormat)] ReadOnlySpan format, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); bool parsed = DateTimeParse.TryParseExact(input, format, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string?[]? formats, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); if (input == null) { result = default; @@ -948,32 +901,34 @@ public static bool TryParseExact([NotNullWhen(true)] string? input, [NotNullWhen styles, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } public static bool TryParseExact( ReadOnlySpan input, [NotNullWhen(true), StringSyntax(StringSyntaxAttribute.DateTimeFormat)] string?[]? formats, IFormatProvider? formatProvider, DateTimeStyles styles, out DateTimeOffset result) { - styles = ValidateStyles(styles, nameof(styles)); + styles = ValidateStyles(styles); bool parsed = DateTimeParse.TryParseExactMultiple(input, formats, DateTimeFormatInfo.GetInstance(formatProvider), styles, out DateTime dateResult, out TimeSpan offset); - result = new DateTimeOffset(dateResult.Ticks, offset); + result = CreateValidateOffset(dateResult, offset); return parsed; } // Ensures the TimeSpan is valid to go in a DateTimeOffset. - private static short ValidateOffset(TimeSpan offset) + private static int ValidateOffset(TimeSpan offset) { - long ticks = offset.Ticks; - if (ticks % TimeSpan.TicksPerMinute != 0) + long minutes = offset.Ticks / TimeSpan.TicksPerMinute; + if (offset.Ticks != minutes * TimeSpan.TicksPerMinute) { - throw new ArgumentException(SR.Argument_OffsetPrecision, nameof(offset)); + ThrowOffsetPrecision(); + static void ThrowOffsetPrecision() => throw new ArgumentException(SR.Argument_OffsetPrecision, nameof(offset)); } - if (ticks < MinOffset || ticks > MaxOffset) + if (minutes < MinOffsetMinutes || minutes > MaxOffsetMinutes) { - throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_OffsetOutOfRange); + ThrowOffsetOutOfRange(); + static void ThrowOffsetOutOfRange() => throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_OffsetOutOfRange); } - return (short)(offset.Ticks / TimeSpan.TicksPerMinute); + return (int)minutes; } // Ensures that the time and offset are in range. @@ -986,37 +941,36 @@ private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset) // This operation cannot overflow because offset should have already been validated to be within // 14 hours and the DateTime instance is more than that distance from the boundaries of long. long utcTicks = dateTime.Ticks - offset.Ticks; - if (utcTicks < DateTime.MinTicks || utcTicks > DateTime.MaxTicks) + if ((ulong)utcTicks > DateTime.MaxTicks) { - throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange); + ThrowOutOfRange(); + static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange); } // make sure the Kind is set to Unspecified - // - return new DateTime(utcTicks, DateTimeKind.Unspecified); + return DateTime.UnsafeCreate(utcTicks); } - private static DateTimeStyles ValidateStyles(DateTimeStyles style, string parameterName) + private static DateTimeStyles ValidateStyles(DateTimeStyles styles) { - if ((style & DateTimeFormatInfo.InvalidDateTimeStyles) != 0) - { - throw new ArgumentException(SR.Argument_InvalidDateTimeStyles, parameterName); - } - if (((style & (DateTimeStyles.AssumeLocal)) != 0) && ((style & (DateTimeStyles.AssumeUniversal)) != 0)) - { - throw new ArgumentException(SR.Argument_ConflictingDateTimeStyles, parameterName); - } - if ((style & DateTimeStyles.NoCurrentDateDefault) != 0) + const DateTimeStyles localUniversal = DateTimeStyles.AssumeLocal | DateTimeStyles.AssumeUniversal; + + if ((styles & (DateTimeFormatInfo.InvalidDateTimeStyles | DateTimeStyles.NoCurrentDateDefault)) != 0 + || (styles & localUniversal) == localUniversal) { - throw new ArgumentException(SR.Argument_DateTimeOffsetInvalidDateTimeStyles, parameterName); + ThrowInvalid(styles); } // 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 - style &= ~DateTimeStyles.AssumeLocal; + return styles & (~DateTimeStyles.RoundtripKind & ~DateTimeStyles.AssumeLocal); - return style; + static void ThrowInvalid(DateTimeStyles styles) + { + string message = (styles & DateTimeFormatInfo.InvalidDateTimeStyles) != 0 ? SR.Argument_InvalidDateTimeStyles + : (styles & localUniversal) == localUniversal ? SR.Argument_ConflictingDateTimeStyles + : SR.Argument_DateTimeOffsetInvalidDateTimeStyles; + throw new ArgumentException(message, nameof(styles)); + } } // Operators @@ -1025,35 +979,35 @@ public static implicit operator DateTimeOffset(DateTime dateTime) => new DateTimeOffset(dateTime); public static DateTimeOffset operator +(DateTimeOffset dateTimeOffset, TimeSpan timeSpan) => - new DateTimeOffset(dateTimeOffset.ClockDateTime + timeSpan, dateTimeOffset.Offset); + dateTimeOffset.Add(dateTimeOffset.ClockDateTime + timeSpan); public static DateTimeOffset operator -(DateTimeOffset dateTimeOffset, TimeSpan timeSpan) => - new DateTimeOffset(dateTimeOffset.ClockDateTime - timeSpan, dateTimeOffset.Offset); + dateTimeOffset.Add(dateTimeOffset.ClockDateTime - timeSpan); public static TimeSpan operator -(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime - right.UtcDateTime; + new TimeSpan(left.UtcTicks - right.UtcTicks); public static bool operator ==(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime == right.UtcDateTime; + left.UtcTicks == right.UtcTicks; public static bool operator !=(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime != right.UtcDateTime; + left.UtcTicks != right.UtcTicks; /// public static bool operator <(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime < right.UtcDateTime; + left.UtcTicks < right.UtcTicks; /// public static bool operator <=(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime <= right.UtcDateTime; + left.UtcTicks <= right.UtcTicks; /// public static bool operator >(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime > right.UtcDateTime; + left.UtcTicks > right.UtcTicks; /// public static bool operator >=(DateTimeOffset left, DateTimeOffset right) => - left.UtcDateTime >= right.UtcDateTime; + left.UtcTicks >= right.UtcTicks; /// /// Deconstructs into , and . diff --git a/src/libraries/System.Private.CoreLib/src/System/Enum.cs b/src/libraries/System.Private.CoreLib/src/System/Enum.cs index 28d95be309436..aa17ad8a5149c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Enum.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Enum.cs @@ -938,7 +938,7 @@ private static bool TryParseByValueOrName( return TryParseByName(enumType, value, ignoreCase, throwOnFailure, out Unsafe.As(ref result)); } - NumberFormatInfo numberFormat = CultureInfo.InvariantCulture.NumberFormat; + NumberFormatInfo numberFormat = NumberFormatInfo.InvariantInfo; const NumberStyles NumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite; Number.ParsingStatus status = Number.TryParseBinaryIntegerStyle(value, NumberStyle, numberFormat, out result); 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 2f07897b50220..547fbc212b0a3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormatInfo.cs @@ -310,12 +310,23 @@ public static DateTimeFormatInfo CurrentInfo } } - public static DateTimeFormatInfo GetInstance(IFormatProvider? provider) => - provider == null ? CurrentInfo : - provider is CultureInfo cultureProvider && !cultureProvider._isInherited ? cultureProvider.DateTimeFormat : - provider is DateTimeFormatInfo info ? info : - provider.GetFormat(typeof(DateTimeFormatInfo)) is DateTimeFormatInfo info2 ? info2 : - CurrentInfo; // Couldn't get anything, just use currentInfo as fallback + public static DateTimeFormatInfo GetInstance(IFormatProvider? provider) + { + return provider == null ? CurrentInfo : GetProviderNonNull(provider); + + static DateTimeFormatInfo GetProviderNonNull(IFormatProvider provider) + { + if (provider.GetType() == typeof(CultureInfo) && ((CultureInfo)provider)._dateTimeInfo is { } info) + { + return info; + } + + return + provider as DateTimeFormatInfo ?? + provider.GetFormat(typeof(DateTimeFormatInfo)) as DateTimeFormatInfo ?? + CurrentInfo; + } + } public object? GetFormat(Type? formatType) { 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 032e6963e167d..4e93e595399d1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/GregorianCalendarHelper.cs @@ -58,6 +58,8 @@ internal sealed class GregorianCalendarHelper private readonly int m_minYear; private readonly Calendar m_Cal; private readonly EraInfo[] m_EraInfo; + private readonly long _minSupportedTicks; + private readonly long _maxSupportedTicks; // Construct an instance of gregorian calendar. internal GregorianCalendarHelper(Calendar cal, EraInfo[] eraInfo) @@ -66,6 +68,8 @@ internal GregorianCalendarHelper(Calendar cal, EraInfo[] eraInfo) m_EraInfo = eraInfo; m_maxYear = eraInfo[0].maxEraYear; m_minYear = eraInfo[0].minEraYear; + _minSupportedTicks = cal.MinSupportedDateTime.Ticks; + _maxSupportedTicks = cal.MaxSupportedDateTime.Ticks; } // EraInfo.yearOffset: The offset to Gregorian year when the era starts. Gregorian Year = Era Year + yearOffset @@ -168,7 +172,9 @@ internal bool IsValidYear(int year, int era) internal void CheckTicksRange(long ticks) { - if (ticks < m_Cal.MinSupportedDateTime.Ticks || ticks > m_Cal.MaxSupportedDateTime.Ticks) + if (ticks < _minSupportedTicks || ticks > _maxSupportedTicks) ThrowOutOfRange(); + + void ThrowOutOfRange() { throw new ArgumentOutOfRangeException( "time", diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs index 60882f7aaaa0e..8921b87b94536 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/NumberFormatInfo.cs @@ -218,9 +218,9 @@ public static NumberFormatInfo GetInstance(IFormatProvider? formatProvider) static NumberFormatInfo GetProviderNonNull(IFormatProvider provider) { // Fast path for a regular CultureInfo - if (provider is CultureInfo cultureProvider && !cultureProvider._isInherited) + if (provider.GetType() == typeof(CultureInfo) && ((CultureInfo)provider)._numInfo is { } info) { - return cultureProvider._numInfo ?? cultureProvider.NumberFormat; + return info; } return diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs index 87f0bae47348f..0733fdd434b9f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/File.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/File.cs @@ -174,9 +174,12 @@ public static SafeFileHandle OpenHandle(string path, FileMode mode = FileMode.Op // File and Directory UTC APIs treat a DateTimeKind.Unspecified as UTC whereas // ToUniversalTime treats this as local. internal static DateTimeOffset GetUtcDateTimeOffset(DateTime dateTime) - => dateTime.Kind == DateTimeKind.Unspecified - ? DateTime.SpecifyKind(dateTime, DateTimeKind.Utc) - : dateTime.ToUniversalTime(); + { + if (dateTime.Kind == DateTimeKind.Local) + dateTime = dateTime.ToUniversalTime(); + + return new DateTimeOffset(dateTime.Ticks, default); + } public static void SetCreationTime(string path, DateTime creationTime) => FileSystem.SetCreationTime(Path.GetFullPath(path), creationTime, asDirectory: false); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index 56c4edcff5eac..e3879ddf6bf65 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -466,12 +466,10 @@ private static void SetFileTime( bool asDirectory, long creationTime = 0, long lastAccessTime = 0, - long lastWriteTime = 0, - long changeTime = 0, - uint fileAttributes = 0) + long lastWriteTime = 0) { using SafeFileHandle handle = OpenHandleToWriteAttributes(fullPath, asDirectory); - SetFileTime(handle, fullPath, creationTime, lastAccessTime, lastWriteTime, changeTime, fileAttributes); + SetFileTime(handle, fullPath, creationTime, lastAccessTime, lastWriteTime); } private static unsafe void SetFileTime( @@ -479,17 +477,13 @@ private static unsafe void SetFileTime( string? fullPath = null, long creationTime = 0, long lastAccessTime = 0, - long lastWriteTime = 0, - long changeTime = 0, - uint fileAttributes = 0) + long lastWriteTime = 0) { var basicInfo = new Interop.Kernel32.FILE_BASIC_INFO { CreationTime = creationTime, LastAccessTime = lastAccessTime, LastWriteTime = lastWriteTime, - ChangeTime = changeTime, - FileAttributes = fileAttributes }; if (!Interop.Kernel32.SetFileInformationByHandle(fileHandle, Interop.Kernel32.FileBasicInfo, &basicInfo, (uint)sizeof(Interop.Kernel32.FILE_BASIC_INFO))) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs index d85d93f91a6df..5f733324bbf1b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.StringSerializer.cs @@ -535,8 +535,7 @@ private TransitionTime GetNextTransitionTimeValue() TransitionTime transition; - DateTime timeOfDay = GetNextDateTimeValue(TimeOfDayFormat); - timeOfDay = new DateTime(1, 1, 1, timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + DateTime timeOfDay = TimeOnly.FromDateTime(GetNextDateTimeValue(TimeOfDayFormat)).ToDateTime(); int month = GetNextInt32Value(); diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.TransitionTime.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.TransitionTime.cs index 5b8cca7450b2f..fb9cdfdba2798 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.TransitionTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.TransitionTime.cs @@ -99,8 +99,7 @@ private static void ValidateTransitionTime(DateTime timeOfDay, int month, int we throw new ArgumentOutOfRangeException(nameof(dayOfWeek), SR.ArgumentOutOfRange_DayOfWeek); } - timeOfDay.GetDate(out int timeOfDayYear, out int timeOfDayMonth, out int timeOfDayDay); - if (timeOfDayYear != 1 || timeOfDayMonth != 1 || timeOfDayDay != 1 || (timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0)) + if (timeOfDay.Ticks >= TimeSpan.TicksPerDay || (ulong)timeOfDay.Ticks % TimeSpan.TicksPerMillisecond != 0) { throw new ArgumentException(SR.Argument_DateTimeHasTicks, nameof(timeOfDay)); } diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs index 562eacce7bf0e..8c28866238387 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeZoneInfo.cs @@ -711,8 +711,8 @@ public static DateTimeOffset ConvertTime(DateTimeOffset dateTimeOffset, TimeZone long ticks = utcDateTime.Ticks + destinationOffset.Ticks; return - ticks > DateTimeOffset.MaxValue.Ticks ? DateTimeOffset.MaxValue : - ticks < DateTimeOffset.MinValue.Ticks ? DateTimeOffset.MinValue : + ticks > DateTime.MaxTicks ? DateTimeOffset.MaxValue : + ticks < DateTime.MinTicks ? DateTimeOffset.MinValue : new DateTimeOffset(ticks, destinationOffset); } @@ -836,10 +836,7 @@ public static DateTime ConvertTimeToUtc(DateTime dateTime) /// internal static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags) { - if (dateTime.Kind == DateTimeKind.Utc) - { - return dateTime; - } + Debug.Assert(dateTime.Kind != DateTimeKind.Utc); CachedData cachedData = s_cachedData; return ConvertTime(dateTime, cachedData.Local, s_utcTimeZone, flags, cachedData); } @@ -1298,8 +1295,8 @@ private DateTime ConvertToFromUtc(DateTime dateTime, TimeSpan daylightDelta, Tim long ticks = dateTime.Ticks + offset.Ticks; return - ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : - ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : + ticks > DateTime.MaxTicks ? DateTime.MaxValue : + ticks < DateTime.MinTicks ? DateTime.MinValue : new DateTime(ticks); } @@ -1312,8 +1309,8 @@ private static DateTime ConvertUtcToTimeZone(long ticks, TimeZoneInfo destinatio { // used to calculate the UTC offset in the destinationTimeZone DateTime utcConverted = - ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : - ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : + ticks > DateTime.MaxTicks ? DateTime.MaxValue : + ticks < DateTime.MinTicks ? DateTime.MinValue : new DateTime(ticks); // verify the time is between MinValue and MaxValue in the new time zone @@ -1321,8 +1318,8 @@ private static DateTime ConvertUtcToTimeZone(long ticks, TimeZoneInfo destinatio ticks += offset.Ticks; return - ticks > DateTime.MaxValue.Ticks ? DateTime.MaxValue : - ticks < DateTime.MinValue.Ticks ? DateTime.MinValue : + ticks > DateTime.MaxTicks ? DateTime.MaxValue : + ticks < DateTime.MinTicks ? DateTime.MinValue : new DateTime(ticks); } @@ -1373,11 +1370,11 @@ private static bool GetIsDaylightSavings(DateTime time, AdjustmentRule rule, Day // startTime and endTime represent the period from either the start of // DST to the end and ***includes*** the potentially overlapped times startTime = rule.IsStartDateMarkerForBeginningOfYear() ? - new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : + new DateTime(daylightTime.Start.Year, 1, 1) : daylightTime.Start + daylightTime.Delta; endTime = rule.IsEndDateMarkerForEndOfYear() ? - new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : + new DateTime(daylightTime.End.Year + 1, 1, 1).AddTicks(-1) : daylightTime.End; } else @@ -1402,11 +1399,11 @@ private static bool GetIsDaylightSavings(DateTime time, AdjustmentRule rule, Day bool invalidAtStart = rule.DaylightDelta > TimeSpan.Zero; startTime = rule.IsStartDateMarkerForBeginningOfYear() ? - new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : + new DateTime(daylightTime.Start.Year, 1, 1) : daylightTime.Start + (invalidAtStart ? rule.DaylightDelta : TimeSpan.Zero); /* FUTURE: - rule.StandardDelta; */ endTime = rule.IsEndDateMarkerForEndOfYear() ? - new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : + new DateTime(daylightTime.End.Year + 1, 1, 1).AddTicks(-1) : daylightTime.End + (invalidAtStart ? -rule.DaylightDelta : TimeSpan.Zero); } @@ -1484,15 +1481,15 @@ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpa bool ignoreYearAdjustment = false; TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule, ruleIndex); DateTime startTime; - if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year) + if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year is > 1 and int startYear) { - if (TryGetStartOfDstIfYearEndWithDst(daylightTime.Start.Year - 1, utc, zone, out startTime)) + if (TryGetStartOfDstIfYearEndWithDst(startYear - 1, utc, zone, out startTime)) { ignoreYearAdjustment = true; } else { - startTime = new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) - dstStartOffset; + startTime = new DateTime(startYear, 1, 1) - dstStartOffset; } } else @@ -1502,15 +1499,15 @@ private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpa TimeSpan dstEndOffset = GetDaylightSavingsEndOffsetFromUtc(utc, rule); DateTime endTime; - if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year) + if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year is < 9999 and int endYear) { - if (TryGetEndOfDstIfYearStartWithDst(daylightTime.End.Year + 1, utc, zone, out endTime)) + if (TryGetEndOfDstIfYearStartWithDst(endYear + 1, utc, zone, out endTime)) { ignoreYearAdjustment = true; } else { - endTime = new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) - dstEndOffset; + endTime = new DateTime(endYear + 1, 1, 1).AddTicks(-1) - dstEndOffset; } } else @@ -2190,8 +2187,8 @@ private static void ValidateTimeZoneInfo(string id, TimeSpan baseUtcOffset, Adju } } - private static readonly TimeSpan MaxOffset = TimeSpan.FromHours(14.0); - private static readonly TimeSpan MinOffset = -MaxOffset; + private static TimeSpan MaxOffset => TimeSpan.FromHours(14); + private static TimeSpan MinOffset => TimeSpan.FromHours(-14); /// /// Helper function that validates the TimeSpan is within +/- 14.0 hours