get entries by date component in core data - ios

I want to filter entries from my database by single date components such as month=12. Notice that I can't query for entries with dates between to fixed dates because the year might be different.
It should look/work like this:
let request = NSPredicate(format: "date.month=%i", 12)

You will have to use a time interval for the predicate.
NSPredicate(format: "date >= %# && date < %#", aDate, aDate.date(byAddingMonths:1))
Please note that you perhaps mixed up the NSFetchRequest with the NSPredicate in your question.
To add a month:
extension Date {
func date(byAddingMonths months: Int) -> Date {
var diff = DateComponents()
diff.month = months
return Calendar.current.date(byAdding: diff, to: self)!
}
}
EDIT
After clarifying the question, it appears you need a month attribute. Add an Int attribute to your entity that should be filled out with the month automatically whenever you change the date (override didSet). Here is how you get the month from the date:
let month = Calendar.current.component(.month, from:theDate)

Related

In the calendar function "date(byAdding: .day, value: , to: )", does the date we give to the function include the current date or not?

I have an app that uses Core Data. I have a function
func showHistory(for days:Int)->Int {
var historyForXDaysInt = Int()
var calendar = Calendar.current
calendar.locale = .current
calendar.timeZone = .current
var startDate:Date?
var endDate = Date()
switch days{
case 1:
startDate = Date()
startDate = calendar.startOfDay(for: startDate!)
default:
startDate = calendar.date(byAdding: .day, value: -days+1, to: Date())
startDate = calendar.startOfDay(for: startDate!)
}
//There is more to this function, where I make the request but I just included the relevant part for my question
}
My question: I am trying to get relevant data from Core Data between two dates via the predicate let predicate = NSPredicate(format: "(completedDate >= %#) AND (completedDate <= %#)", startDate! as NSDate, endDate as NSDate).There is a completedDate property in my core data model. When I make the request I get data for 1 more day than I asked for. I tried solving it with adding 1 to the days variable. For example, if I am asking for 2 days of data (meaning days = 2) I get data for 3 days. What might be the cause? Why does it not work as intended without the +1 ?

Trying to return ONLY tomorrow's data using Swift and Core Data but it returns both today's and tomorrow's together?

I am trying to return ONLY tomorrow's data using Swift and Core Data but it returns both today's and tomorrow's together. Any idea why? Below is the code I am using. Thank you in advance!
let today = NSDate()
let tomorrow = NSCalendar.currentCalendar()
.dateByAddingUnit(
.Day,
value: 1,
toDate: today,
options: []
)
todoTomorrow = CoreDataManager.getData("ToDos", predicate: NSPredicate(format:"dueDate<%#", ((tomorrow))!)) as! [ToDos]
You need to modify your predicate to be dueDate < %# AND dueDate > %# with parameters tomorrow and today so it checks both the upper and lower allowed range of the dates.

Swift Core Data Fetch results from a certain date

I'm fairly new to ioS dev so this might be obvious.
With Core Data, I have an Entity : 'Post' , with an attribute for "dateAdded".
When the user selects an NSDate on a calendar- I want to fetch all the entries on that day only and load that into a table.
I was thinking of using a predicate where dateAdded (NSDate) would be equal to the calendarSelectedNSDate. But I don't think that would work as their NSDates would be different because of different times.
I would really appreciate a solution! Thank you!
Use (NS)Calendar to calculate the start and end of the selected date and create a predicate:
// get the current calendar
let calendar = Calendar.current
// get the start of the day of the selected date
let startDate = calendar.startOfDay(for: calendarSelectedNSDate)
// get the start of the day after the selected date
let endDate = calendar.date(byAdding: .day, value: 1, to: startDate, options: .matchNextTime)!
// create a predicate to filter between start date and end date
let predicate = NSPredicate(format: "dateAdded >= %# AND dateAdded < %#", startDate as NSDate, endDate as NSDate)

check value existence by NSDate as key in dictionary

I have a dictionary like this:
var dic = [NSDate: Int]()
it is used in my iOS to-do app to get the number of finished tasks of a particular date. I only care about the year, month and day sections in NSDate and also want to be able to get the number of tasks in a particular date using this dictionary, how can I do that? thanks.
Instead of storing your date as NSDate in your dictionary you can save it as String so that comparison will be easier. Use following code to store it as a string
func dateFromString(date : NSDate) -> String {
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.stringFromDate(date)
}
You can pass NSDate() to above function and it will give you string containing only year, month and date. For retrieving your data from dictionary use following.
func dateFrom(year:Int, month:Int, day:Int) -> String {
let components = NSDateComponents()
components.year = year
components.month = month
components.day = day
let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let date = gregorian!.dateFromComponents(components)
return dateFromString(date!)
}
You can pass year, month and date to above function and it will return corresponding date in string format. So your dictionary operations will look like
dict[dateFromString(NSDate())] = 1 //for insertion or updation
let numOfTasks = dict[dateFrom(2016, month: 1, day: 15)] //to get task for any particular day
EDIT
If you want to proceed with NSDate as key for your dictionary then you'll have to modify above code as follows. dateFrom will return date with year,month and date of your choice, and time will be some constant value. Time will be set to midnight in your current time zone if you don't set it.
func dateFrom(year:Int, month:Int, day:Int) -> NSDate {
let components = NSDateComponents()
components.year = year
components.month = month
components.day = day
let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let date = gregorian!.dateFromComponents(components)
return date!
}
And for getting current date use following so that you store date object with current year, date, month and time to some constant value.
func getCurrentDate()->NSDate {
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day , .Month , .Year], fromDate: date)
return dateFrom(components.year, month: components.month, day: components.day)
}
Usage will be as follows
dict[getCurrentDate()] = i //for insertion or updation
let numOfTasks = dict[dateFrom(2016, month: 1, day: 15)] //to get task for any particular day

Search CoreData for Matching NSDate - Swift

I want to search through all existing objects to see if there are any matching date objects in CoreData:
Dates are currently saved in my CoreData via the start_date attribute with the following format: 2013-08-29 14:27:47 +0000.
I am then letting a user select a date from a UIDatePicker and assigning .date() to variable date.
e.g. My Selected date = 2013-08-29 17:34:23 +0000.
Below is how i search CoreData, using a predicate.
let predicate = NSPredicate(format: "start_date contains[search] %#", date)
let request:NSFetchRequest = NSFetchRequest(entityName: "Project")
let sortDescriptor:NSSortDescriptor = NSSortDescriptor(key: "number", ascending: true)
let sortDescriptorsAry:NSArray = [sortDescriptor]
request.sortDescriptors = sortDescriptorsAry
request.predicate = predicate
return request
However i get no results. I assume this because both attributes don't match because of the time:
start_date = 2013-08-29 14:27:47 +0000
date = 2013-08-29 17:34:23 +0000
How can i some how tell CoreData to ignore the "177:34:23 +0000" bit, or is there a better way?
Edit:
I do have the option to change the way in which the date format is stored initially:
I have tried this:
var now:NSDate = self.startDatePicker.date
var calendar:NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
var components:NSDateComponents = calendar.components(NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay, fromDate: now)
components.hour = 00
components.minute = 00
components.second = 00
var newDate:NSDate = calendar.dateFromComponents(components)!
However on some days, my time is being set to a day before.
For Example:
Selecting the 30th August, after transform I get 2014-08-29 23:00:00 +0000
Before to save start_date attributes in Core Data, you need to be sure that their time is set to 12:00 AM:
//Get "Aug 29, 2014, 12:00 AM" from "Aug 29, 2014, 10:07 PM"
let newDate = NSDate() //or any other date
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone.systemTimeZone()
var startDate: NSDate?
var duration: NSTimeInterval = 0
calendar.rangeOfUnit(.DayCalendarUnit, startDate: &startDate, interval: &duration, forDate: newDate)
//Create, set and save a new managedObject
//Records, here, is the name of your NSManagedObject subclass
let record = NSEntityDescription.insertNewObjectForEntityForName("Records", inManagedObjectContext: managedObjectContext) as Records
record.start_date = startDate
/* set other attributes here */
var error: NSError?
if !managedObjectContext.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
println("Unresolved error \(error), \(error!.userInfo)")
abort()
}
Then, you will be able to fetch a date in Core Data with the predicate you want this way:
//Set your datePicker date to 12:00 AM
let pickerDate = dateFromMyDatePicker //as NSDate
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone.systemTimeZone()
var predicateDate: NSDate?
var duration: NSTimeInterval = 0
calendar.rangeOfUnit(.DayCalendarUnit, startDate: &predicateDate, interval: &duration, forDate: pickerDate)
//Create your fetchRequest
/* ... */
//Set your predicate
let predicate = NSPredicate(format: "start_date == %#", predicateDate)
Edit
As an alternative to rangeOfUnit:startDate:interval:forDate:, you can use the code you provide. But in both cases, don't forget to add the following line:
calendar.timeZone = NSTimeZone.systemTimeZone()
If you still have to option of changing the way you store data, I would store it on your project-entity as three attributes: an Integer for year, an Integer for month and an Integer for day. It will be easier to code (you can create a predicate like:
NSInteger year = ....
NSInteger month = ....
NSInteger day = ....
[NSPredicate predicateWithFormat: #"day == %# AND month == %# and year == %#", #(day), #(month), #(year)];
) and this probably work quicker compared to using a string. If you want to go with the string (beware extraneous spaces or other interpunction!), you can in code construct a string with the format 'yyyy-mm-dd' (use NSString stringWithFormat:) and then use an NSPredicate like:
NSInteger year = ....
NSInteger month = ....
NSInteger day = ....
NSString * dateString = [NSStringWithFormat: #"%4i-%2i-%2i", year, month, day];
[NSPredicate predicateWithFormat: #"dateString contains %#", dateString];
However, this will only work if YOU constructed the string that you stored, not the NSDate.description(). If you store actual NSDate, see my first original answer :).
But really, if you still have an option, don't store it as a string. It wil really be problematic, if not now, then later (I can tell from experience).
<edit>
Your second way of doing things seems to me like a good way to go. Store the integer values for year, month and day, and create a predicate with that. Your usage of NSDateComponents and calendar is great, and so this should be the easiest way to go. Very important: don't use for the time of 00, as midnight is a tricky time (there is daylight savings times in parts of the world, there is the question of whether exactly midnight is part of the previous of the next day). Just enter 12 as the time, to be as far away as you can from midnight.
Also, i would advice you to watch the WWDC from 2011 'Performing Calendar Calculations, Session 117. It talks among other very interesting things about why midnight is tricky (if not, then that is explained in the 2013 session :) ).
</edit>
NSDate has sub-second accuracy. It's description method (that gets called when you NSLog an NSDate) only displays second-accuracy.
So to use an NSDate in an NSPredicate, always specify 'larger/smaller then or equal to' operators.
For example:
NSDate * lastWeek = // ... created using NSCalendar and NSDateComponents.
NSDate * now = [NSDate date];
NSPredicate * entitiesFromAfterLastWeekAndBeforeNow = [NSPredicate predicateWithFormat: #"start_date >= %# AND start_date <= %#", lastWeek, now];
I would advice against storing a data in something else the a native NSDate, as you might store dates that are incorrect due to conversions. Also, storing dates as string will make queries a lot slower (as strings have to be parsed, and NSDate is simply a number). But that is another discussion, and you might want to ship, so you have to do what you think is best.
Edit: I'm sorry, only now noticed that you are writing Swift. But I think my example is clear enough that it is convertible to Swift, right?

Resources