A Closer look at DateTime Operations

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.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s