How do I get an ISO 8601 date on iOS? - ios

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.

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())

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())

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)

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.

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.

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

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
Converting an ISO 8601 timestamp into an NSDate: How does one deal with the UTC time offset?
Why NSDateFormatter can not parse date from ISO 8601 format
Milliseconds NSDateFormatter to parse ISO8601 with and without milliseconds

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"];
}

With iOS 15 you get ISO860 as follows:
let iso8601String = Date.now.ISO8601Format()

If you are here because of a search result of the opposite way, here's the easiest solution:
let date = ISO8601DateFormatter().date(from: dateString)

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()

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
}
}
}

Related

cast "1900-01-01T00:00:00" string value to date

I've watching trough stack overflow to find the answer and I can't find it I want to cast this string value "1900-01-01T00:00:00" to Date format, I was trying with some formats like those:
"yyyy-MM-dd HH:mm:ss"
"EEE, dd MMM yyyy hh:mm:ss +zzzz"
"YYYY-MM-dd HH:mm:ss.A"
"yyyy-MM-dd HH:mm:ss.S"
but anyone of those its working.
and I want the date format like this
"dd-mm-yyyy"
Hope you can help me!
Thanks.
It is a two step process, first converting 1900-01-01T00:00:00 (known as a RFC 3999 or ISO 8601 date, referred to the specifications that define this format) into a Date object, and then converting that Date object back to a string in the form of 01-01-1900:
To convert your string in the form of 1900-01-01T00:00:00 into a Date object, you can use ISO8601DateFormatter:
let formatter = ISO8601DateFormatter()
formatter.formatOptions.remove(.withTimeZone)
let date = formatter.date(from: string)!
That is equivalent to the following DateFormat, in which one has to manually set the locale to en_US_POSIX (because RFC 3999/ISO 8601 dates use a Gregorian calendar, regardless of what the device's default calendar type) and sets the timeZone to GMT/Zulu, because usually RFC 3999/ISO 8601 dates are representing GMT unless specified otherwise:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
let date = formatter.date(from: string)!
For more information about the importance of timezones and locales in parsing RFC 3999 and ISO 8601 dates, see Apple's Technical Q&A 1480.
Then, to convert that Date object to a string into 01-01-1900 (day, month, and year), you'd use a format string of dd-MM-yyyy (note the uppercase MM for "month", to distinguish it from mm for "minute"):
let formatter2 = DateFormatter()
formatter2.dateFormat = "dd-MM-yyyy"
let string = formatter2.string(from: date)
Two observations regarding the dateFormat string:
If this string is for displaying to the user, you might use use dateStyle rather than dateFormat, e.g.:
formatter2.dateStyle = .short
While this will generate a slightly different format, e.g. dd/MM/yy, the virtue of this approach is that the string will be localized (e.g. UK users will see MM/dd/yyyy, their preferred way of seeing short dates).
It just depends upon the purpose of your dd-MM-yyyy format. If it's for internal purposes, go ahead and use dateFormat. But if it's for showing dates in your UI, use dateStyle instead, and enjoy the localization that DateFormatter does automatically for you. For more information, see "Working With User-Visible Representations of Dates and Times" section of the DateFormatter reference.
Note that in the absence of a timeZone specified for this second formatter, it assumes that while the ISO 8601 date was in GMT, that you want to see the date in your local timezone. For example, (1900-01-01T00:00:00 GMT was Dec 31, 1899 at 4pm in California). If you want to see the date string of the original ISO 8601 object, not corrected for timezones, you'd just set the timeZone of this second formatter to be GMT as well, e.g.
formatter2.timeZone = TimeZone(secondsFromGMT: 0)
As others have pointed out, you want to avoid unnecessarily re-instantiating DateFormatter objects. So you might put these formatters in properties that are instantiated only once, or use an extension:
extension DateFormatter {
static let customInputFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions.remove(.withTimeZone)
return formatter
}()
static let customOutputFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
formatter.timeZone = TimeZone(secondsFromGMT: 0) // if you want date in your local timezone, remove this line
return formatter
}()
}
And then:
let input = "1900-01-01T00:00:00"
let date = DateFormatter.customInputFormatter.date(from: input)!
let output = DateFormatter.customOutputFormatter.string(from: date)
print(output)
This is how I do custom date formatters:
extension DateFormatter {
static let inDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return dateFormatter
}()
static let outDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-mm-yyyy"
return dateFormatter
}()
}
And then use it like:
if let date = DateFormatter.inDateFormatter.date(from: "1900-01-01T00:00:00") {
let newDateString = DateFormatter.outDateFormatter.string(from: date);
print(newDateString) //prints 01-00-1900
}
This avoids any potential performance issues and is clear at the point of use, while still being concise.
Use this extension I created, where you can pass the format as a parameter.
extension String
{
func toDate( dateFormat format : String) -> Date
{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
if let date = dateFormatter.date(from: self)
{
return date
}
print("Invalid arguments ! Returning Current Date . ")
return Date()
}
}
"1900-01-01T00:00:00".toDate(dateFormat: "yyyy-MM-dd'T'HH:mm:ss") //Plyground call test

Swift how to convert string to NSDate

I've got a date stored in my database and I want to retrieve it and display it nicely in my tableview cells.
The format it comes in from the database and stored in option1 is BO05151530
Where the first two letters have meaning in the program but are not needed for the date so I take those off using the substringFromIndex function.
So what is left is 05151530 where it represents MMddhmm
I would like to display it nicely like MM-dd # h:mm a
For example 12-05 # 3:45 am
Here is what I tried but unfortunately ns_date1 comes up as nil each time.
What would you suggest I do?
let date1 = option1.substringFromIndex(option1.startIndex.advancedBy(2))
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMddhhmm"
let ns_date1 = dateFormatter.dateFromString(date1)
Try this. you don't need to separate BO NSDateFormatter can handle extra string
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "'BO'MMddHHmm"
let ns_date1 = dateFormatter.dateFromString("BO05151530")
dateFormatter.dateFormat = "'BO' MM-dd # hh':'mm a"
let string = dateFormatter.stringFromDate(ns_date1!)
try
dateFormatter.dateFormat = "MMddHHmm"
HH is 24 hour format and hh is 12 hour format. you need the 24 one
Check out this app, this will help you know the right format and give you code
https://itunes.apple.com/ae/app/date-format-creator/id965645209?mt=12
NOTE: you need to add year to get a correct NSDate
I have this function :
class func getTheDateString(stringForInputDate: String, fromFormat inputFormat: String, toFormat outputFormat: String) -> String {
let formatter: NSDateFormatter = NSDateFormatter()
formatter.dateFormat = inputFormat
formatter.timeZone = NSTimeZone.localTimeZone()
let dateForInput: NSDate = formatter.dateFromString(stringForInputDate)!
formatter.dateFormat = outputFormat
return formatter.stringFromDate(dateForInput)
}
and used it as:
let stringDate : String = "0515930" // "05151530"
if stringDate.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) < 8 {
print([ViewControllerForScreen1 .getTheDateString(stringDate, fromFormat: "MMddHmm", toFormat: "MM-dd # h:mm a")]);
}else {
print([ViewControllerForScreen1 .getTheDateString(stringDate, fromFormat: "MMddHHmm", toFormat: "MM-dd # h:mm a")]);
}
OUTPUT:
["05-15 # 9:30 AM"]
["05-15 # 3:30 PM"]

How to convert a string to a date, without losing the formatting

I have this date 2015-11-06T18:00:00-0500
My format is yyyy-MM-dd'T'HH:mm:ssZ
i try
let startDateString = "2015-11-06T18:00:00-0500"
let format = NSDateFormatter()
format.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let startDateBtnEnd = format.dateFromString(startDateString)
println("startDateBtnEnd 2 \(startDateBtnEnd)")
But the log is
startDateBtnEnd 2 Optional(2015-11-07 00:00:00 +0000)
NSDate stores dates in UTC. You can convert it to the same moment in time in any timezone. But after losing too many neurons to mentally convert NSDate from one timezone to another, I decided to add my own extension to NSDate to print it out in the local timezone instead:
extension NSDate {
func toString(timeZone: NSTimeZone = NSTimeZone.localTimeZone()) -> String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
formatter.timeZone = timeZone
return formatter.stringFromDate(self)
}
}
// Usage:
print("startDateBtnEnd 2 \(startDateBtnEnd.toString())")

How to constuct NSDate object from String with milliseconds

I need to construct NSDate object from String, so I wrote the following code:
func getNSDateObjectFromString(string: String) -> NSDate {
var formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let date = formatter.dateFromString(string)
return date!
}
Unfortunately, the input string sometimes may contain milliseconds too. What can I do in this case? I don't find any way to read milliseconds (not in the day) according to the http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
Thanks in advance.
As far as I know, the format doesn't support "optional" fields. So you have to try the formats one by one:
func getNSDateObjectFromString(string: String) -> NSDate {
var formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
var date = formatter.dateFromString(string)
if date == nil {
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.S"
date = formatter.dateFromString(string)
}
return date!
}
You can try something like:
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
A Swift 3 Solution
After a bit of trial and error with the date format this is what worked for me with Swift 3.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
let date = formatter.date(from: "2017-03-11 13:16:31.177")!
debugPrint(dateFormatter.string(from: date))
and after a round trip results in the expected debug output of
"2017-03-11 13:16:31.177"
Note that using the format "yyyy-MM-dd'T'HH:mm:ss.SSS" resulted in formatter.date(from: returning a nil optional Date.
Are they important?
In DateFormatter you create your matching string in years, months, days, hours, mins, secs, but you don't need to. If your matching string does not contain any of them, formatter will just ignore them.

Is it possible to add custom text in NSDateFormatter's format string?

Suppose I want the date to look like this:
|1988|December|30|
How can add these to the dateFormatter or for that matter let the format be something like this:
30 in the month of December in the year of 1998
now for 1988,December and 30, I would like to use standard formats but I want the text I put to also accompany them.
specially in case of the above one where, in the format and pipes just come adjacent to the date format for date or month where there is no space between the format and the pipe.
Is any of this possible by just setting the format ?
You can insert arbitrary text (enclosed in single quotes) in the date format, for example.
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:#"dd' in the month of 'MMMM' in the year of 'yyyy"];
NSString *s = [fmt stringFromDate:[NSDate date]];
Result:
09 in the month of July in the year of 2013
Swift Version:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE' text here 'h:mm' and there 'a"
Updated for iOS 13, Swift 5, Xcode 11 and building on Martin R's answer
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone.current
dateFormatter.locale = Locale.current
dateFormatter.dateFormat = "dd' in the month of 'MMMM' in the year of 'yyyy"
let stringDate = dateFormatter.string(from: Date())
print(stringDate)
// printed:
// 7 in the month of October in the year of 2019
P.S. If you wanted an apostrophe, then use: '' directly inside the string. For example "MMM d, ''yy" -> Nov 10, '19
Extension:
If you wanted to add ordinal indicators too (ex. the 'th' after '13th'), you can actually do it inside the date formatter string.
So if you wanted Nov 10th, the code would be:
/// Get date.
let date = Date()
/// Get just the day of the date.
let dayAsInt = Calendar.current.component(.day, from: date)
/// Init the formatter.
let dateFormatter = DateFormatter()
/// Set the format string.
/// Notice we include the 'MMM' to extract the month from the date, but we use a variable to get the 'th' part.
dateFormatter.dateFormat = "MMM '\(dayAsInt.getStringWithOrdinalIndicatorIfPossible)'"
let formattedDate = dateFormatter.string(from: date)
/// Will print out Nov 10th or Apr 1st or whatever.
Here's the extension I made to help:
/// This variable only adds the ordinal indicator if the Int that is calling this function can be converted to an NSNumber.
/// An ordinal indicator is the `rd` after `3rd` or the `st` after `1st`.
var getStringWithOrdinalIndicatorIfPossible: String {
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
return formatter.string(from: NSNumber(value: self)) ?? "\(self)"
}

Resources