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?

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

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'.


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.


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

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

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

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

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.


Na sua resposta, completeArrayé ter Matriz de dictionaryou dataModel quero aplicar no DataModel.
Sumeet Mourya
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.