2008-12-19 9 views
25

Por defecto C# compara los objetos DateTime con los 100ns tic. Sin embargo, mi base de datos devuelve los valores de DateTime al milisegundo más cercano. ¿Cuál es la mejor manera de comparar dos objetos DateTime en C# usando una tolerancia especificada?¿Cómo se comparan los objetos DateTime con una tolerancia especificada en C#?

Editar: Estoy lidiando con un problema de truncamiento, no un problema de redondeo. Como Joe señala a continuación, un problema de redondeo introduciría nuevas preguntas.

La solución que funciona para mí es una combinación de los siguientes.

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

Esto devuelve verdadero si la diferencia es inferior a un milisegundo. La llamada a Duración() es importante para obtener el valor absoluto de la diferencia entre las dos fechas.

Respuesta

23

I Normalamente utilizar los métodos TimeSpan.FromXXX a hacer algo como esto:

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

una manera ligeramente diferente: if ((fecha1 - date2) .TotalSeconds> 10) – Thomas

+4

Esto debería ser '(dateTime1 - dateTime2) .Duration() Marius

5

Es necesario eliminar el componente de milisegundos desde el objeto de fecha. Una forma es:

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

también se pueden restar dos datetimes

d.Subtract (DateTime.Now);

Esto devolverá un objeto de intervalo de tiempo que puede usar para comparar los componentes de días, horas, minutos y segundos para ver la diferencia.

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

+1 para la respuesta usando absolute para tener en cuenta la desviación del reloj –

10

¿Qué tal un método de extensión de DateTime para hacer un poco de una interfaz fluida (esos son todos el derecho de la rabia?)

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); 
    } 
} 

Esto se basa en una clase para almacenar el estado y definir una ! sobrecargas de operadores par de == y =:

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; 
    } 
} 

Luego, en su código que puede hacer:

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

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

La clase extensión también alberga una tolerancia estática por defecto por lo que se puede establecer una tolerancia para todo el proyecto y utilizar el método Dentro sin parámetros:

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

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

Tengo un par de pruebas de unidad, pero eso sería un poco demasiado código para pegar aquí.

+0

Esto parece un uso aceptable de metra de extensión ods, ¡hurra! El método – EndangeredMassa

+0

debe ser estático – darasd

+0

y necesitará un nombre diferente, ya que el compilador cree que está tratando de llamar al método estático DateTime.Equals, y luego se queja de que no se puede llamar como un método de instancia. – darasd

1

Por defecto C# compara los objetos DateTime con el milésimo de segundo.

Actualmente la resolución es 100ns tick.

Si está comparando dos valores de DateTime de la base de datos, que tienen una resolución de 1s, no hay problema.

Si está comparando con un DateTime de otra fuente (por ejemplo, el DateTime actual usando DateTime.Now)), entonces necesita decidir cómo quiere que se traten las fracciones de segundo. P.ej. redondeado al más cercano o truncado? Cómo redondear si es exactamente medio segundo.

te sugieren redonda o truncar a un número entero de segundos, y luego comparar con el valor de la base de datos. Here's a post that describes how to round a DateTime (este ejemplo se redondea a minutos, pero el principal es el mismo).

0

tuve un problema similar como el que pregunta original, pero para hacer las cosas más interesante estaba guardando y recuperando Nullable<DateTime>.

me gusta joshperry's answer y lo extendió a trabajar para mis propósitos:

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; 
    } 
} 

Y una prueba de unidad rápida para verificar que todo está funcionando correctamente:

[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 
} 
+0

Por si acaso alguien estaba buscando una manera de hacer esto fácilmente con las fechas 'Nullable' también. La solución de joshperry ya funciona perfectamente para objetos 'DateTime' normales. –

Cuestiones relacionadas