Skip to content

Commit

Permalink
Fix user-specified Date objects not being output in ISO8601 format (#273
Browse files Browse the repository at this point in the history
)

* Fix user-specified dates not being output in ISO8601

* Who knew `now` was such a recent swift addition.
  • Loading branch information
bsneed authored Nov 21, 2023
1 parent 86aeff4 commit 2fa2929
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Sources/Segment/ObjC/ObjCAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ extension ObjCAnalytics {
var result: [String: Any]? = nil
if let system: System = analytics.store.currentState() {
do {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
let json = try encoder.encode(system.settings)
if let r = try JSONSerialization.jsonObject(with: json) as? [String: Any] {
result = r
Expand Down
4 changes: 2 additions & 2 deletions Sources/Segment/ObjC/ObjCConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class ObjCConfiguration: NSObject {
get {
var result = [String: Any]()
do {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
let json = try encoder.encode(configuration.values.defaultSettings)
if let r = try JSONSerialization.jsonObject(with: json) as? [String: Any] {
result = r
Expand All @@ -89,7 +89,7 @@ public class ObjCConfiguration: NSObject {
set(value) {
do {
let json = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let decoder = JSONDecoder()
let decoder = JSONDecoder.default
let settings = try decoder.decode(Settings.self, from: json)
configuration.defaultSettings(settings)
} catch {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Segment/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct Settings: Codable {
static public func load(from url: URL?) -> Settings? {
guard let url = url else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
let settings = try? JSONDecoder().decode(Settings.self, from: data)
let settings = try? JSONDecoder.default.decode(Settings.self, from: data)
return settings
}

Expand Down Expand Up @@ -80,7 +80,7 @@ public struct Settings: Codable {
var result: T? = nil
guard let settings = integrations?.dictionaryValue else { return nil }
if let dict = settings[key], let jsonData = try? JSONSerialization.data(withJSONObject: dict) {
result = try? JSONDecoder().decode(T.self, from: jsonData)
result = try? JSONDecoder.default.decode(T.self, from: jsonData)
}
return result
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Segment/Utilities/HTTPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public class HTTPClient {
}

do {
let responseJSON = try JSONDecoder().decode(Settings.self, from: data)
let responseJSON = try JSONDecoder.default.decode(Settings.self, from: data)
completion(true, responseJSON)
} catch {
self?.analytics?.reportInternalError(AnalyticsError.jsonUnableToDeserialize(error))
Expand Down
14 changes: 10 additions & 4 deletions Sources/Segment/Utilities/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public enum JSON: Equatable {

// For Value types
public init<T: Codable>(with value: T) throws {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
let json = try encoder.encode(value)
let output = try JSONSerialization.jsonObject(with: json)
try self.init(output)
Expand All @@ -58,6 +58,8 @@ public enum JSON: Equatable {
// handle swift types
case Optional<Any>.none:
self = .null
case let date as Date:
self = .string(date.iso8601())
case let url as URL:
self = .string(url.absoluteString)
case let string as String:
Expand Down Expand Up @@ -134,7 +136,7 @@ extension Encodable {
public func toString(pretty: Bool) -> String {
var returnString = ""
do {
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
if pretty {
encoder.outputFormatting = .prettyPrinted
}
Expand Down Expand Up @@ -182,7 +184,11 @@ extension JSON {
public func codableValue<T: Codable>() -> T? {
var result: T? = nil
if let dict = dictionaryValue, let jsonData = try? JSONSerialization.data(withJSONObject: dict) {
result = try? JSONDecoder().decode(T.self, from: jsonData)
do {
result = try JSONDecoder.default.decode(T.self, from: jsonData)
} catch {
print(error)
}
}
return result
}
Expand Down Expand Up @@ -402,7 +408,7 @@ extension JSON {
if let v = value as? [String: Any] {
if let jsonData = try? JSONSerialization.data(withJSONObject: v) {
do {
result = try JSONDecoder().decode(T.self, from: jsonData)
result = try JSONDecoder.default.decode(T.self, from: jsonData)
} catch {
Analytics.segmentLog(message: "Unable to decode object (\(keyPath)) to a Codable: \(error)", kind: .error)
}
Expand Down
28 changes: 27 additions & 1 deletion Sources/Segment/Utilities/iso8601.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import Foundation

enum SegmentISO8601DateFormatter {

static let shared: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions.update(with: .withFractionalSeconds)
Expand All @@ -28,3 +27,30 @@ internal extension String {
return SegmentISO8601DateFormatter.shared.date(from: self)
}
}

extension DateFormatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.calendar = Calendar(identifier: .iso8601)
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter
}()
}

extension JSONDecoder {
static var `default`: JSONDecoder {
let d = JSONDecoder()
d.dateDecodingStrategy = .formatted(DateFormatter.iso8601)
return d
}
}

extension JSONEncoder {
static var `default`: JSONEncoder {
let e = JSONEncoder()
e.dateEncodingStrategy = .formatted(DateFormatter.iso8601)
return e
}
}
6 changes: 3 additions & 3 deletions Tests/Segment-Tests/Analytics_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ final class Analytics_Tests: XCTestCase {

}

func testAsyncOperatingMode() {
func testAsyncOperatingMode() throws {
// Use a specific writekey to this test so we do not collide with other cached items.
let analytics = Analytics(configuration: Configuration(writeKey: "testFlush_asyncMode")
.flushInterval(9999)
Expand Down Expand Up @@ -721,7 +721,7 @@ final class Analytics_Tests: XCTestCase {
XCTAssertEqual(analytics.pendingUploads!.count, 0)
}

func testSyncOperatingMode() {
func testSyncOperatingMode() throws {
// Use a specific writekey to this test so we do not collide with other cached items.
let analytics = Analytics(configuration: Configuration(writeKey: "testFlush_syncMode")
.flushInterval(9999)
Expand Down Expand Up @@ -755,7 +755,7 @@ final class Analytics_Tests: XCTestCase {
XCTAssertEqual(analytics.pendingUploads!.count, 0)
}

func testFindAll() {
func testFindAll() throws {
let analytics = Analytics(configuration: Configuration(writeKey: "testFindAll")
.flushInterval(9999)
.flushAt(9999)
Expand Down
36 changes: 32 additions & 4 deletions Tests/Segment-Tests/JSON_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class JSONTests: XCTestCase {
let traits = try? JSON(["email": "[email protected]"])
let userInfo = UserInfo(anonymousId: "1234", userId: "brandon", traits: traits, referrer: nil)

let encoder = JSONEncoder()
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
Expand All @@ -51,6 +51,34 @@ class JSONTests: XCTestCase {
}
}

func testJSONDateHandling() throws {
struct TestStruct: Codable {
let myDate: Date
}

let now = Date(timeIntervalSinceNow: 0)

let test = TestStruct(myDate: now)
let object = try JSON(with: test)
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
let json = try encoder.encode(object)
XCTAssertNotNil(json)
let newTest = try! JSONDecoder.default.decode(TestStruct.self, from: json)
XCTAssertEqual(newTest.myDate.toString(), now.toString())
} catch {
print(error)
XCTFail()
}

let dummyProps = ["myDate": now] // <- conforms to Codable
let j = try! JSON(dummyProps)
let anotherTest: TestStruct! = j.codableValue()
XCTAssertEqual(anotherTest.myDate.toString(), now.toString())
}

func testJSONCollectionTypes() throws {
let testSet: Set = ["1", "2", "3"]
let traits = try! JSON(["type": NSNull(), "preferences": ["bwack"], "key": testSet])
Expand All @@ -63,13 +91,13 @@ class JSONTests: XCTestCase {

func testJSONNil() throws {
let traits = try JSON(["type": NSNull(), "preferences": ["bwack"], "key": nil] as [String : Any?])
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
let json = try encoder.encode(traits)
XCTAssertNotNil(json)
let decoded = try JSONDecoder().decode(Personal.self, from: json)
let decoded = try JSONDecoder.default.decode(Personal.self, from: json)
XCTAssertNil(decoded.type, "Type should be nil")
}
}
Expand All @@ -81,7 +109,7 @@ class JSONTests: XCTestCase {

let test = TestStruct(blah: "hello")
let object = try JSON(with: test)
let encoder = JSONEncoder()
let encoder = JSONEncoder.default
encoder.outputFormatting = .prettyPrinted

do {
Expand Down

0 comments on commit 2fa2929

Please sign in to comment.