Skip to content

Commit 98dc189

Browse files
committed
Add conversions between Swift.Duration and Google_Protobuf_Duration.
1 parent e4c3a0c commit 98dc189

File tree

3 files changed

+114
-0
lines changed

3 files changed

+114
-0
lines changed

Sources/SwiftProtobuf/Google_Protobuf_Duration+Extensions.swift

+39
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,45 @@ extension Google_Protobuf_Duration {
169169
}
170170
}
171171

172+
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
173+
extension Google_Protobuf_Duration {
174+
/// Creates a new `Google_Protobuf_Duration` by rounding a `Duration` to
175+
/// the nearest nanosecond according to the given rounding rule.
176+
///
177+
/// - Parameters:
178+
/// - duration: The `Duration`.
179+
/// - rule: The rounding rule to use.
180+
public init(
181+
rounding duration: Duration,
182+
rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero
183+
) {
184+
let secs = duration.components.seconds
185+
let attos = duration.components.attoseconds
186+
let fracNanos =
187+
(Double(attos % attosPerNanosecond) / Double(attosPerNanosecond)).rounded(rule)
188+
let nanos = Int32(attos / attosPerNanosecond) + Int32(fracNanos)
189+
let (s, n) = normalizeForDuration(seconds: secs, nanos: nanos)
190+
self.init(seconds: s, nanos: n)
191+
}
192+
}
193+
194+
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
195+
extension Duration {
196+
/// Creates a new `Duration` that is equal to the given duration.
197+
///
198+
/// Swift `Duration` has a strictly higher precision than `Google_Protobuf_Duration`
199+
/// (attoseconds vs. nanoseconds, respectively), so this conversion is always
200+
/// value-preserving.
201+
///
202+
/// - Parameters:
203+
/// - duration: The `Google_Protobuf_Duration`.
204+
public init(_ duration: Google_Protobuf_Duration) {
205+
self.init(
206+
secondsComponent: duration.seconds,
207+
attosecondsComponent: Int64(duration.nanos) * attosPerNanosecond)
208+
}
209+
}
210+
172211
private func normalizeForDuration(
173212
seconds: Int64,
174213
nanos: Int32

Sources/SwiftProtobuf/TimeUtils.swift

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let secondsPerDay: Int32 = 86400
1818
let secondsPerHour: Int32 = 3600
1919
let secondsPerMinute: Int32 = 60
2020
let nanosPerSecond: Int32 = 1_000_000_000
21+
let attosPerNanosecond: Int64 = 1_000_000_000
2122

2223
internal func timeOfDayFromSecondsSince1970(seconds: Int64) -> (hh: Int32, mm: Int32, ss: Int32) {
2324
let secondsSinceMidnight = Int32(mod(seconds, Int64(secondsPerDay)))

Tests/SwiftProtobufTests/Test_Duration.swift

+74
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,78 @@ final class Test_Duration: XCTestCase, PBTestHelpers {
312312
let t2 = Google_Protobuf_Duration(seconds: 123, nanos: 123_456_789)
313313
XCTAssertEqual(t2.timeInterval, 123.123456789)
314314
}
315+
316+
func testConvertFromStdlibDuration() throws {
317+
// Full precision
318+
do {
319+
let sd = Duration.seconds(123) + .nanoseconds(123_456_789)
320+
let pd = Google_Protobuf_Duration(rounding: sd)
321+
XCTAssertEqual(pd.seconds, 123)
322+
XCTAssertEqual(pd.nanos, 123_456_789)
323+
}
324+
325+
// Default rounding (toNearestAwayFromZero)
326+
do {
327+
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_499_999_999)
328+
let pd = Google_Protobuf_Duration(rounding: sd)
329+
XCTAssertEqual(pd.seconds, 123)
330+
XCTAssertEqual(pd.nanos, 123_456_789)
331+
}
332+
do {
333+
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_500_000_000)
334+
let pd = Google_Protobuf_Duration(rounding: sd)
335+
XCTAssertEqual(pd.seconds, 123)
336+
XCTAssertEqual(pd.nanos, 123_456_790)
337+
}
338+
339+
// Other rounding rules
340+
do {
341+
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_499_999_999)
342+
let pd = Google_Protobuf_Duration(rounding: sd, rule: .awayFromZero)
343+
XCTAssertEqual(pd.seconds, 123)
344+
XCTAssertEqual(pd.nanos, 123_456_790)
345+
}
346+
do {
347+
let sd = Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_999_999_999)
348+
let pd = Google_Protobuf_Duration(rounding: sd, rule: .towardZero)
349+
XCTAssertEqual(pd.seconds, 123)
350+
XCTAssertEqual(pd.nanos, 123_456_789)
351+
}
352+
353+
// Negative duration
354+
do {
355+
let sd = Duration.zero - .seconds(123) - .nanoseconds(123_456_789)
356+
let pd = Google_Protobuf_Duration(rounding: sd)
357+
XCTAssertEqual(pd.seconds, -123)
358+
XCTAssertEqual(pd.nanos, -123_456_789)
359+
}
360+
do {
361+
let sd = .zero
362+
- Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_000_000_001)
363+
let pd = Google_Protobuf_Duration(rounding: sd, rule: .towardZero)
364+
XCTAssertEqual(pd.seconds, -123)
365+
XCTAssertEqual(pd.nanos, -123_456_789)
366+
}
367+
do {
368+
let sd = .zero
369+
- Duration(secondsComponent: 123, attosecondsComponent: 123_456_789_000_000_001)
370+
let pd = Google_Protobuf_Duration(rounding: sd, rule: .awayFromZero)
371+
XCTAssertEqual(pd.seconds, -123)
372+
XCTAssertEqual(pd.nanos, -123_456_790)
373+
}
374+
}
375+
376+
func testConvertToStdlibDuration() throws {
377+
do {
378+
let pd = Google_Protobuf_Duration(seconds: 123, nanos: 123_456_789)
379+
let sd = Duration(pd)
380+
XCTAssertEqual(sd, .seconds(123) + .nanoseconds(123_456_789))
381+
}
382+
// Negative duration
383+
do {
384+
let pd = Google_Protobuf_Duration(seconds: -123, nanos: -123_456_789)
385+
let sd = Duration(pd)
386+
XCTAssertEqual(sd, .zero - (.seconds(123) + .nanoseconds(123_456_789)))
387+
}
388+
}
315389
}

0 commit comments

Comments
 (0)