From bada7268c8ce8f9454f355c5a8c7b0322fbe4047 Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Thu, 16 Jan 2025 12:00:06 -0500 Subject: [PATCH 1/2] Add a initializer to Google_Protobuf_Duration with rounding control. Provide a new initializer with explicit rounding controls and map the other initializer though the single code paths. --- .../Google_Protobuf_Duration+Extensions.swift | 25 +++++-- Tests/SwiftProtobufTests/Test_Duration.swift | 70 +++++++++++++++++++ 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift index 203d91eff..0e030d8fd 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift @@ -144,10 +144,7 @@ extension Google_Protobuf_Duration: ExpressibleByFloatLiteral { /// that is interpreted as a duration in seconds, rounded to the nearest /// nanosecond. public init(floatLiteral value: Double) { - let sd = trunc(value) - let nd = round((value - sd) * TimeInterval(nanosPerSecond)) - let (s, n) = normalizeForDuration(seconds: Int64(sd), nanos: Int32(nd)) - self.init(seconds: s, nanos: n) + self.init(rounding: value, rule: .toNearestOrAwayFromZero) } } @@ -157,9 +154,23 @@ extension Google_Protobuf_Duration { /// /// - Parameter timeInterval: The `TimeInterval`. public init(timeInterval: TimeInterval) { - let sd = trunc(timeInterval) - let nd = round((timeInterval - sd) * TimeInterval(nanosPerSecond)) - let (s, n) = normalizeForDuration(seconds: Int64(sd), nanos: Int32(nd)) + self.init(rounding: timeInterval, rule: .toNearestOrAwayFromZero) + } + + /// Creates a new `Google_Protobuf_Duration` that is equal to the given + /// `TimeInterval` (measured in seconds), rounded to the nearest nanosecond + /// according to the given rounding rule. + /// + /// - Parameters: + /// - timeInterval: The `TimeInterval`. + /// - rule: The rounding rule to use. + public init( + rounding timeInterval: TimeInterval, + rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero + ) { + let sd = Int64(timeInterval) + let nd = ((timeInterval - Double(sd)) * TimeInterval(nanosPerSecond)).rounded(rule) + let (s, n) = normalizeForDuration(seconds: sd, nanos: Int32(nd)) self.init(seconds: s, nanos: n) } diff --git a/Tests/SwiftProtobufTests/Test_Duration.swift b/Tests/SwiftProtobufTests/Test_Duration.swift index 803424db4..aab517f09 100644 --- a/Tests/SwiftProtobufTests/Test_Duration.swift +++ b/Tests/SwiftProtobufTests/Test_Duration.swift @@ -305,6 +305,76 @@ final class Test_Duration: XCTestCase, PBTestHelpers { XCTAssertEqual(t9.nanos, 0) } + func testInitializationRoundingTimeIntervals() throws { + // Negative interval + let t1 = Google_Protobuf_Duration(rounding: -123.456) + XCTAssertEqual(t1.seconds, -123) + XCTAssertEqual(t1.nanos, -456_000_000) + + // Full precision + let t2 = Google_Protobuf_Duration(rounding: -123.999999999) + XCTAssertEqual(t2.seconds, -123) + XCTAssertEqual(t2.nanos, -999_999_999) + + // Value past percision, default and some explicit rules + let t3 = Google_Protobuf_Duration(rounding: -123.9999999994) + XCTAssertEqual(t3.seconds, -123) + XCTAssertEqual(t3.nanos, -999_999_999) + let t3u = Google_Protobuf_Duration(rounding: -123.9999999994, rule: .up) + XCTAssertEqual(t3u.seconds, -123) + XCTAssertEqual(t3u.nanos, -999_999_999) + let t3d = Google_Protobuf_Duration(rounding: -123.9999999994, rule: .down) + XCTAssertEqual(t3d.seconds, -124) + XCTAssertEqual(t3d.nanos, 0) + + // Value past percision, default and some explicit rules + let t4 = Google_Protobuf_Duration(rounding: -123.9999999996) + XCTAssertEqual(t4.seconds, -124) + XCTAssertEqual(t4.nanos, 0) + let t4u = Google_Protobuf_Duration(rounding: -123.9999999996, rule: .up) + XCTAssertEqual(t4u.seconds, -123) + XCTAssertEqual(t4u.nanos, -999_999_999) + let t4d = Google_Protobuf_Duration(rounding: -123.9999999996, rule: .down) + XCTAssertEqual(t4d.seconds, -124) + XCTAssertEqual(t4d.nanos, 0) + + let t5 = Google_Protobuf_Duration(rounding: 0) + XCTAssertEqual(t5.seconds, 0) + XCTAssertEqual(t5.nanos, 0) + + // Positive interval + let t6 = Google_Protobuf_Duration(rounding: 123.456) + XCTAssertEqual(t6.seconds, 123) + XCTAssertEqual(t6.nanos, 456_000_000) + + // Full precision + let t7 = Google_Protobuf_Duration(rounding: 123.999999999) + XCTAssertEqual(t7.seconds, 123) + XCTAssertEqual(t7.nanos, 999_999_999) + + // Value past percision, default and some explicit rules + let t8 = Google_Protobuf_Duration(rounding: 123.9999999994) + XCTAssertEqual(t8.seconds, 123) + XCTAssertEqual(t8.nanos, 999_999_999) + let t8u = Google_Protobuf_Duration(rounding: 123.9999999994, rule: .up) + XCTAssertEqual(t8u.seconds, 124) + XCTAssertEqual(t8u.nanos, 0) + let t8d = Google_Protobuf_Duration(rounding: 123.9999999994, rule: .down) + XCTAssertEqual(t8d.seconds, 123) + XCTAssertEqual(t8d.nanos, 999_999_999) + + // Value past percision, default and some explicit rules + let t9 = Google_Protobuf_Duration(rounding: 123.9999999996) + XCTAssertEqual(t9.seconds, 124) + XCTAssertEqual(t9.nanos, 0) + let t9u = Google_Protobuf_Duration(rounding: 123.9999999996, rule: .up) + XCTAssertEqual(t9u.seconds, 124) + XCTAssertEqual(t9u.nanos, 0) + let t9d = Google_Protobuf_Duration(rounding: 123.9999999996, rule: .down) + XCTAssertEqual(t9d.seconds, 123) + XCTAssertEqual(t9d.nanos, 999_999_999) + } + func testGetters() throws { let t1 = Google_Protobuf_Duration(seconds: -123, nanos: -123_456_789) XCTAssertEqual(t1.timeInterval, -123.123456789) From 4ac640a6423a52e09d7bf3c8e9993ccdd606dd54 Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Thu, 16 Jan 2025 12:26:18 -0500 Subject: [PATCH 2/2] Deprecate `init(timeInterval:)` pointing at new api. Also remove the tests so we can still build/test warnings free and with warnings upgraded to errors. --- .../Google_Protobuf_Duration+Extensions.swift | 1 + Tests/SwiftProtobufTests/Test_Duration.swift | 46 ------------------- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift b/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift index 0e030d8fd..6d84702fe 100644 --- a/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift +++ b/Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift @@ -153,6 +153,7 @@ extension Google_Protobuf_Duration { /// `TimeInterval` (measured in seconds), rounded to the nearest nanosecond. /// /// - Parameter timeInterval: The `TimeInterval`. + @available(*, deprecated, renamed: "init(rounding:rule:)") public init(timeInterval: TimeInterval) { self.init(rounding: timeInterval, rule: .toNearestOrAwayFromZero) } diff --git a/Tests/SwiftProtobufTests/Test_Duration.swift b/Tests/SwiftProtobufTests/Test_Duration.swift index aab517f09..4fff17d7f 100644 --- a/Tests/SwiftProtobufTests/Test_Duration.swift +++ b/Tests/SwiftProtobufTests/Test_Duration.swift @@ -259,52 +259,6 @@ final class Test_Duration: XCTestCase, PBTestHelpers { XCTAssertEqual("{\"optionalDuration\":\"100.000000001s\"}", try c.jsonString()) } - func testInitializationByTimeIntervals() throws { - // Negative interval - let t1 = Google_Protobuf_Duration(timeInterval: -123.456) - XCTAssertEqual(t1.seconds, -123) - XCTAssertEqual(t1.nanos, -456_000_000) - - // Full precision - let t2 = Google_Protobuf_Duration(timeInterval: -123.999999999) - XCTAssertEqual(t2.seconds, -123) - XCTAssertEqual(t2.nanos, -999_999_999) - - // Round up - let t3 = Google_Protobuf_Duration(timeInterval: -123.9999999994) - XCTAssertEqual(t3.seconds, -123) - XCTAssertEqual(t3.nanos, -999_999_999) - - // Round down - let t4 = Google_Protobuf_Duration(timeInterval: -123.9999999996) - XCTAssertEqual(t4.seconds, -124) - XCTAssertEqual(t4.nanos, 0) - - let t5 = Google_Protobuf_Duration(timeInterval: 0) - XCTAssertEqual(t5.seconds, 0) - XCTAssertEqual(t5.nanos, 0) - - // Positive interval - let t6 = Google_Protobuf_Duration(timeInterval: 123.456) - XCTAssertEqual(t6.seconds, 123) - XCTAssertEqual(t6.nanos, 456_000_000) - - // Full precision - let t7 = Google_Protobuf_Duration(timeInterval: 123.999999999) - XCTAssertEqual(t7.seconds, 123) - XCTAssertEqual(t7.nanos, 999_999_999) - - // Round down - let t8 = Google_Protobuf_Duration(timeInterval: 123.9999999994) - XCTAssertEqual(t8.seconds, 123) - XCTAssertEqual(t8.nanos, 999_999_999) - - // Round up - let t9 = Google_Protobuf_Duration(timeInterval: 123.9999999996) - XCTAssertEqual(t9.seconds, 124) - XCTAssertEqual(t9.nanos, 0) - } - func testInitializationRoundingTimeIntervals() throws { // Negative interval let t1 = Google_Protobuf_Duration(rounding: -123.456)