Skip to content

Commit e5ed253

Browse files
authored
Optimize DateOnly, TimeOnly, ISOWeek (#111244)
* Optimize DateOnly, TimeOnly, ISOWeek * Address PR feedback
1 parent 4ae1cf2 commit e5ed253

File tree

6 files changed

+121
-113
lines changed

6 files changed

+121
-113
lines changed

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

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ public readonly struct DateOnly
2121
ISpanParsable<DateOnly>,
2222
IUtf8SpanFormattable
2323
{
24-
private readonly int _dayNumber;
24+
private readonly uint _dayNumber;
2525

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

2929
// Maps to December 31 year 9999.
3030
private const int MaxDayNumber = DateTime.DaysTo10000 - 1;
3131

32-
private static int DayNumberFromDateTime(DateTime dt) => (int)((ulong)dt.Ticks / TimeSpan.TicksPerDay);
32+
private static uint DayNumberFromDateTime(DateTime dt) => (uint)((ulong)dt.Ticks / TimeSpan.TicksPerDay);
3333

34-
internal DateTime GetEquivalentDateTime() => DateTime.UnsafeCreate(_dayNumber * TimeSpan.TicksPerDay);
34+
internal DateTime GetEquivalentDateTime() => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay);
3535

36-
private DateOnly(int dayNumber)
36+
private DateOnly(uint dayNumber)
3737
{
38-
Debug.Assert((uint)dayNumber <= MaxDayNumber);
38+
Debug.Assert(dayNumber <= MaxDayNumber);
3939
_dayNumber = dayNumber;
4040
}
4141

@@ -77,7 +77,7 @@ public static DateOnly FromDayNumber(int dayNumber)
7777
ThrowHelper.ThrowArgumentOutOfRange_DayNumber(dayNumber);
7878
}
7979

80-
return new DateOnly(dayNumber);
80+
return new DateOnly((uint)dayNumber);
8181
}
8282

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

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

113113
/// <summary>
114114
/// Adds the specified number of days to the value of this instance.
@@ -120,8 +120,8 @@ public static DateOnly FromDayNumber(int dayNumber)
120120
/// </exception>
121121
public DateOnly AddDays(int value)
122122
{
123-
int newDayNumber = _dayNumber + value;
124-
if ((uint)newDayNumber > MaxDayNumber)
123+
uint newDayNumber = _dayNumber + (uint)value;
124+
if (newDayNumber > MaxDayNumber)
125125
{
126126
ThrowOutOfRange();
127127
}
@@ -214,15 +214,15 @@ public void Deconstruct(out int year, out int month, out int day)
214214
/// </summary>
215215
/// <param name="time">The time of the day.</param>
216216
/// <returns>The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time.</returns>
217-
public DateTime ToDateTime(TimeOnly time) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks);
217+
public DateTime ToDateTime(TimeOnly time) => DateTime.CreateUnchecked(_dayNumber * TimeSpan.TicksPerDay + time.Ticks);
218218

219219
/// <summary>
220220
/// 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.
221221
/// </summary>
222222
/// <param name="time">The time of the day.</param>
223223
/// <param name="kind">One of the enumeration values that indicates whether ticks specifies a local time, Coordinated Universal Time (UTC), or neither.</param>
224224
/// <returns>The DateTime instance composed of the date of the current DateOnly instance and the time specified by the input time.</returns>
225-
public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => new DateTime(_dayNumber * TimeSpan.TicksPerDay + time.Ticks, kind);
225+
public DateTime ToDateTime(TimeOnly time, DateTimeKind kind) => DateTime.SpecifyKind(ToDateTime(time), kind);
226226

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

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

504504
case 'r':
505505
format = RFormat;
506-
provider = CultureInfo.InvariantCulture.DateTimeFormat;
506+
provider = DateTimeFormat.InvariantFormatInfo;
507507
break;
508508
}
509509
}
@@ -575,12 +575,12 @@ private static ParseFailureKind TryParseExactInternal(ReadOnlySpan<char> s, stri
575575
{
576576
case 'o':
577577
format = OFormat;
578-
dtfiToUse = CultureInfo.InvariantCulture.DateTimeFormat;
578+
dtfiToUse = DateTimeFormat.InvariantFormatInfo;
579579
break;
580580

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

723723
/// <summary>
724724
/// 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.
@@ -732,7 +732,7 @@ private static void ThrowOnError(ParseFailureKind result, ReadOnlySpan<char> s)
732732
/// </summary>
733733
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
734734
/// <returns>A string representation of value of the current DateOnly object as specified by provider.</returns>
735-
public string ToString(IFormatProvider? provider) => ToString("d", provider);
735+
public string ToString(IFormatProvider? provider) => DateTimeFormat.Format(GetEquivalentDateTime(), "d", provider);
736736

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

760760
'r' => string.Create(16, this, (destination, value) =>
761761
{
762-
DateTimeFormat.TryFormatDateOnlyR(value.DayOfWeek, value.Year, value.Month, value.Day, destination, out int charsWritten);
762+
DateTimeFormat.TryFormatDateOnlyR(value, destination, out int charsWritten);
763763
Debug.Assert(charsWritten == destination.Length);
764764
}),
765765

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

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

809809
case 'm':
810810
case 'd':

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,11 @@ public DateTime(long ticks)
148148

149149
private DateTime(ulong dateData)
150150
{
151-
this._dateData = dateData;
151+
Debug.Assert((dateData & TicksMask) <= MaxTicks);
152+
_dateData = dateData;
152153
}
153154

154-
internal static DateTime UnsafeCreate(long ticks) => new DateTime((ulong)ticks);
155+
internal static DateTime CreateUnchecked(long ticks) => new DateTime((ulong)ticks);
155156

156157
public DateTime(long ticks, DateTimeKind kind)
157158
{
@@ -1086,7 +1087,7 @@ private static ulong DateToTicks(int year, int month, int day)
10861087
ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay();
10871088
}
10881089

1089-
ReadOnlySpan<uint> days = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
1090+
ReadOnlySpan<uint> days = RuntimeHelpers.IsKnownConstant(month) && month == 1 || IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
10901091
if ((uint)day > days[month] - days[month - 1])
10911092
{
10921093
ThrowHelper.ThrowArgumentOutOfRange_BadYearMonthDay();

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ public readonly partial struct DateTimeOffset
5959

6060
// Static Fields
6161
public static readonly DateTimeOffset MinValue;
62-
public static readonly DateTimeOffset MaxValue = new DateTimeOffset(0, DateTime.UnsafeCreate(DateTime.MaxTicks));
63-
public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(0, DateTime.UnsafeCreate(DateTime.UnixEpochTicks));
62+
public static readonly DateTimeOffset MaxValue = new DateTimeOffset(0, DateTime.CreateUnchecked(DateTime.MaxTicks));
63+
public static readonly DateTimeOffset UnixEpoch = new DateTimeOffset(0, DateTime.CreateUnchecked(DateTime.UnixEpochTicks));
6464

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

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

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

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

334334
public DateTime DateTime => ClockDateTime;
335335

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

338338
public DateTime LocalDateTime => UtcDateTime.ToLocalTime();
339339

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

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

594593
long ticks = seconds * TimeSpan.TicksPerSecond + DateTime.UnixEpochTicks;
595-
return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks));
594+
return new DateTimeOffset(0, DateTime.CreateUnchecked(ticks));
596595
}
597596

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

603602
if (milliseconds < MinMilliseconds || milliseconds > MaxMilliseconds)
604603
{
605-
throw new ArgumentOutOfRangeException(nameof(milliseconds),
606-
SR.Format(SR.ArgumentOutOfRange_Range, MinMilliseconds, MaxMilliseconds));
604+
ThrowHelper.ThrowArgumentOutOfRange_Range(nameof(milliseconds), milliseconds, MinMilliseconds, MaxMilliseconds);
607605
}
608606

609607
long ticks = milliseconds * TimeSpan.TicksPerMillisecond + DateTime.UnixEpochTicks;
610-
return new DateTimeOffset(0, DateTime.UnsafeCreate(ticks));
608+
return new DateTimeOffset(0, DateTime.CreateUnchecked(ticks));
611609
}
612610

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

791-
return CreateValidateOffset(DateTime.UnsafeCreate(localTicks), offset);
789+
return CreateValidateOffset(DateTime.CreateUnchecked(localTicks), offset);
792790
}
793791

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

953951
private static DateTimeStyles ValidateStyles(DateTimeStyles styles)

0 commit comments

Comments
 (0)