Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize DateOnly, TimeOnly, ISOWeek #111244

Merged
merged 2 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions src/libraries/System.Private.CoreLib/src/System/DateOnly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ public readonly struct DateOnly
ISpanParsable<DateOnly>,
IUtf8SpanFormattable
{
private readonly int _dayNumber;
private readonly uint _dayNumber;

// Maps to Jan 1st year 1
private const int MinDayNumber = 0;

// 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);
private static uint DayNumberFromDateTime(DateTime dt) => (uint)((ulong)dt.Ticks / TimeSpan.TicksPerDay);

internal DateTime GetEquivalentDateTime() => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay);
internal DateTime GetEquivalentDateTime() => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay);

private DateOnly(int dayNumber)
private DateOnly(uint dayNumber)
{
Debug.Assert((uint)dayNumber <= MaxDayNumber);
Debug.Assert(dayNumber <= MaxDayNumber);
_dayNumber = dayNumber;
}

Expand Down Expand Up @@ -77,7 +77,7 @@ public static DateOnly FromDayNumber(int dayNumber)
ThrowHelper.ThrowArgumentOutOfRange_DayNumber(dayNumber);
}

return new DateOnly(dayNumber);
return new DateOnly((uint)dayNumber);
}

/// <summary>
Expand All @@ -98,7 +98,7 @@ public static DateOnly FromDayNumber(int dayNumber)
/// <summary>
/// Gets the day of the week represented by this instance.
/// </summary>
public DayOfWeek DayOfWeek => (DayOfWeek)(((uint)_dayNumber + 1) % 7);
public DayOfWeek DayOfWeek => (DayOfWeek)((_dayNumber + 1) % 7);

/// <summary>
/// Gets the day of the year represented by this instance.
Expand All @@ -108,7 +108,7 @@ public static DateOnly FromDayNumber(int dayNumber)
/// <summary>
/// Gets the number of days since January 1, 0001 in the Proleptic Gregorian calendar represented by this instance.
/// </summary>
public int DayNumber => _dayNumber;
public int DayNumber => (int)_dayNumber;

/// <summary>
/// Adds the specified number of days to the value of this instance.
Expand All @@ -120,8 +120,8 @@ public static DateOnly FromDayNumber(int dayNumber)
/// </exception>
public DateOnly AddDays(int value)
{
int newDayNumber = _dayNumber + value;
if ((uint)newDayNumber > MaxDayNumber)
uint newDayNumber = _dayNumber + (uint)value;
if (newDayNumber > MaxDayNumber)
{
ThrowOutOfRange();
}
Expand Down Expand Up @@ -214,15 +214,15 @@ public void Deconstruct(out int year, out int month, out int day)
/// </summary>
/// <param name="time">The time of the day.</param>
/// <returns>The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time.</returns>
public DateTime ToDateTime(TimeOnly time) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks);
public DateTime ToDateTime(TimeOnly time) => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay + time.Ticks);

/// <summary>
/// Returns a DateTime instance with the specified input kind that is set to the date of this DateOnly instance and the time of specified input time.
/// </summary>
/// <param name="time">The time of the day.</param>
/// <param name="kind">One of the enumeration values that indicates whether ticks specifies a local time, Coordinated Universal Time (UTC), or neither.</param>
/// <returns>The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time.</returns>
public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks, kind);
public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => DateTime.SpecifyKind(ToDateTime(time), kind);

/// <summary>
/// Returns a DateOnly instance that is set to the date part of the specified dateTime.
Expand Down Expand Up @@ -272,7 +272,7 @@ public int CompareTo(object? value)
/// Returns the hash code for this instance.
/// </summary>
/// <returns>A 32-bit signed integer hash code.</returns>
public override int GetHashCode() => _dayNumber;
public override int GetHashCode() => (int)_dayNumber;

private const ParseFlags ParseFlagsDateMask = ParseFlags.HaveHour | ParseFlags.HaveMinute | ParseFlags.HaveSecond | ParseFlags.HaveTime | ParseFlags.TimeZoneUsed |
ParseFlags.TimeZoneUtc | ParseFlags.CaptureOffset | ParseFlags.UtcSortPattern;
Expand Down Expand Up @@ -498,12 +498,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, Read
{
case 'o':
format = OFormat;
provider = CultureInfo.InvariantCulture.DateTimeFormat;
provider = DateTimeFormat.InvariantFormatInfo;
break;

case 'r':
format = RFormat;
provider = CultureInfo.InvariantCulture.DateTimeFormat;
provider = DateTimeFormat.InvariantFormatInfo;
break;
}
}
Expand Down Expand Up @@ -575,12 +575,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, stri
{
case 'o':
format = OFormat;
dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat;
dtfiToUse = DateTimeFormat.InvariantFormatInfo;
break;

case 'r':
format = RFormat;
dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat;
dtfiToUse = DateTimeFormat.InvariantFormatInfo;
break;
}
}
Expand Down Expand Up @@ -718,7 +718,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan<char> s)
/// The DateOnly object will be formatted in short form.
/// </summary>
/// <returns>A string that contains the short date string representation of the current DateOnly object.</returns>
public override string ToString() => ToString("d");
public override string ToString() => DateTimeFormat.Format(GetEquivalentDateTime(), "d", null);

/// <summary>
/// Converts the value of the current DateOnly object to its equivalent string representation using the specified format and the formatting conventions of the current culture.
Expand All @@ -732,7 +732,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan<char> s)
/// </summary>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <returns>A string representation of value of the current DateOnly object as specified by provider.</returns>
public string ToString(IFormatProvider? provider) => ToString("d", provider);
public string ToString(IFormatProvider? provider) => DateTimeFormat.Format(GetEquivalentDateTime(), "d", provider);

/// <summary>
/// Converts the value of the current DateOnly object to its equivalent string representation using the specified culture-specific format information.
Expand All @@ -753,13 +753,13 @@ public string ToString([StringSyntax(StringSyntaxAttribute.DateOnlyFormat)] stri
{
'o' => string.Create(10, this, (destination, value) =>
{
DateTimeFormat.TryFormatDateOnlyO(value.Year, value.Month, value.Day, destination, out int charsWritten);
DateTimeFormat.TryFormatDateOnlyO(value, destination, out int charsWritten);
Debug.Assert(charsWritten == destination.Length);
}),

'r' => string.Create(16, this, (destination, value) =>
{
DateTimeFormat.TryFormatDateOnlyR(value.DayOfWeek, value.Year, value.Month, value.Day, destination, out int charsWritten);
DateTimeFormat.TryFormatDateOnlyR(value, destination, out int charsWritten);
Debug.Assert(charsWritten == destination.Length);
}),

Expand Down Expand Up @@ -801,10 +801,10 @@ private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten,
switch (format[0] | 0x20)
{
case 'o':
return DateTimeFormat.TryFormatDateOnlyO(Year, Month, Day, destination, out charsWritten);
return DateTimeFormat.TryFormatDateOnlyO(this, destination, out charsWritten);

case 'r':
return DateTimeFormat.TryFormatDateOnlyR(DayOfWeek, Year, Month, Day, destination, out charsWritten);
return DateTimeFormat.TryFormatDateOnlyR(this, destination, out charsWritten);

case 'm':
case 'd':
Expand Down
7 changes: 4 additions & 3 deletions src/libraries/System.Private.CoreLib/src/System/DateTime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,11 @@ public DateTime(long ticks)

private DateTime(ulong dateData)
{
this._dateData = dateData;
Debug.Assert((dateData & TicksMask) <= MaxTicks);
_dateData = dateData;
}

internal static DateTime UnsafeCreate(long ticks) => new DateTime((ulong)ticks);
internal static DateTime CreateUnchecked(long ticks) => new DateTime((ulong)ticks);

public DateTime(long ticks, DateTimeKind kind)
{
Expand Down Expand Up @@ -1086,7 +1087,7 @@ private static ulong DateToTicks(int year, int month, int day)
ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay();
}

ReadOnlySpan<uint> days = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
ReadOnlySpan<uint> days = RuntimeHelpers.IsKnownConstant(month) && month == 1 || IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
if ((uint)day > days[month] - days[month - 1])
{
ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay();
Expand Down
28 changes: 13 additions & 15 deletions src/libraries/System.Private.CoreLib/src/System/DateTimeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ public readonly partial struct DateTimeOffset

// Static Fields
public static readonly DateTimeOffset MinValue;
public static readonly DateTimeOffset MaxValue = new DateTimeOffset(0, DateTime.UnsafeCreate(DateTime.MaxTicks));
public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(0, DateTime.UnsafeCreate(DateTime.UnixEpochTicks));
public static readonly DateTimeOffset MaxValue = new DateTimeOffset(0, DateTime.CreateUnchecked(DateTime.MaxTicks));
public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(0, DateTime.CreateUnchecked(DateTime.UnixEpochTicks));

// Instance Fields
private readonly DateTime _dateTime;
Expand Down Expand Up @@ -167,7 +167,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se
: this(year, month, day, hour, minute, second, offset)
{
if ((uint)millisecond >= TimeSpan.MillisecondsPerSecond) DateTime.ThrowMillisecondOutOfRange();
_dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond);
_dateTime = DateTime.CreateUnchecked(UtcTicks + (uint)millisecond * (uint)TimeSpan.TicksPerMillisecond);
}

// Constructs a DateTimeOffset from a given year, month, day, hour,
Expand Down Expand Up @@ -252,7 +252,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se
: this(year, month, day, hour, minute, second, millisecond, offset)
{
if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) DateTime.ThrowMicrosecondOutOfRange();
_dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond);
_dateTime = DateTime.CreateUnchecked(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond);
}

/// <summary>
Expand Down Expand Up @@ -326,14 +326,14 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se
: this(year, month, day, hour, minute, second, millisecond, calendar, offset)
{
if ((uint)microsecond >= TimeSpan.MicrosecondsPerMillisecond) DateTime.ThrowMicrosecondOutOfRange();
_dateTime = DateTime.UnsafeCreate(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond);
_dateTime = DateTime.CreateUnchecked(UtcTicks + (uint)microsecond * (uint)TimeSpan.TicksPerMicrosecond);
}

public static DateTimeOffset UtcNow => new DateTimeOffset(0, DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified));

public DateTime DateTime => ClockDateTime;

public DateTime UtcDateTime => DateTime.UnsafeCreate((long)(_dateTime._dateData | DateTime.KindUtc));
public DateTime UtcDateTime => DateTime.CreateUnchecked((long)(_dateTime._dateData | DateTime.KindUtc));

public DateTime LocalDateTime => UtcDateTime.ToLocalTime();

Expand All @@ -346,7 +346,7 @@ public DateTimeOffset(int year, int month, int day, int hour, int minute, int se
// The clock or visible time represented. This is just a wrapper around the internal date because this is
// the chosen storage mechanism. Going through this helper is good for readability and maintainability.
// This should be used for display but not identity.
private DateTime ClockDateTime => DateTime.UnsafeCreate(UtcTicks + _offsetMinutes * TimeSpan.TicksPerMinute);
private DateTime ClockDateTime => DateTime.CreateUnchecked(UtcTicks + _offsetMinutes * TimeSpan.TicksPerMinute);

// Returns the date part of this DateTimeOffset. The resulting value
// corresponds to this DateTimeOffset with the time-of-day part set to
Expand Down Expand Up @@ -587,12 +587,11 @@ public static DateTimeOffset FromUnixTimeSeconds(long seconds)
{
if (seconds < UnixMinSeconds || seconds > UnixMaxSeconds)
{
throw new ArgumentOutOfRangeException(nameof(seconds),
SR.Format(SR.ArgumentOutOfRange_Range, UnixMinSeconds, UnixMaxSeconds));
ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(seconds), seconds, UnixMinSeconds, UnixMaxSeconds);
}

long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks;
return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks));
return new DateTimeOffset(0, DateTime.CreateUnchecked(ticks));
}

public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds)
Expand All @@ -602,12 +601,11 @@ public static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds)

if (milliseconds < MinMilliseconds || milliseconds > MaxMilliseconds)
{
throw new ArgumentOutOfRangeException(nameof(milliseconds),
SR.Format(SR.ArgumentOutOfRange_Range, MinMilliseconds, MaxMilliseconds));
ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(milliseconds), milliseconds, MinMilliseconds, MaxMilliseconds);
}

long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks;
return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks));
return new DateTimeOffset(0, DateTime.CreateUnchecked(ticks));
}

// ----- SECTION: private serialization instance methods ----------------*
Expand Down Expand Up @@ -788,7 +786,7 @@ private static DateTimeOffset ToLocalTime(DateTime utcDateTime, bool throwOnOver
localTicks = localTicks < DateTime.MinTicks ? DateTime.MinTicks : DateTime.MaxTicks;
}

return CreateValidateOffset(DateTime.UnsafeCreate(localTicks), offset);
return CreateValidateOffset(DateTime.CreateUnchecked(localTicks), offset);
}

public override string ToString() =>
Expand Down Expand Up @@ -947,7 +945,7 @@ private static DateTime ValidateDate(DateTime dateTime, TimeSpan offset)
static void ThrowOutOfRange() => throw new ArgumentOutOfRangeException(nameof(offset), SR.Argument_UTCOutOfRange);
}
// make sure the Kind is set to Unspecified
return DateTime.UnsafeCreate(utcTicks);
return DateTime.CreateUnchecked(utcTicks);
}

private static DateTimeStyles ValidateStyles(DateTimeStyles styles)
Expand Down
Loading
Loading