我正在尝试在 JSON 中存储和检索日期。从相同字符串创建的 NSDates 失败 isEqualToDate:
。这当然是由于浮点精度问题,但我不确定如何解决它。
给 dateFromString:
两个相同的输入字符串,生成的 NSDate
对象应该相等:
NSDate *date1 = [NSDate date];
NSString *string1 = [dateFormatter stringFromDate:date1];
NSDate *dateA = [dateFormatter dateFromString:string1];
NSDate *dateB = [dateFormatter dateFromString:string1];
XCTAssertTrue([dateA isEqualToDate:dateB]);
...但是,实际上生成的日期对象很幸运是相等的(而且很少是相等的),因为由于浮点精度问题引入了随机的杂物:
(lldb) p [dateA timeIntervalSinceReferenceDate]
(NSTimeInterval) $4 = 560455653.79073596
(lldb) p [dateB timeIntervalSinceReferenceDate]
(NSTimeInterval) $5 = 560455653.79099989
那么,有人遇到过这个问题并解决了吗?我想到的唯一选择是编写自己的 isEquals:
但这并不理想。
编辑:
特别是我正在寻找的是一种将日期的字符串表示形式转换回日期对象的方法,对于相同的输入字符串,该日期对象将被视为相等。
NSDate
将其内部状态存储在 float 中的(明显的)事实是无关紧要的,因为 Foundation 应该提供一种机制来在给定相同输入(即相同字符串)时实现输出日期的奇偶校验in 应该产生相等的对象)。要么 Foundation 提供了这个,但我错过了它(希望 SO 社区了解一些我不知道的 Foundation),要么 Foundation 有问题(在这种情况下),我正在向 SO 社区寻求我没有的解决方法'已经考虑过了。
编辑 2:
抱歉,我的问题和最初问的一样,是胡说八道。为了简化这篇文章的问题,我引入了两个不可能相等的数字之间的意外比较。
原创废话,留给后人:
I'm trying to store and retrieve dates to/from JSON. The
NSDate
I store into JSON failsisEqualToDate:
on theNSDate
object I recreate from the stored string. This is certainly due to floating point precision issue, but I'm not sure how to work around it. Specifically:NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; NSDate *date1 = [NSDate date]; NSString *string1 = [dateFormatter stringFromDate:date1]; NSDate *date2 = [dateFormatter dateFromString:string1]; NSString *string2 = [dateFormatter stringFromDate:date2]; // This test passes XCTAssertTrue([string1 isEqualToString:string2]); // This test fails XCTAssertTrue([date1 isEqualToDate:date2]); // This test fails XCTAssertTrue([date1 isEqual:date2]);
Looking at
date1
anddate2
in the debugger we see the difference:(lldb) p [date1 timeIntervalSinceReferenceDate] 560363055.21521103 (lldb) p [date2 timeIntervalSinceReferenceDate] 560363055.21499991
(note the thousandth's place)
NSDate
'sisEqual:
(and variant) is almost certainly comparing the instances' time offsets, and thus failing.I have tried adding more precision to the stored string (i.e.
.SSSSSS
) but this does not seem to have an impact.So, anyone encounter this and workaround it? The only option I have thought of is to write my own
isEquals:
but that's not ideal.
最佳答案
tl;dr - 您正在尝试将纳秒与毫秒进行比较。这些结果不会相同。
当您使用 [NSDate date];
创建一个 NSDate
时,您会得到一个包含精确到微秒甚至纳秒的小数秒的值。
当您将日期转换为格式为 yyyy-MM-dd'T'HH:mm:ss.SSS
的字符串时,您将创建一个精确到小数点后 3 位(毫秒)的字符串。然后当您将该字符串转换回 NSDate
时,您会得到一个 float ,它尽可能接近那些毫秒数。
因此您的原始日期精度为微秒或纳秒,而第二个日期仅为毫秒。当然,由于精度水平不同,这两个日期会有所不同。这与 float 无关。即使你有完美的 float ,你也在比较 100.123456789 和 100.123。它们不是同一个数字。
您声明您尝试使用 SSSSSS
而不是 SSS
但 NSDateFormatter
不支持任何超过三位的小数,因此任何超过 SSS
是白费力气。
有了这个解释,你有什么解决方案来比较你的两个日期?
一种是将两个日期只比较到小数点后三位。这是一个有用的小 NSDate
类别方法,它就是这样做的:
@interface NSDate (extra)
- (BOOL)isEqualToDateMilliseconds:(NSDate *)otherDate;
@end
@implementation NSDate (extra)
- (BOOL)isEqualToDateMilliseconds:(NSDate *)otherDate {
TimeInterval secs1 = [self timeIntervalSinceReferenceDate];
TimeInterval secs2 = [self timeIntervalSinceReferenceDate];
return abs(secs1 - secs2) < 0.001;
}
@end
如果您自己的代码,请使用更好的类别名称。
现在你可以替换:
XCTAssertTrue([date1 isEqualToDate:date2]);
与:
XCTAssertTrue([date1 isEqualToDateMilliseconds:date2]);
你会得到正确的结果。
关于ios - 与 NSString 转换后 NSDate 不相等,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52654637/