diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index efe43d734a377a..12c4dceca9b663 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -104,10 +104,9 @@ public readonly partial struct DateTime // All OA dates must be less than (not <=) OADateMaxAsDouble private const double OADateMaxAsDouble = 2958466.0; - private const int DatePartYear = 0; - private const int DatePartDayOfYear = 1; - private const int DatePartMonth = 2; - private const int DatePartDay = 3; + // Euclidean Affine Functions Algorithm constants + private const ulong TicksPer6Hours = TicksPerHour * 6; + private const int March1BasedDayOfNewYear = 306; // Days between March 1 and January 1 private static readonly uint[] s_daysToMonth365 = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; @@ -1349,94 +1348,31 @@ public DateTime Date } } - // Returns a given date part of this DateTime. This method is used - // to compute the year, day-of-year, month, or day part. - private int GetDatePart(int part) - { - // n = number of days since 1/1/0001 - uint n = (uint)(UTicks / TicksPerDay); - // y400 = number of whole 400-year periods since 1/1/0001 - uint y400 = n / DaysPer400Years; - // n = day number within 400-year period - n -= y400 * DaysPer400Years; - // y100 = number of whole 100-year periods within 400-year period - uint y100 = n / DaysPer100Years; - // Last 100-year period has an extra day, so decrement result if 4 - if (y100 == 4) y100 = 3; - // n = day number within 100-year period - n -= y100 * DaysPer100Years; - // y4 = number of whole 4-year periods within 100-year period - uint y4 = n / DaysPer4Years; - // n = day number within 4-year period - n -= y4 * DaysPer4Years; - // y1 = number of whole years within 4-year period - uint y1 = n / DaysPerYear; - // Last year has an extra day, so decrement result if 4 - if (y1 == 4) y1 = 3; - // If year was requested, compute and return it - if (part == DatePartYear) - { - return (int)(y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1); - } - // n = day number within year - n -= y1 * DaysPerYear; - // If day-of-year was requested, return it - if (part == DatePartDayOfYear) return (int)n + 1; - // Leap year calculation looks different from IsLeapYear since y1, y4, - // and y100 are relative to year 1, not year 0 - uint[] days = y1 == 3 && (y4 != 24 || y100 == 3) ? s_daysToMonth366 : s_daysToMonth365; - // All months have less than 32 days, so n >> 5 is a good conservative - // estimate for the month - uint m = (n >> 5) + 1; - // m = 1-based month number - while (n >= days[m]) m++; - // If month was requested, return it - if (part == DatePartMonth) return (int)m; - // Return 1-based day-of-month - return (int)(n - days[m - 1] + 1); - } - - // Exactly the same as GetDatePart, except computing all of + // 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 // are needed rather than redoing the computations for each. + // + // Implementation based on article https://arxiv.org/pdf/2102.06959.pdf + // Cassio Neri, Lorenz Schneiderhttps - Euclidean Affine Functions and Applications to Calendar Algorithms - 2021 internal void GetDate(out int year, out int month, out int day) { - // n = number of days since 1/1/0001 - uint n = (uint)(UTicks / TicksPerDay); - // y400 = number of whole 400-year periods since 1/1/0001 - uint y400 = n / DaysPer400Years; - // n = day number within 400-year period - n -= y400 * DaysPer400Years; - // y100 = number of whole 100-year periods within 400-year period - uint y100 = n / DaysPer100Years; - // Last 100-year period has an extra day, so decrement result if 4 - if (y100 == 4) y100 = 3; - // n = day number within 100-year period - n -= y100 * DaysPer100Years; - // y4 = number of whole 4-year periods within 100-year period - uint y4 = n / DaysPer4Years; - // n = day number within 4-year period - n -= y4 * DaysPer4Years; - // y1 = number of whole years within 4-year period - uint y1 = n / DaysPerYear; - // Last year has an extra day, so decrement result if 4 - if (y1 == 4) y1 = 3; - // compute year - year = (int)(y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1); - // n = day number within year - n -= y1 * DaysPerYear; - // dayOfYear = n + 1; - // Leap year calculation looks different from IsLeapYear since y1, y4, - // and y100 are relative to year 1, not year 0 - uint[] days = y1 == 3 && (y4 != 24 || y100 == 3) ? s_daysToMonth366 : s_daysToMonth365; - // All months have less than 32 days, so n >> 5 is a good conservative - // estimate for the month - uint m = (n >> 5) + 1; - // m = 1-based month number - while (n >= days[m]) m++; + // y400 = number of whole 400-year periods since 3/1/0000 + // r1 = day number within 400-year period + (uint y400, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years); + ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + int n3 = 2141 * daySinceMarch1 + 197913; + year = (int)(100 * y400 + (uint)(u2 >> 32)); // compute month and day - month = (int)m; - day = (int)(n - days[m - 1] + 1); + month = (ushort)(n3 >> 16); + day = (ushort)n3 / 2141 + 1; + + // rollover December 31 + if (daySinceMarch1 >= March1BasedDayOfNewYear) + { + ++year; + month -= 12; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1479,7 +1415,19 @@ internal void GetTimePrecise(out int hour, out int minute, out int second, out i // Returns the day-of-month part of this DateTime. The returned // value is an integer between 1 and 31. // - public int Day => GetDatePart(DatePartDay); + public int Day + { + get + { + // r1 = day number within 400-year period + uint r1 = (((uint)(UTicks / TicksPer6Hours) | 3U) + 1224) % DaysPer400Years; + ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + int n3 = 2141 * daySinceMarch1 + 197913; + // Return 1-based day-of-month + return (ushort)n3 / 2141 + 1; + } + } // Returns the day-of-week part of this DateTime. The returned value // is an integer between 0 and 6, where 0 indicates Sunday, 1 indicates @@ -1491,7 +1439,22 @@ internal void GetTimePrecise(out int hour, out int minute, out int second, out i // Returns the day-of-year part of this DateTime. The returned value // is an integer between 1 and 366. // - public int DayOfYear => GetDatePart(DatePartDayOfYear); + public int DayOfYear + { + get + { + // y400 = number of whole 400-year periods since 3/1/0000 + // r1 = day number within 400-year period + (uint y400, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years); + ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + + int year = (int)(100 * y400 + (uint)(u2 >> 32)) + (daySinceMarch1 >= March1BasedDayOfNewYear ? 1 : 0); + return daySinceMarch1 >= March1BasedDayOfNewYear // DatePartDayOfYear case + ? daySinceMarch1 - March1BasedDayOfNewYear + 1 // rollover December 31 + : daySinceMarch1 + (366 - March1BasedDayOfNewYear) + (IsLeapYear(year) ? 1 : 0); + } + } // Returns the hash code for this DateTime. // @@ -1543,7 +1506,18 @@ public DateTimeKind Kind // Returns the month part of this DateTime. The returned value is an // integer between 1 and 12. // - public int Month => GetDatePart(DatePartMonth); + public int Month + { + get + { + // r1 = day number within 400-year period + uint r1 = (((uint)(UTicks / TicksPer6Hours) | 3U) + 1224) % DaysPer400Years; + ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + int n3 = 2141 * daySinceMarch1 + 197913; + return (ushort)(n3 >> 16) - (daySinceMarch1 >= March1BasedDayOfNewYear ? 12 : 0); + } + } // Returns a DateTime representing the current date and time. The // resolution of the returned value depends on the system timer. @@ -1591,7 +1565,19 @@ public static DateTime Now // Returns the year part of this DateTime. The returned value is an // integer between 1 and 9999. // - public int Year => GetDatePart(DatePartYear); + public int Year + { + get + { + // y400 = number of whole 400-year periods since 3/1/0000 + // r1 = day number within 400-year period + (uint y400, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years); + ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + + return (int)(100 * y400 + (uint)(u2 >> 32)) + (daySinceMarch1 >= March1BasedDayOfNewYear ? 1 : 0); + } + } // Checks whether a given year is a leap year. This method returns true if // year is a leap year, or false if not.