diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index afbe00c0..2c170861 100644 --- a/Sources/Confidence/ConfidenceValue.swift +++ b/Sources/Confidence/ConfidenceValue.swift @@ -21,10 +21,16 @@ public class ConfidenceValue: Equatable, Encodable { self.value = .double(double) } + /// `date` should have at least precision to the "day". + /// If a custom TimeZone is set for the input DateComponents, the internal + /// serializers will convert the input to the local TimeZone before extracting the date. public init(date: DateComponents) { self.value = .date(date) } + /// If a custom TimeZone is set for the input Date, the internal serializers will convert + /// the input to the local TimeZone before extracting the date (i.e. the local offset + /// information is maintained rather than the one customly set in Date). public init(timestamp: Date) { self.value = .timestamp(timestamp) } @@ -211,17 +217,19 @@ extension ConfidenceValueInternal { case .boolean(let boolean): try container.encode(boolean) case .date(let dateComponents): - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" + let dateFormatter = ISO8601DateFormatter() + dateFormatter.timeZone = TimeZone.current + dateFormatter.formatOptions = [.withFullDate] if let date = Calendar.current.date(from: dateComponents) { try container.encode(dateFormatter.string(from: date)) } else { throw ConfidenceError.internalError(message: "Could not create date from components") } case .timestamp(let date): - let isoFormatter = ISO8601DateFormatter() - let formattedDate = isoFormatter.string(from: date) - try container.encode(formattedDate) + let timestampFormatter = ISO8601DateFormatter() + timestampFormatter.timeZone = TimeZone.current + let timestamp = timestampFormatter.string(from: date) + try container.encode(timestamp) case .structure(let structure): try container.encode(structure) case .list(let list): diff --git a/Tests/ConfidenceTests/ConfidenceValueTests.swift b/Tests/ConfidenceTests/ConfidenceValueTests.swift index f315b577..b9cab42f 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -101,8 +101,8 @@ final class ConfidenceConfidenceValueTests: XCTestCase { func testEncodeDecode() throws { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - formatter.timeZone = TimeZone(abbreviation: "UTC") - let date = try XCTUnwrap(formatter.date(from: "2022-01-01 12:00:00")) + formatter.timeZone = TimeZone(abbreviation: "EDT") // Verify TimeZone conversion + let date = try XCTUnwrap(formatter.date(from: "2024-04-05 16:00:00")) let dateComponents = DateComponents(year: 2024, month: 4, day: 3) let value = ConfidenceValue(structure: ([ @@ -119,6 +119,10 @@ final class ConfidenceConfidenceValueTests: XCTestCase { let encoder = JSONEncoder() encoder.outputFormatting = .sortedKeys let resultString = String(data: try encoder.encode(value), encoding: .utf8) + + let isoFormatter = ISO8601DateFormatter() + isoFormatter.timeZone = TimeZone.current + let expectedSerializedTimestamp = isoFormatter.string(from: date) let expectedString = """ {\"bool\":true, \"date\":\"2024-04-03\", @@ -128,9 +132,11 @@ final class ConfidenceConfidenceValueTests: XCTestCase { \"null\":null, \"string\":\"value\", \"structure\":{\"int\":5}, - \"timestamp\":\"2022-01-01T12:00:00Z\"} + \"timestamp\":\"\(expectedSerializedTimestamp)\"} """.replacingOccurrences(of: "\n", with: "") // Newlines were added for readability + // The "base" timestamp is in UTC, but the local offset is added (e.g. "+002"). + XCTAssertTrue(expectedSerializedTimestamp.starts(with: "2024-04-05T22:00:00")) XCTAssertEqual(resultString, expectedString) } }