Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a predicate on NSTimeInterval?

I would like to get all records for the current month, so I stack two predicates for first date of the month and last day of the month. Since I use CoreData the dates are stored actually as 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];

Upon returning the filtered array, it crashes with this error message:

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

I used this loop also to indicate that the recordDate truly exists within the array:

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
like image 583
Houman Avatar asked Oct 25 '13 18:10

Houman


2 Answers

For solution in 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])
like image 94
jo3birdtalk Avatar answered Oct 23 '22 16:10

jo3birdtalk


You chose the "Use scalar properties for primitive data types" option when creating the managed object subclass, so that the recordDate is represented as NSTimeInterval in the FTRecord class.

But in a predicate like "recordDate >= 123.45", the left-hand side is stored as a NSKeyPathExpression, and that uses valueForKeyPath:@"recordDate" to access the property, which returns an NSDate object. The right-hand side of that predicate is stored as NSConstantValueExpression with a reference to an NSNumber object. Therefore an NSDate is compared with an NSNumber, which leads exactly to the exception that you got.

To fix the problem, you have to compare the property with an NSDate (which is what @rmaddy initially suggested):

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

I tested this and it seems to produce the expected result.

Note however that Core Data uses timeIntervalSinceReferenceDate and dateWithTimeIntervalSinceReferenceDate to convert between the scalar values and NSDate, not timeIntervalSince1970.

like image 16
Martin R Avatar answered Oct 23 '22 16:10

Martin R