Understanding dateFormatter in Swift 4 - ios

I'm having trouble understanding date formatter in Swift. I'm parsing my date from server in this format 2018-02-05T14:44:01Z and because of that I set DateFormatter like this dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'".
After that, I'm converting it to Date like this dateFormatter.date(from: stringDateFromServer). And that's ok, it works (on a device with 24h time format) and I understand why.
But if I run this on device with 12h time format dateFormatter.date(from: stringDateFromServer) it returns nil. I have to add dateFormatter.locale = Locale(identifier: "en_US_POSIX") for it to work on both 12/24h formats.
So my question is: Why should I add .locale if I already set my date format with HH. Shouldn't dateFormatter automatically convert it to users local settings? Also when I add Locale(identifier: "en_US_POSIX") I lose PM/AM in formated string from date [HH:MM (yes I know HH is for 24h)]

Your format string doesn't match your actual date string. I guess the formatter is trying a backup, locale-specific format when the dateFormat doesn't match the string. You should be using this format string:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssz"
Or, if your deployment target is iOS 10 or later (or at least macOS 10.12, tvOS 10, or watchOS 3), you should use ISO8601DateFormatter instead:
let formatter = ISO8601DateFormatter()
let dateString = "2018-02-05T14:44:01Z"
let date = formatter.date(from: dateString)
print(date)
# Output:
Optional(2018-02-05 14:44:01 +0000)

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.

Trouble retrieving 'localised' strings from DateFormatter that indicate time using "AM/PM" format

My app has a section where I indicate the current time in a graph of 24 hour strings that follow AM/PM format i.e: a set of strings from 12 AM to 11PM
I'm trying to localise the above set of strings but I'm confused.
I'm using a DateFormatter with a dateFormat of "h a". I've tried setting the locale property of the formatter and it does alter the string in few languages but it does not work universally.
let df = DateFormatter()
df.locale = .current
df.dateFormat = "h a"
myLabel.text = df.string(from: myDate)
I've heard that a lot of countries don't even follow the AM/PM format. So how do I go about this? Should I change the dateFormat according to the locale? Is it not really a good idea to use a AM/PM format?
I thought that the DateFormatter would handle this for me accordingly but it doesn't. Help needed! TYIA!
If you want to display the time depending on the user's Locale, you can use timeStyle on DateFormatter
let df = DateFormatter()
df.locale = Locale(identifier: "en_US")
df.setLocalizedDateFormatFromTemplate("j")
// 2 PM
let df = DateFormatter()
df.locale = Locale(identifier: "fr_FR")
df.setLocalizedDateFormatFromTemplate("j")
// 14

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

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.

Swift NSDate ISO 8601 format

I am working on date formats in Swift and am trying to convert a string date to NSDate and an NSSate to string date (ISO 8601 format).
This is my code
let stringDate = "2016-05-14T09:30:00.000Z" // ISO 8601 format
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" // ISO 8601
let date = dateFormatter.dateFromString(stringDate)
print("Date = \(date)") // Output is 2016-05-14 16:30:00 +0000
// Again converting it date to string using stringFromDate
print("\(dateFormatter.stringFromDate(date!))") // 2016-05-14T09:30:00.000Z
I am trying to understand why I am getting NSDate in GMT format (adding 7 hours to time 09:30 to 16:30)?
If I convert that NSDate date variable to string, then I am getting the original string date. What is happening here?
You can use NSISO8601DateFormatter or ISO8601DateFormatter for Swift 3.0+
Your format string was wrong. You indicate a literal Z instead of "Z as zulu time". Remove the single quotes:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
You should always specified the locale as en_US_POSIX when parsing Internet time. This is so commonly overlooked that Apple created the ISO8601DateFormatter class in OS X v10.12 (Sierra).
If you're targeting iOS 11.0+ / macOS v10.13+ (High Sierra), you can simply use ISO8601DateFormatter with the withInternetDateTime and withFractionalSeconds options, like so:
let stringDate = "2016-05-14T09:30:00.000Z" // ISO 8601 format
let iso8601DateFormatter = ISO8601DateFormatter()
iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let date = iso8601DateFormatter.date(from: stringDate)
print("Date = \(date)") // Output is 2016-05-14 09:30:00 +0000
For working with DateFormatter on older systems, see Apple Tech Note QA1480:
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).
Here is a snippet of Swift 5, based on the sample code provided:
let stringDate = "2016-05-14T09:30:00.000Z" // ISO 8601 format
let rfc3339DateFormatter = DateFormatter()
rfc3339DateFormatter.locale = Locale(identifier: "en_US_POSIX")
rfc3339DateFormatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'"
rfc3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let date = rfc3339DateFormatter.date(from: stringDate)
print("Date = \(date)") // Output is 2016-05-14 09:30:00 +0000

Resources