Skip to content
168 changes: 77 additions & 91 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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
Expand All @@ -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.
//
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down