默认情况下,C#将DateTime对象与100ns滴答进行比较。但是,我的数据库将DateTime值返回到最接近的毫秒。使用指定的公差比较C#中的两个DateTime对象的最佳方法是什么?
编辑:我正在处理截断问题,而不是舍入问题。正如Joe在下面指出的那样,四舍五入的问题将引入新的问题。
适用于我的解决方案是以下解决方案的组合。
(dateTime1 - dateTime2).Duration() < TimeSpan.FromMilliseconds(1)
如果差异小于一毫秒,则返回true。为了获得两个日期之间的差的绝对值,对Duration()的调用很重要。
if((myDate - myOtherDate) > TimeSpan.FromSeconds(10))
{
//Do something here
}
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
}
扩展类还具有默认的静态公差,因此您可以为整个项目设置公差,并使用不带参数的Inside方法:
DateTimeTolerance.SetDefault(TimeSpan.FromMinutes(1));
if(d1 == d2.Within()) { // Uses default tolerance
// TRUE! Do whatever
}
我有一些单元测试,但是要粘贴到这里有点太多代码。
DateTime d = DateTime.Now;
d.Subtract(new TimeSpan(0, 0, 0, 0, d.Millisecond));
您还可以减去两个日期时间
d.Subtract(DateTime.Now);这将返回一个时间跨度对象,可用于比较日期,小时,分钟和秒的组成部分以查看差异。
实际上分辨率是100ns刻度。如果您要比较数据库中两个具有1s分辨率的DateTime值,则没问题。
如果要与其他来源的DateTime(例如,使用DateTime.Now的当前DateTime)进行比较,则需要决定如何处理一秒的小数。例如。四舍五入到最接近或截断?如果正好是半秒,该如何取整。
我建议您舍入或截断至整数秒,然后与数据库中的值进行比较。 Here's a post that describes how to round a DateTime(此示例四舍五入为分钟,但原理相同)。
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
}
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;
}