Search CoreData for Matching NSDate - Swift - ios

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?

Related

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)

Searching by NSDate in Coredata not working - Swift

I am searching for a record in coredata by a specific NSDate but it's returning no results. It does not throw any error too..
When I loop through all the records I can output to the console the date.
Here is the predicate.. created is NSDate
let resultPredicate = NSPredicate(format: "created == %#", created)
..After digging around some more, the issue appears to be when I am handling the JSON of the timestamp:
var createdJSON:String = subJson["created"].string!
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = NSTimeZone(abbreviation: "EST")
var dateConverted:NSDate = dateFormatter.dateFromString(createdJSON)!
The time was stored in the MySQL database under eastern timezone, and maybe that's my mistake? Even though I can convert that JSON response to appear to be the same value, maybe it has to be UTC from the beginning?
-edit-
Converting to UTC does not help.
I think you'll have better luck if you search for a date range e.g.
let resultPredicate = NSPredicate(format: "created >= %# AND created <= %#", startDate, endDate)
where startDate and endDate are NSDates that you expect your results to fall between.

Compare a string with current time

I have a string with a specific hour-minut timestamp like this:
let timeToday = "14:00"
and I want to compare this time to the current time of the current day. I'm having problems with NSDate and the fact that it uses a UTC timestamp. I havn't used NSDate, NSCalendar or NSDateFormatter before, and I'm a bit frustrated over how dificult it is to solve this seemingly simple task...
So I was trying to set both the same time from the string and the current time, into two NSDate variables, and then use some sort of compare method, but i can't seem to get them on the same basis.
How can get the two timestamps on the same basic?
so the I have a NSDate with:
NSDate: timeToday // "2015-12-05 14:00:00 +0000"
NSDate: currentTime // "2015-12-05 22:46:54 +0000"
So I ended up reading through the documentation to understands the conpects of the datatypes. NSDate represents a point in time, which will be displayed differently depending on where you are on the globe. So using the UTC as the basis for both the time string and the current time is totally fine.
I solved the problem like this, using :
let calender = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
calender!.locale = NSLocale.currentLocale()
let timeToday = "14:00"
let timeArray = timeToday.componentsSeparatedByString(":")
let timeTodayHours = Int(timeArray[0])
let timeTodayMin = Int(timeArray[1])
let timeTodayDate = calender!.dateBySettingHour(timeTodayHours!, minute: timeTodayMin!, second: 00, ofDate: NSDate(), options: NSCalendarOptions())
let now = NSDate()
if now.compare(timeTodayDate!) == .OrderedDescending {
print(" 'timeToday' has passed!!! ")
}

Dealing with two timezones

I've been struggling with this issue since yesterday and I still lost. Date related stuff is really my bane in programming.
Let me explain the situation. I'm from Sri Lanka (GMT +5:30) and I'm working with a client in Sweden (GMT +1). I'm retrieving a JSON response from an API hosted there. Here's a stub of it. Each array of dictionary in the response is called a Handover.
[
{
"Tim" : 8,
"Pat" : {
"Id" : 5104
},
"Sta" : "Ej utfört",
"SB" : 1066,
"CB" : 0,
"Date" : "2015-02-19T00:00:00+01:00",
"DD" : null,
"HTI" : 1
},
{
"Tim" : 8,
"Pat" : {
"Id" : 5029
},
"Sta" : "",
"SB" : null,
"CB" : 0,
"Date" : "2015-02-19T00:00:00+01:00",
"DD" : null,
"HTI" : 1
}
]
The troublemaker here is that Date field. As you can see the date string is sent in ISO 8601 format. What I do from the app's side is create objects out of them and store it in core data. So in order to save this date value, first I use this library called ISO8601DateFormatter to convert the date string to NSDate. Below is the helper method I wrote for that.
public class func getDateFromISO8601DateString(dateString: String?) -> NSDate? {
if let dateString = dateString {
let formatter = ISO8601DateFormatter()
formatter.defaultTimeZone = NSTimeZone.localTimeZone()
formatter.includeTime = true
let convertedDate = formatter.dateFromString(dateString)
return convertedDate
} else {
return nil
}
}
Now when I convert the date string 2015-02-19T00:00:00+01:00, I get this NSDate value, 2015-02-18 23:00:00 +0000.
Later in the app, I need to retrieve a set of handovers for the current date. Below is my the code I wrote for that.
let fetchRequest = NSFetchRequest()
let entityDescription = NSEntityDescription.entityForName("Handover", inManagedObjectContext: NSManagedObjectContext.MR_defaultContext())
let datePredicate = NSPredicate(format: "date > %# AND date < %#", NSDate().beginningOfDay(), NSDate().endOfDay())
fetchRequest.entity = entityDescription
fetchRequest.predicate = datePredicate
var error: NSError?
let handovers = NSManagedObjectContext.MR_defaultContext().executeFetchRequest(fetchRequest, error: &error) as? [Handover]
return handovers
Here's another place where dates are used. To filter out records according to a date value, I needed to get the start time and the end time of the date. So I have these following methods to return those values. These methods were taken from this library.
func beginningOfDay() -> NSDate {
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone.localTimeZone()
let components = calendar.components(.YearCalendarUnit | .MonthCalendarUnit | .DayCalendarUnit, fromDate: self)
return calendar.dateFromComponents(components)!
}
func endOfDay() -> NSDate {
let calendar = NSCalendar.currentCalendar()
calendar.timeZone = NSTimeZone.localTimeZone()
let components = NSDateComponents()
components.day = 1
return calendar.dateByAddingComponents(components, toDate: self.beginningOfDay(), options: .allZeros)!.dateByAddingTimeInterval(-1)
}
Here are the values I get from these methods.
beginningOfDay() - 2015-02-18 18:30:00 +0000
endOfDay() - 2015-02-19 18:29:59 +0000
Here's the crazy part. All this code works when I run the app from where I live. But when my client runs it, the handover fetching method returns zero results!
I have tracked the issue down and found out it's something wrong with dates and times. But I just can't figure out a way to correct it. Everywhere a date operation is done, I've set the timezone to localTimeZone(). In the AppDelegate's didFinishLaunchingWithOptions method, I also resets the timezone NSTimeZone.resetSystemTimeZone(). Nothing seems to work.
Anyone has any ideas/suggestions? I'd be more than grateful if you could help me out on this one.
Thank you.
The date "2015-02-19T00:00:00+01:00" is exactly the beginning of a day in
the GMT+01 time zone, and therefore does not match the predicate
NSPredicate(format: "date > %# AND date < %#", NSDate().beginningOfDay(), NSDate().endOfDay())
Replacing the first > by >= should solve the problem:
NSPredicate(format: "date >= %# AND date < %#", NSDate().beginningOfDay(), NSDate().endOfDay())
There is also no need to subtract one second in your endOfDay() method,
since you already compare the second date with <.

Resources