diff --git a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs index 214d710a17ae6..e65b0defbd93e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/DateTime.cs +++ b/src/libraries/System.Private.CoreLib/src/System/DateTime.cs @@ -109,9 +109,16 @@ public readonly partial struct DateTime // All OA dates must be less than (not <=) OADateMaxAsDouble private const double OADateMaxAsDouble = 2958466.0; - // Euclidean Affine Functions Algorithm constants + // Euclidean Affine Functions Algorithm (EAF) constants + + // 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 + private const ulong TicksPer6Hours = TicksPerHour * 6; - private const int March1BasedDayOfNewYear = 306; // Days between March 1 and January 1 + 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 }; @@ -1347,13 +1354,13 @@ public DateTime Date // 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) { - // 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); + // 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); + ulong u2 = (ulong)Math.BigMul((int)EafMultiplier, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider); int n3 = 2141 * daySinceMarch1 + 197913; - year = (int)(100 * y400 + (uint)(u2 >> 32)); + year = (int)(100 * y100 + (uint)(u2 >> 32)); // compute month and day month = (ushort)(n3 >> 16); day = (ushort)n3 / 2141 + 1; @@ -1410,10 +1417,10 @@ public int Day { get { - // r1 = day number within 400-year period + // r1 = (day number within 100-year period) * 4 uint r1 = (((uint)(UTicks / TicksPer6Hours) | 3U) + 1224) % DaysPer400Years; - ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); - ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + ulong u2 = (ulong)Math.BigMul((int)EafMultiplier, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider); int n3 = 2141 * daySinceMarch1 + 197913; // Return 1-based day-of-month return (ushort)n3 / 2141 + 1; @@ -1430,22 +1437,8 @@ public int Day // Returns the day-of-year part of this DateTime. The returned value // is an integer between 1 and 366. // - 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); - } - } + public int DayOfYear => + 1 + (int)(((((uint)(UTicks / TicksPer6Hours) | 3U) % (uint)DaysPer400Years) | 3U) * EafMultiplier / EafDivider); // Returns the hash code for this DateTime. // @@ -1501,10 +1494,10 @@ public int Month { get { - // r1 = day number within 400-year period + // r1 = (day number within 100-year period) * 4 uint r1 = (((uint)(UTicks / TicksPer6Hours) | 3U) + 1224) % DaysPer400Years; - ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3); - ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980); + ulong u2 = (ulong)Math.BigMul((int)EafMultiplier, (int)r1 | 3); + ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider); int n3 = 2141 * daySinceMarch1 + 197913; return (ushort)(n3 >> 16) - (daySinceMarch1 >= March1BasedDayOfNewYear ? 12 : 0); } @@ -1560,13 +1553,10 @@ 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); + // 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); } } diff --git a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs index f4dac6708279d..ce6f14c7b9e7c 100644 --- a/src/libraries/System.Runtime/tests/System/DateTimeTests.cs +++ b/src/libraries/System.Runtime/tests/System/DateTimeTests.cs @@ -811,6 +811,20 @@ public void DayOfYear_Get_ReturnsExpected() Assert.Equal(170, dateTime.DayOfYear); } + [Fact] + public void DayOfYear_Random() + { + var random = new Random(2022); + var tries = 1000; + for (int i = 0; i < tries; ++i) + { + var dateTime = new DateTime(random.NextInt64(DateTime.MaxValue.Ticks)); + var startOfYear = new DateTime(dateTime.Year, 1, 1); + var expectedDayOfYear = 1 + (dateTime - startOfYear).Days; + Assert.Equal(expectedDayOfYear, dateTime.DayOfYear); + } + } + [Fact] public void TimeOfDay_Get_ReturnsExpected() {