Reputation: 1408
I'm storing a list of birthdays in Core Data, and I'm using an NSFetchedResultsController
to return all the birthdays in Core Data. The main issue I'm having is with the year. I want to show birthdays that occur one month from now. But as you know a person could be born on October 18, 2005 but comparing it with a date a month from now doesn't work, because assuming today is October 15, 2016 I would be comparing November 1, 2016 with October 18, 2005 so my app would think the birthday doesn't happen a month from now. How do I check if a birthday occurs in the next month using NSPredicate?
Here is some code I'm using right now without success:
class BirthdayFetchedResultsController: NSFetchedResultsController<Birthday>, NSFetchedResultsControllerDelegate {
private let tableView: UITableView
init(managedObjectContext: NSManagedObjectContext, tableView: UITableView) {
self.tableView = tableView
let request: NSFetchRequest<Birthday> = Birthday.fetchRequest()
let dateSortDescriptor = NSSortDescriptor(key: "date", ascending: true)
request.sortDescriptors = [dateSortDescriptor]
let startOfDay = Calendar.current.startOfDay(for: Date()) as NSDate
// FIXME: The year matters here when it shouldn't.
let predicate = NSPredicate(format: "date >= %@", startOfDay)
request.predicate = predicate
// TODO: Add section name key path for sorting the birthdays into sections
super.init(fetchRequest: request, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
self.delegate = self
tryFetch()
}
func tryFetch() {
do {
try performFetch()
} catch let error as NSError {
// TODO: Implement Error Handling
print("Unresolved error: \(error), \(error.userInfo)")
}
}
}
UPDATE: Now, I have an idea of how to deal with this issue. Either the month has to be less than or equal to the current month plus one, it can be less than the current month minus eleven or the month can be the same and the date has to be greater. I tried to build a predicate for this but it doesn't seem to be working:
let calendar = Calendar.current
let now = Date()
let month = calendar.component(.month, from: now)
let date = calendar.component(.day, from: now)
let predicate = NSPredicate(format: "((month <= %@) or (month <= %@)) or ((month == %@) AND (date >= %@))", argumentArray: [month + 2, month - 10, month, date])
request.predicate = predicate
Upvotes: 2
Views: 589
Reputation: 70966
If you're looking for all instances of a recurring event like a birthday that occur in the next week or month-- then a "date" attribute is not a good choice. It will be, at best, very awkward to get the result you need. A date in Core Data corresponds to a Swift Date
, which represents an instant in time and which does not store details like month or day of month.
It would be better to store these details in the form you need, for example with integer values for the month and day. That makes the lookup fairly simple, since you have fields that store the values you need to use as filters. There's really no good way to use a date attribute for what you need. A bad way would be to calculate month intervals for every past year for the past N years (for whatever N you select) and construct a massive predicate combining all of them. I don't recommend it.
You would also have to handle a special case for December where the current date could be 12/18 and the birthday is 01/01. In that case, it won't detect that the birthday is upcoming because 12 > 1.
Here is an example predicate:
// Month Conditions
let monthCondition1 = NSPredicate(format: "month <= %i", month + 1)
let monthCondition2 = NSPredicate(format: "month > %i", month)
let monthConditions = NSCompoundPredicate(andPredicateWithSubpredicates: [monthCondition1, monthCondition2])
// Date Conditions
let dateCondition1 = NSPredicate(format: "month == %i", month)
let dateCondition2 = NSPredicate(format: "date >= %i", date)
let dateConditions = NSCompoundPredicate(andPredicateWithSubpredicates: [dateCondition1, dateCondition2])
// Special Month Condition - Example: If currentDate is 12/31 and the birthday is 01/01 current month is greater than birthday but we still show it.
var predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [monthConditions, dateConditions])
if month == 12 {
let specialMonthCondition1 = NSPredicate(format: "month == %i", 1)
predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate, specialMonthCondition1])
}
request.predicate = predicate
Upvotes: 4