我正在尝试将日期存储到JSON中或从中检索日期。从相同字符串创建的NSDate失败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]);
在调试器中查看
date1
和date2
,我们看到了不同之处:(lldb) p [date1 timeIntervalSinceReferenceDate] 560363055.21521103 (lldb) p [date2 timeIntervalSinceReferenceDate] 560363055.21499991
(注意千分之一的位置)
NSDate
的{{1}}(和变体)几乎可以比较 实例的时间偏移,从而失败。我尝试为存储的字符串增加更高的精度(即
isEqual:
),但这似乎没有影响。那么,有人遇到这个问题并解决吗?我唯一的选择 想到写我自己的
.SSSSSS
,但这并不理想。
答案 0 :(得分:5)
tl; dr-您试图将纳秒与毫秒进行比较。这些结果将不同。
使用NSDate
创建一个[NSDate date];
时,您会得到一个包含小数秒到微秒甚至十亿分之一秒精度的值。
将日期转换为格式为yyyy-MM-dd'T'HH:mm:ss.SSS
的字符串时,您正在创建的字符串正好是3个小数位(毫秒)。然后,当您将该字符串转换回NSDate
时,您将获得一个浮点数,该浮点数尽可能近似地表示这些毫秒。
因此,您拥有的原始日期的精度为微秒或纳秒,而第二个日期仅为毫秒。当然,由于精确度的不同,两个日期也会有所不同。这与浮点数无关。即使您有完美的浮点数,也要比较100.123456789与100.123。他们不是同一号码。
您说您尝试使用SSSSSS
而不是SSS
,但是NSDateFormatter
不接受超过三位小数的数字,因此SSS
以外的任何内容都是浪费精力。 / p>
有了这样的解释,您必须用什么解决方案来比较两个日期?
一种是仅将两个日期与三个小数位进行比较。这是一个有用的小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]);
您将得到正确的结果。
答案 1 :(得分:1)
因为NSDates底层存储是浮点型的,所以我认为没有任何使用NSDateFormatter的方法不会造成精度损失。如果希望使用isEqualToDate:可以尝试使用timeIntervalSinceReferenceDate(或timeIntervalSince1970)将日期存储为浮点型。但是问题是isEqualToDate:只能告诉您两个日期是否足够接近,以至于任何差异都小于浮点数可表示的差异,是您实际上想将其视为相等,还是宁愿将两个日期视为相等如果它们相差不到一毫秒,那么我可以想象有些情况下确实要使用isEqualToDate:但是您应该使用timeIntervalSinceReferenceDate而不是NSDateFormatter,这是一种解析并生成不存储的人类可读字符串的方法数据以有损格式显示。