Pergunta

Por padrão C # compara DateTime objetos aos 100ns carrapato. No entanto, o meu banco de dados retorna DateTime valores para o milissegundo mais próximo. Qual é a melhor maneira de comparar dois DateTime objetos em C # usando uma tolerância especificada?

Edit: Eu estou lidando com um problema de truncagem, não uma questão de arredondamento. Como aponta Joe abaixo, uma questão de arredondamento iria introduzir novas questões.

A solução que funciona para mim é uma combinação dos abaixo.

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

Isso retorna true se a diferença for inferior a um milissegundo. A chamada para Duração () é importante, a fim de obter o valor absoluto da diferença entre as duas datas.

Foi útil?

Solução

I usally usar os métodos TimeSpan.FromXXX para fazer algo como isto:

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

Outras dicas

Que tal um método de extensão para DateTime para fazer um pouco de uma interface fluente (aqueles são todos o direito raiva?)

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

Esta se baseia em uma classe para armazenar o estado e definir um operador sobrecargas casal para == e =:

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

Em seguida, em seu código que você pode fazer:

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

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

A classe de extensão também abriga uma tolerância estática padrão para que você pode definir uma tolerância para todo o seu projeto e usar o Dentro método sem parâmetros:

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

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

Eu tenho alguns testes de unidade, mas que seria um pouco demais código para colar aqui.

Você precisa remover os milissegundos de componentes do objeto data. Uma maneira é:

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

Você também pode subtrair dois datetimes

d.Subtract (DateTime.Now);

Isso irá retornar um objeto timespan que você pode usar para comparar os dias, horas, minutos e segundos componentes para ver a diferença.

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

Por padrão C # compara DateTime objetos ao millesecond.

Na verdade, a resolução é para os 100ns carrapato.

Se você está comparando dois valores DateTime do banco de dados, que têm 1s resolução, não há problema.

Se você está comparando com um DateTime de outra fonte (por exemplo, o atual DateTime usando DateTime.Now)), então você precisa decidir como deseja que as frações de segundo a ser tratada. Por exemplo. arredondado para mais próximo ou truncado? Como rodada se é exatamente meio segundo.

Eu sugiro que você redonda ou truncar para um número inteiro de segundos, e depois comparar com o valor do banco de dados. Aqui está um post que descreve como arredondar um DateTime (este exemplo rodadas para minutos, mas o principal é o mesmo).

Eu tive um problema semelhante como a pergunta original, mas para tornar as coisas mais interessantes que eu estava salvando e recuperando Nullable<DateTime>.

de joshperry resposta e estendeu-o para o trabalho para os meus 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;
    }
}

E um teste de unidade rápido para verificar se tudo está funcionando corretamente:

[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
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top