How to handle two possible date formats? - swift

My app calls a web api that sometimes returns json dates in this format:
"2017-01-18T10:49:00Z"
and sometimes in this format:
"2017-02-14T19:53:38.1173228Z"
I can use the following dateformat to convert the 2nd one to a Date object:
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
But of course it doesn't work for the 1st one.
I've tried utilities like https://github.com/melvitax/DateHelper to see if it will work, but I haven't found a way to convert a json date (in any format) into a Date object.
Any recommendations?

Try both formats:
let parser1 = DateFormatter()
parser1.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let parser2 = DateFormatter()
parser2.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
func parse(_ dateString: String) -> Date? {
let parsers = [parser1, parser2]
for parser in parsers {
if let result = parser.date(from: dateString) {
return result
}
}
return nil
}
print(parse("2017-01-18T10:49:00Z"))
print(parse("2017-02-14T19:53:38.1173228Z"))
Also note that the Z in the format is not a literal value.

Related

Swift DateTime Formatter returning nil with matching format

When parsing an input datetime string with the matching format, I am still getting nil back from DateFormatter in swift.
Input string (copied at runtime from variable) and confirmed that is the string running the call with Postman.
"2020-06-30T14:41:33.9"
Parsing method
static func FormatDateTimeString(dateTimeString: String) -> String {
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "YYYY-MM-DDThh:mm:ss.s"
let date = inputDateFormatter.date(from:dateTimeString) //date is always nil
let outputDateFormatter = DateFormatter()
outputDateFormatter.dateFormat = "MMM d, yyyy HH:mm"
return outputDateFormatter.string(from: date!)
}
I have also tried "YYYY-MM-DD'T'hh:mm:ss.s" and "YYYY-MM-DDThh:mm:ss" but none seem to work.
EDIT: Trying the format suggested below I am still getting nil.
EDIT: As Suggested from the comments I am still getting nil. Format below is: inputDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.S"
EDIT: Still getting null using the following config
Use this format:
"yyyy-MM-dd'T'HH:mm:ss.S"
Demo
let dateTimeString = "2020-06-30T14:41:33.9"
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.S"
let date = inputDateFormatter.date(from: dateTimeString) // "Jun 30, 2020 at 2:41 PM"

Swift Why isn't my date object that's (Equatable) equal after converting it to a string and back?

I'm writing a unit test to check that my conversion from a date to string and back is successful.
I convert it to a string via:
func convertDateToString(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.string(from: date)
}
and convert it back via:
func convertStringToDate(string: String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.date(from: string)!
}
If you try to use the Equatable protocol on the pre-conversion date and post-conversion date it says they are not the same. However, if you convert both pre and post dates to strings and compare them, they are Equatable. This is what it says when I run XCAssertEqual on pre and post dates:
XCTAssertEqual failed: ("2020-01-22 19:35:40 +0000") is not equal to ("2020-01-22 19:35:40 +0000")
Which looks pretty identical to me. I even tried converting the pre-conversion date to a string and back to check if the dates were equal and they still weren't
The problem there is that Date is stored as a FloatingPoint value (timeIntervalSinceReferenceDate). There is fractional seconds being discarded there when converting your Date to String and back to Date. Take a look at post.

Converting string to date returns nil

I am trying to convert my string to a date using a static date formatter. When I make the call to stringToDate() using the variables below, a nil value is returned.
I've checked previous posts about this issue where people are saying it's because of the dateformatter locale or timeZone. However, that doesn't seem to be the issue in this case.
Does anyone know what the issue could be in this case? My code is below:
import Foundation
class DateHelper {
private static let dateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd hh:mm:ss"
df.locale = Locale(identifier: "en_GB")
df.timeZone = TimeZone.current
return df
}()
static func stringToDate(str: String, with dateFormat: String) -> Date? {
dateFormatter.dateFormat = dateFormat
let date = dateFormatter.date(from: str)
return date
}
}
var myDate = Date()
var dateStr = "2019-02-19T17:10:08+0000"
print(DateHelper.stringToDate(str: dateStr, with: "MMM d yyyy")) // prints nil
Looks like your string is in ISO8601 format. Use the ISO8601DateFormatter to get date instance. You can use ISO8601DateFormatter.Options to parse varieties of ISO8601 formats. For your string,
For Swift 4.2.1
let formatter = ISO8601DateFormatter()
let date = formatter.date(from: dateStr)
print(date!)
Should output
"2019-02-19 17:10:08 +0000\n"
Your date format doesn't match your input date. Try this code:
print(DateHelper.stringToDate(str: dateStr, with: "yyyy-MM-dd'T'HH:mm:ssZZZZZ"))
Hope this helps.

Swift converting Eureka date into String for firebase storing

I am attempting to collect the date value from a Eureka DateTimeRow to then store it into Firebase but to store it I would need it to be in a string format. I have attempted this conversion but I receive the error 'Could not cast value of type 'Foundation.Date' (0x108af27e8) to 'Swift.String' (0x1086e99f8).'
I would like to know if there is something I am missing from my conversion method.
DateTimeRow:
<<< DateTimeRow("startDate"){
$0.title = "Start Date"
$0.value = NSDate() as Date
$0.cellUpdate { (cell, row) in
cell.datePicker.minimumDate = Date()
}
$0.onChange { row in
start = row.value!
}
}
Code getting the values of the Erueka form and converting:
let valuesDictionary = form.values()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let formattedDate = formatter.date(from: valuesDictionary["startDate"] as! String)
Thank you all feedback welcomed.
As you want to convert from a Date (Eureka) to a String (Firebase), you should use the string(from:) method of the DateFormatter, whereas you are attempting to use the date(from:) method.
// Date to String
func string(from date: Date) -> String
// String to Date
func date(from string: String) -> Date?

iso8601 date json decoding using swift4 [duplicate]

This question already has answers here:
How to convert a date string with optional fractional seconds using Codable in Swift?
(4 answers)
Closed 5 years ago.
So, I've got iso8601 dates in my json which look like "2016-06-07T17:20:00.000+02:00"
Is there a way to parse these iso8601 dates using swift4? Am I missing something obvious?
I tried the following, but only the dateString "2016-06-07T17:20:00Z" from jsonShipA is parsable....
import Foundation
struct Spaceship : Codable {
var name: String
var createdAt: Date
}
let jsonShipA = """
{
"name": "Skyhopper",
"createdAt": "2016-06-07T17:20:00Z"
}
"""
let jsonShipB = """
{
"name": "Skyhopper",
"createdAt": "2016-06-07T17:20:00.000+02:00"
}
"""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let dataA = jsonShipA.data(using: .utf8)!
if let decodedShip = try? decoder.decode(Spaceship.self, from: dataA) {
print("jsonShipA date = \(decodedShip.createdAt)")
} else {
print("Failed to decode iso8601 date format from jsonShipA")
}
let dataB = jsonShipB.data(using: .utf8)!
if let decodedShip = try? decoder.decode(Spaceship.self, from: dataB) {
print("jsonShipA date = \(decodedShip.createdAt)")
} else {
print("Failed to decode iso8601 date format from jsonShipB")
}
The output of the playground is:
jsonShipA date = 2016-06-07 17:20:00 +0000
Failed to decode iso8601 date format from jsonShipB
The error being thrown is "Expected date string to be ISO8601-formatted."
But to my knowledge, the date "2016-06-07T17:20:00.000+02:00" is a valid ISO8601 date
You can use like this :
enum DateError: String, Error {
case invalidDate
}
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
if let date = formatter.date(from: dateStr) {
return date
}
throw DateError.invalidDate
})
TL;DR version: it only parses the withInternetDateTime format of the ISO8601DateFormatter described here. This means that your string should not have milliseconds.
More info:
Looking at the Swift source on line 787, the comment says:
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
Looking at that RFC, it gives a couple of (admittedly tricky) examples in section 5.8:
1985-04-12T23:20:50.52Z
1996-12-19T16:39:57-08:00
1996-12-20T00:39:57Z
1990-12-31T23:59:60Z
1990-12-31T15:59:60-08:00
1937-01-01T12:00:27.87+00:20
Only the second and the third example are actually decoded by Swift, the rest fails. It seems to me that either the comment is incorrect, or the implementation is not complete. As for the real implementation, that's outside the Swift source, it simply seems to use the ISO8601DateFormatter class in Foundation.
The Swift unittest is also very limited, see line 180. It simply encodes a single date, and then decodes it back. So in other words, the only thing that's tested, is the format that the ISO8601DateFormatter outputs by default, which is hardcoded to the option .withInternetDateTime, described here.