Swift NSDate ISO 8601 format - ios

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

Related

Swift date format returning wrong date

I need to convert my date to string and then string to date. date is "2020-10-17 1:22:01 PM +0000"
Here is my date to string conversion code:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
let string = formatter.string(from: "2020-10-17 1:22:01 PM +0000")
let createdAT = string
its returning "2020-10-17 18:51:30+05:30"
Here is my string to date conversion code:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ssZ"
let date = dateFormatter.date(from:date)!
its returning "2020-10-17 1:21:30 PM +0000 - timeIntervalSinceReferenceDate : 624633690.0"
its returning the wrong date after i convert string to date. i need "2020-10-17 18:51:30+05:30" this time to be return when i convert string to date.....
The code in your question is muddled up. You try to convert a string into a string in the first example and something unspecified into a Date in the second example.
Here's how to convert a Date into a String:
import Foundation
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
let string: String = formatter.string(from: Date())
print(string) // prints for example 2020-10-18T10:54:07+01:00
Here's how to convert a string into a date
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ssZ"
let date: Date = formatter.date(from: "2020-10-18 10:59:56+0100")! // In real life, handle the optional properly
print(date) // prints 2020-10-18 09:59:56 +0000
When you print a Date directly, it automatically uses UTC as the time zone. This is why it changed it in the code above.
In the examples, I explicitly specified the type of string and date to show what type they are. Type inference means you can omit these in normal code.
As a general rule when handling dates:
always use Date in your code. Date is a type that stores the number of seconds since Jan 1st 1970 UTC.
Only convert dates to strings when displaying them to the user or communicating with an external system.
When calculating periods etc, always use a Calendar to get things like date components and intervals in units other than seconds. You might think to get "the same time tomorrow" you could just add 24 * 60 * 60 to a Date but in many countries, like mine, that will work on only 363 days in the year. Calendar will correctly handle things like daylight saving and leap years.

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

Understanding dateFormatter in Swift 4

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)

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

Getting back a date from a string

im dealing with dates, and i'm having problems getting my date back from a string, i simplified my problem here:
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-YYYY"
formatter.stringFromDate(date)
formatter.dateFromString(formatter.stringFromDate(date))
And the output is:
"25-05-2015" (Which is fine)
"Dec 21, 2014, 12:00 AM" (???)
The problem there is that Y is for weekOfYear. You have to use "dd-MM-yyyy". Btw don't forget to set your date formatter locale to "en_US_POSIX" .
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).
You should use yyyy for the year, not YYYY (which has a different meaning)
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let s = formatter.stringFromDate(date) // "25-05-2015"
let d = formatter.dateFromString(s) // "2015-05-24 22:00:00 UTC" (*)
(*) it's 22:00 because I'm in the +0200 timezone, so this result is effectively 2015-05-25 00:00:00 in my timezone
You should change your dateFormat to make it work , YYYY is not correct.
import Foundation
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-YYYY"
var firstDate = formatter.stringFromDate(date)
formatter.dateFormat = "yyyy-MM-dd hh:mm:ss.SSSSxxx"
var secondDate = formatter.dateFromString(formatter.stringFromDate(date))
println("\(firstDate)")
println("\(secondDate)")

Resources