How to save datetime with timezone in iOS? - 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.

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.

why Date will return nil when using correct format in swift 4?

so here is the string Date that I want to Convert
2019-03-22T00:00:00
and here is the problem
Remember that I used "yyyy-MM-dd'T'HH:mm:ss" or "yyyy-MM-ddTHH:mm:ss" or "yyyy-MM-dd HH:mm:ss" or yyyy-MM-dd'T'HH:mm:ssZ and some other similar formats
You have to set a time zone and locale when parsing internet dates:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
// necessary to avoid daylight saving (and other time shift) problems
dateFormatter.timeZone = TimeZone(secondsFromGMT: TimeZone.current.secondsFromGMT())
// necessary to avoid problems with 12h vs 24h time formatting
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
print(dateFormatter.date(from: "2019-03-22T00:00:00"))
The default time zone contains information about daylight saving time and some specific times does not exist there. Instead, we have to use a generic time zone.
See https://developer.apple.com/library/archive/qa/qa1480/_index.html
22 March 2019 around midnight is the day & time when daylight saving change in Iran makes an hour non-existent.
The same can be achieved using ISO8601DateFormatter:
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = ISO8601DateFormatter.Options.withInternetDateTime.subtracting(.withTimeZone)
print(dateFormatter.date(from: "2019-03-22T00:00:00"))
Try this function might help
public func dateFormatter(strDate: String) -> String{
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "fa_IR")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = dateFormatter.date(from: strDate)
let dateString = dateFormatter.string(from: date!)
return dateString
}
dateFormatter.isLenient = true
Ignores missing hours during daylight saving time offset

Date() returns hour over 24

I have to send date from my iPhone through my app to server, but I was surprised that the date had stored as NULL (not always) in server, that's the code used for that:
timeS = (Date().millisecondsSince1970)
// make some assertions on timeS
let date = Date(timeIntervalSince1970: timeS)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let dateString = dateFormatter.string(from: date)
return dateString
It seems like the c# code in the server side tried to convert the string to date and it failed, so it was stored NULL.
So recently I tried to send the date without any modifications in a string to see what happened:
self.backS.sendLocation(msg: "data fitched \(Date())")
and that's what I found in the database:
data fitched 2018-05-08 77:45:44 AM +0000
it must be:
data fitched 2018-05-08 07:45:44 AM +0000 (because that was the time for the server)
I don't know why is that happened!!
I really appreciate anybody's help, thanks in advance.
Try to get your current date and time like this in Swift.
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let result = formatter.string(from: date)
self.backS.sendLocation(msg: "data fetched \(result)")
Your result will be like this.
"2018-05-12 14:34:51 +0000"
To send the date to server you should rather use Unix timestamp, instead of date in string format.
But, if you insist on a string, you should use DateFormatter. When using DateFormatter you should always set the locale property of your formatter. That ensures the formateer will return consistens results, regardless of your phone's regional settings.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let dateString = formatter.string(from: Date())
It's important to set the locale property before you set the dateFormat property.
If you don't set the locale property, the formatter might return nil when working with 24-format on devices with 12-hour format set, and vice versa.
Source: Technical Q&A QA1480

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.

Getting back a date from a string

im dealing with dates, and i'm having problems getting my date back from a string, i simplified my problem here:
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-YYYY"
formatter.stringFromDate(date)
formatter.dateFromString(formatter.stringFromDate(date))
And the output is:
"25-05-2015" (Which is fine)
"Dec 21, 2014, 12:00 AM" (???)
The problem there is that Y is for weekOfYear. You have to use "dd-MM-yyyy". Btw don't forget to set your date formatter locale to "en_US_POSIX" .
If you're working with fixed-format dates, you
should first set the locale of the date formatter to something
appropriate for your fixed format. In most cases the best locale to
choose is "en_US_POSIX", a locale that's specifically designed to
yield US English results regardless of both user and system
preferences. "en_US_POSIX" is also invariant in time (if the US, at
some point in the future, changes the way it formats dates, "en_US"
will change to reflect the new behaviour, but "en_US_POSIX" will not),
and between machines ("en_US_POSIX" works the same on iOS as it does
on OS X, and as it it does on other platforms).
You should use yyyy for the year, not YYYY (which has a different meaning)
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let s = formatter.stringFromDate(date) // "25-05-2015"
let d = formatter.dateFromString(s) // "2015-05-24 22:00:00 UTC" (*)
(*) it's 22:00 because I'm in the +0200 timezone, so this result is effectively 2015-05-25 00:00:00 in my timezone
You should change your dateFormat to make it work , YYYY is not correct.
import Foundation
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-YYYY"
var firstDate = formatter.stringFromDate(date)
formatter.dateFormat = "yyyy-MM-dd hh:mm:ss.SSSSxxx"
var secondDate = formatter.dateFromString(formatter.stringFromDate(date))
println("\(firstDate)")
println("\(secondDate)")

Resources