From 56488e067bced2125ae96607ac77f77ef7dbdf84 Mon Sep 17 00:00:00 2001 From: Pent Ploompuu Date: Thu, 9 Jan 2025 19:11:34 +0200 Subject: [PATCH 1/2] Optimize DateOnly, TimeOnly, ISOWeek --- .../src/System/DateOnly.cs | 44 ++++----- .../src/System/DateTime.cs | 2 +- .../src/System/DateTimeOffset.cs | 6 +- .../System/Globalization/DateTimeFormat.cs | 25 ++--- .../src/System/Globalization/ISOWeek.cs | 30 +++--- .../src/System/TimeOnly.cs | 97 +++++++++---------- 6 files changed, 105 insertions(+), 99 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 7e88362f83f228..4bf03ebc03750e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -21,7 +21,7 @@ public readonly struct DateOnly ISpanParsable, IUtf8SpanFormattable { - private readonly int _dayNumber; + private readonly uint _dayNumber; // Maps to Jan 1st year 1 private const int MinDayNumber = 0; @@ -29,13 +29,13 @@ public readonly struct DateOnly // Maps to December 31 year 9999. private const int MaxDayNumber = DateTime.DaysTo10000 - 1; - private static int DayNumberFromDateTime(DateTime dt) => (int)((ulong)dt.Ticks / TimeSpan.TicksPerDay); + private static uint DayNumberFromDateTime(DateTime dt) => (uint)((ulong)dt.Ticks / TimeSpan.TicksPerDay); internal DateTime GetEquivalentDateTime() => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay); - private DateOnly(int dayNumber) + private DateOnly(uint dayNumber) { - Debug.Assert((uint)dayNumber <= MaxDayNumber); + Debug.Assert(dayNumber <= MaxDayNumber); _dayNumber = dayNumber; } @@ -77,7 +77,7 @@ public static DateOnly FromDayNumber(int dayNumber) ThrowHelper.ThrowArgumentOutOfRange_DayNumber(dayNumber); } - return new DateOnly(dayNumber); + return new DateOnly((uint)dayNumber); } /// @@ -98,7 +98,7 @@ public static DateOnly FromDayNumber(int dayNumber) /// /// Gets the day of the week represented by this instance. /// - public DayOfWeek DayOfWeek => (DayOfWeek)(((uint)_dayNumber + 1) % 7); + public DayOfWeek DayOfWeek => (DayOfWeek)((_dayNumber + 1) % 7); /// /// Gets the day of the year represented by this instance. @@ -108,7 +108,7 @@ public static DateOnly FromDayNumber(int dayNumber) /// /// Gets the number of days since January 1, 0001 in the Proleptic Gregorian calendar represented by this instance. /// - public int DayNumber => _dayNumber; + public int DayNumber => (int)_dayNumber; /// /// Adds the specified number of days to the value of this instance. @@ -120,8 +120,8 @@ public static DateOnly FromDayNumber(int dayNumber) /// public DateOnly AddDays(int value) { - int newDayNumber = _dayNumber + value; - if ((uint)newDayNumber > MaxDayNumber) + uint newDayNumber = _dayNumber + (uint)value; + if (newDayNumber > MaxDayNumber) { ThrowOutOfRange(); } @@ -214,7 +214,7 @@ public void Deconstruct(out int year, out int month, out int day) /// /// The time of the day. /// The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time. - public DateTime ToDateTime(TimeOnly time) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks); + public DateTime ToDateTime(TimeOnly time) => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay + time.Ticks); /// /// Returns a DateTime instance with the specified input kind that is set to the date of this DateOnly instance and the time of specified input time. @@ -222,7 +222,7 @@ public void Deconstruct(out int year, out int month, out int day) /// The time of the day. /// One of the enumeration values that indicates whether ticks specifies a local time, Coordinated Universal Time (UTC), or neither. /// The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time. - public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks, kind); + public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => DateTime.SpecifyKind(ToDateTime(time), kind); /// /// Returns a DateOnly instance that is set to the date part of the specified dateTime. @@ -272,7 +272,7 @@ public int CompareTo(object? value) /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. - public override int GetHashCode() => _dayNumber; + public override int GetHashCode() => (int)_dayNumber; private const ParseFlags ParseFlagsDateMask = ParseFlags.HaveHour | ParseFlags.HaveMinute | ParseFlags.HaveSecond | ParseFlags.HaveTime | ParseFlags.TimeZoneUsed | ParseFlags.TimeZoneUtc | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern; @@ -498,12 +498,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read { case 'o': format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + provider = DateTimeFormat.InvariantFormatInfo; break; case 'r': format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + provider = DateTimeFormat.InvariantFormatInfo; break; } } @@ -575,12 +575,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri { case 'o': format = OFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = DateTimeFormat.InvariantFormatInfo; break; case 'r': format = RFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = DateTimeFormat.InvariantFormatInfo; break; } } @@ -718,7 +718,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan s) /// The DateOnly object will be formatted in short form. /// /// A string that contains the short date string representation of the current DateOnly object. - public override string ToString() => ToString("d"); + public override string ToString() => DateTimeFormat.Format(GetEquivalentDateTime(), "d", null); /// /// Converts the value of the current DateOnly object to its equivalent string representation using the specified format and the formatting conventions of the current culture. @@ -732,7 +732,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan s) /// /// An object that supplies culture-specific formatting information. /// A string representation of value of the current DateOnly object as specified by provider. - public string ToString(IFormatProvider? provider) => ToString("d", provider); + public string ToString(IFormatProvider? provider) => DateTimeFormat.Format(GetEquivalentDateTime(), "d", provider); /// /// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information. @@ -753,13 +753,13 @@ public string ToString([StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] stri { 'o' => string.Create(10, this, (destination, value) => { - DateTimeFormat.TryFormatDateOnlyO(value.Year, value.Month, value.Day, destination, out int charsWritten); + DateTimeFormat.TryFormatDateOnlyO(value, destination, out int charsWritten); Debug.Assert(charsWritten == destination.Length); }), 'r' => string.Create(16, this, (destination, value) => { - DateTimeFormat.TryFormatDateOnlyR(value.DayOfWeek, value.Year, value.Month, value.Day, destination, out int charsWritten); + DateTimeFormat.TryFormatDateOnlyR(value, destination, out int charsWritten); Debug.Assert(charsWritten == destination.Length); }), @@ -801,10 +801,10 @@ private bool TryFormatCore(Span destination, out int charsWritten, switch (format[0] | 0x20) { case 'o': - return DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination, out charsWritten); + return DateTimeFormat.TryFormatDateOnlyO(this, destination, out charsWritten); case 'r': - return DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination, out charsWritten); + return DateTimeFormat.TryFormatDateOnlyR(this, destination, out charsWritten); case 'm': case 'd': diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index dd13190b043ecc..ca3c69d2eb8d73 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -1086,7 +1086,7 @@ private static ulong DateToTicks(int year, int month, int day) ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay(); } - ReadOnlySpan days = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365; + ReadOnlySpan days = month > 1 && IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365; if ((uint)day > days[month] - days[month - 1]) { ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay(); diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index 811eafc1ab4eed..0516b3ba014e9b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -587,8 +587,7 @@ public static DateTimeOffset FromUnixTimeSeconds(long seconds) { if (seconds < UnixMinSeconds || seconds > UnixMaxSeconds) { - throw new ArgumentOutOfRangeException(nameof(seconds), - SR.Format(SR.ArgumentOutOfRange_Range, UnixMinSeconds, UnixMaxSeconds)); + ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(seconds), seconds, UnixMinSeconds, UnixMaxSeconds); } long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks; @@ -602,8 +601,7 @@ public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds) if (milliseconds < MinMilliseconds || milliseconds > MaxMilliseconds) { - throw new ArgumentOutOfRangeException(nameof(milliseconds), - SR.Format(SR.ArgumentOutOfRange_Range, MinMilliseconds, MaxMilliseconds)); + ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(milliseconds), milliseconds, MinMilliseconds, MaxMilliseconds); } long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs index a7ca7828e6e493..bb7952affe4b73 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/DateTimeFormat.cs @@ -1313,7 +1313,7 @@ internal static bool IsValidCustomTimeOnlyFormat(ReadOnlySpan format, bool // 012345678901234567890123456789012 // --------------------------------- // 05:30:45.7680000 - internal static unsafe bool TryFormatTimeOnlyO(int hour, int minute, int second, long fraction, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar + internal static unsafe bool TryFormatTimeOnlyO(TimeOnly value, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 16) { @@ -1322,6 +1322,7 @@ internal static unsafe bool TryFormatTimeOnlyO(int hour, int minute, int } charsWritten = 16; + value.ToDateTime().GetTimePrecise(out int hour, out int minute, out int second, out int fraction); fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1340,7 +1341,7 @@ internal static unsafe bool TryFormatTimeOnlyO(int hour, int minute, int // 012345678901234567890123456789012 // --------------------------------- // 05:30:45 - internal static unsafe bool TryFormatTimeOnlyR(int hour, int minute, int second, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar + internal static unsafe bool TryFormatTimeOnlyR(TimeOnly value, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 8) { @@ -1349,6 +1350,7 @@ internal static unsafe bool TryFormatTimeOnlyR(int hour, int minute, int } charsWritten = 8; + value.ToDateTime().GetTime(out int hour, out int minute, out int second); fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1366,7 +1368,7 @@ internal static unsafe bool TryFormatTimeOnlyR(int hour, int minute, int // 012345678901234567890123456789012 // --------------------------------- // 2017-06-12 - internal static unsafe bool TryFormatDateOnlyO(int year, int month, int day, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar + internal static unsafe bool TryFormatDateOnlyO(DateOnly value, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 10) { @@ -1375,6 +1377,7 @@ internal static unsafe bool TryFormatDateOnlyO(int year, int month, int d } charsWritten = 10; + (int year, int month, int day) = value; fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1392,7 +1395,7 @@ internal static unsafe bool TryFormatDateOnlyO(int year, int month, int d // 01234567890123456789012345678 // ----------------------------- // Tue, 03 Jan 2017 - internal static unsafe bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int year, int month, int day, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar + internal static unsafe bool TryFormatDateOnlyR(DateOnly value, Span destination, out int charsWritten) where TChar : unmanaged, IUtfChar { if (destination.Length < 16) { @@ -1401,9 +1404,9 @@ internal static unsafe bool TryFormatDateOnlyR(DayOfWeek dayOfWeek, int y } charsWritten = 16; + (int year, int month, int day) = value; - Debug.Assert((uint)dayOfWeek < 7); - string dayAbbrev = s_invariantAbbreviatedDayNames[(int)dayOfWeek]; + string dayAbbrev = s_invariantAbbreviatedDayNames[(int)value.DayOfWeek]; Debug.Assert(dayAbbrev.Length == 3); string monthAbbrev = s_invariantAbbreviatedMonthNames[month - 1]; @@ -1467,7 +1470,6 @@ internal static unsafe bool TryFormatO(DateTime dateTime, TimeSpan offset charsWritten = charsRequired; dateTime.GetDate(out int year, out int month, out int day); - dateTime.GetTimePrecise(out int hour, out int minute, out int second, out int tick); fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1477,6 +1479,7 @@ internal static unsafe bool TryFormatO(DateTime dateTime, TimeSpan offset dest[7] = TChar.CastFrom('-'); Number.WriteTwoDigits((uint)day, dest + 8); dest[10] = TChar.CastFrom('T'); + dateTime.GetTimePrecise(out int hour, out int minute, out int second, out int tick); Number.WriteTwoDigits((uint)hour, dest + 11); dest[13] = TChar.CastFrom(':'); Number.WriteTwoDigits((uint)minute, dest + 14); @@ -1527,7 +1530,6 @@ internal static unsafe bool TryFormatS(DateTime dateTime, Span des charsWritten = FormatSLength; dateTime.GetDate(out int year, out int month, out int day); - dateTime.GetTime(out int hour, out int minute, out int second); fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1537,6 +1539,7 @@ internal static unsafe bool TryFormatS(DateTime dateTime, Span des dest[7] = TChar.CastFrom('-'); Number.WriteTwoDigits((uint)day, dest + 8); dest[10] = TChar.CastFrom('T'); + dateTime.GetTime(out int hour, out int minute, out int second); Number.WriteTwoDigits((uint)hour, dest + 11); dest[13] = TChar.CastFrom(':'); Number.WriteTwoDigits((uint)minute, dest + 14); @@ -1567,7 +1570,6 @@ internal static unsafe bool TryFormatu(DateTime dateTime, TimeSpan offset } dateTime.GetDate(out int year, out int month, out int day); - dateTime.GetTime(out int hour, out int minute, out int second); fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1577,6 +1579,7 @@ internal static unsafe bool TryFormatu(DateTime dateTime, TimeSpan offset dest[7] = TChar.CastFrom('-'); Number.WriteTwoDigits((uint)day, dest + 8); dest[10] = TChar.CastFrom(' '); + dateTime.GetTime(out int hour, out int minute, out int second); Number.WriteTwoDigits((uint)hour, dest + 11); dest[13] = TChar.CastFrom(':'); Number.WriteTwoDigits((uint)minute, dest + 14); @@ -1609,7 +1612,6 @@ internal static unsafe bool TryFormatR(DateTime dateTime, TimeSpan offset } dateTime.GetDate(out int year, out int month, out int day); - dateTime.GetTime(out int hour, out int minute, out int second); string dayAbbrev = s_invariantAbbreviatedDayNames[(int)dateTime.DayOfWeek]; Debug.Assert(dayAbbrev.Length == 3); @@ -1634,6 +1636,7 @@ internal static unsafe bool TryFormatR(DateTime dateTime, TimeSpan offset dest[11] = TChar.CastFrom(' '); Number.WriteFourDigits((uint)year, dest + 12); dest[16] = TChar.CastFrom(' '); + dateTime.GetTime(out int hour, out int minute, out int second); Number.WriteTwoDigits((uint)hour, dest + 17); dest[19] = TChar.CastFrom(':'); Number.WriteTwoDigits((uint)minute, dest + 20); @@ -1674,7 +1677,6 @@ internal static unsafe bool TryFormatInvariantG(DateTime value, TimeSpan bytesWritten = bytesRequired; value.GetDate(out int year, out int month, out int day); - value.GetTime(out int hour, out int minute, out int second); fixed (TChar* dest = &MemoryMarshal.GetReference(destination)) { @@ -1685,6 +1687,7 @@ internal static unsafe bool TryFormatInvariantG(DateTime value, TimeSpan Number.WriteFourDigits((uint)year, dest + 6); dest[10] = TChar.CastFrom(' '); + value.GetTime(out int hour, out int minute, out int second); Number.WriteTwoDigits((uint)hour, dest + 11); dest[13] = TChar.CastFrom(':'); Number.WriteTwoDigits((uint)minute, dest + 14); diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/ISOWeek.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/ISOWeek.cs index ee175f0f1bf3aa..3dfb11ba63acdd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/ISOWeek.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/ISOWeek.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; using static System.Globalization.GregorianCalendar; namespace System.Globalization @@ -24,7 +25,7 @@ public static int GetWeekOfYear(DateTime date) return GetWeeksInYear(date.Year - 1); } - if (week > GetWeeksInYear(date.Year)) + if (week > WeeksInShortYear && GetWeeksInYear(date.Year) == WeeksInShortYear) { // If a week number of 53 is obtained, one must check that // the date is not actually in week 1 of the following year. @@ -44,22 +45,22 @@ public static int GetWeekOfYear(DateTime date) public static int GetYear(DateTime date) { int week = GetWeekNumber(date); + int year = date.Year; if (week < MinWeek) { // If the week number obtained equals 0, it means that the // given date belongs to the preceding (week-based) year. - return date.Year - 1; + year--; } - - if (week > GetWeeksInYear(date.Year)) + else if (week > WeeksInShortYear && GetWeeksInYear(year) == WeeksInShortYear) { // If a week number of 53 is obtained, one must check that // the date is not actually in week 1 of the following year. - return date.Year + 1; + year++; } - return date.Year; + return year; } /// @@ -100,12 +101,17 @@ public static int GetWeeksInYear(int year) { if (year < MinYear || year > MaxYear) { - throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_Year); + ThrowHelper.ThrowArgumentOutOfRange_Year(); } - static int P(int y) => (y + (y / 4) - (y / 100) + (y / 400)) % 7; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint P(uint y) + { + uint cent = y / 100; + return (y + (y / 4) - cent + cent / 4) % 7; + } - if (P(year) == 4 || P(year - 1) == 3) + if (P((uint)year) == 4 || P((uint)year - 1) == 3) { return WeeksInLongYear; } @@ -127,7 +133,7 @@ public static DateTime ToDateTime(int year, int week, DayOfWeek dayOfWeek) { if (year < MinYear || year > MaxYear) { - throw new ArgumentOutOfRangeException(nameof(year), SR.ArgumentOutOfRange_Year); + ThrowHelper.ThrowArgumentOutOfRange_Year(); } if (week < MinWeek || week > MaxWeek) @@ -149,7 +155,7 @@ public static DateTime ToDateTime(int year, int week, DayOfWeek dayOfWeek) int ordinal = (week * 7) + GetWeekday(dayOfWeek) - correction; - return new DateTime(year, month: 1, day: 1).AddDays(ordinal - 1); + return jan4.AddTicks((ordinal - 4) * TimeSpan.TicksPerDay); } @@ -172,7 +178,7 @@ public static DateTime ToDateTime(int year, int week, DayOfWeek dayOfWeek) // If a week number of 53 is obtained, one must check that the date is not actually in week 1 of the following year. private static int GetWeekNumber(DateTime date) { - return (date.DayOfYear - GetWeekday(date.DayOfWeek) + 10) / 7; + return (int)((uint)(date.DayOfYear - GetWeekday(date.DayOfWeek) + 10) / 7); } // Day of week in ISO is represented by an integer from 1 through 7, beginning with Monday and ending with Sunday. diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 8e3a11bb2ebac2..d8d22d73e50597 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -21,7 +21,7 @@ public readonly struct TimeOnly IUtf8SpanFormattable { // represent the number of ticks map to the time of the day. 1 ticks = 100-nanosecond in time measurements. - private readonly long _ticks; + private readonly ulong _ticks; // MinTimeTicks is the ticks for the midnight time 00:00:00.000 AM private const long MinTimeTicks = 0; @@ -84,68 +84,66 @@ public TimeOnly(long ticks) throw new ArgumentOutOfRangeException(nameof(ticks), SR.ArgumentOutOfRange_TimeOnlyBadTicks); } - _ticks = ticks; + _ticks = (ulong)ticks; } // exist to bypass the check in the public constructor. - internal TimeOnly(ulong ticks) => _ticks = (long)ticks; + internal TimeOnly(ulong ticks) => _ticks = ticks; /// /// Gets the hour component of the time represented by this instance. /// - public int Hour => new TimeSpan(_ticks).Hours; + public int Hour => (int)(_ticks / TimeSpan.TicksPerHour); /// /// Gets the minute component of the time represented by this instance. /// - public int Minute => new TimeSpan(_ticks).Minutes; + public int Minute => (int)((uint)(_ticks / TimeSpan.TicksPerMinute) % 60); /// /// Gets the second component of the time represented by this instance. /// - public int Second => new TimeSpan(_ticks).Seconds; + public int Second => (int)((uint)(_ticks / TimeSpan.TicksPerSecond) % 60); /// /// Gets the millisecond component of the time represented by this instance. /// - public int Millisecond => new TimeSpan(_ticks).Milliseconds; + public int Millisecond => (int)((uint)(_ticks / TimeSpan.TicksPerMillisecond) % 1000); /// /// Gets the microsecond component of the time represented by this instance. /// - public int Microsecond => new TimeSpan(_ticks).Microseconds; + public int Microsecond => (int)(_ticks / TimeSpan.TicksPerMicrosecond % 1000); /// /// Gets the nanosecond component of the time represented by this instance. /// - public int Nanosecond => new TimeSpan(_ticks).Nanoseconds; + public int Nanosecond => (int)(_ticks % TimeSpan.TicksPerMicrosecond * TimeSpan.NanosecondsPerTick); /// /// Gets the number of ticks that represent the time of this instance. /// - public long Ticks => _ticks; + public long Ticks => (long)_ticks; - private TimeOnly AddTicks(long ticks) => new TimeOnly((_ticks + TimeSpan.TicksPerDay + (ticks % TimeSpan.TicksPerDay)) % TimeSpan.TicksPerDay); + private TimeOnly AddTicks(long ticks) => new TimeOnly((_ticks + TimeSpan.TicksPerDay + (ulong)(ticks % TimeSpan.TicksPerDay)) % TimeSpan.TicksPerDay); private TimeOnly AddTicks(long ticks, out int wrappedDays) { - wrappedDays = (int)(ticks / TimeSpan.TicksPerDay); - long newTicks = _ticks + ticks % TimeSpan.TicksPerDay; + (long days, long newTicks) = Math.DivRem(ticks, TimeSpan.TicksPerDay); + newTicks += (long)_ticks; if (newTicks < 0) { - wrappedDays--; + days--; newTicks += TimeSpan.TicksPerDay; } - else + else if (newTicks >= TimeSpan.TicksPerDay) { - if (newTicks >= TimeSpan.TicksPerDay) - { - wrappedDays++; - newTicks -= TimeSpan.TicksPerDay; - } + days++; + newTicks -= TimeSpan.TicksPerDay; } - return new TimeOnly(newTicks); + wrappedDays = (int)days; + return new TimeOnly((ulong)newTicks); } /// @@ -209,12 +207,13 @@ private TimeOnly AddTicks(long ticks, out int wrappedDays) /// public bool IsBetween(TimeOnly start, TimeOnly end) { - long startTicks = start._ticks; - long endTicks = end._ticks; + ulong time = _ticks; + ulong startTicks = start._ticks; + ulong endTicks = end._ticks; return startTicks <= endTicks - ? (startTicks <= _ticks && endTicks > _ticks) - : (startTicks <= _ticks || endTicks > _ticks); + ? (time - startTicks < endTicks - startTicks) + : (time - endTicks >= startTicks - endTicks); } /// @@ -277,7 +276,11 @@ public bool IsBetween(TimeOnly start, TimeOnly end) /// The first TimeOnly instance. /// The second TimeOnly instance.. /// The elapsed time between t1 and t2. - public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) => new TimeSpan((t1._ticks - t2._ticks + TimeSpan.TicksPerDay) % TimeSpan.TicksPerDay); + public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) + { + long diff = (long)(t1._ticks - t2._ticks); + return new TimeSpan(diff + ((diff >> 63) & TimeSpan.TicksPerDay)); + } /// /// Deconstructs by and . @@ -310,8 +313,7 @@ public void Deconstruct(out int hour, out int minute) [EditorBrowsable(EditorBrowsableState.Never)] public void Deconstruct(out int hour, out int minute, out int second) { - (hour, minute) = this; - second = Second; + ToDateTime().GetTime(out hour, out minute, out second); } /// @@ -332,8 +334,7 @@ public void Deconstruct(out int hour, out int minute, out int second) [EditorBrowsable(EditorBrowsableState.Never)] public void Deconstruct(out int hour, out int minute, out int second, out int millisecond) { - (hour, minute, second) = this; - millisecond = Millisecond; + ToDateTime().GetTime(out hour, out minute, out second, out millisecond); } /// @@ -373,15 +374,15 @@ public void Deconstruct(out int hour, out int minute, out int second, out int mi /// /// The time DateTime object to extract the time of the day from. /// A TimeOnly object representing time of the day specified in the DateTime object. - public static TimeOnly FromDateTime(DateTime dateTime) => new TimeOnly(dateTime.TimeOfDay.Ticks); + public static TimeOnly FromDateTime(DateTime dateTime) => new TimeOnly((ulong)dateTime.TimeOfDay.Ticks); /// /// Convert the current TimeOnly instance to a TimeSpan object. /// /// A TimeSpan object spanning to the time specified in the current TimeOnly object. - public TimeSpan ToTimeSpan() => new TimeSpan(_ticks); + public TimeSpan ToTimeSpan() => new TimeSpan((long)_ticks); - internal DateTime ToDateTime() => new DateTime(_ticks); + internal DateTime ToDateTime() => DateTime.UnsafeCreate((long)_ticks); /// /// Compares the value of this instance to a specified TimeOnly value and indicates whether this instance is earlier than, the same as, or later than the specified TimeOnly value. @@ -436,7 +437,7 @@ public int CompareTo(object? value) /// A 32-bit signed integer hash code. public override int GetHashCode() { - long ticks = _ticks; + ulong ticks = _ticks; return unchecked((int)ticks) ^ (int)(ticks >> 32); } @@ -626,8 +627,7 @@ private static ParseFailureKind TryParseInternal(ReadOnlySpan s, IFormatPr return ParseFailureKind.Format_DateTimeOnlyContainsNoneDateParts; } - result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); - + result = FromDateTime(dtResult.parsedDate); return ParseFailureKind.None; } @@ -668,12 +668,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read { case 'o': format = OFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + provider = DateTimeFormat.InvariantFormatInfo; break; case 'r': format = RFormat; - provider = CultureInfo.InvariantCulture.DateTimeFormat; + provider = DateTimeFormat.InvariantFormatInfo; break; } } @@ -693,8 +693,7 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, Read return ParseFailureKind.Format_DateTimeOnlyContainsNoneDateParts; } - result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); - + result = FromDateTime(dtResult.parsedDate); return ParseFailureKind.None; } @@ -745,12 +744,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri { case 'o': format = OFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = DateTimeFormat.InvariantFormatInfo; break; case 'r': format = RFormat; - dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat; + dtfiToUse = DateTimeFormat.InvariantFormatInfo; break; } } @@ -761,7 +760,7 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan s, stri dtResult.Init(s); if (DateTimeParse.TryParseExact(s, format, dtfiToUse, style, ref dtResult) && ((dtResult.flags & ParseFlagsTimeMask) == 0)) { - result = new TimeOnly(dtResult.parsedDate.TimeOfDay.Ticks); + result = FromDateTime(dtResult.parsedDate); return ParseFailureKind.None; } } @@ -888,7 +887,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan s) /// The TimeOnly object will be formatted in short form. /// /// A string that contains the short time string representation of the current TimeOnly object. - public override string ToString() => ToString("t"); + public override string ToString() => DateTimeFormat.Format(ToDateTime(), "t", null); /// /// Converts the value of the current TimeOnly object to its equivalent string representation using the specified format and the formatting conventions of the current culture. @@ -903,7 +902,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan s) /// /// An object that supplies culture-specific formatting information. /// A string representation of value of the current TimeOnly object as specified by provider. - public string ToString(IFormatProvider? provider) => ToString("t", provider); + public string ToString(IFormatProvider? provider) => DateTimeFormat.Format(ToDateTime(), "t", provider); /// /// Converts the value of the current TimeOnly object to its equivalent string representation using the specified culture-specific format information. @@ -925,13 +924,13 @@ public string ToString([StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] stri { 'o' => string.Create(16, this, (destination, value) => { - DateTimeFormat.TryFormatTimeOnlyO(value.Hour, value.Minute, value.Second, value._ticks % TimeSpan.TicksPerSecond, destination, out int charsWritten); + DateTimeFormat.TryFormatTimeOnlyO(value, destination, out int charsWritten); Debug.Assert(charsWritten == destination.Length); }), 'r' => string.Create(8, this, (destination, value) => { - DateTimeFormat.TryFormatTimeOnlyR(value.Hour, value.Minute, value.Second, destination, out int charsWritten); + DateTimeFormat.TryFormatTimeOnlyR(value, destination, out int charsWritten); Debug.Assert(charsWritten == destination.Length); }), @@ -973,10 +972,10 @@ private bool TryFormatCore(Span destination, out int written, [Str switch (format[0] | 0x20) { case 'o': - return DateTimeFormat.TryFormatTimeOnlyO(Hour, Minute, Second, _ticks % TimeSpan.TicksPerSecond, destination, out written); + return DateTimeFormat.TryFormatTimeOnlyO(this, destination, out written); case 'r': - return DateTimeFormat.TryFormatTimeOnlyR(Hour, Minute, Second, destination, out written); + return DateTimeFormat.TryFormatTimeOnlyR(this, destination, out written); case 't': return DateTimeFormat.TryFormat(ToDateTime(), destination, out written, format, provider); From e155c38a9c0c2d412fa5c9ae2eca204156aa707a Mon Sep 17 00:00:00 2001 From: Pent Ploompuu Date: Mon, 13 Jan 2025 20:41:01 +0200 Subject: [PATCH 2/2] Address PR feedback --- .../src/System/DateOnly.cs | 4 ++-- .../src/System/DateTime.cs | 7 +++--- .../src/System/DateTimeOffset.cs | 22 +++++++++---------- .../src/System/TimeOnly.cs | 11 +++++----- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 4bf03ebc03750e..f24aa06296c2a4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -31,7 +31,7 @@ public readonly struct DateOnly private static uint DayNumberFromDateTime(DateTime dt) => (uint)((ulong)dt.Ticks / TimeSpan.TicksPerDay); - internal DateTime GetEquivalentDateTime() => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay); + internal DateTime GetEquivalentDateTime() => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay); private DateOnly(uint dayNumber) { @@ -214,7 +214,7 @@ public void Deconstruct(out int year, out int month, out int day) /// /// The time of the day. /// The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time. - public DateTime ToDateTime(TimeOnly time) => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay + time.Ticks); + public DateTime ToDateTime(TimeOnly time) => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay + time.Ticks); /// /// Returns a DateTime instance with the specified input kind that is set to the date of this DateOnly instance and the time of specified input time. diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index ca3c69d2eb8d73..fcff7871dc0492 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -148,10 +148,11 @@ public DateTime(long ticks) private DateTime(ulong dateData) { - this._dateData = dateData; + Debug.Assert((dateData & TicksMask) <= MaxTicks); + _dateData = dateData; } - internal static DateTime UnsafeCreate(long ticks) => new DateTime((ulong)ticks); + internal static DateTime CreateUnchecked(long ticks) => new DateTime((ulong)ticks); public DateTime(long ticks, DateTimeKind kind) { @@ -1086,7 +1087,7 @@ private static ulong DateToTicks(int year, int month, int day) ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay(); } - ReadOnlySpan days = month > 1 && IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365; + ReadOnlySpan days = RuntimeHelpers.IsKnownConstant(month) && month == 1 || IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365; if ((uint)day > days[month] - days[month - 1]) { ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay(); diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs index 0516b3ba014e9b..8846329fe93afa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs @@ -59,8 +59,8 @@ public readonly partial struct DateTimeOffset // Static Fields 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)); + public static readonly DateTimeOffset MaxValue = new DateTimeOffset(0, DateTime.CreateUnchecked(DateTime.MaxTicks)); + public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(0, DateTime.CreateUnchecked(DateTime.UnixEpochTicks)); // Instance Fields private readonly DateTime _dateTime; @@ -167,7 +167,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se : this(year, month, day, hour, minute, second, offset) { if ((uint)millisecond >= TimeSpan.MillisecondsPerSecond) DateTime.ThrowMillisecondOutOfRange(); - _dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond); + _dateTime = DateTime.CreateUnchecked(UtcTicks + (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond); } // Constructs a DateTimeOffset from a given year, month, day, hour, @@ -252,7 +252,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se : this(year, month, day, hour, minute, second, millisecond, offset) { if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) DateTime.ThrowMicrosecondOutOfRange(); - _dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond); + _dateTime = DateTime.CreateUnchecked(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond); } /// @@ -326,14 +326,14 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se : this(year, month, day, hour, minute, second, millisecond, calendar, offset) { if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) DateTime.ThrowMicrosecondOutOfRange(); - _dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond); + _dateTime = DateTime.CreateUnchecked(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond); } public static DateTimeOffset UtcNow => new DateTimeOffset(0, DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified)); public DateTime DateTime => ClockDateTime; - public DateTime UtcDateTime => DateTime.UnsafeCreate((long)(_dateTime._dateData | DateTime.KindUtc)); + public DateTime UtcDateTime => DateTime.CreateUnchecked((long)(_dateTime._dateData | DateTime.KindUtc)); public DateTime LocalDateTime => UtcDateTime.ToLocalTime(); @@ -346,7 +346,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se // 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 => DateTime.UnsafeCreate(UtcTicks + _offsetMinutes * TimeSpan.TicksPerMinute); + private DateTime ClockDateTime => DateTime.CreateUnchecked(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 @@ -591,7 +591,7 @@ public static DateTimeOffset FromUnixTimeSeconds(long seconds) } long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks; - return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks)); + return new DateTimeOffset(0, DateTime.CreateUnchecked(ticks)); } public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds) @@ -605,7 +605,7 @@ public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds) } long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks; - return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks)); + return new DateTimeOffset(0, DateTime.CreateUnchecked(ticks)); } // ----- SECTION: private serialization instance methods ----------------* @@ -786,7 +786,7 @@ private static DateTimeOffset ToLocalTime(DateTime utcDateTime, bool throwOnOver localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks; } - return CreateValidateOffset(DateTime.UnsafeCreate(localTicks), offset); + return CreateValidateOffset(DateTime.CreateUnchecked(localTicks), offset); } public override string ToString() => @@ -945,7 +945,7 @@ private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset) static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange); } // make sure the Kind is set to Unspecified - return DateTime.UnsafeCreate(utcTicks); + return DateTime.CreateUnchecked(utcTicks); } private static DateTimeStyles ValidateStyles(DateTimeStyles styles) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index d8d22d73e50597..72e15d2faada6a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -98,22 +98,22 @@ public TimeOnly(long ticks) /// /// Gets the minute component of the time represented by this instance. /// - public int Minute => (int)((uint)(_ticks / TimeSpan.TicksPerMinute) % 60); + public int Minute => (int)((uint)(_ticks / TimeSpan.TicksPerMinute) % (uint)TimeSpan.MinutesPerHour); /// /// Gets the second component of the time represented by this instance. /// - public int Second => (int)((uint)(_ticks / TimeSpan.TicksPerSecond) % 60); + public int Second => (int)((uint)(_ticks / TimeSpan.TicksPerSecond) % (uint)TimeSpan.SecondsPerMinute); /// /// Gets the millisecond component of the time represented by this instance. /// - public int Millisecond => (int)((uint)(_ticks / TimeSpan.TicksPerMillisecond) % 1000); + public int Millisecond => (int)((uint)(_ticks / TimeSpan.TicksPerMillisecond) % (uint)TimeSpan.MillisecondsPerSecond); /// /// Gets the microsecond component of the time represented by this instance. /// - public int Microsecond => (int)(_ticks / TimeSpan.TicksPerMicrosecond % 1000); + public int Microsecond => (int)(_ticks / TimeSpan.TicksPerMicrosecond % (uint)TimeSpan.MicrosecondsPerMillisecond); /// /// Gets the nanosecond component of the time represented by this instance. @@ -279,6 +279,7 @@ public bool IsBetween(TimeOnly start, TimeOnly end) public static TimeSpan operator -(TimeOnly t1, TimeOnly t2) { long diff = (long)(t1._ticks - t2._ticks); + // If the result is negative, add 24h to make it positive again using the sign bit. return new TimeSpan(diff + ((diff >> 63) & TimeSpan.TicksPerDay)); } @@ -382,7 +383,7 @@ public void Deconstruct(out int hour, out int minute, out int second, out int mi /// A TimeSpan object spanning to the time specified in the current TimeOnly object. public TimeSpan ToTimeSpan() => new TimeSpan((long)_ticks); - internal DateTime ToDateTime() => DateTime.UnsafeCreate((long)_ticks); + internal DateTime ToDateTime() => DateTime.CreateUnchecked((long)_ticks); /// /// Compares the value of this instance to a specified TimeOnly value and indicates whether this instance is earlier than, the same as, or later than the specified TimeOnly value.