首页 > 解决方案 > NSDate 在与 NSString 之间转换后不相等

问题描述

我正在尝试从 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将其内部状态存储在浮点数中的(明显)事实是无关紧要的,因为 Foundation 应该提供一种机制来在给定相同输入时实现输出日期的奇偶校验(即,相同的字符串应该产生相等的对象)。要么是 Foundation 提供了这个,但我很想念它(希望 SO 社区知道一些我不知道的 Foundation),或者 Foundation 有问题(在这种情况下),我正在向 SO 社区寻求我没有的解决方法'还没有考虑。

编辑2:

抱歉,我的问题,正如最初提出的那样,是无稽之谈。为了简化这篇文章的问题,我引入了两个不可能相等的数字之间的无意比较。

原创废话,供后人参考:

我正在尝试从 JSON 存储和检索日期。我从存储的字符串重新创建的对象上NSDate存储到 JSON 中失败。这当然是由于浮点精度问题,但我不知道如何解决它。具体来说:isEqualToDate:NSDate

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

查看date1date2在调试器中我们看到了不同之处:

(lldb) p [date1 timeIntervalSinceReferenceDate]
560363055.21521103

(lldb) p [date2 timeIntervalSinceReferenceDate]
560363055.21499991

(注意千位)

NSDateisEqual:(和变体)几乎可以肯定是在比较实例的时间偏移量,因此失败了。

我尝试为存储的字符串(即 .SSSSSS)添加更多精度,但这似乎没有影响。

那么,有人遇到过这种情况并解决它吗?我想到的唯一选择是自己写isEquals:,但这并不理想。

标签: iosobjective-c

解决方案


tl; dr - 您正在尝试将纳秒与毫秒进行比较。这些结果将不一样。

当你创建一个NSDatewith时,[NSDate date];你会得到一个包含小数秒到微秒甚至纳秒精度的值。

当您将日期转换为具有格式的字符串时,yyyy-MM-dd'T'HH:mm:ss.SSS您正在创建一个精确到 3 位小数(毫秒)的字符串。然后,当您将该字符串转换回一个NSDate时,您会得到一个浮点数,它尽可能地接近那些毫秒。

因此,您的原始日期精度为微秒或纳秒,而第二个日期仅为毫秒。当然,由于精度不同,这两个日期也会有所不同。这与浮点数无关。即使您有完美的浮点数,您也将 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]);

你会得到正确的结果。


推荐阅读