How do I get an ISO 8601 date on iOS?
IosNsdateNsdateformatterIso8601Ios Problem Overview
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.
Ios Solutions
Solution 1 - Ios
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())
Solution 2 - Ios
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())
Solution 3 - Ios
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)
Solution 4 - Ios
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.
Solution 5 - Ios
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.
Solution 6 - Ios
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
- Parse step by step, like soffes ISO8601
- Convert to basic format, like onmyway133 ISO8601
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
- https://stackoverflow.com/questions/5185230/converting-an-iso-8601-timestamp-into-an-nsdate-how-does-one-deal-with-the-utc
- https://stackoverflow.com/questions/7925038/why-nsdateformatter-can-not-parse-date-from-iso-8601-format
- Milliseconds https://stackoverflow.com/questions/21074676/nsdateformatter-to-parse-iso8601-with-and-without-milliseconds
Solution 7 - Ios
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
Solution 8 - Ios
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"];
}
Solution 9 - Ios
With iOS 15 you get ISO860 as follows:
let iso8601String = Date.now.ISO8601Format()
Solution 10 - Ios
If you are here because of a search result of the opposite way, here's the easiest solution:
let date = ISO8601DateFormatter().date(from: dateString)
Solution 11 - Ios
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()
Solution 12 - Ios
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
}
}
}