How to convert a GMT date from server to a current user's time zone? - ios

I have such a response from server
date: "2021-04-24T16:00:34.969"
timezone: "Asia/Saigon"
How can I convert this date with specified time zone to a user's time zone?

You can create a time zone object from the given string and use it when parsing the date
let dateString = "2021-04-24T16:00:34.969"
let timezoneString = "Asia/Saigon"
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: timezoneString)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
let date = dateFormatter.date(from: dateString)
Then when you want to display the date to the user you use another DateFormatter instance where the timezone property is set to .current

Related

I can't convert iso8601 to string swift

I have a string coming from API and its format will be like this
"2021-03-01T15:00:00+07:00"
so i try to convert this string to date using this code
// string to date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let date = dateFormatter.date(from: isoDate)!
print("date from date Formatter = \(date)")
// convert date back to string
dateFormatter.dateFormat = "EEEE HH:mm"
let dateString = dateFormatter.string(from: date)
print("date string \(dateString)")
return dateString
The result that I expect is -> "2021-03-01 08:00:00 +0000", "Monday 15:00"
When I try this on playground the result is what I want, but when I try this on my project the result is
-> "1478-03-01 08:00:00 +0000", "Sunday 14:42"
How can I change the result to the same as i expect? Thanks
It looks like you are using a different calendar than you expect in your project (buddhist maybe?) and I guess this is because you haven't set one explicitly so it's the one set in System Preferences.
So if you for some reason do not want to use the users current calendar (and locale and time zone) you need to set those properties on your date formatter instance
//Gregorian calendar
dateFormatter.calendar = Calendar.init(identifier: .gregorian)
//UTC time zone
dateFormatter.timeZone = TimeZone(identifier: "UTC")
//English locale
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
This will give you the expected output.
Note that the playground is a bit inconsequent in what it uses and it seems to be a mix of what we have set in our System preferences and hardcoded values.

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.

dateFromString() returning wrong date swift 3.0

i am passing "01/12/2017" in the fromDate.text(textfield), but receiving unexpected output.
let formatter = DateFormatter.init()
formatter.dateFormat = "dd/mm/yyyy"
startDate = formatter.date(from: fromDate.text!)
print("startDate = \(startDate)")
output is : 31/12/2016
The format of date should be dd/MM/yyyy not dd/mm/yyyy. The mm indicates the minutes and MM indicates the month.
And also add the below line in your code
formatter.timeZone = TimeZone(abbreviation: "GMT+0:00")
This line of code set time zone. If you not, then you get 30/11/2017 in output.
The reason behind this is when string date not contain time then formatter assume that it is midnight and you also not given the timezone so it will take current timezone.
It has to be dd/MM/yyyy dateformat. MM in capital.
func convertToString(of dateTo: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy" //Your New Date format as per requirement change it own
let newDate: String = dateFormatter.string(from: dateTo) //pass Date here
print(newDate) //New formatted Date string
return newDate
}

How to save datetime with timezone in iOS?

I am new to iOS development, being an Android developer I am used to use have an object that saves a datetime with a given timezone (from Joda-Time library).
After reading the iOS documentation about dates and times (https://developer.apple.com/documentation/foundation/dates_and_times) I still have doubts about which class should I use to save datetimes. Given the Date/NSDate class description "A specific point in time, independent of any calendar or time zone." it seems very useless because it is timezone independent and time without a timezone does not make any sense, since it does not have any context.
My real problem (TL;DR):
I have a database where date times are stored in UTC like this "yyyy-MM-dd hh:mm:ss". I would like to init an object with some kind of DateFormatter (string with this format "yyyy-MM-dd hh:mm:ss") plus a timezone (UTC) to easily convert to any Timezone that I want (to show to the user on his default timezone time). How can I accomplish this in iOS?
Edit: Imagine I have a class Event with a title and a start time. Title is a String, what start time should be?
You use a DateFormatter for this.
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
The formatter.locale sets the current locale for the user and formatter.dateFormat sets the desired date format. In your case yyyy-MM-dd HH:mm:ss.
To call it simply:
let utcDateFromServer = "2017-01-01 22:10:10"
let date = formatter.date(from: utcDateFromServer)
A Date is a point in time, as mentioned in other comments & in the documentation.
If you want to convert the UTC time into local time, you'll need to first convert the String "yyyy-MM-dd hh:mm:ss" from your database into a Date using DateFormatter.
let dateStringUTC = "2018-01-01 00:00:00"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
//Set the input timezone (if you don't set anything, the default is user's local time)
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let date : Date = dateFormatter.date(from: dateStringUTC)!
Then convert the Date back into String using DateFormatter with the respective TimeZone
let outputDateFormatter = DateFormatter()
outputDateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
//Set the output timezone (if you don't set anything, the default is user's local time)
//outputDateFormatter.timeZone = someTimeZone
let dateString = outputDateFormatter.string(from: date)
print(dateString)
Output: 2017-12-31 17:00:00
And you can just change the input and output timezone to do the opposite.

How to change the timeZone in SwiftDate?

I have displayed the list of timezones in my app. If user selects a particular timezones, I need to change the local timezone to the selected timezone by the user.
let region = Region(tz: timeZoneName.timeZone , cal: cal, loc: cal.locale!)
let date = Date().inRegion(region: region).absoluteDate
Here is the problem, the region is changed to the selected timezone but the date issuing the local timezone.
A Date contains no timezone. From apple's docs: A specific point in time, independent of any calendar or time zone.
The timezone comes into play as soon as you want to present a date to the user. And that's what a DateFormatter is for. As #AlexWoe89 already pointed out, it let's you convert a string, containing a date into a Date object, but also lets you convert a given date into a string representing the date in the time zone you set to the timeZone property of DateFormatter.
let date = Date()
var dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
dateFormatter.timeZone = TimeZone(identifier: "America/Los_Angeles")
let dateString1 = dateFormatter.string(from: date)
dateFormatter.timeZone = TimeZone(identifier: "Germany/Berlin")
let dateString2 = dateFormatter.string(from: date)
This will store 2017-10-23 04:27 in dateString1, while the same date leads to 2017-10-23 13:27 in dateString2.
You can use DateFormatter as a solution, try something like this:
let dateString = "<yourDateAsString>"
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // => there are a lot of identifiers you can use
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.defaultDate = Date()
dateFormatter.dateFormat = "dd-MM-yyyy hh:mm” // => your needed time format
let convertedDate = dateFormatter.date(from: dateString)

Resources