125

It's easy enough to get the ISO 8601 date string (for example, 2004-02-12T15:19:21+00:00) in PHP via date('c'), but how does one get it in Objective-C (iPhone)? Is there a similarly short way to do it?

Here's the long way I found to do it:

NSDateFormatter* dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";

NSDate *now = [NSDate date];
NSString *formattedDateString = [dateFormatter stringFromDate:now];
NSLog(@"ISO-8601 date: %@", formattedDateString);

// Output: ISO-8601 date: 2013-04-27T13:27:50-0700

It seems an awful lot of rigmarole for something so central.

JohnK
  • 6,865
  • 8
  • 49
  • 75
  • 1
    I guess short is in the eye of the beholder. Three lines of code is pretty short, no? There is a C NSDate subclass you can add that will do it with one line, I just tripped on it: https://github.com/soffes/SAMCategories/tree/master/SAMCategories/Foundation. That said, really, you got pretty good answers (IMHO) and you should award either Etienne or rmaddy the answer. Its just good practice and rewards people providing real helpful info (it helped me, I needed this info today). Etienne could use the points more than rmaddy. As a sign of good measure I'll even up vote your question! – David H Nov 13 '13 at 18:57

12 Answers12

222

Use NSDateFormatter:

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];   
NSLocale *enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
[dateFormatter setCalendar:[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]];

NSDate *now = [NSDate date];
NSString *iso8601String = [dateFormatter stringFromDate:now];

And in Swift:

let dateFormatter = DateFormatter()
let enUSPosixLocale = Locale(identifier: "en_US_POSIX")
dateFormatter.locale = enUSPosixLocale
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
dateFormatter.calendar = Calendar(identifier: .gregorian)

let iso8601String = dateFormatter.string(from: Date())
tig
  • 25,841
  • 10
  • 64
  • 96
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • 1
    Thanks, Maddy. I just saw your answer now, after I'd posted my code. Is the `setLocale` important? – JohnK Apr 27 '13 at 17:32
  • Yes, the special locale is very important. Without it, the resulting string may be wrong for some users' devices. That locale should always be used when parsing or creating date/time strings in a specific format that is not meant for user display. – rmaddy Apr 27 '13 at 17:35
  • What if the date-time string *is* meant for user display? – JohnK Apr 27 '13 at 18:20
  • You would never show a user an ISO8601 date string but in general, when formatting a date to a string for user display, then you want to use the user's locale (which is the default behavior). – rmaddy Apr 27 '13 at 18:23
  • 2
    @rmaddy You might just edit your example to use ZZZZZ so that people copy-pasting don't get burned. – Jesse Rusak Mar 31 '14 at 15:40
  • It should be @"YYYY-MM-dd'T'HH:mm:ssZZZZZ" (notice the capitalized Y's) instead. According to http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns Ys are for "Week of Year" and I quote: "This year designation is used in ISO year-week calendar as defined by ISO 8601, but can be used in non-Gregorian based calendar systems where week date processing is desired. May not always be the same value as calendar year." – jlmendezbonini Sep 25 '14 at 00:45
  • 4
    @jlmendezbonini No. It's very rare you want YYYY. You usually want yyyy. – rmaddy Sep 25 '14 at 01:12
  • That's what I've seen in every reference (yyyy), however the confusion lies on the fact that ISO 8601 explicitly calls for YYYY. Do you have a reference or know why we would use yyyy instead of YYYY when the standard calls for the latter? – jlmendezbonini Sep 25 '14 at 05:29
  • 8
    Source for why the locale should be set to en_US_POSIX: https://developer.apple.com/library/mac/qa/qa1480/_index.html – yood Feb 21 '15 at 21:54
  • @Robin - why add Swift code to my answer instead of posting your own answer? – rmaddy Mar 24 '15 at 18:59
  • 11
    @maddy - Wanted to make it easy to discover for others finding this through Google and wanted to give you credit for actually digging up the correct format, locale etc. But happy to post it as a separate answer if you want – Robin Mar 24 '15 at 19:11
  • I found that yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ which gives milliseconds the best format. ie. 2018-01-01T11:59:59.123-0800 more useful – maninvan Nov 24 '17 at 18:16
  • Use `ISO8601DateFormatter` for this purpose. See https://stackoverflow.com/a/44970859/6870041 and https://stackoverflow.com/a/39165437/6870041 for more details. – Vadim Bulavin May 08 '18 at 10:05
  • Converting ISO8601 date-times NSString to NSDate and vice-versa https://gist.github.com/jungchris/93727d8fbc1a81cc608e – gauravds May 10 '18 at 14:25
  • It's usually a good idea to set the calendar of the date formatter to Gregorian if you're planning to send that data somewhere. Otherwise you'll be getting year like 30-something for Japaneese calendar, 2019-ish something for Gregorian and 4000-something for Buddhist calendars set on user's device... which might not be perfect for server-side or date comparison/time difference calculation between different users (or even time calculation, if user chooses to change the calendars in between). I've added the calendar to your code, please check if it's ok :) – kender Apr 11 '19 at 07:02
  • can you please explain why you haven't used (or mentioned) the NSISO8601DateFormatter class, available since iOS10 and MacOS 10.12 ? few answers below mention it, but since this is the recommended answer, I think it should also give reference to this specific formatter. – Motti Shneor Aug 05 '20 at 15:37
71

iOS 10 introduces a new NSISO8601DateFormatter class to handle just this. If you're using Swift 3, your code would be something like this:

let formatter = ISO8601DateFormatter()
let date = formatter.date(from: "2016-08-26T12:39:00Z")
let string = formatter.string(from: Date())
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
TwoStraws
  • 12,862
  • 3
  • 57
  • 71
  • and what format does it give? Also it would be good to know if it can handle from: 2016-08-26T12:39:00-0000 and 2016-08-26T12:39:00-00:00 and 2016-08-26T12:39:00.374-0000 – malhal Sep 03 '16 at 19:40
  • The first two parse correctly; the third does not. The format is down to you – it creates a `Date` object that you can manipulate however you want. – TwoStraws Sep 03 '16 at 20:33
  • Thanks for testing that. For the format thing I meant can you show us what its default output looks like printed out? Interested to see how they chose to output the timezone. Cheers – malhal Sep 03 '16 at 21:09
  • 1
    After the three lines in my answer are run, `string` contains "2016-09-03T21:47:27Z". – TwoStraws Sep 03 '16 at 21:48
  • 6
    you **CAN NOT** handle **milliseconds** with this format. – Enrique Sep 11 '17 at 22:23
  • 4
    @Enrique: you should add the NSISO8601DateFormatWithFractionalSeconds to the formatter options to be able to work with milliseconds, check the docs. – PakitoV Nov 07 '18 at 14:08
  • 1
    NSISO8601DateFormatWithFractionalSeconds works only on 11.2+. Otherwise you got a crash! – hash3r Apr 26 '19 at 14:50
  • Just to confirm that .withFractionalSeconds also works with or without the presence of milliseconds, at least on iOS 13. – Airsource Ltd May 26 '20 at 18:31
29

As a complement to maddy's answer, the time zone format should be "ZZZZZ" (5 times Z) for ISO 8601 instead of a single "Z" (which is for RFC 822 format). At least on iOS 6.

(see http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns)

Etienne
  • 1,269
  • 1
  • 10
  • 13
  • Great catch! Anyone else interested in this, go to the link, search for 8601 you will see it. – David H Nov 13 '13 at 15:27
  • 1
    When the time zone on the date is UTC, is there a way you can get it to show the 00:00 instead of the Z (which are equivalent)? – shim Mar 27 '15 at 23:13
  • Thanks a lot ;) was banging my head to the wall with a dateformatter returning nil date !!! :) – polo987 Jan 21 '16 at 16:12
26

An often overlooked issue is that strings in ISO 8601 format might have milliseconds and might not.

In other words, both "2016-12-31T23:59:59.9999999" and "2016-12-01T00:00:00" are legit, but if you are using static-typed date formatter, one of them won't be parsed.

Starting from iOS 10 you should use ISO8601DateFormatter that handles all variations of ISO 8601 date strings. See example below:

let date = Date()
var string: String

let formatter = ISO8601DateFormatter()
string = formatter.string(from: date)

let GMT = TimeZone(abbreviation: "GMT")
let options: ISO8601DateFormatOptions = [.withInternetDateTime, .withDashSeparatorInDate, .withColonSeparatorInTime, .withTimeZone]
string = ISO8601DateFormatter.string(from: date, timeZone: GMT, formatOptions: options)

For iOS 9 and below use the following approach with multiple data formatters.

I haven't found an answer that covers both cases and abstracts away this subtle difference. Here is the solution that addresses it:

extension DateFormatter {

    static let iso8601DateFormatter: DateFormatter = {
        let enUSPOSIXLocale = Locale(identifier: "en_US_POSIX")
        let iso8601DateFormatter = DateFormatter()
        iso8601DateFormatter.locale = enUSPOSIXLocale
        iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
        iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
        return iso8601DateFormatter
    }()

    static let iso8601WithoutMillisecondsDateFormatter: DateFormatter = {
        let enUSPOSIXLocale = Locale(identifier: "en_US_POSIX")
        let iso8601DateFormatter = DateFormatter()
        iso8601DateFormatter.locale = enUSPOSIXLocale
        iso8601DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
        iso8601DateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
        return iso8601DateFormatter
    }()

    static func date(fromISO8601String string: String) -> Date? {
        if let dateWithMilliseconds = iso8601DateFormatter.date(from: string) {
            return dateWithMilliseconds
        }

        if let dateWithoutMilliseconds = iso8601WithoutMillisecondsDateFormatter.date(from: string) {
            return dateWithoutMilliseconds
        }

        return nil
    }
}

Usage:

let dateToString = "2016-12-31T23:59:59.9999999"
let dateTo = DateFormatter.date(fromISO8601String: dateToString)
// dateTo: 2016-12-31 23:59:59 +0000

let dateFromString = "2016-12-01T00:00:00"
let dateFrom = DateFormatter.date(fromISO8601String: dateFromString)
// dateFrom: 2016-12-01 00:00:00 +0000

I also recommend checking Apple article about date formatters.

Vadim Bulavin
  • 3,697
  • 25
  • 19
8

So use Sam Soffee's category on NSDate found here. With that code added to your project, you can from then on use a single method on NSDate:

- (NSString *)sam_ISO8601String

Not only is it one line, its much faster than the NSDateFormatter approach, since its written in pure C.

Unixmonkey
  • 18,485
  • 7
  • 55
  • 78
David H
  • 40,852
  • 12
  • 92
  • 138
  • 1
    The current location of the category is [here](https://github.com/soffes/SAMCategories/blob/master/SAMCategories/NSDate+SAMAdditions.h) – Perry Mar 09 '14 at 22:36
  • 1
    i would advise against it , it has memory exception problem which happens very randomly – M_Waly Jun 02 '15 at 08:43
  • 1
    @M_Waly did you report this to him? I built his code with no warnings and it passed analysis checks just fine. – David H Jun 02 '15 at 12:22
  • Hi, Sam Soffes here. Please don't this anymore. You should use `ISO8601DateFormatter` in Foundation instead. – Sam Soffes Nov 02 '21 at 04:20
6

Update

From iOS 10, you can just use NSISO8601DateFormatter from Foundation

Original answer

From IS8601, the problems are the representation and time zone

  • ISO 8601 = year-month-day time timezone
  • For date and time, there are basic (YYYYMMDD, hhmmss, ...) and extended format (YYYY-MM-DD, hh:mm:ss, ...)
  • Time zone can be Zulu, offset or GMT
  • Separator for date and time can be space, or T
  • There are week format for date, but it is rarely used
  • Timezone can be a lot of spaces after
  • Second is optional

Here are some valid strings

2016-04-08T10:25:30Z
2016-04-08 11:25:30+0100
2016-04-08 202530GMT+1000
20160408 08:25:30-02:00
2016-04-08 11:25:30     +0100

Solutions

NSDateFormatter

So here is the format that I'm using in onmyway133 ISO8601

let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.dateFormat = "yyyyMMdd HHmmssZ"

About the Z identifier Date Field Symbol Table

Z: The ISO8601 basic format with hours, minutes and optional seconds fields. The format is equivalent to RFC 822 zone format (when optional seconds field is absent)

About locale Formatting Data Using the Locale Settings

Locales represent the formatting choices for a particular user, not the user’s preferred language. These are often the same but can be different. For example, a native English speaker who lives in Germany might select English as the language and Germany as the region

About en_US_POSIX Technical Q&A QA1480 NSDateFormatter and Internet Dates

On the other hand, 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).

Interesting related quetions

onmyway133
  • 45,645
  • 31
  • 257
  • 263
  • I just tried with 2016-07-23T07:24:23Z, it does not work – Kamen Dobrev Aug 01 '16 at 09:55
  • @KamenDobrev what do you mean by "not work"? I just add your example to the tests https://github.com/onmyway133/ISO8601/blob/master/ISO8601Tests/Tests.swift#L46 and it works – onmyway133 Aug 01 '16 at 10:16
  • I made the ISO6801 library linked in this issue. Please don't this anymore. You should use `ISO8601DateFormatter` in Foundation instead. – Sam Soffes Nov 02 '21 at 04:20
6

Just use NSISO8601DateFormatter from Foundation framework.

let isoDateFormatter = ISO8601DateFormatter()
print("ISO8601 string: \(isoDateFormatter.string(from: Date()))")
// ISO8601 string: 2018-03-21T19:11:46Z

https://developer.apple.com/documentation/foundation/nsiso8601dateformatter?language=objc

LLIAJLbHOu
  • 1,313
  • 12
  • 17
4

Based on this gist: https://github.com/justinmakaila/NSDate-ISO-8601/blob/master/NSDateISO8601.swift, the following method can be used to convert NSDate to ISO 8601 date string in the format of yyyy-MM-dd'T'HH:mm:ss.SSSZ

-(NSString *)getISO8601String
    {
        static NSDateFormatter *formatter = nil;
        if (!formatter)
        {
            formatter = [[NSDateFormatter alloc] init];
            [formatter setLocale: [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
            formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation: @"UTC"];
            [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS"];

        }
        NSString *iso8601String = [formatter stringFromDate: self];
        return [iso8601String stringByAppendingString: @"Z"];
    }
Zack Zhu
  • 378
  • 4
  • 8
4

With iOS 15 you get ISO860 as follows:

let iso8601String = Date.now.ISO8601Format()
Abdullah Umer
  • 4,234
  • 5
  • 36
  • 65
2

If you are here because of a search result of the opposite way, here's the easiest solution:

let date = ISO8601DateFormatter().date(from: dateString)
Display Name
  • 4,502
  • 2
  • 47
  • 63
-1

This is a little bit simpler and puts the date into UTC.

extension NSDate
{
    func iso8601() -> String
    {
        let dateFormatter = NSDateFormatter()
        dateFormatter.timeZone = NSTimeZone(name: "UTC")
        let iso8601String = dateFormatter.stringFromDate(NSDate())
        return iso8601String
    }
}

let date = NSDate().iso8601()
iphaaw
  • 6,764
  • 11
  • 58
  • 83
-1

Using Swift 3.0 and iOS 9.0

extension Date {
    private static let jsonDateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(identifier: "UTC")!
        return formatter
    }()

    var IOS8601String: String {
        get {
            return Date.jsonDateFormatter.string(from: self)
        }
    }

    init?(fromIOS8601 dateString: String) {
        if let d = Date.jsonDateFormatter.date(from: dateString) {
            self.init(timeInterval: 0, since:d)
        } else {
            return nil
        }
    }
}
Denis Krivitski
  • 654
  • 8
  • 11