ios - 与 NSString 转换后 NSDate 不相等

标签 ios objective-c

我正在尝试在 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 fails isEqualToDate: on the NSDate 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 and date2 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's isEqual: (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 而不是 SSSNSDateFormatter 不支持任何超过三位的小数,因此任何超过 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/

相关文章:

ios - 用于在图像上叠加文本和图形的图形库和函数

objective-c - 调用restorePreviousTransactionsOnComplete后,MKStorekit isFeaturePurchased返回NO

objective-c - 使用 RCocoa 在 R 中加载 objective-c 库

ios - UITableViewCell accessoryType 不起作用 Xcode 6.0.3

ios - 显示结果后关闭 UISearchBar

ios - Flutter Spinkit 进度指示器

iphone - 将段控件设置为未选中

ios - 客户的账单信息未显示在 Authorize.Net 中

iphone - 增强现实寻宝

ios - 自定义 UIView 不显示