0

I am working on an app that initializes dates from strings returned from the backend. The dateString is returned using the following format: "2020-03-05T09:00:00+00:00"

The method I have to do the conversion is:

extension Date {
    static func convertDate(_ dateString: String?) -> Date? {
        guard let dateString = dateString else { return nil }

        let dateFormatter = DateFormatter()

        dateFormatter.setLocalizedDateFormatFromTemplate("yyyy-MM-dd'T'HH:mm:ssZZZZZ")
        dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"

        return dateFormatter.date(from: dateString)
    }

}

Everything was working fine until someone reported that if the user switches off "24-Hour Time" in settings the method above returns nil.

What am I doing wrong?

Thank you

Reimond Hill
  • 4,278
  • 40
  • 52
  • Hi, I've attempted to run this code and I'm getting an error with the line 'dateFormatter.setLocalizedDateFormatFromTemplate(Date.dateFormatterLocale)', Specifically that dateFormatterLocale is not a member of Date. Is this something you have created elsewhere? I've attempted to change it to dateFormatter.locale but that gives another error. – Tom Bailey Mar 05 '20 at 16:49
  • You need to set your dateFormatter locale to "en_US_POSIX". **When working with fixed format dates, such as RFC 3339, you set the dateFormat property to specify a format string. For most fixed formats, you should also set the locale property to a POSIX locale ("en_US_POSIX"), and set the timeZone property to UTC.** https://developer.apple.com/documentation/foundation/dateformatter – Leo Dabus Mar 05 '20 at 16:54
  • Sorry, you are right. It was a constant ... – Reimond Hill Mar 05 '20 at 16:54
  • Remove `dateFormatter.setLocalizedDateFormatFromTemplate`. You don't need it – Leo Dabus Mar 05 '20 at 16:54
  • If you are also creating a timeStamp from date you should set your timeZone to UTC – Leo Dabus Mar 05 '20 at 16:57
  • Note that you should set your DateFormatter locale before before setting other properties – Leo Dabus Mar 05 '20 at 16:58
  • You can also use `ISO8601DateFormatter` instead of `DateFormatter` to parse your string. The default settings should work. – Leo Dabus Mar 05 '20 at 17:00
  • **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),** – Leo Dabus Mar 05 '20 at 17:06
  • **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).** https://developer.apple.com/library/archive/qa/qa1480/_index.html – Leo Dabus Mar 05 '20 at 17:06

1 Answers1

1

You're using a very standardized timestamp format, which allows you to take advantage of the ISO8601DateFormatter.

let dateString = "2020-03-05T09:00:00+00:00"

let df = ISO8601DateFormatter()
df.formatOptions = [.withInternetDateTime]

if let date = df.date(from: dateString) {
    print(date) // 2020-03-05 09:00:00 +0000
}

If a machine (like your server) is generating the timestamp then it will (should) always be in zulu time (GMT) so you don't need to do anything beyond this. You could specify a time zone but there isn't a point since the string will always zero it out for you.

df.timeZone = TimeZone(secondsFromGMT: 0)

This string represents an absolute moment in time. If you need a relative moment in time, such as the local time from the source, you'll need to identify that time zone and apply it here, which is also very straighforward.

trndjc
  • 11,654
  • 3
  • 38
  • 51
  • This is definetly a duplicate question. There is too many questions on StackOverflow already regarding this topic – Leo Dabus Mar 05 '20 at 17:05
  • Note that ISODateFormatter it is only available for iOS 11 or later – Leo Dabus Mar 05 '20 at 17:07
  • As I already mentioned in comments the default settings should work. `df.formatOptions = [.withInternetDateTime]` it is not needed – Leo Dabus Mar 05 '20 at 17:08
  • When creating a timestamp with this method the timezone representation of resulting string will be different from the original string. It will use Z for UTC instead of +00:00. It means the same but it is not consistent – Leo Dabus Mar 05 '20 at 17:12
  • The question has been marked as duplicated but, ISO8601DateFormatter solves the issue straightaway. – Reimond Hill Mar 05 '20 at 17:34