Skip to content

Commit 72f86bb

Browse files
committed
Improve performance of LocalDate and YearMonth comparisons
- Avoid redundant calendar checks in comparison operators - Use calendar ordinals for comparisons For LocalDate, this improves CompareTo on my machine from 15ns to about 9n, and the less than operator from 24ns to 9ns.
1 parent cbaeabd commit 72f86bb

File tree

3 files changed

+45
-22
lines changed

3 files changed

+45
-22
lines changed

src/NodaTime.Benchmarks/NodaTimeTests/LocalDateBenchmarks.cs

+6
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,11 @@ public IsoDayOfWeek DayOfWeek_BeforeEpoch()
129129

130130
[Benchmark]
131131
public LocalDate FromDateTime_WithCalendar() => LocalDate.FromDateTime(SampleDateTime, CalendarSystem.Julian);
132+
133+
[Benchmark]
134+
public int CompareTo() => Sample.CompareTo(SampleBeforeEpoch);
135+
136+
[Benchmark]
137+
public bool LessThanOperator() => Sample < SampleBeforeEpoch;
132138
}
133139
}

src/NodaTime/LocalDate.cs

+19-11
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,9 @@ public LocalDate(Era era, int yearOfEra, int month, int day, CalendarSystem cale
140140

141141
/// <summary>Gets the calendar system associated with this local date.</summary>
142142
/// <value>The calendar system associated with this local date.</value>
143-
public CalendarSystem Calendar => CalendarSystem.ForOrdinal(yearMonthDayCalendar.CalendarOrdinal);
143+
public CalendarSystem Calendar => CalendarSystem.ForOrdinal(CalendarOrdinal);
144+
145+
private CalendarOrdinal CalendarOrdinal => yearMonthDayCalendar.CalendarOrdinal;
144146

145147
/// <summary>Gets the year of this local date.</summary>
146148
/// <remarks>This returns the "absolute year", so, for the ISO calendar,
@@ -445,8 +447,8 @@ public static LocalDate FromYearMonthWeekAndDay(int year, int month, int occurre
445447
/// <returns>true if the <paramref name="lhs"/> is strictly earlier than <paramref name="rhs"/>, false otherwise.</returns>
446448
public static bool operator <(LocalDate lhs, LocalDate rhs)
447449
{
448-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
449-
return lhs.CompareTo(rhs) < 0;
450+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
451+
return lhs.TrustedCompareTo(rhs) < 0;
450452
}
451453

452454
/// <summary>
@@ -460,8 +462,8 @@ public static LocalDate FromYearMonthWeekAndDay(int year, int month, int occurre
460462
/// <returns>true if the <paramref name="lhs"/> is earlier than or equal to <paramref name="rhs"/>, false otherwise.</returns>
461463
public static bool operator <=(LocalDate lhs, LocalDate rhs)
462464
{
463-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
464-
return lhs.CompareTo(rhs) <= 0;
465+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
466+
return lhs.TrustedCompareTo(rhs) <= 0;
465467
}
466468

467469
/// <summary>
@@ -475,8 +477,8 @@ public static LocalDate FromYearMonthWeekAndDay(int year, int month, int occurre
475477
/// <returns>true if the <paramref name="lhs"/> is strictly later than <paramref name="rhs"/>, false otherwise.</returns>
476478
public static bool operator >(LocalDate lhs, LocalDate rhs)
477479
{
478-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
479-
return lhs.CompareTo(rhs) > 0;
480+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
481+
return lhs.TrustedCompareTo(rhs) > 0;
480482
}
481483

482484
/// <summary>
@@ -490,8 +492,8 @@ public static LocalDate FromYearMonthWeekAndDay(int year, int month, int occurre
490492
/// <returns>true if the <paramref name="lhs"/> is later than or equal to <paramref name="rhs"/>, false otherwise.</returns>
491493
public static bool operator >=(LocalDate lhs, LocalDate rhs)
492494
{
493-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
494-
return lhs.CompareTo(rhs) >= 0;
495+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
496+
return lhs.TrustedCompareTo(rhs) >= 0;
495497
}
496498

497499
/// <summary>
@@ -506,10 +508,16 @@ public static LocalDate FromYearMonthWeekAndDay(int year, int month, int occurre
506508
/// later than <paramref name="other"/>.</returns>
507509
public int CompareTo(LocalDate other)
508510
{
509-
Preconditions.CheckArgument(Calendar.Equals(other.Calendar), nameof(other), "Only values with the same calendar system can be compared");
510-
return Calendar.Compare(YearMonthDay, other.YearMonthDay);
511+
Preconditions.CheckArgument(CalendarOrdinal == other.CalendarOrdinal, nameof(other), "Only values with the same calendar system can be compared");
512+
return TrustedCompareTo(other);
511513
}
512514

515+
/// <summary>
516+
/// Performs a comparison with another date, trusting that the calendar of the other date is already correct.
517+
/// This avoids duplicate calendar checks.
518+
/// </summary>
519+
private int TrustedCompareTo([Trusted] LocalDate other) => Calendar.Compare(YearMonthDay, other.YearMonthDay);
520+
513521
/// <summary>
514522
/// Implementation of <see cref="IComparable.CompareTo"/> to compare two LocalDates.
515523
/// See the type documentation for a description of ordering semantics.

src/NodaTime/YearMonth.cs

+20-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// as found in the LICENSE.txt file.
44

55
using JetBrains.Annotations;
6+
using NodaTime.Annotations;
67
using NodaTime.Calendars;
78
using NodaTime.Text;
89
using NodaTime.Utility;
@@ -40,7 +41,9 @@ public struct YearMonth : IEquatable<YearMonth>, IComparable<YearMonth>, ICompar
4041

4142
/// <summary>Gets the calendar system associated with this year/month.</summary>
4243
/// <value>The calendar system associated with this year/month.</value>
43-
public CalendarSystem Calendar => CalendarSystem.ForOrdinal(startOfMonth.CalendarOrdinal);
44+
public CalendarSystem Calendar => CalendarSystem.ForOrdinal(CalendarOrdinal);
45+
46+
private CalendarOrdinal CalendarOrdinal => startOfMonth.CalendarOrdinal;
4447

4548
/// <summary>Gets the year of this year/month.</summary>
4649
/// <remarks>This returns the "absolute year", so, for the ISO calendar,
@@ -167,10 +170,16 @@ public LocalDate OnDayOfMonth(int day)
167170
/// later than <paramref name="other"/>.</returns>
168171
public int CompareTo(YearMonth other)
169172
{
170-
Preconditions.CheckArgument(Calendar.Equals(other.Calendar), nameof(other), "Only values with the same calendar system can be compared");
171-
return Calendar.Compare(YearMonthDay, other.YearMonthDay);
173+
Preconditions.CheckArgument(CalendarOrdinal == other.CalendarOrdinal, nameof(other), "Only values with the same calendar system can be compared");
174+
return TrustedCompareTo(other);
172175
}
173176

177+
/// <summary>
178+
/// Performs a comparison with another YearMonth, trusting that the calendar of the other date is already correct.
179+
/// This avoids duplicate calendar checks.
180+
/// </summary>
181+
private int TrustedCompareTo([Trusted] YearMonth other) => Calendar.Compare(YearMonthDay, other.YearMonthDay);
182+
174183
/// <summary>
175184
/// Implementation of <see cref="IComparable.CompareTo"/> to compare two YearMonth values.
176185
/// See the type documentation for a description of ordering semantics.
@@ -205,8 +214,8 @@ int IComparable.CompareTo(object obj)
205214
/// <returns>true if the <paramref name="lhs"/> is strictly earlier than <paramref name="rhs"/>, false otherwise.</returns>
206215
public static bool operator <(YearMonth lhs, YearMonth rhs)
207216
{
208-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
209-
return lhs.CompareTo(rhs) < 0;
217+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
218+
return lhs.TrustedCompareTo(rhs) < 0;
210219
}
211220

212221
/// <summary>
@@ -220,8 +229,8 @@ int IComparable.CompareTo(object obj)
220229
/// <returns>true if the <paramref name="lhs"/> is earlier than or equal to <paramref name="rhs"/>, false otherwise.</returns>
221230
public static bool operator <=(YearMonth lhs, YearMonth rhs)
222231
{
223-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
224-
return lhs.CompareTo(rhs) <= 0;
232+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
233+
return lhs.TrustedCompareTo(rhs) <= 0;
225234
}
226235

227236
/// <summary>
@@ -235,8 +244,8 @@ int IComparable.CompareTo(object obj)
235244
/// <returns>true if the <paramref name="lhs"/> is strictly later than <paramref name="rhs"/>, false otherwise.</returns>
236245
public static bool operator >(YearMonth lhs, YearMonth rhs)
237246
{
238-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
239-
return lhs.CompareTo(rhs) > 0;
247+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
248+
return lhs.TrustedCompareTo(rhs) > 0;
240249
}
241250

242251
/// <summary>
@@ -250,8 +259,8 @@ int IComparable.CompareTo(object obj)
250259
/// <returns>true if the <paramref name="lhs"/> is later than or equal to <paramref name="rhs"/>, false otherwise.</returns>
251260
public static bool operator >=(YearMonth lhs, YearMonth rhs)
252261
{
253-
Preconditions.CheckArgument(lhs.Calendar.Equals(rhs.Calendar), nameof(rhs), "Only values in the same calendar can be compared");
254-
return lhs.CompareTo(rhs) >= 0;
262+
Preconditions.CheckArgument(lhs.CalendarOrdinal == rhs.CalendarOrdinal, nameof(rhs), "Only values in the same calendar can be compared");
263+
return lhs.TrustedCompareTo(rhs) >= 0;
255264
}
256265

257266
/// <summary>

0 commit comments

Comments
 (0)