Searching by NSDate in Coredata not working - Swift - ios

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.

Related

Core Date date comparion from date picker text field

I am saving date in Core data successfully using Date type in model. Date saved in the format like 2017-04-02 14:56:41. When i retreive i want to filter date with current date text field which has a format like 02 April 2017. I am using predicate to compare both dates but app is crashed due to the difference or may be current date is in string.
let predicate = NSPredicate(format: "(dateSchedule >= %# )", fromTextField.text!)
core data date: 2017-04-02 14:56:41
currentdate textfield date: 02 April 2017
Any help?
I'm assuming dateSchedule is a Date in coredata.
You can convert your text on a date variable (make sure the format is correct)
let dateString = "2017-04-02 14:56:41"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
let dateObj = dateFormatter.date(from: dateString)
Then you create the predicate:
let predicate = NSPredicate(format: "(dateSchedule >= %# )", dateObj)
EDIT:
If the time of the date is annoying you, you can use a utility like this one:
https://github.com/erica/SwiftDates
So you can filter with:
let predicate = NSPredicate(format: "(dateSchedule >= %# )", dateObj.startOfDay)
In that way you are removing the time part and filtering by the beginning of the day
I would have formatted the dates this way:
let dateFormatter = Dateformatter()
dateFormatter.timeStyle = .none
dateFormatter.dateStyle = .medium
You can apply this to a date and get it as a string like this:
whatever you are saving it as = dateFormatter.string(from: Date())
Then you will get a date like this, "Mar 17, 2017".
This way of using the date formatter is very practical in your very situation.
When you are fetching you just do it the same way:
let predicate = NSPredicate(format: "(dateSchedule >= %# )", fromTextField.text!)
but I would advise you to use a UIDatePickerView instead:
let predicate = NSPredicate(format: "(dateSchedule >= %# ), dateFormatter.string(from: datePickerView.date)")

Same Date... But Different? Maybe timezone confusion

My problem is that I save a date into a string and as a date in CoreData. Later, I need to pull the date out of the string, compare the two, and find that they're the same date. Right now, that equality check fails. The two dates are 7 hours apart but with the minutes correct. I think it's a timezone issue but I can't figure out how to solve it.
The Origin of the Dates
I have a date from a date picker that I save to CoreData like this:
task.setValue(dueDatePicker.date, forKey: "dueDate")
After that I format the date and insert that date into a message:
let dateFormatter = DateFormatter()
let dateFormat = DateFormatter.Style.medium
let timeFormat = DateFormatter.Style.short
dateFormatter.dateStyle = dateFormat
dateFormatter.timeStyle = timeFormat
let formattedDate = dateFormatter.string(from: date)
let message = ("Upcoming task on \(formattedDate)")
That message becomes part of a notification. Hours or days later (when the notification fires and the user selects an action) I get the CoreData date:
fetchRequest.predicate = NSPredicate(format: "dueDate = %#", dateOfTask)
Then I decompose the notification message and get the date:
let start = notifString.range(of: "on ")
let rawDate = notifString[(start.upperBound)!..<(notifString.endIndex)]
let dateFormatter = DateFormatter()
dateFormatter.timeZone =
dateFormatter.dateFormat = "MMM-d-yyyy, H:mm a"
let dateFromString = dateFormatter.date(from: rawDate)
Lastly, I compare them. Currently the times are clearly the same day and minute but the timezones differ by about 7 hours. However, I don't want to just force a timezone that matches (Maybe force UTC for example) because that may not work for a user in another location.
How do I retrieve both dates without getting this apparent timezone issue?
Blatantly obvious, use userInfo as #Paulw11 suggested:
newLocalNotif.fireDate = dueDateWarningTime
newLocalNotif.alertBody = message
newLocalNotif.timeZone = TimeZone.autoupdatingCurrent
newLocalNotif.soundName = UILocalNotificationDefaultSoundName
newLocalNotif.category = "DueDates"
newLocalNotif.userInfo = ["name": name, "desc": desc, "dueDate" : date]

Cannot convert to NSDate from this specific date (1994-04-01) String

Something strange happen to me.
I'm not able to convert this specific date (1994-04-01) String.
Can anyone check this and let me know if it reproduce in your code?
Steps to Reproduce:
Swift:-
let dateString = "1994-04-01"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let dateFromString = dateFormatter.date(from: dateString)
Obj c:-
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:#"yyyy-MM-dd"];
NSString *birthdayStr = #"1994-04-01";
NSDate *birthday = [formatter dateFromString:birthdayStr];
Expected Results:
birthday = 1994-03-31 21:00:00 +0000
In UTC
Actual Results:
birthday = nil
Version:
Xcode ver :- Version 8.1 (8B62)
OS X ver :- 10.12.1 (16B2555)
you can try any different date then (1994-04-01) and it will work fine.
Test code:
import Foundation
for timeZoneId in TimeZone.knownTimeZoneIdentifiers {
let dateString = "1994-04-01"
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: timeZoneId)
dateFormatter.dateFormat = "yyyy-MM-dd"
if dateFormatter.date(from: dateString) == nil {
print(timeZoneId)
}
}
Output:
Asia/Amman
Asia/Damascus
Asia/Gaza
Asia/Hebron
Asia/Jerusalem
I conclude your system time zone is set to one of the ones printed. If I type “jerusalem time zone 1994” into Google, the first result tells me that Daylight Saving Time started at midnight on April 1 in 1994. This means there was no midnight. The first instant of April 1, 1994 was in fact 1 AM in that time zone.
A DateFormatter uses a time of day of midnight by default when not parsing a time of day from the string. This makes it fail when midnight doesn't exist on the date in the string.
The solution is to not use midnight as your default time of day. Noon is a much safer default time. So, one solution is to include the time of day in the input and parse it in the format:
let dateString = "1994-04-01 12:00:00"
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: timeZoneId)
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
Another solution is to give the date formatter a default date that is noon of some day:
// Reference date was 00:00:00 UTC on 1 January 2001. This is noon of the same day in UTC.
dateFormatter.defaultDate = Date(timeIntervalSinceReferenceDate: 12*60*60)
If you are going to do any date parsing or manipulation on iOS or macOS, it would be a very good idea to watch WWDC 2013 Session 227: Solutions to Common Date and Time Challenges.

Swift - extracting hours from NSDate, parsing string issue

I receive a string in Json and first of all I have to do is to convert it into NSDate. The problem is, none of string formats I used is valid. the code goes as follows:
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
var output = formatter.dateFromString("2000-01-01T00:00:00.000Z")
let timeString = formatter.stringFromDate(output)
as far as I know, if I want to retrieve hours from NSData, I have to call formatter once more
formatter.dateFormat = "hh"
and call it on NSDate obtained from string. Am I right?
My first question is: how to make the determine proper date format so the output will not be evaluated to nil? The second question is: Do I get it right or there is a simpler method or generally way to retrieve the hours from the following string: "2000-01-01T00:00:00.000Z" ? I know I can do it via dealing with mere string without involving dateFormatter and NSDate, but won't such solution be vulnerable? Please advice me what's the simplest(and robust) way to deal with this.
Thanks in advance
First of all, you have your formatter wrong...
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'"
var output = formatter.dateFromString("2000-01-01T00:00:00.000Z")
After that, you can get the hour component with
if let date = output {
var hours = NSCalendar.currentCalendar().component(.HourCalendarUnit, fromDate: date)
}

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