Skip to content

Commit

Permalink
Add conversions between Swift.Duration and Google_Protobuf_Duration.
Browse files Browse the repository at this point in the history
  • Loading branch information
allevato committed Jan 14, 2025
1 parent e4c3a0c commit 110ef9f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
40 changes: 40 additions & 0 deletions Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,46 @@ extension Google_Protobuf_Duration {
}
}

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension Google_Protobuf_Duration {
/// Creates a new `Google_Protobuf_Duration` by rounding a `Duration` to
/// the nearest nanosecond according to the given rounding rule.
///
/// - Parameters:
/// - duration: The `Duration`.
/// - rule: The rounding rule to use.
public init(
rounding duration: Duration,
rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero
) {
let secs = duration.components.seconds
let attos = duration.components.attoseconds
let fracNanos =
(Double(attos % attosPerNanosecond) / Double(attosPerNanosecond)).rounded(rule)
let nanos = Int32(attos / attosPerNanosecond) + Int32(fracNanos)
let (s, n) = normalizeForDuration(seconds: secs, nanos: nanos)
self.init(seconds: s, nanos: n)
}
}

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension Duration {
/// Creates a new `Duration` that is equal to the given duration.
///
/// Swift `Duration` has a strictly higher precision than `Google_Protobuf_Duration`
/// (attoseconds vs. nanoseconds, respectively), so this conversion is always
/// value-preserving.
///
/// - Parameters:
/// - duration: The `Google_Protobuf_Duration`.
public init(_ duration: Google_Protobuf_Duration) {
self.init(
secondsComponent: duration.seconds,
attosecondsComponent: Int64(duration.nanos) * attosPerNanosecond
)
}
}

private func normalizeForDuration(
seconds: Int64,
nanos: Int32
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftProtobuf/TimeUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ let secondsPerDay: Int32 = 86400
let secondsPerHour: Int32 = 3600
let secondsPerMinute: Int32 = 60
let nanosPerSecond: Int32 = 1_000_000_000
let attosPerNanosecond: Int64 = 1_000_000_000

internal func timeOfDayFromSecondsSince1970(seconds: Int64) -> (hh: Int32, mm: Int32, ss: Int32) {
let secondsSinceMidnight = Int32(mod(seconds, Int64(secondsPerDay)))
Expand Down
72 changes: 72 additions & 0 deletions Tests/SwiftProtobufTests/Test_Duration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,76 @@ final class Test_Duration: XCTestCase, PBTestHelpers {
let t2 = Google_Protobuf_Duration(seconds: 123, nanos: 123_456_789)
XCTAssertEqual(t2.timeInterval, 123.123456789)
}

func testConvertFromStdlibDuration() throws {
// Full precision
do {
let sd = Duration.seconds(123) + .nanoseconds(123_456_789)
let pd = Google_Protobuf_Duration(rounding: sd)
XCTAssertEqual(pd.seconds, 123)
XCTAssertEqual(pd.nanos, 123_456_789)
}

// Default rounding (toNearestAwayFromZero)
do {
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_499_999_999)
let pd = Google_Protobuf_Duration(rounding: sd)
XCTAssertEqual(pd.seconds, 123)
XCTAssertEqual(pd.nanos, 123_456_789)
}
do {
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_500_000_000)
let pd = Google_Protobuf_Duration(rounding: sd)
XCTAssertEqual(pd.seconds, 123)
XCTAssertEqual(pd.nanos, 123_456_790)
}

// Other rounding rules
do {
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_499_999_999)
let pd = Google_Protobuf_Duration(rounding: sd, rule: .awayFromZero)
XCTAssertEqual(pd.seconds, 123)
XCTAssertEqual(pd.nanos, 123_456_790)
}
do {
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_999_999_999)
let pd = Google_Protobuf_Duration(rounding: sd, rule: .towardZero)
XCTAssertEqual(pd.seconds, 123)
XCTAssertEqual(pd.nanos, 123_456_789)
}

// Negative duration
do {
let sd = Duration.zero - .seconds(123) - .nanoseconds(123_456_789)
let pd = Google_Protobuf_Duration(rounding: sd)
XCTAssertEqual(pd.seconds, -123)
XCTAssertEqual(pd.nanos, -123_456_789)
}
do {
let sd = .zero - Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_000_000_001)
let pd = Google_Protobuf_Duration(rounding: sd, rule: .towardZero)
XCTAssertEqual(pd.seconds, -123)
XCTAssertEqual(pd.nanos, -123_456_789)
}
do {
let sd = .zero - Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_000_000_001)
let pd = Google_Protobuf_Duration(rounding: sd, rule: .awayFromZero)
XCTAssertEqual(pd.seconds, -123)
XCTAssertEqual(pd.nanos, -123_456_790)
}
}

func testConvertToStdlibDuration() throws {
do {
let pd = Google_Protobuf_Duration(seconds: 123, nanos: 123_456_789)
let sd = Duration(pd)
XCTAssertEqual(sd, .seconds(123) + .nanoseconds(123_456_789))
}
// Negative duration
do {
let pd = Google_Protobuf_Duration(seconds: -123, nanos: -123_456_789)
let sd = Duration(pd)
XCTAssertEqual(sd, .zero - (.seconds(123) + .nanoseconds(123_456_789)))
}
}
}

0 comments on commit 110ef9f

Please sign in to comment.