NSPredicate- For finding events that occur between a certain date range

冷暖自知 提交于 2019-12-09 00:14:04

问题


This is a little more complicated then just

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"startDate >= %@ AND endDate <= %@", startDay,endDay];

I'm building a calendar app and I need to pull events out of my core data store that occur on a given day. However, it's possible for an event to start/end on a different day then the day I'm trying to display, e.g.

My Date Range (Start = 2011-12-02 00:00:00 to End = 2011-12-02 23:59:59)

An Event (Start = 2011-12-01 23:00:00 to End = 2011-12-02 05:00:00)

How can I write a predicate to determine if that event falls in that date range. Keep in mind that an event could start before and after the date range.

Thanks!


回答1:


Here is a method to build a predicate to retrieve non recurring events occurring on a given day (recurring events require additional processing):

- (NSPredicate *) predicateToRetrieveEventsForDate:(NSDate *)aDate {

    // start by retrieving day, weekday, month and year components for the given day
    NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *todayComponents = [gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:aDate];
    NSInteger theDay = [todayComponents day];
    NSInteger theMonth = [todayComponents month];
    NSInteger theYear = [todayComponents year];

    // now build a NSDate object for the input date using these components
    NSDateComponents *components = [[NSDateComponents alloc] init];
    [components setDay:theDay]; 
    [components setMonth:theMonth]; 
    [components setYear:theYear];
    NSDate *thisDate = [gregorian dateFromComponents:components];
    [components release];

    // build a NSDate object for aDate next day
    NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
    [offsetComponents setDay:1];
    NSDate *nextDate = [gregorian dateByAddingComponents:offsetComponents toDate:thisDate options:0];
    [offsetComponents release];

    [gregorian release];


    // build the predicate 
    NSPredicate *predicate = [NSPredicate predicateWithFormat: @"startDate < %@ && endDate > %@", nextDate, thisDate];

        return predicate;

}

The predicate is almost equal to the one proposed by @Tony, except it does not check for equality. Here is why. Suppose you have an event starting on December 8 23:00 and ending at December 9 00:00. If you check for events whose ending date is >= rather than > of the given day, your app will report the event in both December 8 and 9, which is clearly wrong. Try adding such an event to both iCal and Google Calendar, and you will see that the event only appears on December 8. In practice, you should not assume that a given day ends at 23:59:59 (even though this is of course true): you need to treat midnight as the last second of a given day (with regard to the end of an event). Also, note that this does not prevent events starting at midnight.




回答2:


What you want is:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"startDate <= %@ AND endDate >= %@", endDay, startDay];

In other words, you're eliminating events that start after the end of the range or end before the start, i.e., events that have an empty intersection with the range.




回答3:


While the proposed solution is correct, it is also quite verbose. I have re-implemented it in Swift 3.0:

import Foundation

extension NSPredicate {

    static func filter(key: String, onDayRangeForDate date: Date, calendar: Calendar = Calendar(identifier: .gregorian)) -> NSPredicate {

        let dateComponents = calendar.dateComponents([.day, .month, .year], from: date)

        let startOfDay = calendar.date(from: dateComponents)!

        let offsetComponents = NSDateComponents()
        offsetComponents.day = 1
        let endOfDay = calendar.date(byAdding: offsetComponents as DateComponents, to: startOfDay)!

        return NSPredicate(format: "\(key) >= %@ && \(key) < %@",
            startOfDay as NSDate, endOfDay as NSDate)
    }
}



回答4:


Massimo Camaro had a great answer. I took the liberty of porting it to Swift and modifying it a little to support a different column name.

Swift 2.0

func predicateToRetrieveEventsForDate(aDate:NSDate, predicateColumn:String) -> NSPredicate {

    // start by retrieving day, weekday, month and year components for the given day
    let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
    let todayComponents = gregorian?.components([.Day, .Month, .Year], fromDate: aDate)
    let theDay = todayComponents?.day
    let theMonth = todayComponents?.month
    let theYear = todayComponents?.year

    // now build a NSDate object for the input date using these components
    let components = NSDateComponents()
    components.day = theDay!
    components.month = theMonth!
    components.year = theYear!
    let thisDate = gregorian?.dateFromComponents(components)

    // build a NSDate object for aDate next day
    let offsetComponents = NSDateComponents()
    offsetComponents.day = 1
    let nextDate = gregorian?.dateByAddingComponents(offsetComponents, toDate: thisDate!, options: NSCalendarOptions(rawValue: 0))

    // build the predicate
    let predicate = NSPredicate(format: "\(predicateColumn) >= %@ && \(predicateColumn) < %@", thisDate!, nextDate!)

    return predicate
}


来源:https://stackoverflow.com/questions/8364495/nspredicate-for-finding-events-that-occur-between-a-certain-date-range

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!