Am bit curious about the following behavior when attempting to round off Minutes/Seconds to nearest Hour (in this case ignore the minute/second part).
I tried two approaches, and benchmarked both with BenchMarkDotNet.
private DateTime testData = DateTime.Now;
[Benchmark]
public DateTime CeilingUsingNewOperator() => new DateTime(testData.Year,testData.Month,testData.Day,testData.Hour + 1,0,0);
[Benchmark]
public DateTime CeilingUsingAddOperator() => testData.AddHours(1).AddMinutes(-testData.Minute).AddSeconds(-testData.Second);
Following were the results.
+-------------------------+-----------+----------+----------+
| Method | Mean | Error | StdDev |
+-------------------------+-----------+----------+----------+
| CeilingUsingNewOperator | 207.99 ns | 3.465 ns | 3.072 ns |
| CeilingUsingAddOperator | 108.74 ns | 1.429 ns | 1.337 ns |
+-------------------------+-----------+----------+----------+
I was curious why the New Operator is slower, if I were to assume, each time we invoke the AddX method in second approach, we get a Datetime.
Could someone shed some light ?
Further benchmarks could be used to validate this, but I strongly suspect it's because the AddHours
, AddMinutes
and AddSeconds
methods can all work without performing any complex date/time arithmetic. They just need to:
DateTime
DateTime
value with that new number of ticksYou're also using the Minute
and Second
properties, but they can be computed without working out month/day/year values.
Compare that with the "single constructor" call which needs to:
The code to work out the year, month and day needs to "understand" the Gregorian calendar - there are far more complex calculations to perform in order to extract that information. Some can be cached (e.g. the number of ticks at the start of each year) but then you lose some locality of reference in terms of memory access.
I'd expect the most efficient approach to be one which computed the ticks directly, just with simple arithmetic:
long originalTicks = testData.Ticks;
long hoursSinceEpoch = originalTicks / TimeSpan.TicksPerHour;
long newTicks = hoursSinceEpoch * TimeSpan.TicksPerHour;
return new DateTime(newTicks, testData.Kind);
One significant downside of that: I believe it will have problems around daylight saving boundaries when the DateTimeKind
is Local
, as there are really four kinds rather than the three we normally use (Utc
, Local
, Unspecified
). Local
is effectively split in two to "know" which date/time is being represented when the value is ambiguous between an earlier option and a later one. If you're not using a Local
kind, it should be okay though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With