NSPredicate: filtrado de objetos por día de propiedad NSDate

123

Tengo un modelo de Core Data con una NSDatepropiedad. Quiero filtrar la base de datos por día. Supongo que la solución implicará un NSPredicate, pero no estoy seguro de cómo ponerlo todo junto.

Sé cómo comparar el día de dos NSDates usando NSDateComponentsy NSCalendar, pero ¿cómo lo filtro con un NSPredicate?

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

Jonathan Sterling
fuente

Respuestas:

193

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];
diciu
fuente
44
¿Qué pasa si solo tenemos una cita?
Wasim
44
Parece que puedes 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 startDate y endDate, vea mi respuesta a continuación
d.ennis
@jlstrecker, ¿puedes mostrarme un ejemplo de cómo usar un rango de medianoche a medianoche? por ejemplo: tengo 2 mismos días pero tiempos diferentes. y quiero filtrar por día (no me importa el momento). ¿Cómo puedo hacer eso?
iosMentalist
3
@Shady En el ejemplo anterior, puede establecer startDate2012-09-17 0:00:00 y endDate2012-09-18 0:00:00 y el predicado para startDate <= date <endDate. Eso atraparía todos los tiempos el 17/09/2012.
jlstrecker
63

Ejemplo sobre cómo configurar startDate y endDate para la respuesta dada anteriormente:

...

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

...

Aquí estaba buscando todas las entradas dentro de un mes. Vale la pena mencionar que este ejemplo también muestra cómo buscar entradas de fecha 'nulas'.

d.ennis
fuente
10

En Swift obtuve 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 descubrir que la interpolación de cadenas "\(this notation)"no funciona para comparar fechas en NSPredicate.

Glauco Neves
fuente
Gracias por esta respuesta La versión Swift 3 se agrega a continuación.
DS.
9

Extensión Swift 3.0 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!])
}
}

Luego use como:

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

Porté la respuesta de Glauco Neves a Swift 2.0 y la envolví dentro de una función que recibe una fecha y devuelve el NSPredicatedí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!])
}
Rafael Bugajewski
fuente
Gracias por esta respuesta La versión Swift 3 se agrega a continuación.
DS.
6

Agregando a la respuesta de Rafael (increíblemente útil, ¡gracias!), Portando 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!])
}
DS.
fuente
1

Recientemente, pasé algún tiempo intentando resolver este mismo problema y agregué lo siguiente a la lista de alternativas para preparar las fechas de inicio y finalización (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 NSPredicatepara los datos básicos NSFetchRequest(como ya se mostró anteriormente en otras respuestas) ...

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

Para mí esto ha funcionado.

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 de la fecha actual a 7 días atrás. Quiero decir que estoy obteniendo datos de una semana de la fecha actual. Esto debería funcionar.

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

Narasimha Nallamsetty
fuente
En su respuesta, completeArrayes tener una matriz de dictionaryo dataModel quiero aplicar en DataModel.
Sumeet Mourya