There are many ways you could round off a DateTime to the nearest Hour. You could create a new Instance of DateTime, ignoring the current instance’s Minute/Seconds parts. You could also use the Add Minute method as seen in the following code.
Constructor Vs Add Method
private DateTime testData = DateTime.Now; public DateTime FloorWithConstructor() => new DateTime(testData.Year, testData.Month, testData.Day, testData.Hour, 0, 0); public DateTime FloorWithAddOperator() => testData.AddMinute(-testData.Hour).AddSeconds(-testData.Second);
But which of the two is more efficient ? Let’s attempt to benchmark them with BenchmarkDotNet.
Method | Mean | Error | StdDev |
FloorWithConstructor | 203.92 ns | 4.057 ns | 6.435 ns |
FloorWithAddOperator | 97.28 ns | 2.319 ns | 3.326 ns |
Since I was under the impression that since each call to AddMinute/AddSeconds, methods would create a new instance of DateTime, I was very curious why the Add Method approach performed marginally better. But thanks to Daisy Shipton @Stackoverflow, I was able to understand why the difference in a much better way. This is also visible if you happen to refer the DateTime Source at ReferenceSource.
The Constructor : From Reference Source
public DateTime(int year, int month, int day, int hour, int minute, int second) { this.dateData = (UInt64)(DateToTicks(year, month, day) + TimeToTicks(hour, minute, second)); } private static long DateToTicks(int year, int month, int day) { if (year >= 1 && year = 1 && month = 1 && day = 0 && hour = 0 && minute =0 && second = 0? 0.5: -0.5)); if (millis = MaxMillis) throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_AddValue")); return AddTicks(millis * TicksPerMillisecond); } public DateTime AddTicks(long value) { long ticks = InternalTicks; if (value > MaxTicks - ticks || value < MinTicks - ticks) { throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_DateArithmetic")); } return new DateTime((UInt64)(ticks + value) | InternalKind); }
The difference is obvious. The significant more arthimatic/conditional operations happening the particular constructor makes it rather more slower, compared to Add Methods.
The Ticks Approach
So what would be a more efficient approach ? How about if we manually work with the Ticks ? Let’s write a method and run it against the other methods.
public class DateTimeMethods { private DateTime testData = DateTime.Now; [Benchmark] public DateTime FloorWithConstructor() => new DateTime(testData.Year, testData.Month, testData.Day, testData.Hour, 0, 0); [Benchmark] public DateTime FloorWithAddOperator() => testData.AddMinutes(-testData.Minute).AddSeconds(-testData.Second); [Benchmark] public DateTime FloorWithTicks() { var originalTicks = testData.Ticks; var hoursSinceEpoch = originalTicks / TimeSpan.TicksPerHour; var newTicks = hoursSinceEpoch * TimeSpan.TicksPerHour; return new DateTime(newTicks); } }
Method | Mean | Error | StdDev |
FloorWithConstructor | 206.37 ns | 4.1382 ns | 5.2335 ns |
FloorWithAddOperator | 97.81 ns | 2.3568 ns | 2.0892 ns |
FloorWithTicks | 24.47 ns | 0.1492 ns | 0.1246 ns |
As you can see it has made a significant improvement now.No prizes for guessing how the Constructor that accepts Ticks looks like.
public DateTime(long ticks) { if (ticks MaxTicks) throw new ArgumentOutOfRangeException("ticks", Environment.GetResourceString("ArgumentOutOfRange_DateTimeBadTicks")); Contract.EndContractBlock(); dateData = (UInt64)ticks; }
The sample code for this demo can be found in my GitHub.