diff --git a/Sources/Confidence/ConfidenceValue.swift b/Sources/Confidence/ConfidenceValue.swift index afbe00c0..1efd482a 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 calendar day. 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..2fddf30c 100644 --- a/Tests/ConfidenceTests/ConfidenceValueTests.swift +++ b/Tests/ConfidenceTests/ConfidenceValueTests.swift @@ -61,8 +61,14 @@ final class ConfidenceConfidenceValueTests: XCTestCase { XCTAssertEqual(integerListValue.asList(), [ConfidenceValue(integer: 3), ConfidenceValue(integer: 4)]) XCTAssertEqual(doubleListValue.asList(), [ConfidenceValue(double: 3.14), ConfidenceValue(double: 4.0)]) XCTAssertEqual(stringListValue.asList(), [ConfidenceValue(string: "val1"), ConfidenceValue(string: "val2")]) - XCTAssertEqual(timestampListValue.asList(), [ConfidenceValue(timestamp: date1), ConfidenceValue(timestamp: date2)]) - XCTAssertEqual(dateListValue.asList(), [ConfidenceValue(date: dateComponents1), ConfidenceValue(date: dateComponents2)]) + XCTAssertEqual(timestampListValue.asList(), [ + ConfidenceValue(timestamp: date1), + ConfidenceValue(timestamp: date2) + ]) + XCTAssertEqual(dateListValue.asList(), [ + ConfidenceValue(date: dateComponents1), + ConfidenceValue(date: dateComponents2) + ]) } func testStructShouldConvertToStruct() { @@ -101,8 +107,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 +125,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 +138,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) } }