NSPredicate: filtering objects by day of NSDate property

Objective CCore DataNsdateNspredicateNsfetchedresultscontroller

Objective C Problem Overview


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?

Objective C Solutions


Solution 1 - Objective C

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];

Solution 2 - Objective C

Example on how to also set up startDate and endDate to the above given answer:

...

NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth ) fromDate:[NSDate date]];
//create a date with these components
NSDate *startDate = [calendar dateFromComponents:components];
[components setMonth:1];
[components setDay:0]; //reset the other components
[components setYear:0]; //reset the other components
NSDate *endDate = [calendar dateByAddingComponents:components toDate:startDate options:0];
predicate = [NSPredicate predicateWithFormat:@"((date >= %@) AND (date < %@)) || (date = nil)",startDate,endDate];

...

Here I was searching for all entries within one month. It's worth to mention, that this example also shows how to search 'nil' date-entires.

Solution 3 - Objective C

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()

Solution 4 - Objective C

In Swift I got something similar to:

        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let date = dateFormatter.dateFromString(predicate)
        let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
        let components = calendar!.components(
            NSCalendarUnit.CalendarUnitYear |
                NSCalendarUnit.CalendarUnitMonth |
                NSCalendarUnit.CalendarUnitDay |
                NSCalendarUnit.CalendarUnitHour |
                NSCalendarUnit.CalendarUnitMinute |
                NSCalendarUnit.CalendarUnitSecond, fromDate: date!)
        components.hour = 00
        components.minute = 00
        components.second = 00
        let startDate = calendar!.dateFromComponents(components)
        components.hour = 23
        components.minute = 59
        components.second = 59
        let endDate = calendar!.dateFromComponents(components)
        predicate = NSPredicate(format: "day >= %@ AND day =< %@", argumentArray: [startDate!, endDate!])

I had a hard time to discover that string interpolation "\(this notation)" doesn't work for comparing dates in NSPredicate.

Solution 5 - Objective C

I ported the answer from Glauco Neves to Swift 2.0 and wrapped it inside a function that receives a date and returns the NSPredicate for the corresponding day:

func predicateForDayFromDate(date: NSDate) -> NSPredicate {
    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
    let components = calendar!.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: date)
    components.hour = 00
    components.minute = 00
    components.second = 00
    let startDate = calendar!.dateFromComponents(components)
    components.hour = 23
    components.minute = 59
    components.second = 59
    let endDate = calendar!.dateFromComponents(components)
   
    return NSPredicate(format: "day >= %@ AND day =< %@", argumentArray: [startDate!, endDate!])
}

Solution 6 - Objective C

Adding to Rafael's answer (incredibly useful, thank you!), porting for Swift 3.

func predicateForDayFromDate(date: Date) -> NSPredicate {
	let calendar = Calendar(identifier: Calendar.Identifier.gregorian)
	var components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
	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: "YOUR_DATE_FIELD >= %@ AND YOUR_DATE_FIELD =< %@", argumentArray: [startDate!, endDate!])
}

Solution 7 - Objective C

I've recently spent some time attempting to solve this same problem and add the following to the list of alternatives to prepare start and end dates (includes updated method for iOS 8 and above)...

NSDate *dateDay = nil;
NSDate *dateDayStart = nil;
NSDate *dateDayNext = nil;

dateDay = <<USER_INPUT>>;

dateDayStart = [[NSCalendar currentCalendar] startOfDayForDate:dateDay];

//	dateDayNext EITHER
dateDayNext = [dateDayStart dateByAddingTimeInterval:(24 * 60 * 60)];

//	dateDayNext OR
NSDateComponents *dateComponentDay = nil;
dateComponentDay = [[NSDateComponents alloc] init];
[dateComponentDay setDay:1];
dateDayNext = [[NSCalendar currentCalendar] dateByAddingComponents:dateComponentDay
															toDate:dateDayStart
														   options:NSCalendarMatchNextTime];

...and the NSPredicate for the Core Data NSFetchRequest (as already shown above in other answers)...

[NSPredicate predicateWithFormat:@"(dateAttribute >= %@) AND (dateAttribute < %@)", dateDayStart, dateDayNext]]

Solution 8 - Objective C

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 <= %@.

Solution 9 - Objective C

For me this is worked.

NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDateComponents *components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit ) fromDate:[NSDate date]];
    //create a date with these components
    NSDate *startDate = [calendar dateFromComponents:components];
    [components setMonth:0];
    [components setDay:0]; //reset the other components
    [components setYear:0]; //reset the other components
    NSDate *endDate = [calendar dateByAddingComponents:components toDate:startDate options:0];
    
    startDate = [NSDate date];
    endDate = [startDate dateByAddingTimeInterval:-(7 * 24 * 60 * 60)];//change here
            
    NSString *startTimeStamp = [[NSNumber numberWithInt:floor([endDate timeIntervalSince1970])] stringValue];
    NSString *endTimeStamp = [[NSNumber numberWithInt:floor([startDate timeIntervalSince1970])] stringValue];

    
   NSPredicate *predicate = [NSPredicate predicateWithFormat:@"((paidDate1 >= %@) AND (paidDate1 < %@))",startTimeStamp,endTimeStamp];
    NSLog(@"predicate is %@",predicate);
    totalArr = [completeArray filteredArrayUsingPredicate:predicate];
    [self filterAndPopulateDataBasedonIndex];
    [self.tableviewObj reloadData];
    NSLog(@"result is %@",totalArr);

I have filtered array from current date to 7 days back. I mean I am getting one week data from current date. This should work.

Note: I am converting date which is coming with milli seconds by 1000, and comparing after. Let me know if you need any clarity.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonathan SterlingView Question on Stackoverflow
Solution 1 - Objective CdiciuView Answer on Stackoverflow
Solution 2 - Objective Cd.ennisView Answer on Stackoverflow
Solution 3 - Objective CRoni LeshesView Answer on Stackoverflow
Solution 4 - Objective CGlauco NevesView Answer on Stackoverflow
Solution 5 - Objective CRafael BugajewskiView Answer on Stackoverflow
Solution 6 - Objective CDS.View Answer on Stackoverflow
Solution 7 - Objective CandrewbuilderView Answer on Stackoverflow
Solution 8 - Objective CandrewbuilderView Answer on Stackoverflow
Solution 9 - Objective CNarasimha NallamsettyView Answer on Stackoverflow