Skip to content

Commit

Permalink
Add a initializer to Google_Protobuf_Duration with rounding control. (#…
Browse files Browse the repository at this point in the history
…1747)

Provide a new initializer with explicit rounding controls and map the
other initializer though the single code paths.
  • Loading branch information
thomasvl authored Jan 23, 2025
1 parent 42195e7 commit 035c0c4
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 24 deletions.
26 changes: 19 additions & 7 deletions Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand All @@ -156,10 +153,25 @@ 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) {
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)
}

Expand Down
58 changes: 41 additions & 17 deletions Tests/SwiftProtobufTests/Test_Duration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -259,50 +259,74 @@ final class Test_Duration: XCTestCase, PBTestHelpers {
XCTAssertEqual("{\"optionalDuration\":\"100.000000001s\"}", try c.jsonString())
}

func testInitializationByTimeIntervals() throws {
func testInitializationRoundingTimeIntervals() throws {
// Negative interval
let t1 = Google_Protobuf_Duration(timeInterval: -123.456)
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(timeInterval: -123.999999999)
let t2 = Google_Protobuf_Duration(rounding: -123.999999999)
XCTAssertEqual(t2.seconds, -123)
XCTAssertEqual(t2.nanos, -999_999_999)

// Round up
let t3 = Google_Protobuf_Duration(timeInterval: -123.9999999994)
// 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)

// Round down
let t4 = Google_Protobuf_Duration(timeInterval: -123.9999999996)
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 t5 = Google_Protobuf_Duration(timeInterval: 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(timeInterval: 123.456)
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(timeInterval: 123.999999999)
let t7 = Google_Protobuf_Duration(rounding: 123.999999999)
XCTAssertEqual(t7.seconds, 123)
XCTAssertEqual(t7.nanos, 999_999_999)

// Round down
let t8 = Google_Protobuf_Duration(timeInterval: 123.9999999994)
// 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)

// Round up
let t9 = Google_Protobuf_Duration(timeInterval: 123.9999999996)
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 {
Expand Down

0 comments on commit 035c0c4

Please sign in to comment.