NSPredicate: filtrage des objets par jour de propriété NSDate

123

J'ai un modèle Core Data avec une NSDatepropriété. Je souhaite filtrer la base de données par jour. Je suppose que la solution impliquera un NSPredicate, mais je ne sais pas comment mettre tout cela ensemble.

Je sais comment comparer le jour de deux NSDates en utilisant NSDateComponentset NSCalendar, mais comment puis-je le filtrer avec un NSPredicate?

Peut-être que j'ai besoin de créer une catégorie sur ma NSManagedObjectsous-classe qui peut renvoyer une date nue avec juste l'année, le mois et le jour. Ensuite, je pourrais comparer cela dans un fichier NSPredicate. Est-ce votre recommandation ou y a-t-il quelque chose de plus simple?

Jonathan Sterling
la source

Réponses:

193

Étant donné un NSDate * startDate et endDate et un 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
la source
4
et si nous n'avons qu'une seule date?
Wasim le
4
On dirait que vous pouvez utiliser ==. Cependant, si vous effectuez une recherche par jour, n'oubliez pas que NSDate est une date et une heure - vous voudrez peut-être utiliser une plage de minuit à minuit.
jlstrecker
1
Exemple sur la façon de configurer également startDate et endDate, voir ma réponse ci
d.ennis
@jlstrecker pouvez-vous me montrer un exemple comment utiliser une plage de minuit à minuit. par exemple: j'ai 2 jours identiques mais un timing différent. et je veux filtrer par jour (je me fiche du timing). Comment puis je faire ça?
iosMentalist
3
@Shady Dans l'exemple ci-dessus, vous pouvez définir startDatesur 2012-09-17 0:00:00 et endDate2012-09-18 0:00:00 et le prédicat sur startDate <= date <endDate. Cela attraperait tous les temps le 2012-09-17.
jlstrecker
63

Exemple sur la façon de configurer également startDate et endDate à la réponse donnée ci-dessus:

...

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

...

Ici, je recherchais toutes les entrées dans un délai d'un mois. Il convient de mentionner que cet exemple montre également comment rechercher des entiers de date «nil».

d.ennis
la source
10

Dans Swift, j'ai quelque chose de similaire à:

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

J'ai eu du mal à découvrir que l'interpolation de chaînes "\(this notation)"ne fonctionne pas pour comparer des dates dans NSPredicate.

Glauco Neves
la source
Merci pour cette réponse. Version Swift 3 ajoutée ci-dessous.
DS.
9

Extension Swift 3.0 pour 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!])
}
}

Ensuite, utilisez comme:

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

J'ai porté la réponse de Glauco Neves vers Swift 2.0 et l' ai enveloppée dans une fonction qui reçoit une date et renvoie le NSPredicatepour le jour correspondant:

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
la source
Merci pour cette réponse. Version Swift 3 ajoutée ci-dessous.
DS.
6

Ajout à la réponse de Rafael (incroyablement utile, merci!), Portage pour 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.
la source
1

J'ai récemment passé du temps à essayer de résoudre ce même problème et d'ajouter ce qui suit à la liste des alternatives pour préparer les dates de début et de fin (comprend la méthode mise à jour pour iOS 8 et plus) ...

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

... et NSPredicatepour les données de base NSFetchRequest(comme déjà indiqué ci-dessus dans d'autres réponses) ...

[NSPredicate predicateWithFormat:@"(dateAttribute >= %@) AND (dateAttribute < %@)", dateDayStart, dateDayNext]]
Andrewbuilder
la source
0

Pour moi, cela fonctionne.

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

J'ai filtré le tableau de la date actuelle à 7 jours en arrière. Je veux dire que je reçois des données d'une semaine à partir de la date actuelle. Cela devrait fonctionner.

Remarque: je convertis la date qui vient avec des milli secondes par 1000, et je compare après. Faites-moi savoir si vous avez besoin de clarté.

Narasimha Nallamsetty
la source
Dans votre réponse, completeArrayil a Array de dictionaryou dataModel je souhaite postuler sur DataModel.
Sumeet Mourya