Convert UTC NSDate into Local NSDate not working - ios

I have a string getting from server as "yyyy-MM-dd'T'HH:mm:ss.SSS'Z"
I convert this string into NSDate by this formate.
class func convertUTCDateToLocateDate(dateStr:String) -> NSDate{
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateFormatter.timeZone = NSTimeZone(name: "UTC")
let date = dateFormatter.dateFromString(dateStr)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
let timeStamp = dateFormatter.stringFromDate(date!)
let dateForm = NSDateFormatter()
dateForm.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateForm.timeZone = NSTimeZone.localTimeZone()
let dateObj = dateForm.dateFromString(timeStamp)
return dateObj!
}
Suppose the parameter string is "2016-11-05T12:00:00.000Z" but when i convert this string and return a NSDate object it doesn't change the time according to my local time. I get my correct time in the timeStamp string (in above code). But when i try to convert that timeStamp string into NSDate it again shows that date and time which i got as a parameter.

You shouldnt change a NSDate's time. NSDates are just a point in time, counted by seconds. They have no clue about timezones, days, month, years, hours, minutes, seconds,… If printed directly they will always output the time in UTC.
If you change the date to show you the time of your timezone you are actually altering the time in UTC — hence your date becomes representing another point in time, no matter of the timezone.
Keep them intact by not altering them, instead when you need to display them do it via a date formatter.
If you need to do time calculations that are independent of timezones you also can work with NSDateComponents instead.

NSDate doesn't have a timezone. It's a point in time, independent of anything, especially timezones. You cannot "convert a UTC NSDate to a local NSDate", the statement itself doesn't make any sense.

Related

String to date with UTC timezone

I am struggling with Date and I'm assuming is TimeZone.
Currently I get from my backend a string like this "2020-04-07" and when I try to convert it to date it turns into 2020-04-06 22:00:00 +0000. I am in Spain (UTC+2) which I guess this is why it removes 2 hours?
This is my date formatter:
var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
dateFormatter.timeZone = TimeZone.current
return dateFormatter
}()
And I call it dateFormatter.date(from: startDateString)
I am setting my current timezone but seems to be ignoring it or am I missing something?
I have followed a lot of answers from here but it's always the same result.
Thank you
The Date object does not have any inherent locale / time zone. It just represents a moment in time. If you want to see that Date as a string in a specific locale/time zone you have to use a date formatter. Or there's descriptionWithLocale. If you use print it will print a debug description of the Date instance in UTC.

How to change back year that change into 2000 after using date format

hi I want to get current hour and minute from Date(), so I need to format it into string and want to bring back into date again. But after I try to convert to date the year change into 2000, how can I got back to current year.
//date formatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
dateFormatter.timeZone = TimeZone.current
// Get current time and format it to compare
var currentTime = Date() //Get current time
let currentTimeStr = dateFormatter.string(from: currentTime) //get current time only hour and minute
currentTime = dateFormatter.date(from: currentTimeStr)! //this is where the problem because the year change into 1 January 2000
From what I read in the comments, I think you want both the current time in a Date object and also a string with only hours and minutes in "HH:MM" format.
The problem comes from trying to use a formatter that doesn't have a year specified. You are overwriting the currentTime from a string that doesn't have a year (or day, or month) defined, so it defaults to Jan 1st 2000 (the hours and minutes should be correct).
You're also saying you need to format it into a String, and then go back to a Date object. You don't, you already have all the data you need in the Date object, so keep it around and use it when you need to. If this means creating a bunch of DateFormatters all over your project, you can always extend Date to have a function or variable that returns the string with the format you want.
extension Date {
var hoursAndMinutesString: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
dateFormatter.timeZone = TimeZone.current
return dateFormatter.string(from: self)
}
}
Then just call the function from the Date object when you need to, like this:
currentTime.hoursAndMinutesString

Preventing Date from being localized

I have the following string:
let dateString = "2018-04-18T04:54:00-04:00"
I initialize a Date via the ISO8601DateForamtter by doing the following:
let formatter = ISO8601DateFormatter()
let date = formatter.date(from: dateString)
If I print the date, I get the following:
Apr 18, 2018 at 1:54am
The formatter is automatically converting the time into my local time. How can I prevent accounting for my time zone? For example, I want the Date object to show the following instead:
Apr 18, 2018 at 4:54am
With ISO8601, 2018-04-18T04:54:00-04:00 means 2018-04-18 04:54:00 in GMT -4h. To print the time as it is in the original string, you need to create a date formatter with the specific time zone which is -4.
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(secondsFromGMT: -4 * 60 * 60)
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dateFormatter.string(from: date))
You will get
2018-04-17 04:54:00
FYI, I'm adding a link for ISO8601
You need to parse the timezone from your date string and use it to set the timezone from your date formatter:
func secondsFromGMT(from string: String) -> Int {
guard !string.hasSuffix("Z") else { return 0 }
let timeZone = string.suffix(6)
let comps = timeZone.components(separatedBy: ":")
guard let hours = comps.first,
let minutes = comps.last,
let hr = Int(hours),
let min = Int(minutes) else { return 0 }
return hr * 3600 + min * 60
}
let dateString = "2018-04-18T04:54:00-04:00"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxxxx"
formatter.locale = Locale(identifier: "en_US_POSIX")
if let dateFromString = formatter.date(from: dateString) {
formatter.timeZone = TimeZone(secondsFromGMT: secondsFromGMT(from: dateString))
formatter.dateFormat = "MMM dd, yyyy 'at' h:mma"
formatter.amSymbol = "am"
formatter.pmSymbol = "pm"
print(formatter.string(from: dateFromString)) // Apr 18, 2018 at 4:54am
}
Instead of logging the Date directly, have a look at the string(from:timeZone:formatOptions:) method on ISO8601DateFormatter. With this, you should be able to get a date string for any time zone you desire.
You should set your formatter to the appropriate timezone such as (UTC example below):
formatter.timeZone = TimeZone(identifier: "UTC")
or alternatively specify against GMT:
formatter.timeZone = TimeZone(secondsFromGMT: 0)
The date that you are receiving from your current formatter is technically correct. Setting the date backwards as described in the currently accepted answer is not advised because you are effectively hard-coding an intended time zone. As soon as your device enters another time zone (or if a user downloads your app outside of the current time zone), your information will be incorrect.
If you are trying to display this time in the UTC time zone, you need to use another formatter to correctly format the output in the target time zone.
let utcFormatter = DateFormatter()
utcFormatter.timeZone = TimeZone(secondsFromGMT: 0)
// Perform any other transformations you'd like
let output = utcFormatter.string(from: date)
But why is your original date correct?
The Date API is incredibly robust and doing a lot of things under-the-hood, but is effectively implemented using a simple Double. The automaic time-zone information that it's displaying to you is an abstraction to make it easier to reason about. A date technically has no knowledge of what time zone it's in – but converting it to a string implicitly applies an inferred date formatter on the date and returns information it thinks will be most useful to you.
If you're doing manipulations on a date, you're likely using the Calendar API. You typically get a new instance from using Calendar.current, which will create a new calendar with your current time zone information. You can change the represented time zone of the calendar like this:
var calendar = Calendar.current
calendar.timeZone = TimeZone(secondsFromGMT: 0)
This will give you relative dates that will work in any time zone without modifying the base Date object that you're working with.

Converting time from GMT in iOS behaving oddly (Reverse)

So I am trying to convert some times I receive in UTC/GMT to the local time zone using the following code:
let gmtFormatter: NSDateFormatter = NSDateFormatter()
gmtFormatter.timeZone = NSTimeZone(name: "GMT")
gmtFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let gmtDate: NSDate = gmtFormatter.dateFromString(dateString)!
print("GMT \(gmtDate)")
let localFormatter: NSDateFormatter = NSDateFormatter()
localFormatter.timeZone = NSTimeZone.localTimeZone()
localFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let localDate: NSDate = localFormatter.dateFromString(gmtFormatter.stringFromDate(gmtDate))!
print("Local \(localDate)")
However, I find that the conversion is working in revers. PST is UTC-8 and the outputs are 8 hours ahead instead of 8 hours behind. So I tested with a London Time Zone (CET) which is UTC+1 and should be 1 hour ahead of GMT. The results was a time that was 1 hour behind.
This was the string I am testing with: "2016-02-24 00:05:54"
For PST this should convert to 2016-02-23 04:05:54, but instead converts to 2016-02-24 08:05:54 (notice the way it should be is on the 23rd and not the 24th)
For CET this should convert to 2016-02-24 01:05:54, but instead converts to 2016-02-23 23:05:54 (notice the way it should be is on the 24th and not the 23rd)
Am I missing something glaring?
The problem is that in the second part of your code...
let localDate: NSDate =
localFormatter.dateFromString(gmtFormatter.stringFromDate(gmtDate))!
... you're doing the opposite of what you want. An NSDate is absolute. Its string representation is what is volatile, as it were. So having turned a string into a date with your first formatter, what you now want to do is turn that date into a string with your second formatter.
For example, I'm in California. Now, watch this:
let dateString = "2016-02-24 10:09:08"
let gmtFormatter: NSDateFormatter = NSDateFormatter()
gmtFormatter.timeZone = NSTimeZone(name: "GMT")
gmtFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let gmtDate: NSDate = gmtFormatter.dateFromString(dateString)!
print("GMT \(gmtDate)")
let localFormatter: NSDateFormatter = NSDateFormatter()
localFormatter.timeZone = NSTimeZone.localTimeZone()
localFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let localDateString = localFormatter.stringFromDate(gmtDate) // <-- !!!
print("Local \(localDateString)")
Result:
GMT 2016-02-24 10:09:08 +0000
Local 2016-02-24 02:09:08
That's correct. When it's 10 AM in London, it's 2 AM here.
To put it another way, if you think what you're doing is converting a date from one time zone to another, you have not understood what a date is. It is a date-time and a time zone. There is nothing to convert: it is completely determined, and it is correct no matter where you are, because it says what time zone it is.
Perhaps it would be a bit clearer to you if I changed the next-to-last line format to this:
localFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
The final output is then:
Local 2016-02-24 02:09:08 -0800
which is obviously correct.

NSDateFormatter decreases the day of date

I need to store Date variable in CoreData in iOS
I need to store the Date only without the Time, So I made a formatter that discard the time partition from the NSDate variable.
But I have a strange result:
This is my code:
let dateStr = "2016-02-14 11:27:01"
let df2 = NSDateFormatter()
df2.timeZone = NSTimeZone.defaultTimeZone()
df2.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dateStr)
if let date = df2.dateFromString(dateStr) {
df2.dateFormat = "yyyy-MM-dd"
print("-> \(df2.dateFromString(df2.stringFromDate(date)))")
}
and this is the output:
2016-02-14 11:27:01
-> Optional(2016-02-13 20:00:00 +0000)
Why does the formatter decrease the day by one ?
I tried many dates with same issue
Your time zone is obviously UTC+4.
To get UTC set the time zone accordingly.
df2.timeZone = NSTimeZone(forSecondsFromGMT: 0)
But although you see a date 4 hours ago the NSDate object is treated correctly depending on your time zone. The print command displays always UTC ignoring the time zone information, because NSDate is just a wrapper for a Double number.

Resources