views:

4119

answers:

6

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.

A: 

Subtract the dates and test that against your tolerance level.

EndangeredMassa
+11  A: 

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

if((myDate - myOtherDate) > TimeSpan.FromSeconds(10))
{
   //Do something here
}
Micah
A slightly different way: if( (date1 - date2).TotalSeconds > 10 )
Thomas
+2  A: 

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.

JoshBerke
why the downvote whats wrong with this approach?
JoshBerke
+1  A: 
        if (Math.Abs(dt1.Subtract(dt2).TotalSeconds) < 1.0)
Charles Bretana
+6  A: 

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.

joshperry
This looks like an acceptable use of extension methods, hooray!
EndangeredMassa
method should be static
darasd
and it will need a diffenent name, as the compiler thinks you are trying to call the static DateTime.Equals method, and then complains that it cannot be called as an instance method.
darasd
@darasd Wow, thanks for bringing me back to this ancient answer and helping fix it. But I'm going to have to go one step further and change the answer a bit.
joshperry
+1  A: 

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).

Joe