114

Tengo un modelo de Core Data con una propiedad NSDate. Quiero filtrar la base de datos por día. Supongo que la solución implicará un NSPredicate, pero no estoy seguro de cómo armarlo todo.NSPredicar: filtrar objetos por día de la propiedad de NSDate

sé cómo comparar el día de dos NSDate s utilizando NSDateComponents y NSCalendar, pero ¿Cómo filtro con un NSPredicate?

Quizás deba crear una categoría en mi subclase NSManagedObject que pueda devolver una fecha concreta con solo el año, el mes y el día. Entonces podría comparar eso en un NSPredicate. ¿Es esta tu recomendación, o hay algo más simple?

Respuesta

181

Dado un NSDate * startDate y endDate y una 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]; 
+3

¿y si sólo tenemos una cita? – Wasim

+3

Parece que puede usar '=='. Aunque si está buscando por día, recuerde que NSDate es una fecha y hora; es posible que desee utilizar un rango de medianoche a medianoche. – jlstrecker

+1

Ejemplo sobre cómo configurar también startDate y endDate, vea mi respuesta a continuación –

60

Ejemplo de cómo también para establecer startDate y endDate de lo anterior respuesta dada:

... 

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

... 

Aquí estaba buscando todas las entradas en un mes. Vale la pena mencionar que este ejemplo también muestra cómo buscar 'nil' date-entires.

+1

Guardado mi día :) – Xeieshan

9

En Swift Tengo algo similar a:

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

Me costó mucho tiempo para descubrir que cadena de interpolación "\(this notation)" no funciona para la comparación de fechas en NSPredicate.

+0

Gracias por esta respuesta. Swift 3 versión añadida a continuación. –

0

Para mí esto funciona.

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

He filtrado la matriz desde la fecha actual hasta 7 días. Quiero decir que recibo datos de una semana a partir de la fecha actual. Esto debería funcionar.

Nota: Estoy convirtiendo la fecha que viene con mili segundos por 1000, y comparando después. Avísame si necesitas alguna claridad.

+0

En su respuesta 'completeArray' está teniendo una matriz de' dictionary' o 'dataModel' Quiero aplicar en DataModel. –

8

porté la respuesta de Glauco Neves a Swift 2.0, lo envolvió dentro de una función que recibe una fecha y devuelve el NSPredicate para el día correspondiente:

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

Gracias por esta respuesta. Swift 3 versión añadida a continuación. –

1

He pasado recientemente algún tiempo tratando de resolver este mismo problema y añadir lo siguiente a la lista de alternativas para preparar las fechas de inicio y final (incluye el método actualizado para iOS 8 y superior) ...

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

... y el NSPredicate de los datos básicos NSFetchRequest (como ya se ha mostrado anteriormente en otras respuestas) ...

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

Agregando a la respuesta de Rafael (increíblemente útil, gracias!), Portar para 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!]) 
} 
5

Swift 3.0 extensión para Fecha:

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

A continuación, utilice como:

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