Question

By default C# compares DateTime objects to the 100ns tick. However, my database returns DateTime values to the nearest millisecond. What's the best way to compare two DateTime objects in C# using a specified tolerance?

Edit: I'm dealing with a truncation issue, not a rounding issue. As Joe points out below, a rounding issue would introduce new questions.

The solution that works for me is a combination of those below.

(dateTime1 - dateTime2).Duration() < TimeSpan.FromMilliseconds(1)

This returns true if the difference is less than one millisecond. The call to Duration() is important in order to get the absolute value of the difference between the two dates.

Was it helpful?

Solution

I usally use the TimeSpan.FromXXX methods to do something like this:

if((myDate - myOtherDate) > TimeSpan.FromSeconds(10))
{
   //Do something here
}

OTHER TIPS

How about an extension method for DateTime to make a bit of a fluent interface (those are all the rage right?)

public static class DateTimeTolerance
{
    private static TimeSpan _defaultTolerance = TimeSpan.FromSeconds(10);
    public static void SetDefault(TimeSpan tolerance)
    {
        _defaultTolerance = tolerance;
    }

    public static DateTimeWithin Within(this DateTime dateTime, TimeSpan tolerance)
    {
        return new DateTimeWithin(dateTime, tolerance);
    }

    public static DateTimeWithin Within(this DateTime dateTime)
    {
        return new DateTimeWithin(dateTime, _defaultTolerance);
    }
}

This relies on a class to store the state and define a couple operator overloads for == and != :

public class DateTimeWithin
{
    public DateTimeWithin(DateTime dateTime, TimeSpan tolerance)
    {
        DateTime = dateTime;
        Tolerance = tolerance;
    }

    public TimeSpan Tolerance { get; private set; }
    public DateTime DateTime { get; private set; }

    public static bool operator ==(DateTime lhs, DateTimeWithin rhs)
    {
        return (lhs - rhs.DateTime).Duration() <= rhs.Tolerance;
    }

    public static bool operator !=(DateTime lhs, DateTimeWithin rhs)
    {
        return (lhs - rhs.DateTime).Duration() > rhs.Tolerance;
    }

    public static bool operator ==(DateTimeWithin lhs, DateTime rhs)
    {
        return rhs == lhs;
    }

    public static bool operator !=(DateTimeWithin lhs, DateTime rhs)
    {
        return rhs != lhs;
    }
}

Then in your code you can do:

DateTime d1 = DateTime.Now;
DateTime d2 = d1 + TimeSpan.FromSeconds(20);

if(d1 == d2.Within(TimeSpan.FromMinutes(1))) {
    // TRUE! Do whatever
}

The extension class also houses a default static tolerance so that you can set a tolerance for your whole project and use the Within method with no parameters:

DateTimeTolerance.SetDefault(TimeSpan.FromMinutes(1));

if(d1 == d2.Within()) {  // Uses default tolerance
    // TRUE! Do whatever
}

I have a few unit tests but that'd be a bit too much code for pasting here.

You need to remove the milliseconds component from the date object. One way is:

    DateTime d = DateTime.Now;
    d.Subtract(new TimeSpan(0, 0, 0, 0, d.Millisecond));

You can also subtract two datetimes

d.Subtract(DateTime.Now);

This will return a timespan object which you can use to compare the days, hours, minutes and seconds components to see the difference.

        if (Math.Abs(dt1.Subtract(dt2).TotalSeconds) < 1.0)

By default C# compares DateTime objects to the millesecond.

Actually the resolution is to the 100ns tick.

If you're comparing two DateTime values from the database, which have 1s resolution, no problem.

If you're comparing with a DateTime from another source (e.g. the current DateTime using DateTime.Now)), then you need to decide how you want the fractions of a second to be treated. E.g. rounded to nearest or truncated? How to round if it's exactly half a second.

I suggest you round or truncate to an integral number of seconds, then compare with the value from the database. Here's a post that describes how to round a DateTime (this example rounds to minutes, but the principal is the same).

I had a similar problem as the original questioner but to make things more interesting I was saving and retrieving Nullable<DateTime>.

I liked joshperry's answer and extended it to work for my purposes:

public static class DateTimeTolerance
{
    private static TimeSpan _defaultTolerance = TimeSpan.FromMilliseconds(10); // 10ms default resolution
    public static void SetDefault(TimeSpan tolerance)
    {
        _defaultTolerance = tolerance;
    }

    public static DateTimeWithin Within(this DateTime dateTime, TimeSpan tolerance)
    {
        return new DateTimeWithin(dateTime, tolerance);
    }

    public static DateTimeWithin Within(this DateTime dateTime)
    {
        return new DateTimeWithin(dateTime, _defaultTolerance);
    }

    // Additional overload that can deal with Nullable dates
    // (treats null as DateTime.MinValue)
    public static DateTimeWithin Within(this DateTime? dateTime)
    {
        return dateTime.GetValueOrDefault().Within();
    }

    public static DateTimeWithin Within(this DateTime? dateTime, TimeSpan tolerance)
    {
        return dateTime.GetValueOrDefault().Within(tolerance);
    }
}

public class DateTimeWithin
{
    public DateTimeWithin(DateTime dateTime, TimeSpan tolerance)
    {
        DateTime = dateTime;
        Tolerance = tolerance;
    }

    public TimeSpan Tolerance { get; private set; }
    public DateTime DateTime { get; private set; }

    public static bool operator ==(DateTime lhs, DateTimeWithin rhs)
    {
        return (lhs - rhs.DateTime).Duration() <= rhs.Tolerance;
    }

    public static bool operator !=(DateTime lhs, DateTimeWithin rhs)
    {
        return (lhs - rhs.DateTime).Duration() > rhs.Tolerance;
    }

    public static bool operator ==(DateTimeWithin lhs, DateTime rhs)
    {
        return rhs == lhs;
    }

    public static bool operator !=(DateTimeWithin lhs, DateTime rhs)
    {
        return rhs != lhs;
    }

    // Overloads that can deal with Nullable dates
    public static bool operator !=(DateTimeWithin lhs, DateTime? rhs)
    {
        return rhs != lhs;
    }

    public static bool operator ==(DateTime? lhs, DateTimeWithin rhs)
    {
        if (!lhs.HasValue && rhs.DateTime == default(DateTime)) return true;
        if (!lhs.HasValue) return false;
        return (lhs.Value - rhs.DateTime).Duration() <= rhs.Tolerance;
    }

    public static bool operator !=(DateTime? lhs, DateTimeWithin rhs)
    {
        if (!lhs.HasValue && rhs.DateTime == default(DateTime)) return true;
        if (!lhs.HasValue) return false;
        return (lhs.Value - rhs.DateTime).Duration() > rhs.Tolerance;
    }

    public static bool operator ==(DateTimeWithin lhs, DateTime? rhs)
    {
        return rhs == lhs;
    }
}

And a quick unit test to verify everything is working correctly:

[TestMethod]
public void DateTimeExtensions_Within_WorksWithNullable()
{
    var now = DateTime.Now;
    var dtNow1 = new DateTime?(now);
    var dtNow2 = new DateTime?(now.AddMilliseconds(1));
    var dtNowish = new DateTime?(now.AddMilliseconds(25));
    DateTime? dtNull = null;

    Assert.IsTrue(now == dtNow1.Within()); // Compare DateTime to DateTime?
    Assert.IsTrue(dtNow1 == dtNow2.Within()); // Compare two DateTime? using a different syntax
    Assert.IsTrue(dtNow1 == dtNow2.Within()); // Same value should be true
    Assert.IsFalse(dtNow1 == dtNowish.Within()); // Outside of the default 10ms tolerance, should not be equal
    Assert.IsTrue(dtNow1 == dtNowish.Within(TimeSpan.FromMilliseconds(50))); // ... but we can override this
    Assert.IsFalse(dtNow1 == dtNull.Within()); // Comparing a value to null should be false
    Assert.IsTrue(dtNull == dtNull.Within()); // ... but two nulls should be true
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top