34

默认情况下,C# 将 DateTime 对象与 100ns 刻度进行比较。但是,我的数据库将 DateTime 值返回到最接近的毫秒。使用指定容差比较 C# 中两个 DateTime 对象的最佳方法是什么?

编辑:我正在处理截断问题,而不是舍入问题。正如乔在下面指出的那样,四舍五入的问题会引入新的问题。

对我有用的解决方案是以下这些的组合。

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

如果差异小于一毫秒,则返回 true。对 Duration() 的调用对于获取两个日期之间差异的绝对值很重要。

4

7 回答 7

31

我通常使用 TimeSpan.FromXXX 方法来做这样的事情:

if((myDate - myOtherDate) > TimeSpan.FromSeconds(10))
{
   //Do something here
}
于 2008-12-19T16:16:24.283 回答
10

DateTime 的扩展方法如何制作一个流畅的界面(这些都是风靡一时的,对吧?)

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

这依赖于一个类来存储状态并为 == 和 != 定义几个运算符重载:

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

然后在您的代码中,您可以执行以下操作:

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

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

扩展类还包含一个默认的静态容差,以便您可以为整个项目设置容差并使用不带参数的 Within 方法:

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

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

我有一些单元测试,但是在这里粘贴的代码有点太多了。

于 2008-12-19T16:20:45.363 回答
5

您需要从日期对象中删除毫秒组件。一种方法是:

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

您还可以减去两个日期时间

d.减去(日期时间。现在);

这将返回一个时间跨度对象,您可以使用它来比较天、小时、分钟和秒组件以查看差异。

于 2008-12-19T16:18:03.420 回答
2
        if (Math.Abs(dt1.Subtract(dt2).TotalSeconds) < 1.0)
于 2008-12-19T16:20:05.630 回答
2

我和最初的提问者有类似的问题,但为了让事情更有趣,我正在保存和检索Nullable<DateTime>

我喜欢joshperry 的回答并将其扩展到我的目的:

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

并进行快速单元测试以验证一切正常:

[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
}
于 2014-12-08T18:17:26.170 回答
1

默认情况下,C# 将 DateTime 对象与毫秒进行比较。

实际上分辨率是 100ns 滴答声。

如果您要比较数据库中的两个 DateTime 值,它们的分辨率为 1 秒,没问题。

如果您要与来自另一个来源的 DateTime(例如,使用 DateTime.Now 的当前 DateTime)进行比较,那么您需要决定如何处理小数秒。例如,四舍五入到最接近或截断?如果恰好是半秒,如何舍入。

我建议你四舍五入或截断到整数秒,然后与数据库中的值进行比较。 这是一篇描述如何对 DateTime 进行四舍五入的帖子(此示例四舍五入到分钟,但主体是相同的)。

于 2008-12-19T16:41:52.780 回答
0

我创建了扩展方法 IsSimilar

public static bool IsSimilar(this DateTime? lhs, DateTime? rhs, TimeSpan tolerance)
{
    if (!lhs.HasValue && !lhs.HasValue) return true;//both are null
    if (!lhs.HasValue || !lhs.HasValue) return false;//one of 2 is null
    return IsSimilar(lhs.Value, rhs.Value, tolerance);
}
public static bool IsSimilar(this DateTime lhs, DateTime rhs, TimeSpan tolerance)
{
    return (lhs - rhs).Duration() <= tolerance;
 }
于 2020-04-09T04:29:13.683 回答