Как использовать предикат для NSTimeInterval?

Я хотел бы получить все записи за текущий месяц, поэтому я складываю два предиката для первого числа месяца и последнего дня месяца. Поскольку я использую CoreData, даты фактически хранятся как NSTimeInterval.

NSCalendar *calendar = [NSCalendar currentCalendar];

//Get beginning of current month
NSDateComponents *beginningOfCurrentMonthComponents = [calendar components:(NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:date];
[beginningOfCurrentMonthComponents setDay:1];
NSDate *beginningOfCurrentMonthDate = [calendar dateFromComponents:beginningOfCurrentMonthComponents];

//Set a single month to be added to the current month
NSDateComponents *oneMonth = [[NSDateComponents alloc] init];
[oneMonth setMonth:1];

//determine the last day of this month
NSDate *beginningOfNextMonthDate = [calendar dateByAddingComponents:oneMonth toDate:beginningOfCurrentMonthDate options:0];

NSMutableArray *parr = [NSMutableArray array];
[parr addObject:[NSPredicate predicateWithFormat:@"recordDate >= %d", [beginningOfCurrentMonthDate timeIntervalSince1970]]];
[parr addObject:[NSPredicate predicateWithFormat:@"recordDate < %d", [beginningOfNextMonthDate timeIntervalSince1970]]];

//Give me everything from beginning of this month until end of this month
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:parr];

return [allRecords filteredArrayUsingPredicate:predicate];

После возврата отфильтрованного массива происходит сбой с этим сообщением об ошибке:

2013-10-25 19:09:39.702 [3556:a0b] -[__NSCFNumber timeIntervalSinceReferenceDate]: unrecognized selector sent to instance 0x8911440
2013-10-25 19:09:39.704 [3556:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber timeIntervalSinceReferenceDate]: unrecognized selector sent to instance 0x8911440'
*** First throw call stack:
(
    0   CoreFoundation                      0x01aa85e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x0182b8b6 objc_exception_throw + 44
    2   CoreFoundation                      0x01b45903 -[NSObject(NSObject) doesNotRecognizeSelector:] + 275
    3   CoreFoundation                      0x01a9890b ___forwarding___ + 1019
    4   CoreFoundation                      0x01a984ee _CF_forwarding_prep_0 + 14
    5   CoreFoundation                      0x01a7e3e3 -[NSDate compare:] + 67
    6   Foundation                          0x014194fe -[NSComparisonPredicateOperator performPrimitiveOperationUsingObject:andObject:] + 408
    7   Foundation                          0x014b03de -[NSPredicateOperator performOperationUsingObject:andObject:] + 306
    8   Foundation                          0x014b016c -[NSComparisonPredicate evaluateWithObject:substitutionVariables:] + 347
    9   Foundation                          0x014299b6 -[NSCompoundPredicateOperator evaluatePredicates:withObject:substitutionVariables:] + 240
    10  Foundation                          0x01429845 -[NSCompoundPredicate evaluateWithObject:substitutionVariables:] + 294
    11  Foundation                          0x014b0009 -[NSPredicate evaluateWithObject:] + 48
    12  Foundation                          0x014aff89 _filterObjectsUsingPredicate + 418
    13  Foundation                          0x014afd42 -[NSArray(NSPredicateSupport) filteredArrayUsingPredicate:] + 328

Я также использовал этот цикл, чтобы указать, что RecordDate действительно существует в массиве:

for (FTRecord *r in allRecords) {
    NSLog(@"%f", [r recordDate]);
}

2013-10-25 19:09:35.860 [3556:a0b] 1380582000.000000
2013-10-25 19:09:36.556 [3556:a0b] 1380754800.000000

person Houman    schedule 25.10.2013    source источник
comment
Трассировка стека показывает, что в предикат вовлечен объект NSDate. Попробуйте изменить NSPredicate, чтобы сравнить recordDate с NSDate вместо временного интервала.   -  person rmaddy    schedule 25.10.2013
comment
Я только что перепроверил все. Но вы видите, что обе даты преобразуются в NSTimeInterval. Я даже сделал то, что вы предложили, удалив timeIntervalSince1970, и он все равно вылетает с тем же сообщением. Кстати, теперь я добавил, как были созданы даты, возможно, это поможет отследить проблему.   -  person Houman    schedule 25.10.2013


Ответы (3)


Вы выбрали параметр «Использовать скалярные свойства для примитивных типов данных» при создании подкласса управляемого объекта, чтобы в классе FTRecord значение RecordDate было представлено как NSTimeInterval.

Но в таком предикате, как "recordDate >= 123.45", левая часть хранится как NSKeyPathExpression, и это использует valueForKeyPath:@"recordDate" для доступа к свойству, которое возвращает объект NSDate. Правая часть этого предиката хранится как NSConstantValueExpression со ссылкой на объект NSNumber. Поэтому NSDate сравнивается с NSNumber, что приводит именно к тому исключению, которое вы получили.

Чтобы решить эту проблему, вы должны сравнить свойство с NSDate (это то, что изначально предложил @rmaddy):

[parr addObject:[NSPredicate predicateWithFormat:@"recordDate >= %@", beginningOfCurrentMonthDate]];
[parr addObject:[NSPredicate predicateWithFormat:@"recordDate < %@", beginningOfNextMonthDate]];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:parr];

Я проверил это, и, похоже, он дал ожидаемый результат.

Однако обратите внимание, что Core Data использует timeIntervalSinceReferenceDate и dateWithTimeIntervalSinceReferenceDate для преобразования между скалярными значениями и NSDate, а не timeIntervalSince1970.

person Martin R    schedule 25.10.2013
comment
Спасибо за это. Я только что дважды проверил, это правда, что предикаты теперь не будут падать. Однако в отфильтрованном массиве также нет записи. Это немного грязно сейчас. Интересно, должен ли я перегенерировать модель без использования скалярных свойств для примитивных типов данных. Каков ваш совет здесь? - person Houman; 25.10.2013
comment
@Kave: Смотрите мое последнее замечание. Я предполагаю, что вы создали объекты с помощью timeIntervalSince1970, но вам нужно использовать timeIntervalSinceReferenceDate (сравните stackoverflow.com/a /15885204/1187415). Если вы не используете скалярные свойства, вы избавитесь от всех этих проблем преобразования. Но кроме этого, у меня нет реальных советов, лучше скалярные свойства или нет. - person Martin R; 25.10.2013
comment
Мартин, ты был прав. Удаление приложения и изменение его на timeIntervalSinceReferenceDate устранило проблему. Спасибо за этот отличный анализ. В конце концов мне придется сравнивать временные метки (не относящиеся к recordDate) с входящими данными из службы REST. Там метка времени будет сохранена как метка времени unix (1970). Вот почему я выбрал timeIntervalSince1970 в первую очередь, чтобы обойти несовместимости. Но, кажется, я не могу сделать это в конце концов. Я полагаю, что это будет новый вопрос, хотя. - person Houman; 25.10.2013
comment
@Kave: у меня нет опыта работы с REST, так что да, лучше задать новый вопрос. - person Martin R; 25.10.2013

Для решения в Swift 3:

let date = NSDate()
let calender = NSCalendar.current
var beginningOfCurrentMonthComponents = calender.dateComponents([.year, .month, .day], from: date as Date)

beginningOfCurrentMonthComponents.day = 1

let beginningOfCurrentMonthDate = calender.date(from: beginningOfCurrentMonthComponents)
let beginningOfNextMonth = calender.date(byAdding: .month, value: 1, to: beginningOfCurrentMonthDate!)

let pred1 = NSPredicate(format: "date >= %@", beginningOfCurrentMonthDate! as NSDate)
let pred2 = NSPredicate(format: "date < %@", beginningOfNextMonth! as NSDate)

let predicate = NSCompoundPredicate.init(andPredicateWithSubpredicates: [pred1, pred2])
person jo3birdtalk    schedule 21.09.2016

Добавит timeIntervalSince1970 в сам предикат. Это будет

Предикат NSPredicate* = ПРЕДИКАТ(@"(lastUpdatedTime == nil OR lastUpdatedTime.timeIntervalSince1970 ‹= %f)",currentDate.timeIntervalSince1970);

person Boobalan    schedule 27.11.2015