NSPredicate: filtrando objetos por dia da propriedade NSDate

123

Eu tenho um modelo de dados principais com uma NSDatepropriedade Eu quero filtrar o banco de dados por dia. Presumo que a solução envolva um NSPredicate, mas não tenho certeza de como juntar tudo.

Eu sei como comparar o dia de dois NSDatesegundos usando NSDateComponentse NSCalendar, mas como faço para filtrá-lo com um NSPredicate?

Talvez eu precise criar uma categoria na minha NSManagedObjectsubclasse que possa retornar uma data simples apenas com o ano, o mês e o dia. Então eu poderia comparar isso em um NSPredicate. Essa é sua recomendação ou há algo mais simples?

Jonathan Sterling
fonte

Respostas:

193

Dado um NSDate * startDate e endDate e um 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
fonte
4
e se tivermos apenas um encontro?
Wasim
4
Parece que você pode usar ==. Embora se você estiver pesquisando por dia, lembre-se de que o NSDate é uma data e hora - você pode usar um intervalo de meia-noite a meia-noite.
Jlstrecker 17/10/11
1
Exemplo sobre como também configurar startDate e endDate, ver minha resposta abaixo
d.ennis
@ jlstrecker pode me mostrar um exemplo de como usar um intervalo de meia-noite a meia-noite. por exemplo: eu tenho 2 dias iguais, mas com tempo diferente. e eu quero filtrar por dia (eu não me importo com o tempo). Como eu posso fazer isso?
IosMentalist
3
@ Shady No exemplo acima, você pode definir startDatepara 2012-09-17 0:00:00 e endDate2012-09-18 0:00:00 e o predicado para startDate <= date <endDate. Isso ocorreria o tempo todo em 17/09/2012.
Jlstrecker
63

Exemplo de como também configurar startDate e endDate para a resposta fornecida acima:

...

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

...

Aqui, eu estava pesquisando todas as entradas em um mês. Vale ressaltar, que este exemplo também mostra como pesquisar entradas de data 'nulas'.

d.ennis
fonte
10

Em Swift, eu tenho algo parecido com:

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

Foi difícil descobrir que a interpolação de cadeias "\(this notation)"não funciona para comparar datas no NSPredicate.

Glauco Neves
fonte
Obrigado por esta resposta. Versão Swift 3 adicionada abaixo.
DS.
9

Extensão Swift 3.0 para Data:

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

Então use como:

 let fetchReq = NSFetchRequest(entityName: "MyObject")
 fetchReq.predicate = myDate.makeDayPredicate()
Roni Leshes
fonte
8

Enviei a resposta de Glauco Neves para o Swift 2.0 e a envolvi em uma função que recebe uma data e retorna a NSPredicatepara o dia correspondente:

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!])
}
Rafael Bugajewski
fonte
Obrigado por esta resposta. Versão Swift 3 adicionada abaixo.
DS.
6

Adicionando à resposta de Rafael (incrivelmente útil, obrigado!), Portando o 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.
fonte
1

Recentemente, passei algum tempo tentando resolver esse mesmo problema e adicione o seguinte à lista de alternativas para preparar datas de início e término (inclui o método atualizado para iOS 8 e posterior) ...

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

... e NSPredicatepara os dados principais NSFetchRequest(como já mostrado acima em outras respostas) ...

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

Para mim, isso é trabalhado.

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

Eu filtrei a matriz da data atual para 7 dias atrás. Quero dizer, estou recebendo dados de uma semana a partir da data atual. Isso deve funcionar.

Nota: Estou convertendo a data que vem com mili segundos por 1000 e comparando depois. Deixe-me saber se você precisar de alguma clareza.

Narasimha Nallamsetty
fonte
Na sua resposta, completeArrayé ter Matriz de dictionaryou dataModel quero aplicar no DataModel.
Sumeet Mourya