Date string does not match format expected by formatter - json

Was only able to reproduce this issue on a friend's device. The device is from Germany and is set to the German region in Settings. I cannot reproduce on any Canadian devices. Why is it failing when trying to create a Date property from the JSON?
Console:
dataCorrupted(Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "60", intValue: nil), CodingKeys(stringValue: "expiration", intValue: nil)], debugDescription: "Date string does not match format expected by formatter.", underlyingError: nil))
Struct:
struct TokenResponse: Decodable {
var ticket : String
var expiration : Date?
var sessionId: String
}
Inside URLSession:
do {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(formatter)
let json = try decoder.decode([String: TokenResponse].self, from: data)
}
catch {
print(error)
}
JSON:
{
"60":{
"ticket":"aVeryLongJWT",
"expiration":"2022-02-04T22:00:34.8325102Z",
"sessionId":"aUUID"
}
}

You should set the locale before setting dateFormat string:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
See “Working With Fixed Format Date Representations” in DateFormatter documentation.

Previous answer is worked for me! You should check the date format coming from server. Mine was different and its case sensitive. I just arranged df.dateFormat Good luck!
private let dateFormatter: DateFormatter = {
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ss-hh:ss"
return df
}()
and then in the dataFetch function
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(self.dateFormatter)
let results = try! decoder.decode(Model.self, from: data)
print(results)
} catch {
print("Decoding Error!")
}

Related

JSON Serialization returns 105 bytes but decoding returns Nil

Happy Sunday everyone. I have a problem with decoding my data. I can't seem to decode data from when I serialize dictionary strings into an object. Im trying to fetch matches from firebase, and this works for when I fetch user profiles but not with fetching matches.
func fetchMatches(onCompletion: #escaping (Result<[MatchModel], DomainError>) -> ()){
db.collection("matches").whereField("usersMatched", arrayContains: userId!).getDocuments(completion: {doc, err in
guard let documentSnapshot = doc, err == nil else{
onCompletion(.failure(.downloadError))
return
}
var matchList: [MatchModel] = []
var count = 0
let maxCount = documentSnapshot.documents.count
var hasFailed = false
for document in documentSnapshot.documents{
if hasFailed{ break }
let decoder = JSONDecoder()
var dict = document.data()
for (key, value) in dict {
if let value = value as? Timestamp {
let formatter = DateFormatter()
let newValue = value.dateValue()
formatter.dateStyle = .short
formatter.timeStyle = .none
dict[key] = formatter.string(from: newValue)
}
}
Up until here I know that everything is going well. Dict contains -
Dictionary
["timestamp": "10/22/22", "usersMatched": <__NSArrayM 0x600002c61680>(
6euZHDmI7PMDcCmft5MfxUW27jI3,
tbcB0ay0YEgZcY9UsZ00WjZ9h893
)
]
data below prints out 105 bytes, so with that information I know that it isn't empty and that JSONSerialization did its job of converting dict into an object. But then when I try to decode it into FirestoreMatch.self match returns empty
if let data = try? JSONSerialization.data(withJSONObject: dict, options:[]){
do{
let match = try? decoder.decode(FirestoreMatch.self, from: data)
let matchId : String = match!.usersMatched.filter{$0 != self.userId!}.first!
... }
catch{
print(error)
Error returns:
typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "timestamp", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
This is my FirestoreMatch struct
struct FirestoreMatch: Codable{
let usersMatched: [String]
let timestamp: Date
}
Do I require more information for my struct? Im not sure why match returns nil
Thank you #itaiFerber #flanker #loremipsum and #duncanC , I was able to resolve my issue by utilizing
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "mm/dd/yy"
decoder.dateDecodingStrategy = .formatted(dateFormatter)

Decode date/time from String or TimeInterval in Swift

I have a JSON (from a third party) that I need to parse. This JSON returns several nested objects
articles: {
authors: {
birthday: 'DD-MM-YYYY'
}
relevant_until: 'YYYY-MM-DD HH:MM:SS'
publication_date: secondsSince1970,
last_comment: iso8601
}
I'm following this answer to have multiple date formatters and it works, as long as every date extracted from JSON is a string.
But when it comes to the secondsSince1970 (UNIX epoc time) I can't find a way to parse it as a codable object. Everywhere I see the Date(timeIntervalSince1970: timestamp) and I don't know how to use it when decoding it
How do I parse the dates on this object when a date can be passed as a TimeInterval or as a String?
try jsonDecoder.decode(Articles.self, from: jsonData)
extension Formatter {
static let iso8601withFractionalSeconds: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
return formatter
}()
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter
}()
static let ddMMyyyy: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "dd-MM-yyyy"
return formatter
}()
}
extension JSONDecoder.DateDecodingStrategy {
static let multiple = custom {
let container = try $0.singleValueContainer()
do {
return try Date(timeIntervalSince1970: container.decode(Double.self))
} catch DecodingError.typeMismatch {
let string = try container.decode(String.self)
if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ??
Formatter.iso8601.date(from: string) ??
Formatter.ddMMyyyy.date(from: string) {
return date
}
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
}
}
}
Playground testing:
struct Root: Codable {
let articles: Articles
}
struct Articles: Codable {
let authors: Authors
let relevantUntil: Date
let publicationDate: Date
let lastComment: Date
}
struct Authors: Codable {
let birthday: Date
}
let json = """
{"articles": {
"authors": {"birthday": "01-01-1970"},
"relevant_until": "2020-11-19 01:23:45",
"publication_date": 1605705003.0019,
"last_comment": "2020-11-19 01:23:45.678"}
}
"""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .multiple
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(Root.self, from: .init(json.utf8))
print(root.articles) // Articles(authors: __lldb_expr_107.Authors(birthday: 1970-01-01 03:00:00 +0000), relevantUntil: 2020-11-19 04:23:45 +0000, publicationDate: 2020-11-18 13:10:03 +0000, lastComment: 2020-11-19 04:23:45 +0000)
} catch {
print(error)
}
Following the same logic you can try to decode the JSON property as TimeInterval (or Double) and if that fails, fall back to your String handling:
extension JSONDecoder {
var dateDecodingStrategyFormatters: [DateFormatter]? {
#available(*, unavailable, message: "This variable is meant to be set only")
get { return nil }
set {
guard let formatters = newValue else { return }
self.dateDecodingStrategy = .custom { decoder in
let container = try decoder.singleValueContainer()
do {
let timeInterval = try container.decode(TimeInterval.self)
return Date(timeIntervalSince1970: timeInterval)
} catch DecodingError.typeMismatch {
let dateString = try container.decode(String.self)
for formatter in formatters {
if let date = formatter.date(from: dateString) {
return date
}
}
}
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Cannot decode date")
}
}
}
}

Swift ISO 8601 date formatting

I'm making an api call to randomuser.me and getting back a json file containing (amongst other data):
"dob": {
"date": "1993-07-20T09:44:18.674Z",
"age": 26
}
I'd like to display the date string in a text label as "dd-MMM-yyyy" but can't figure out how to format the string to achieve this.
I'v tried converting it into a date using ISO8601DateFormatter and then back into a string but have had no luck so far.
Can anyone help?
func getUserData() {
let config = URLSessionConfiguration.default
config.urlCache = URLCache.shared
let session = URLSession(configuration: config)
let url = URL(string: "https://randomuser.me/api/?page=\(page)&results=20&seed=abc")!
let urlRequest = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 15.0)
let task = session.dataTask(with: urlRequest) { data, response, error in
// Check for errors
guard error == nil else {
print ("error: \(error!)")
return
}
// Check that data has been returned
guard let content = data else {
print("No data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let fetchedData = try decoder.decode(User.self, from: content)
for entry in fetchedData.results {
self.usersData.append(entry)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let err {
print("Err", err)
}
}
// Execute the HTTP request
task.resume()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "userInfoCell") as! UserInfoCell
let user: Result
if isFiltering {
user = filteredData[indexPath.row]
} else {
user = usersData[indexPath.row]
}
cell.nameLabel.text = "\(user.name.first) \(user.name.last)"
cell.dateOfBirthLabel.text = user.dob.date
cell.genderLabel.text = user.gender.rawValue
cell.thumbnailImage.loadImageFromURL(user.picture.thumbnail)
return cell
}
import Foundation
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
let theDate = dateFormatter.date(from: "1993-07-20T09:44:18.674Z")!
let newDateFormater = DateFormatter()
newDateFormater.dateFormat = "dd-MMM-yyyy"
print(newDateFormater.string(from: theDate))
First convert the string to date using proper date format. Then convert it back to string using the format you want.
Generally, if manually converting 1993-07-20T09:44:18.674Z to a Date, we’d use ISO8601DateFormatter:
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
In this approach, it takes care of time zones and locales for us.
That having been said, if you’re using JSONDecoder (and its dateDecodingStrategy outlined below), then we should define our model objects to use Date types, not String for all the dates. Then we tell the JSONDecoder to decode our Date types for us, using a particular dateDecodingStrategy.
But that can’t use the ISO8601DateFormatter. We have to use a DateFormatter with a dateFormat of "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" and a locale of Locale(identifier: "en_US_POSIX"):
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0) // this line only needed if you ever use the same formatter to convert `Date` objects back to strings, e.g. in `dateEncodingStrategy` of `JSONEncoder`
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(formatter)
See the “Working With Fixed Format Date Representations” sections of the DateFormatter documentation.
Then, for your formatter for your UI, if you absolutely want dd-MMM-yyyy format, you would have a separate formatter for that, e.g.:
let formatter = DateFormatter()
formatter.dateFormat = "dd-MMM-yyyy"
Note, for this UI date formatter, we don’t set a locale or a timeZone, but rather let it use the device’s current defaults.
That having been said, we generally like to avoid using dateFormat for date strings presented in the UI. We generally prefer dateStyle, which shows the date in a format preferred by the user:
let formatter = DateFormatter()
formatter.dateStyle = .medium
This way, the US user will see “Nov 22, 2019”, the UK user will see “22 Nov 2019”, and the French user will see “22 nov. 2019”. Users will see dates in the formats with which they are most accustomed.
See “Working With User-Visible Representations of Dates and Times” in the aforementioned DateFormatter documentation.
pass your date into this
let dateFormatterPrint = DateFormatter()
dateFormatterPrint.dateFormat = "dd-MM-yyyy"
let val = dateFormatterPrint.string(from: "pass your date")
You can use extensions for you to be able to convert the date to your desired format.
var todayDate = "1993-07-20T09:44:18.674Z"
extension String {
func convertDate(currentFormat: String, toFormat : String) -> String {
let dateFormator = DateFormatter()
dateFormator.dateFormat = currentFormat
let resultDate = dateFormator.date(from: self)
dateFormator.dateFormat = toFormat
return dateFormator.string(from: resultDate!)
}
}
Then you can implement like this:
cell.dateOfBirthLabel.text = self.todayDate.convertDate(currentFormat: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", toFormat: "dd-MMM-yyyy")

Decoding JSON data in swift4 with dates

Im new at this and having some trouble getting my head around how this all works.
I have this struct:
struct EventDetail:Decodable {
let EventName: String
let EventInformation: String
let EventStartDate: Date
let EventEndDate: Date
}
And this func to download the json:
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://someurl.php")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.events = try JSONDecoder().decode([EventDetail].self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print(error)
}
}
}.resume()
}
}
This is the JSON error I get:
JSON Error
typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)), Cosplay_Life.EventDetail.(CodingKeys in _52013DB7ECF3BE1EBFBF83BE6BA8F9E9).EventStartDate], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
This all seems to be working if the EventStartDate and EventEndDate is a String, but I do wish to have it as a Date because I will sort data later using the date field. The value that gets downloaded is in the format "yyyy-MM-dd" eks: "2017-02-25" What am I doing wrong?
A date is a Double in that it is the number of ms since the epoch.
Try this:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// Use format appropriate to your JSON String. This is for ISO-8601
// You MUST account for the milliseconds even if you don't want them
// or it won't parse properly
dateFormatter.dateFormat = "yy-MM-dd'T'HH:mm:ss.SSS"
eventDetail.EventStartDate = dateFormatter.date(from: jsonEventStartDateField)!
eventDetail.EventEndDate = dateFormatter.date(from: jsonEventEndDateField)!

How do I make JSONDecoder parse this format? (yyyy-MM-dd'T'HH:mm:ss.SSSSSSS)

This is what I'm trying to decode:
{"MessageDate":"2017-11-28T05:04:40.9611765"}
I've omitted the rest of the JSON structure.
How do I have JSONDecoder parse that date format into a Date object?
This is what I've tried so far:
extension Formatter
{
static let dotNetDateTime: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return formatter
}()
/// https://stackoverflow.com/a/46458771/8462094
static let dotNetDateTimeISO8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
/// https://stackoverflow.com/a/46458771/8462094
static let dotNetDateTimeISO8601NoMS: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
return formatter
}()
static let dotNetDateTimeCustom: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"
return formatter
}()
static let iso8601Full: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXX"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}
Some of those have been taken from other Stack Overflow posts.
I've also attempted a lot of other date decoding strategies not listed above.
I've scoured the internet for hours for solutions.
I've looked at the Unicode documentation for date format patterns.
I've enlisted the help of another friend, and he doesn't know either.
I suspect the seven digit float at the end of the date string is the culprit, but I don't know how to tackle that.
Maybe I'm just missing something really small. Another set of eyes would be nice.
Any help is greatly appreciated guys, thanx.
I guess I should include the actual code where I'm parsing:
let attempt1 = JSONDecoder()
attempt1.dateDecodingStrategy = .formatted(Formatter.dotNetDateTime)
let attempt2 = JSONDecoder()
attempt2.dateDecodingStrategy = .formatted(Formatter.dotNetDateTimeISO8601)
//And more attempt initializations
...
print(String(data: data, encoding: .utf8)!)
if let deser = try? attempt1.decode(MessageThreads.self, from: data)
{
return onDone(deser, nil)
}
if let deser = try? attempt2.decode(MessageThreads.self, from: data)
{
return onDone(deser, nil)
}
//And more attempts
...
Got it. Sometimes when you need to debug, use Playgrounds.
extension Formatter
{
static let dotNetDateTimeWithMilliseconds: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.S"
return formatter
}()
}
Just one .S will suffice to parse it.
Edit:
#Leo Dabus said I should include the locale, so I added it in. Thanx Leo for your help!