From ca525746a05ff7276311c7f098d4141132d1a0df Mon Sep 17 00:00:00 2001 From: lilinus Date: Wed, 12 Jun 2024 18:16:16 +0200 Subject: [PATCH] Optimize DateOnly properties and deconstruct --- .../src/System/DateOnly.cs | 68 +++++++++++++++++-- .../src/System/DateTime.cs | 12 ++-- .../src/System/Globalization/Calendar.cs | 14 +--- 3 files changed, 68 insertions(+), 26 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs index 8a16019108d879..41437c3fb36093 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateOnly.cs @@ -26,8 +26,8 @@ public readonly struct DateOnly // Maps to Jan 1st year 1 private const int MinDayNumber = 0; - // Maps to December 31 year 9999. The value calculated from "new DateTime(9999, 12, 31).Ticks / TimeSpan.TicksPerDay" - private const int MaxDayNumber = 3_652_058; + // 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); @@ -83,17 +83,49 @@ public static DateOnly FromDayNumber(int dayNumber) /// /// Gets the year component of the date represented by this instance. /// - public int Year => GetEquivalentDateTime().Year; + public int Year + { + 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)_dayNumber << 2) | 3U), DateTime.DaysPer400Years); + return 1 + (int)(100 * y100 + (r1 | 3) / DateTime.DaysPer4Years); + } + } /// /// Gets the month component of the date represented by this instance. /// - public int Month => GetEquivalentDateTime().Month; + public int Month + { + get + { + // r1 = (day number within 100-year period) * 4 + uint r1 = ((((uint)_dayNumber << 2) | 3U) + 1224) % DateTime.DaysPer400Years; + ulong u2 = Math.BigMul(DateTime.EafMultiplier, r1 | 3U); + ushort daySinceMarch1 = (ushort)((uint)u2 / DateTime.EafDivider); + int n3 = 2141 * daySinceMarch1 + 197913; + return (ushort)(n3 >> 16) - (daySinceMarch1 >= DateTime.March1BasedDayOfNewYear ? 12 : 0); + } + } /// /// Gets the day component of the date represented by this instance. /// - public int Day => GetEquivalentDateTime().Day; + public int Day + { + get + { + // r1 = (day number within 100-year period) * 4 + uint r1 = ((((uint)_dayNumber << 2) | 3U) + 1224) % DateTime.DaysPer400Years; + ulong u2 = Math.BigMul(DateTime.EafMultiplier, r1 | 3U); + ushort daySinceMarch1 = (ushort)((uint)u2 / DateTime.EafDivider); + int n3 = 2141 * daySinceMarch1 + 197913; + // Return 1-based day-of-month + return (ushort)n3 / 2141 + 1; + } + } /// /// Gets the day of the week represented by this instance. @@ -103,7 +135,8 @@ public static DateOnly FromDayNumber(int dayNumber) /// /// Gets the day of the year represented by this instance. /// - public int DayOfYear => GetEquivalentDateTime().DayOfYear; + public int DayOfYear + => 1 + (int)((((((uint)_dayNumber << 2) | 3U) % (uint)DateTime.DaysPer400Years) | 3U) * DateTime.EafMultiplier / DateTime.EafDivider); /// /// Gets the number of days since January 1, 0001 in the Proleptic Gregorian calendar represented by this instance. @@ -207,7 +240,28 @@ public DateOnly AddDays(int value) /// [EditorBrowsable(EditorBrowsableState.Never)] public void Deconstruct(out int year, out int month, out int day) - => GetEquivalentDateTime().GetDate(out year, out month, out day); + { + // Implementation based on article https://arxiv.org/pdf/2102.06959.pdf + // Cassio Neri, Lorenz Schneider - Euclidean Affine Functions and Applications to Calendar Algorithms - 2021 + + // 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)_dayNumber << 2) | 3U) + 1224, DateTime.DaysPer400Years); + ulong u2 = Math.BigMul(DateTime.EafMultiplier, r1 | 3U); + ushort daySinceMarch1 = (ushort)((uint)u2 / DateTime.EafDivider); + int n3 = 2141 * daySinceMarch1 + 197913; + year = (int)(100 * y100 + (uint)(u2 >> 32)); + // compute month and day + month = (ushort)(n3 >> 16); + day = (ushort)n3 / 2141 + 1; + + // rollover December 31 + if (daySinceMarch1 >= DateTime.March1BasedDayOfNewYear) + { + ++year; + month -= 12; + } + } /// /// Returns a DateTime 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 3eeaaabbd358a4..39ab5f51b4bc98 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -76,11 +76,11 @@ public readonly partial struct DateTime // Number of days in a non-leap year private const int DaysPerYear = 365; // Number of days in 4 years - private const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461 + internal const int DaysPer4Years = DaysPerYear * 4 + 1; // 1461 // Number of days in 100 years private const int DaysPer100Years = DaysPer4Years * 25 - 1; // 36524 // Number of days in 400 years - private const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097 + internal const int DaysPer400Years = DaysPer100Years * 4 + 1; // 146097 // Number of days from 1/1/0001 to 12/31/1600 private const int DaysTo1601 = DaysPer400Years * 4; // 584388 @@ -89,7 +89,7 @@ public readonly partial struct DateTime // Number of days from 1/1/0001 to 12/31/1969 internal const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear; // 719,162 // Number of days from 1/1/0001 to 12/31/9999 - private const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059 + internal const int DaysTo10000 = DaysPer400Years * 25 - 366; // 3652059 internal const long MinTicks = 0; internal const long MaxTicks = DaysTo10000 * TicksPerDay - 1; @@ -116,11 +116,11 @@ public readonly partial struct DateTime // Constants used for fast calculation of following subexpressions // x / DaysPer4Years // x % DaysPer4Years / 4 - private const uint EafMultiplier = (uint)(((1UL << 32) + DaysPer4Years - 1) / DaysPer4Years); // 2,939,745 - private const uint EafDivider = EafMultiplier * 4; // 11,758,980 + internal const uint EafMultiplier = (uint)(((1UL << 32) + DaysPer4Years - 1) / DaysPer4Years); // 2,939,745 + internal const uint EafDivider = EafMultiplier * 4; // 11,758,980 private const ulong TicksPer6Hours = TicksPerHour * 6; - private const int March1BasedDayOfNewYear = 306; // Days between March 1 and January 1 + internal const int March1BasedDayOfNewYear = 306; // Days between March 1 and January 1 internal static ReadOnlySpan DaysToMonth365 => [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365]; internal static ReadOnlySpan DaysToMonth366 => [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]; diff --git a/src/libraries/System.Private.CoreLib/src/System/Globalization/Calendar.cs b/src/libraries/System.Private.CoreLib/src/System/Globalization/Calendar.cs index 2b9f984da91d60..052daa6d74fd12 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Globalization/Calendar.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Globalization/Calendar.cs @@ -41,19 +41,7 @@ public abstract class Calendar : ICloneable internal const int MillisPerHour = MillisPerMinute * 60; internal const int MillisPerDay = MillisPerHour * 24; - // Number of days in a non-leap year - internal const int DaysPerYear = 365; - // Number of days in 4 years - internal const int DaysPer4Years = DaysPerYear * 4 + 1; - // Number of days in 100 years - internal const int DaysPer100Years = DaysPer4Years * 25 - 1; - // Number of days in 400 years - internal const int DaysPer400Years = DaysPer100Years * 4 + 1; - - // Number of days from 1/1/0001 to 1/1/10000 - internal const int DaysTo10000 = DaysPer400Years * 25 - 366; - - internal const long MaxMillis = (long)DaysTo10000 * MillisPerDay; + internal const long MaxMillis = (long)DateTime.DaysTo10000 * MillisPerDay; private int _currentEraValue = -1;