Dealing with two timezones - ios

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 <.

Related

Date formatting for next available date in Swift [duplicate]

This question already has answers here:
Convert string with unknown format (any format) to date
(2 answers)
Closed 5 years ago.
I'm currently downloading fixture lists for a sports app from a third party website that runs the league, so the data I have to work with is restricted.
I'm trying to implement a feature that displays the next upcoming fixture.
My problem is the dates being retrieved look like this:
"Sat 9th Sep 17" and "Sat 24th Mar 18" for example.
I've tried numerous date formats in the DateFormatter that I know of and can't find anything that uses this specific format.
If I try to use the Date from string method in the date formatter with one of the above strings I get a nil value.
I have an array of fixtures, each with their own dates. I need to compare each of these to the current date to figure out which is next in line.
My temporary fix for this was just to loop through the fixtures and as soon as one did not have a result to display that as the next fixture. Obviously this doesn't work when a game may not have been played for whatever reason.
What would be the best way to deal with this?
Basically you would just need to convert the current date to the same format as the date you get from your third party website (or the opposite) so you can compare them easily:
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EE MMM"
let firstPartStringDate = dateFormatter.string(from: currentDate)
dateFormatter.dateFormat = "yy"
let lastPartStringDate = dateFormatter.string(from: currentDate)
let day = Calendar.current.component(.day, from: currentDate)
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
guard let ordinalDay = formatter.string(for: day) else {
return
}
let finalDateString = firstPartStringDate + " \(ordinalDay) " + lastPartStringDate
print(finalDateString)
And for today's current date you would get the exactly same format as the one you get from the third-party website :
Sun Sep 17th 17
UPDATE: Here is how you could convert the String you get from the third-party website to a Date, and then compare it with the current date. This solves the problem of having the st, nd, rd and th inside the String at first.
// This is the string you get from the website
var webDateString = "Sat 9th Sep 17"
// First, remove the st, nd, rd or th if it exists :
if let stSubrange = webDateString.range(of:"st") {
webDateString.removeSubrange(stSubrange)
} else if let ndSubrange = webDateString.range(of:"nd") {
webDateString.removeSubrange(ndSubrange)
} else if let rdSubrange = webDateString.range(of:"rd") {
webDateString.removeSubrange(rdSubrange)
} else if let thSubrange = webDateString.range(of:"th") {
webDateString.removeSubrange(thSubrange)
}
// Now convert the string to a date :
dateFormatter.dateFormat = "EE MMM dd yy"
guard let formattedDate = dateFormatter.date(from: finalDateString) else {
return
}
// You can now compare formattedDate and the current date easily like so :
let currentDate = Date()
if formattedDate < currentDate {
// Do something interesting here :)
} else {
// Do something else!
}

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.

Fetch records between two dates using core data ios swift

I am trying to search records between two dates in swift.
I'm storing the date as shown below:
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy'-'MM'-'dd HH':'mm':'ss"
println(date) // prints 2015-09-26 05:22:41 +0000
myreport.setValue(date, forKey: "date")
And below is my code to fetch the records between two dates using NSPredicate:
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let startDate:NSDate = dateFormatter.dateFromString(fromdate)!
let endDate:NSDate = dateFormatter.dateFromString(todate)!
println(fromdate) // prints 2015-09-22
println(todate) // prints 2015-09-29
println(startDate) // prints 2015-09-21 20:00:00 +0000
println(endDate) // prints 2015-09-28 20:00:00 +0000
let predicate1 = NSPredicate(format: "%# >= date AND %# <= date", argumentArray: [startDate, endDate])
let predicate = NSCompoundPredicate(type: NSCompoundPredicateType.OrPredicateType, subpredicates: [predicate1])
// Set the predicate on the fetch request
request.predicate = predicate
request.sortDescriptors = [NSSortDescriptor(key: "report_id", ascending: false)]
var results : NSArray = context.executeFetchRequest(request, error: nil)!
Note:
1) My fromdate and todate is in this format 2015-09-25 (as String)
2) My data type in core data for date attribute is NSDate
3) I am able to store and fetch the date from core data successfully
When I run above code Im not getting any error but the code is not searching for records between two dates. I mean im getting the empty results array.
Can any one plz tell me what im doing wrong here.
Also plz let me know if any more information needed. Thanks in advance!
Finally after many modifications and testing this worked for me as shown below. If you need how im storing the nsdate in coredata plz check the question above.
println(self.appdel.fromdate) // prints 2015-09-25
println(self.appdel.todate) // prints 2015-09-26
var fromdate = "\(self.appdel.fromdate) 00:00" // add hours and mins to fromdate
var todate = "\(self.appdel.todate) 23:59" // add hours and mins to todate
var context : NSManagedObjectContext = appdel.managedObjectContext!
var request = NSFetchRequest(entityName: "TblReportsP")
request.returnsObjectsAsFaults = false
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
dateFormatter.timeZone = NSTimeZone(name: "GMT") // this line resolved me the issue of getting one day less than the selected date
let startDate:NSDate = dateFormatter.dateFromString(fromdate)!
let endDate:NSDate = dateFormatter.dateFromString(todate)!
request.predicate = NSPredicate(format: "(date >= %#) AND (date <= %#)", startDate, endDate)
request.sortDescriptors = [NSSortDescriptor(key: "report_id", ascending: false)]
var results : NSArray = context.executeFetchRequest(request, error: nil)!
println(results.count)
I think your predicate might be wrong, try modifying it like this:
%# <= date AND date <= %#
In this way you will look of date that is between start and end dates.

NSDateformatter dateFromString AnyObject

I have an array of AnyObjects of NSDates and I am trying to convert to weekdays like (Mon, Tue, Wed..)
The Array looks like this when I print it to the console:
[2014-12-03 22:16:26 +0000, 2014-12-05 22:16:26 +0000, 2014-12-11
22:16:26 +0000]
This is my Code:
var xAxisDates = [AnyObject]()
func dateformatter() {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let weekDayFormatter = NSDateFormatter()
weekDayFormatter.dateFormat = "EEE"
println(xAxisDates)
for i in xAxisDates{
let date = formatter.dateFromString(i as String)
println(weekDayFormatter.stringFromDate(date!))
}
}
The code crash at :
let date = formatter.dateFromString(i as String)
I have been trying all day to figure out what I am doing wrong, and read practically every question on NSDateFormatter but to no avail. I am down casting i as String because if I don´t I get error message AnyObject is not convertible to String. My feeling is that the type do not match up, but I am not sure, and I don´t know how to find out. I have attached the error message as picture if that helps. If you can recommend a tutorial or book or something on how to understand what is wrong I would appreciate it, as I am trying to learn.
Any help would be much appreciated !
The xAxisDates array contains NSDate objects, therefore
let date = formatter.dateFromString(i as String)
does not make any sense, and i as String already crashes because i is an NSDate
and not a String.
You just need to convert the dates to a string:
for i in xAxisDates {
println(weekDayFormatter.stringFromDate(i as NSDate))
}
or better check if the array elements actually are NSDate objects:
for i in xAxisDates {
if let date = i as? NSDate {
println(weekDayFormatter.stringFromDate(date))
}
}
extension NSDate {
var weekdayName: String {
let formatter = NSDateFormatter()
formatter.dateFormat = "EEEE"
return formatter.stringFromDate(self)
}
}
let myArrayOfAnyObjects:[AnyObject] = [NSDate()]
myArrayOfAnyObjects.first!.weekdayName // "Tuesday"

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