mike4aday / SwiftlySalesforce

The Swift-est way to build native mobile apps that connect to Salesforce.
MIT License
136 stars 43 forks source link

Example on working with date/datetime fields #64

Closed auctifera-josed closed 6 years ago

auctifera-josed commented 6 years ago

Hi, I've been trying to work with date & datetime fields but I'm sure there is a better way and I'm hopping someone can help me out.

My struct is:

struct Contact: Codable {//Contact
    var id: String?
    var name: String?
    var birthdate: Date?
    var birthdateString: String?

    enum CodingKeys: String, CodingKey {
        case id = "Id"
        case name = "Name"
        case birthdate = "Birthdate"
        case birthdateString = "Birthdate"
    }
}

I have a query as follows

first {
    return salesforce.query(soql: "SELECT Id, Name, Birthdate FROM Contact")
}.then { (result: QueryResult<Contact>) -> () in
    for contact in result.records {
        ...
    }
}.catch { error in
    print(error)
}

which throws the error "Date string does not match format expected by formatter."

So I changed to a typeless query to try to use a decoder to set the date formatter as follows

first {
    return salesforce.query(soql: "SELECT Id, Name, Birthdate FROM Contact")
}.then { (result: QueryResult<Record>) -> () in
    for record in result.records {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .formatted(DateFormatter.salesforceDateFormatter)

        let contact = try decoder.decode(Contact.self, from: record)
    }
}.catch { error in
    print(error)
}

which won't let me compile for the error Cannot convert value of type 'Record' to expected argument type 'Data'

So I try to create the contact by manually getting each field data like

first {
    return salesforce.query(soql: "SELECT Id, Name, Birthdate FROM Contact")
}.then { (result: QueryResult<Record>) -> () in
    for record in result.records {
        var contact = Contact()
        contact.id = record.string(forField: "Id")
        contact.name = record.string(forField: "Name")
        contact.birthdate = record.date(forField: "Birthdate")
        contact.brithdateString = record.string(forField: "Birthdate")
    }
}.catch { error in
    print(error)
}

But the contact.birthdate is nil even though the contact.brithdateString has a date

So I finally end up implementing it like

first {
    return salesforce.query(soql: "SELECT Id, Name, Birthdate FROM Contact")
}.then { (result: QueryResult<Record>) -> () in
    for record in result.records {
        var contact = Contact()
        contact.id = record.string(forField: "Id")
        contact.name = record.string(forField: "Name")
        if let birthDate = record.string(forField: "Birthdate") {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd"
            contact.birthdate = dateFormatter.date(from: birthDate)
        }
    }
}.catch { error in
    print(error)
}

which works, but seems like a lot of work for getting each date/datetime field from a query result/apex response.

mike4aday commented 6 years ago

@auctifera-josed try this for your Contact struct instead:

import Foundation

struct Contact {//Contact

    var id: String?
    var name: String?
    var birthdate: Date?
    //var birthdateString: String?

    enum CodingKeys: String, CodingKey {
        case id = "Id"
        case name = "Name"
        case birthdate = "Birthdate"
        // WON'T COMPILE // case birthdateString = "Birthdate"
    }
}

extension Contact: Decodable {

    public init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.id = try container.decodeIfPresent(String.self, forKey: .id)
        self.name = try container.decodeIfPresent(String.self, forKey: .name)

        if let birthdateString = try container.decodeIfPresent(String.self, forKey: .birthdate) {
            if let bdate = DateFormatter.salesforceDateFormatter.date(from: birthdateString) {
                self.birthdate = bdate
            }
            else {
                throw DecodingError.dataCorruptedError(forKey: .birthdate, in: container, debugDescription: "Date string does not match format expected by formatter.")
            }
        }
        else {
            self.birthdate = nil
        }
    }
}
mike4aday commented 6 years ago

@auctifera-josed here's another, similar example, unrelated to Salesforce: https://useyourloaf.com/blog/swift-codable-with-custom-dates/