Objectmapper: Strange Date Transformation behavior

Created on 14 Dec 2016  Â·  3Comments  Â·  Source: tristanhimmelman/ObjectMapper

Hello,

Before this library used to just transform dates without having to specify a date transform function (at least this is the behavior I saw).

So for example

public private(set) var myDate:Date?

public fund mapping(map: Map) {
  myDate <- map["my_date"]
}

Given the date is of format "2016-12-09T13:37:27.262Z", it would transform this date properly without me having to specify that it was indeed a date.

My current model is failing to parse the date correctly for some reason. I've tried both the example given on the read-me, I've looked at the "closed" issues regarding date formatting and I've tried to use their examples as well...still no luck.

Ive tried these date transforms

  1. DateTransform()
  2. ISO Date transform function

My unit test...

class TripTests: XCTestCase {
    func testInflateTestObjectFromJSON() {
        var tripObj = [String:AnyObject]()
        tripObj["_id"] = "123" as AnyObject?
        tripObj["dropoff_time"] = "2016-12-09T13:37:27.262Z" as AnyObject?
        let trip = Trip(JSON: tripObj)
        XCTAssertNotNil(trip?.id)
        XCTAssertNotNil(trip?.dropoffTime)
        print(trip?.pickupTime)
        print(trip?.dropoffTime)
        dump(trip?.toJSONString())
    }
}

which produces...

Optional(1970-01-01 00:33:36 +0000)
Optional(1970-01-01 00:33:36 +0000)
â–¿ Optional("{\"dropoff_time\":2016,\"pickup_time\":2016,\"_id\":\"123\"}")

Clearly, the date I'm giving it isn't 1970...and the above date is indeed an ISO formatted date. The server I'm running is Express/Mongo and I'm simply using the Date.now javascript call to generate a date. Also, the Schema on the Mongo model is of type 'Date'.

Here is my Trip model

import Foundation
import ObjectMapper

public class Trip : Mappable {
    public private(set) var id: String?
    public var pickupTime:Date?
    public var dropoffTime:Date?
    public var completed:Bool?
    public private(set) var createdAt:Date?
    public private(set) var updatedAt:Date?


    public init(captain: User?, passenger: User?) {
        self.captain = captain
        self.passenger = passenger
    }

    public required init?(map: Map) {

    }

    public func mapping(map: Map) {
        id <- map["_id"]
        pickupTime <- (map["pickup_time"], DateTransform())
        dropoffTime <- (map["dropoff_time"], DateTransform())
        completed <- map["completed"]
        updatedAt <- (map["updated_at"], DateTransform())
        createdAt <- (map["created_at"], DateTransform())
    }
}

Again, as stated above, I've tried using both the DateTransform() and the ISO8601DateTransform().

If I use the DateTransform function, my unit test will "pass" because the date property will no longer be nil however it will be the 1970 date which isn't correct.

If I use the ISO8601DateTransform, the property is nil and the unit test will fail because it was expecting the date to not be nil.

Before I would just specify that the property was a "date" and it would transform everything correctly without me having to specifically set a DateTransform tuple for the mapping. I don't know exactly what I am doing incorrectly here.

==== Updated just to make it look prettier =====

Most helpful comment

So I believe I may have solved my own issue...

I still do not know why the built in ISO date formatter doesn't work however I created my own

import Foundation
import ObjectMapper
open class ISODateTransform: TransformType {
    public typealias Object = Date
    public typealias JSON = String

    public init() {}

    public func transformFromJSON(_ value: Any?) -> Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

        guard let strValue = value as? String else { return nil }
        return formatter.date(from: strValue)
    }

    public func transformToJSON(_ value: Date?) -> String? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
        guard value != nil else { return nil }
        return formatter.string(from: value!)
    }}

this seems to work just fine, unit tests are passing with valid dates now.

All 3 comments

So I believe I may have solved my own issue...

I still do not know why the built in ISO date formatter doesn't work however I created my own

import Foundation
import ObjectMapper
open class ISODateTransform: TransformType {
    public typealias Object = Date
    public typealias JSON = String

    public init() {}

    public func transformFromJSON(_ value: Any?) -> Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

        guard let strValue = value as? String else { return nil }
        return formatter.date(from: strValue)
    }

    public func transformToJSON(_ value: Date?) -> String? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
        guard value != nil else { return nil }
        return formatter.string(from: value!)
    }}

this seems to work just fine, unit tests are passing with valid dates now.

Just to add to this. I couldn't get the solution above to work but it did steer me into the right directions so much appreciated. I made some tweaks and this works for me.

//
//  ISODateTransform.swift
//

import Foundation
import ObjectMapper

open class ISODateTransform: TransformType {
    public typealias Object = Date
    public typealias JSON = String

    public init() {}

    public func transformFromJSON (_ value: Any?) -> Date? {
        guard let datestring = value as? String else { return nil }
        let isoFormatter = ISO8601DateFormatter()
        let date = isoFormatter.date(from: datestring)!

        return date
    }

    public func transformToJSON(_ value: Date?) -> String? {
        let isoFormatter = ISO8601DateFormatter()
        let string = isoFormatter.string(from: value!)

        return string
    }
}

Another Fix:
https://forums.swift.org/t/iso8601dateformatter-fails-to-parse-a-valid-iso-8601-date/22999/12

open class ISODateTransform: TransformType {
public typealias Object = Date
public typealias JSON = String

public init() {}

public func transformFromJSON (_ value: Any?) -> Date? {
    guard let datestring = value as? String else { return nil }
    let isoFormatter = ISO8601DateFormatter()
    //Critical Fix
    isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    let date = isoFormatter.date(from: datestring)!

    return date
}

public func transformToJSON(_ value: Date?) -> String? {
    let isoFormatter = ISO8601DateFormatter()
    //Critical Fix
    isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
    let string = isoFormatter.string(from: value!)
    return string
}

}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Dbigshooter picture Dbigshooter  Â·  4Comments

patchthecode picture patchthecode  Â·  3Comments

zhengying picture zhengying  Â·  4Comments

pcompassion picture pcompassion  Â·  3Comments

loryhuz picture loryhuz  Â·  4Comments