NSDate не равно после преобразования в и из NSString - PullRequest
0 голосов
/ 04 октября 2018

Я пытаюсь сохранить и получить даты в / из 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 сохраняет свое внутреннее состояние в числе с плавающей запятой, не имеет значения в этом фундаментедолжен обеспечить механизм для достижения четности для выходных дат при заданных идентичных входных данных (т. е. одна и та же строка должна давать равные объекты).Либо Фонд предлагает это, и я упускаю это (и надеюсь, что Сообщество знает что-то о Фонде, я не знаю), либо Фонд глючит (в данном случае), и я ищу сообщество Сообщества для обходного пути, которого у меня не было.Это уже не рассматривается.

РЕДАКТИРОВАТЬ 2:

Извинения, мой вопрос, как первоначально задавался, это чепуха.Пытаясь упростить проблему для этого поста, я ввел непреднамеренное сравнение двух чисел, которые не могли быть равны.

Оригинальный бессмысленный пост для потомков:

I'mпытаясь сохранить и получить даты в / из 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 s isEqual: (и вариант) почти наверняка сравнивает временные смещения экземпляров и, таким образом, терпит неудачу.

Я попытался добавить больше точности к сохраненной строке (то есть .SSSSSS), но это делаетпохоже, не оказывает влияния.

Итак, кто-нибудь сталкивался с этим и обходил его?Единственный вариант, о котором я подумал - написать свой isEquals:, но это не идеально.

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Поскольку базовое хранение NSDates является плавающей точкой, я не думаю, что есть какой-либо способ использовать NSDateFormatter без некоторой потери точности.Если вы хотите использовать isEqualToDate: вы можете попробовать сохранить даты как числа с плавающей запятой, используя timeIntervalSinceReferenceDate (или timeIntervalSince1970).Но тогда проблема заключается в isEqualToDate: он может только сказать вам, если две даты достаточно близки, что любая разница меньше представляемой числом с плавающей запятой, действительно ли это то, что вы хотите рассматривать как равные, или вы предпочли бы рассматривать две даты как эквивалентныеесли они находятся на расстоянии менее секунды с точностью до миллисекунды, я могу представить себе, что существуют ситуации, когда вы действительно хотите использовать isEqualToDate: но тогда вам следует использовать timeIntervalSinceReferenceDate, а не NSDateFormatter, который является способом анализа и генерирования нечитаемых человеком строк, не хранимыхданные в формате с потерями.

0 голосов
/ 05 октября 2018

tl; dr - вы пытаетесь сравнить наносекунды с миллисекундами.Эти результаты не будут одинаковыми.

Когда вы создаете NSDate с [NSDate date];, вы получаете значение, которое включает доли секунды с точностью до микросекунд или, возможно, даже наносекунд.

Когда вы конвертируете дату в строку с форматом yyyy-MM-dd'T'HH:mm:ss.SSS, вы создаете строку с ровно 3 десятичными разрядами (миллисекундами).И затем, когда вы преобразуете эту строку обратно в NSDate, вы получите число с плавающей запятой, которое приблизительно наилучшим образом соответствует этим миллисекундам.

Итак, у вас есть исходная дата с точностью до микро или наносекунд, а вторая дата только в миллисекундах.Конечно, две даты будут разными из-за разного уровня точности.Это не имеет ничего общего с числами с плавающей запятой.Даже если у вас есть идеальные числа с плавающей запятой, вы сравниваете 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]);

и вы получите правильный результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...