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.