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;