NSPredicate: filtering objects by day of NSDate property

Viewed 84687

I have a Core Data model with an NSDate property. I want to filter the database by day. I assume the solution will involve an NSPredicate, but I'm not sure how to put it all together.

I know how to compare the day of two NSDates using NSDateComponents and NSCalendar, but how do I filter it with an NSPredicate?

Perhaps I need to create a category on my NSManagedObject subclass that can return a bare date with just the year, month and day. Then I could compare that in an NSPredicate. Is this your recommendation, or is there something simpler?

9 Answers

Given a NSDate * startDate and endDate and a NSManagedObjectContext * moc:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(date >= %@) AND (date <= %@)", startDate, endDate];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:[NSEntityDescription entityForName:@"EntityName" inManagedObjectContext:moc]];
[request setPredicate:predicate];

NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];

Swift 3.0 extension for Date:

extension Date{

func makeDayPredicate() -> NSPredicate {
    let calendar = Calendar.current
    var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self)
    components.hour = 00
    components.minute = 00
    components.second = 00
    let startDate = calendar.date(from: components)
    components.hour = 23
    components.minute = 59
    components.second = 59
    let endDate = calendar.date(from: components)
    return NSPredicate(format: "day >= %@ AND day =< %@", argumentArray: [startDate!, endDate!])
}
}

Then use like:

 let fetchReq = NSFetchRequest(entityName: "MyObject")
 fetchReq.predicate = myDate.makeDayPredicate()

Building on the previous answers, an update and alternative method using Swift 5.x

func predicateForDayUsingDate(_ date: Date) -> NSPredicate {
    
    var calendar = Calendar.current
    calendar.timeZone = NSTimeZone.local
    // following creates exact midnight 12:00:00:000 AM of day
    let startOfDay = calendar.startOfDay(for: date)
    // following creates exact midnight 12:00:00:000 AM of next day
    let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
    
    return NSPredicate(format: "day >= %@ AND day < %@", argumentArray: [startOfDay, endOfDay])
}

If you'd prefer to create the time for endOfDay as 11:59:59 PM, you can instead include...

    let endOfDayLessOneSecond = endOfDay.addingTimeInterval(TimeInterval(-1))

but then you might change the NSPredicate to...

    return NSPredicate(format: "day >= %@ AND day <= %@", argumentArray: [startOfDay, endOfDayLessOneSecond])

...with specific note of the change from day < %@ to day <= %@.

Related