Skip to content

Commit bf17dd6

Browse files
authored
Optimize DateTime.Year/.DayOfYear properties; fix comments (#73277)
1 parent 772444d commit bf17dd6

File tree

2 files changed

+41
-37
lines changed

2 files changed

+41
-37
lines changed

src/libraries/System.Private.CoreLib/src/System/DateTime.cs

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,16 @@ public readonly partial struct DateTime
109109
// All OA dates must be less than (not <=) OADateMaxAsDouble
110110
private const double OADateMaxAsDouble = 2958466.0;
111111

112-
// Euclidean Affine Functions Algorithm constants
112+
// Euclidean Affine Functions Algorithm (EAF) constants
113+
114+
// Constants used for fast calculation of following subexpressions
115+
// x / DaysPer4Years
116+
// x % DaysPer4Years / 4
117+
private const uint EafMultiplier = (uint)(((1UL << 32) + DaysPer4Years - 1) / DaysPer4Years); // 2,939,745
118+
private const uint EafDivider = EafMultiplier * 4; // 11,758,980
119+
113120
private const ulong TicksPer6Hours = TicksPerHour * 6;
114-
private const int March1BasedDayOfNewYear = 306; // Days between March 1 and January 1
121+
private const int March1BasedDayOfNewYear = 306; // Days between March 1 and January 1
115122

116123
private static readonly uint[] s_daysToMonth365 = {
117124
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
@@ -1347,13 +1354,13 @@ public DateTime Date
13471354
// Cassio Neri, Lorenz Schneiderhttps - Euclidean Affine Functions and Applications to Calendar Algorithms - 2021
13481355
internal void GetDate(out int year, out int month, out int day)
13491356
{
1350-
// y400 = number of whole 400-year periods since 3/1/0000
1351-
// r1 = day number within 400-year period
1352-
(uint y400, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years);
1353-
ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3);
1354-
ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980);
1357+
// y100 = number of whole 100-year periods since 3/1/0000
1358+
// r1 = (day number within 100-year period) * 4
1359+
(uint y100, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years);
1360+
ulong u2 = (ulong)Math.BigMul((int)EafMultiplier, (int)r1 | 3);
1361+
ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider);
13551362
int n3 = 2141 * daySinceMarch1 + 197913;
1356-
year = (int)(100 * y400 + (uint)(u2 >> 32));
1363+
year = (int)(100 * y100 + (uint)(u2 >> 32));
13571364
// compute month and day
13581365
month = (ushort)(n3 >> 16);
13591366
day = (ushort)n3 / 2141 + 1;
@@ -1410,10 +1417,10 @@ public int Day
14101417
{
14111418
get
14121419
{
1413-
// r1 = day number within 400-year period
1420+
// r1 = (day number within 100-year period) * 4
14141421
uint r1 = (((uint)(UTicks / TicksPer6Hours) | 3U) + 1224) % DaysPer400Years;
1415-
ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3);
1416-
ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980);
1422+
ulong u2 = (ulong)Math.BigMul((int)EafMultiplier, (int)r1 | 3);
1423+
ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider);
14171424
int n3 = 2141 * daySinceMarch1 + 197913;
14181425
// Return 1-based day-of-month
14191426
return (ushort)n3 / 2141 + 1;
@@ -1430,22 +1437,8 @@ public int Day
14301437
// Returns the day-of-year part of this DateTime. The returned value
14311438
// is an integer between 1 and 366.
14321439
//
1433-
public int DayOfYear
1434-
{
1435-
get
1436-
{
1437-
// y400 = number of whole 400-year periods since 3/1/0000
1438-
// r1 = day number within 400-year period
1439-
(uint y400, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years);
1440-
ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3);
1441-
ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980);
1442-
1443-
int year = (int)(100 * y400 + (uint)(u2 >> 32)) + (daySinceMarch1 >= March1BasedDayOfNewYear ? 1 : 0);
1444-
return daySinceMarch1 >= March1BasedDayOfNewYear // DatePartDayOfYear case
1445-
? daySinceMarch1 - March1BasedDayOfNewYear + 1 // rollover December 31
1446-
: daySinceMarch1 + (366 - March1BasedDayOfNewYear) + (IsLeapYear(year) ? 1 : 0);
1447-
}
1448-
}
1440+
public int DayOfYear =>
1441+
1 + (int)(((((uint)(UTicks / TicksPer6Hours) | 3U) % (uint)DaysPer400Years) | 3U) * EafMultiplier / EafDivider);
14491442

14501443
// Returns the hash code for this DateTime.
14511444
//
@@ -1501,10 +1494,10 @@ public int Month
15011494
{
15021495
get
15031496
{
1504-
// r1 = day number within 400-year period
1497+
// r1 = (day number within 100-year period) * 4
15051498
uint r1 = (((uint)(UTicks / TicksPer6Hours) | 3U) + 1224) % DaysPer400Years;
1506-
ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3);
1507-
ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980);
1499+
ulong u2 = (ulong)Math.BigMul((int)EafMultiplier, (int)r1 | 3);
1500+
ushort daySinceMarch1 = (ushort)((uint)u2 / EafDivider);
15081501
int n3 = 2141 * daySinceMarch1 + 197913;
15091502
return (ushort)(n3 >> 16) - (daySinceMarch1 >= March1BasedDayOfNewYear ? 12 : 0);
15101503
}
@@ -1560,13 +1553,10 @@ public int Year
15601553
{
15611554
get
15621555
{
1563-
// y400 = number of whole 400-year periods since 3/1/0000
1564-
// r1 = day number within 400-year period
1565-
(uint y400, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U) + 1224, DaysPer400Years);
1566-
ulong u2 = (ulong)Math.BigMul(2939745, (int)r1 | 3);
1567-
ushort daySinceMarch1 = (ushort)((uint)u2 / 11758980);
1568-
1569-
return (int)(100 * y400 + (uint)(u2 >> 32)) + (daySinceMarch1 >= March1BasedDayOfNewYear ? 1 : 0);
1556+
// y100 = number of whole 100-year periods since 1/1/0001
1557+
// r1 = (day number within 100-year period) * 4
1558+
(uint y100, uint r1) = Math.DivRem(((uint)(UTicks / TicksPer6Hours) | 3U), DaysPer400Years);
1559+
return 1 + (int)(100 * y100 + (r1 | 3) / DaysPer4Years);
15701560
}
15711561
}
15721562

src/libraries/System.Runtime/tests/System/DateTimeTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,20 @@ public void DayOfYear_Get_ReturnsExpected()
811811
Assert.Equal(170, dateTime.DayOfYear);
812812
}
813813

814+
[Fact]
815+
public void DayOfYear_Random()
816+
{
817+
var random = new Random(2022);
818+
var tries = 1000;
819+
for (int i = 0; i < tries; ++i)
820+
{
821+
var dateTime = new DateTime(random.NextInt64(DateTime.MaxValue.Ticks));
822+
var startOfYear = new DateTime(dateTime.Year, 1, 1);
823+
var expectedDayOfYear = 1 + (dateTime - startOfYear).Days;
824+
Assert.Equal(expectedDayOfYear, dateTime.DayOfYear);
825+
}
826+
}
827+
814828
[Fact]
815829
public void TimeOfDay_Get_ReturnsExpected()
816830
{

0 commit comments

Comments
 (0)