NSPredicate: фильтрация объектов по дню свойства NSDate

123

У меня есть модель Core Data со NSDateсвойством. Я хочу фильтровать базу данных по дням. Я предполагаю, что решение будет включать в себя NSPredicate, но я не уверен, как все это собрать.

Я знаю, как сравнить день из двух NSDateс использованием NSDateComponentsи NSCalendar, но как мне отфильтровать его с помощью NSPredicate?

Возможно, мне нужно создать категорию в моем NSManagedObjectподклассе, которая может возвращать голую дату только с годом, месяцем и днем. Тогда я мог бы сравнить это в файле NSPredicate. Это ваша рекомендация или есть что-то попроще?

Джонатан Стерлинг
источник

Ответы:

193

Учитывая NSDate * startDate и endDate и 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];
diciu
источник
4
что, если у нас только одно свидание?
Wasim
4
Похоже, можно использовать ==. Хотя, если вы выполняете поиск днем, помните, что NSDate - это дата и время - вы можете использовать диапазон от полуночи до полуночи.
jlstrecker
1
Пример того, как также установить startDate и endDate, см. Мой ответ ниже
d.ennis
@jlstrecker может показать мне пример использования диапазона от полуночи до полуночи. например: у меня 2 одинаковых дня, но разное время. и я хочу фильтровать по дням (меня не волнует время). Как я могу это сделать?
iosMentalist
3
@Shady В приведенном выше примере вы можете установить startDateзначение 2012-09-17 0:00:00 и endDate2012-09-18 0:00:00, а предикат - startDate <= date <endDate. Это было бы все время на 2012-09-17.
jlstrecker
63

Пример того, как также настроить startDate и endDate для указанного выше ответа:

...

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

...

Здесь я искал все записи в течение одного месяца. Стоит отметить, что этот пример также показывает, как искать нулевые даты окончания.

d.ennis
источник
10

В Swift у меня получилось примерно следующее:

        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!])

Мне было трудно обнаружить, что интерполяция строк "\(this notation)"не работает для сравнения дат в NSPredicate.

Глауко Невес
источник
Спасибо за этот ответ. Версия Swift 3 добавлена ​​ниже.
ДС.
9

Расширение Swift 3.0 для 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!])
}
}

Затем используйте как:

 let fetchReq = NSFetchRequest(entityName: "MyObject")
 fetchReq.predicate = myDate.makeDayPredicate()
Рони Лешес
источник
8

Я перенес ответ из Glauco Neves в Swift 2.0 и заключил его в функцию, которая получает дату и возвращает значение NSPredicateдля соответствующего дня:

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!])
}
Рафаэль Бугаевский
источник
Спасибо за этот ответ. Версия Swift 3 добавлена ​​ниже.
ДС.
6

Добавление к ответу Рафаэля (невероятно полезно, спасибо!), Перенос на 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!])
}
DS.
источник
1

Недавно я потратил некоторое время, пытаясь решить эту же проблему и добавить следующее в список альтернатив для подготовки дат начала и окончания (включая обновленный метод для iOS 8 и выше) ...

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

... и NSPredicateдля основных данных NSFetchRequest(как уже показано выше в других ответах) ...

[NSPredicate predicateWithFormat:@"(dateAttribute >= %@) AND (dateAttribute < %@)", dateDayStart, dateDayNext]]
andrewbuilder
источник
0

Для меня это сработало.

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

Я отфильтровал массив с текущей даты до 7 дней назад. Я имею в виду, что получаю данные за одну неделю с текущей даты. Это должно сработать.

Примечание: я конвертирую дату, которая идет с миллисекундами, на 1000 и сравниваю после. Дайте мне знать, если вам нужна ясность.

Нарасимха Налламсетти
источник
В вашем ответе completeArrayесть ли у него массив dictionaryили dataModel я хочу применить к DataModel.
Sumeet Mourya