NSPredicate: filtrowanie obiektów według dnia właściwości NSDate

123

Mam model Core Data z NSDatewłaściwością. Chcę filtrować bazę danych według dnia. Zakładam, że rozwiązanie będzie obejmować NSPredicate, ale nie jestem pewien, jak to wszystko połączyć.

Wiem, jak porównać dzień dwóch dni NSDateprzy użyciu NSDateComponentsi NSCalendar, ale jak mogę to przefiltrować za pomocą NSPredicate?

Być może muszę utworzyć kategorię w mojej NSManagedObjectpodklasie, która może zwracać samą datę z tylko rokiem, miesiącem i dniem. Wtedy mógłbym porównać to w pliku NSPredicate. Czy to twoja rekomendacja, czy jest coś prostszego?

Jonathan Sterling
źródło

Odpowiedzi:

193

Biorąc pod uwagę NSDate * startDate i endDate oraz 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
źródło
4
a co jeśli mamy tylko jedną randkę?
Wasim,
4
Wygląda na to, że możesz użyć ==. Chociaż jeśli szukasz według dnia, pamiętaj, że NSDate to data i godzina - możesz użyć zakresu od północy do północy.
jlstrecker
1
Przykład, jak ustawić również datę początkową i datę końcową, zobacz moją odpowiedź poniżej
d.ennis
@jlstrecker możesz pokazać mi przykład, jak używać zakresu od północy do północy. na przykład: mam 2 takie same dni, ale inny czas. i chcę filtrować według dnia (nie obchodzi mnie czas). Jak mogę to zrobić?
iosMentalist,
3
@Shady W powyższym przykładzie można ustawić startDatena 2012-09-17 0:00:00 i endDate2012-09-18 0:00:00, a predykat na startDate <= date <endDate. To będzie się działo przez cały czas 17.09.2012.
jlstrecker
63

Przykład, jak ustawić również startDate i endDate dla powyższej odpowiedzi:

...

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

...

Tutaj szukałem wszystkich wpisów w ciągu miesiąca. Warto wspomnieć, że ten przykład pokazuje również, jak wyszukiwać wpisy z datą „zero”.

d.ennis
źródło
10

W Swift dostałem coś podobnego do:

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

Trudno mi było odkryć, że interpolacja ciągów "\(this notation)"nie działa przy porównywaniu dat w NSPredicate.

Glauco Neves
źródło
Dziękuję za tę odpowiedź. Wersja Swift 3 dodana poniżej.
DS.
9

Rozszerzenie Swift 3.0 dla 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!])
}
}

Następnie użyj:

 let fetchReq = NSFetchRequest(entityName: "MyObject")
 fetchReq.predicate = myDate.makeDayPredicate()
Roni Leshes
źródło
8

Przeniosłem odpowiedź z Glauco Neves do Swift 2.0 i umieściłem ją w funkcji, która odbiera datę i zwraca NSPredicatez odpowiedniego dnia:

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
źródło
Dziękuję za tę odpowiedź. Wersja Swift 3 dodana poniżej.
DS.
6

Dodając do odpowiedzi Rafaela (niezwykle przydatne, dziękuję!), Portowanie na 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.
źródło
1

Niedawno spędziłem trochę czasu próbując rozwiązać ten sam problem i dodać następujące informacje do listy alternatyw w celu przygotowania dat rozpoczęcia i zakończenia (zawiera zaktualizowaną metodę dla iOS 8 i nowszych) ...

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

... i NSPredicatedla danych podstawowych NSFetchRequest(jak już pokazano powyżej w innych odpowiedziach) ...

[NSPredicate predicateWithFormat:@"(dateAttribute >= %@) AND (dateAttribute < %@)", dateDayStart, dateDayNext]]
andrewbuilder
źródło
0

Dla mnie to działa.

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

Odfiltrowałem tablicę od aktualnej daty do 7 dni wstecz. Mam na myśli, że otrzymuję dane z tygodnia od bieżącej daty. To powinno działać.

Uwaga: konwertuję datę, która nadchodzi z mili sekundami na 1000 i porównuję później. Daj mi znać, jeśli potrzebujesz jasności.

Narasimha Nallamsetty
źródło
W twojej odpowiedzi completeArrayjest to, że ma Array of dictionarylub dataModel chcę zastosować na DataModel.
Sumeet Mourya
0

Opierając się na poprzednich odpowiedziach, aktualizacja i alternatywna metoda wykorzystująca Swift 5.x

func predicateForDayUsingDate(_ date: Date) -> NSPredicate {
    
    var calendar = Calendar.current
    calendar.timeZone = NSTimeZone.local
    // following creates exact midnight 12:00:00:000 AM of day
    let startOfDay = calendar.startOfDay(for: date)
    // following creates exact midnight 12:00:00:000 AM of next day
    let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
    
    return NSPredicate(format: "day >= %@ AND day < %@", argumentArray: [startOfDay, endOfDay])
}

Jeśli wolisz endOfDayustawić godzinę na 23:59:59, możesz zamiast tego dołączyć ...

    let endOfDayLessOneSecond = endOfDay.addingTimeInterval(TimeInterval(-1))

ale wtedy możesz zmienić NSPredicate na ...

    return NSPredicate(format: "day >= %@ AND day <= %@", argumentArray: [startOfDay, endOfDayLessOneSecond])

... ze szczególnym uwzględnieniem zmiany z day < %@na day <= %@.

andrewbuilder
źródło