NSPredicate: 通过NSDate属性的日期进行对象过滤

126

我有一个 Core Data 模型,其中包含一个 NSDate 属性。我想按天过滤数据库。我猜解决方案将涉及一个 NSPredicate,但我不确定如何将其组合在一起。

我知道如何使用 NSDateComponentsNSCalendar 比较两个 NSDate 的日期,但是如何使用 NSPredicate 进行过滤呢?

也许我需要在我的 NSManagedObject 子类上创建一个分类,可以返回仅具有年、月和日的裸日期。然后我可以在一个 NSPredicate 中进行比较。这是您的建议,还是有更简单的方法?

9个回答

197

给定一个NSDate * startDateendDate,以及一个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
如果我们只有一个约会,会怎样? - Wasim
4
看起来你可以使用 ==。不过,如果你按日期搜索,请记住 NSDate 是一个日期和时间类型 -- 你可能需要使用从午夜到午夜的时间范围。 - jlstrecker
1
关于如何设置startDate和endDate的示例,请参见我的下面的答案。 - d.ennis
@jlstrecker 能给我展示一个使用从午夜到午夜的时间范围的例子吗?例如:我有两个相同日期但不同时间的数据,我想根据日期进行过滤(不关心时间)。我该怎么做? - iosMentalist
3
在上述示例中,您可以将startDate设置为2012-09-17 0:00:00,将endDate设置为2012-09-18 0:00:00,并将谓词设置为startDate <= date < endDate。这将捕获2012-09-17的所有时间。 - jlstrecker
显示剩余4条评论

64

以下是如何设置startDate和endDate的示例代码(与上述答案相关):

...

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

...

我在寻找一个月内的所有条目。值得一提的是,这个示例还展示了如何搜索“nil”日期条目。


12

Swift 3.0 日期的扩展:

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

然后像这样使用:

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

10

在Swift中,我得到了类似于以下内容:

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

我很难发现字符串插值"\(这种符号)"在NSPredicate中不能用于比较日期。


感谢您的回答。以下是Swift 3版本。 - DS.

8

我将来自Glauco Neves的答案移植到Swift 2.0,并将其封装在一个函数中,该函数接收日期并返回相应日期的NSPredicate

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

感谢您的回答。以下是Swift 3版本。 - DS.

6

补充 Rafael 的答案(非常有用,谢谢!),针对 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!])
}

2

在之前的回答基础上,使用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])
}

如果您更喜欢将endOfDay的时间设置为晚上11:59:59,您可以选择包含...
    let endOfDayLessOneSecond = endOfDay.addingTimeInterval(TimeInterval(-1))

但是你可能需要更改NSPredicate为...

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

...特别注意从day < %@更改为day <= %@


1

我最近花了一些时间尝试解决这个问题,并将以下内容添加到准备开始和结束日期的替代方案列表中(包括适用于iOS 8及以上版本的更新方法)...

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

...并且对于Core Data的NSFetchRequest,需要使用NSPredicate(如其他答案中已经展示)...

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

0

对我来说这个已经起作用了。

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

我已经从当前日期筛选出了过去7天的数组。我的意思是,我正在获取从当前日期开始的一周数据。这应该有效。

注意:我将毫秒级别的日期乘以1000进行转换,并在之后进行比较。如果您需要任何澄清,请告诉我。


在你的答案中,completeArray 是包含 dictionary 还是 dataModel 数组?我想应用于 DataModel。 - Sumeet Mourya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接