Files
swift-mirror/stdlib/public/core/Duration.swift
Alastair Houghton 0813e95569 Merge pull request #81440 from al45tair/currentos-availability
[Concurrency][Stdlib] Add SwiftStdlibCurrentOS availability, use it.
2025-07-07 12:04:00 +01:00

394 lines
14 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// A representation of high precision time.
///
/// `Duration` represents an elapsed time value with high precision in an
/// integral form. It may be used for measurements of varying clock sources. In
/// those cases it represents the elapsed time measured by that clock.
/// Calculations using `Duration` may span from a negative value to a positive
/// value and have a suitable range to at least cover attosecond scale for both
/// small elapsed durations like sub-second precision to durations that span
/// centuries.
///
/// Typical construction of `Duration` values should be created via the
/// static methods for specific time values.
///
/// var d: Duration = .seconds(3)
/// d += .milliseconds(33)
/// print(d) // 3.033 seconds
///
/// `Duration` itself does not ferry any additional information other than the
/// temporal measurement component; specifically leap seconds should be
/// represented as an additional accessor since that is specific only to certain
/// clock implementations.
@available(SwiftStdlibCurrentOS 5.7, *)
@frozen
public struct Duration: Sendable {
/// The low 64 bits of a 128-bit signed integer value counting attoseconds.
public var _low: UInt64
/// The high 64 bits of a 128-bit signed integer value counting attoseconds.
public var _high: Int64
@inlinable
public init(_high: Int64, low: UInt64) {
self._low = low
self._high = _high
}
internal init(_attoseconds: _Int128) {
self.init(_high: _attoseconds.high, low: _attoseconds.low)
}
/// Construct a `Duration` by adding attoseconds to a seconds value.
///
/// This is useful for when an external decomposed components of a `Duration`
/// has been stored and needs to be reconstituted. Since the values are added
/// no precondition is expressed for the attoseconds being limited to 1e18.
///
/// let d1 = Duration(
/// secondsComponent: 3,
/// attosecondsComponent: 123000000000000000)
/// print(d1) // 3.123 seconds
///
/// let d2 = Duration(
/// secondsComponent: 3,
/// attosecondsComponent: -123000000000000000)
/// print(d2) // 2.877 seconds
///
/// let d3 = Duration(
/// secondsComponent: -3,
/// attosecondsComponent: -123000000000000000)
/// print(d3) // -3.123 seconds
///
/// - Parameters:
/// - secondsComponent: The seconds component portion of the `Duration`
/// value.
/// - attosecondsComponent: The attosecond component portion of the
/// `Duration` value.
public init(secondsComponent: Int64, attosecondsComponent: Int64) {
self = Duration.seconds(secondsComponent) +
Duration(_attoseconds: _Int128(attosecondsComponent))
}
internal var _attoseconds: _Int128 {
_Int128(high: _high, low: _low)
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration {
/// The composite components of the `Duration`.
///
/// This is intended for facilitating conversions to existing time types. The
/// attoseconds value will not exceed 1e18 or be lower than -1e18.
@available(SwiftStdlibCurrentOS 5.7, *)
public var components: (seconds: Int64, attoseconds: Int64) {
let (seconds, attoseconds) = _attoseconds.dividedBy1e18()
return (Int64(seconds), Int64(attoseconds))
}
}
@available(SwiftStdlib 6.0, *)
extension Duration {
/// The number of attoseconds represented by this `Duration`.
///
/// This property provides direct access to the underlying number of attoseconds
/// that the current `Duration` represents.
///
/// let d = Duration.seconds(1)
/// print(d.attoseconds) // 1_000_000_000_000_000_000
@available(SwiftStdlib 6.0, *)
@_alwaysEmitIntoClient
public var attoseconds: Int128 {
Int128(_low: _low, _high: _high)
}
/// Construct a `Duration` from the given number of attoseconds.
///
/// This directly constructs a `Duration` from the given number of attoseconds.
///
/// let d = Duration(attoseconds: 1_000_000_000_000_000_000)
/// print(d) // 1.0 seconds
///
/// - Parameter attoseconds: The total duration expressed in attoseconds.
@available(SwiftStdlib 6.0, *)
@_alwaysEmitIntoClient
public init(attoseconds: Int128) {
self.init(_high: attoseconds._high, low: attoseconds._low)
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration {
/// Construct a `Duration` given a number of seconds represented as a
/// `BinaryInteger`.
///
/// let d: Duration = .seconds(77)
///
/// - Returns: A `Duration` representing a given number of seconds.
@available(SwiftStdlibCurrentOS 5.7, *)
@inlinable
public static func seconds<T: BinaryInteger>(_ seconds: T) -> Duration {
guard let high = Int64(exactly: seconds >> 64) else { fatalError() }
let low = UInt64(truncatingIfNeeded: seconds)
let lowScaled = low.multipliedFullWidth(by: 1_000_000_000_000_000_000)
let highScaled = high * 1_000_000_000_000_000_000
return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
}
/// Construct a `Duration` given a duration and scale, taking care so that
/// exact integer durations are preserved exactly.
internal init(_ duration: Double, scale: UInt64) {
// Split the duration into integral and fractional parts, as we need to
// handle them slightly differently to ensure that integer values are
// never rounded if `scale` is representable as Double.
let integralPart = duration.rounded(.towardZero)
let fractionalPart = duration - integralPart
self.init(_attoseconds:
// This term may trap due to overflow, but it cannot round, so if the
// input `seconds` is an exact integer, we get an exact integer result.
_Int128(integralPart).multiplied(by: scale) +
// This term may round, but cannot overflow.
_Int128((fractionalPart * Double(scale)).rounded())
)
}
/// Construct a `Duration` given a number of seconds represented as a
/// `Double` by converting the value into the closest attosecond scale value.
///
/// let d: Duration = .seconds(22.93)
///
/// - Returns: A `Duration` representing a given number of seconds.
@available(SwiftStdlibCurrentOS 5.7, *)
public static func seconds(_ seconds: Double) -> Duration {
Duration(seconds, scale: 1_000_000_000_000_000_000)
}
/// Construct a `Duration` given a number of milliseconds represented as a
/// `BinaryInteger`.
///
/// let d: Duration = .milliseconds(645)
///
/// - Returns: A `Duration` representing a given number of milliseconds.
@available(SwiftStdlibCurrentOS 5.7, *)
@inlinable
public static func milliseconds<T: BinaryInteger>(
_ milliseconds: T
) -> Duration {
guard let high = Int64(exactly: milliseconds >> 64) else { fatalError() }
let low = UInt64(truncatingIfNeeded: milliseconds)
let lowScaled = low.multipliedFullWidth(by: 1_000_000_000_000_000)
let highScaled = high * 1_000_000_000_000_000
return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
}
/// Construct a `Duration` given a number of seconds milliseconds as a
/// `Double` by converting the value into the closest attosecond scale value.
///
/// let d: Duration = .milliseconds(88.3)
///
/// - Returns: A `Duration` representing a given number of milliseconds.
@available(SwiftStdlibCurrentOS 5.7, *)
public static func milliseconds(_ milliseconds: Double) -> Duration {
Duration(milliseconds, scale: 1_000_000_000_000_000)
}
/// Construct a `Duration` given a number of microseconds represented as a
/// `BinaryInteger`.
///
/// let d: Duration = .microseconds(12)
///
/// - Returns: A `Duration` representing a given number of microseconds.
@available(SwiftStdlibCurrentOS 5.7, *)
@inlinable
public static func microseconds<T: BinaryInteger>(
_ microseconds: T
) -> Duration {
guard let high = Int64(exactly: microseconds >> 64) else { fatalError() }
let low = UInt64(truncatingIfNeeded: microseconds)
let lowScaled = low.multipliedFullWidth(by: 1_000_000_000_000)
let highScaled = high * 1_000_000_000_000
return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
}
/// Construct a `Duration` given a number of seconds microseconds as a
/// `Double` by converting the value into the closest attosecond scale value.
///
/// let d: Duration = .microseconds(382.9)
///
/// - Returns: A `Duration` representing a given number of microseconds.
@available(SwiftStdlibCurrentOS 5.7, *)
public static func microseconds(_ microseconds: Double) -> Duration {
Duration(microseconds, scale: 1_000_000_000_000)
}
/// Construct a `Duration` given a number of nanoseconds represented as a
/// `BinaryInteger`.
///
/// let d: Duration = .nanoseconds(1929)
///
/// - Returns: A `Duration` representing a given number of nanoseconds.
@available(SwiftStdlibCurrentOS 5.7, *)
@inlinable
public static func nanoseconds<T: BinaryInteger>(
_ nanoseconds: T
) -> Duration {
guard let high = Int64(exactly: nanoseconds >> 64) else { fatalError() }
let low = UInt64(truncatingIfNeeded: nanoseconds)
let lowScaled = low.multipliedFullWidth(by: 1_000_000_000)
let highScaled = high * 1_000_000_000
return Duration(_high: highScaled + Int64(lowScaled.high), low: lowScaled.low)
}
/// Construct a `Duration` given a number of seconds nanoseconds as a
/// `Double` by converting the value into the closest attosecond scale value.
///
/// let d: Duration = .nanoseconds(382.9)
///
/// - Returns: A `Duration` representing a given number of nanoseconds.
@available(SwiftStdlib 6.2, *)
public static func nanoseconds(_ nanoseconds: Double) -> Duration {
Duration(nanoseconds, scale: 1_000_000_000)
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
@_unavailableInEmbedded
extension Duration: Codable {
@available(SwiftStdlibCurrentOS 5.7, *)
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
let high = try container.decode(Int64.self)
let low = try container.decode(UInt64.self)
self.init(_high: high, low: low)
}
@available(SwiftStdlibCurrentOS 5.7, *)
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(_high)
try container.encode(_low)
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration: Hashable {
@available(SwiftStdlibCurrentOS 5.7, *)
public func hash(into hasher: inout Hasher) {
hasher.combine(_attoseconds)
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration: Equatable {
@available(SwiftStdlibCurrentOS 5.7, *)
public static func == (_ lhs: Duration, _ rhs: Duration) -> Bool {
return lhs._attoseconds == rhs._attoseconds
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration: Comparable {
@available(SwiftStdlibCurrentOS 5.7, *)
public static func < (_ lhs: Duration, _ rhs: Duration) -> Bool {
return lhs._attoseconds < rhs._attoseconds
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration: AdditiveArithmetic {
@available(SwiftStdlibCurrentOS 5.7, *)
public static var zero: Duration { Duration(_attoseconds: 0) }
@available(SwiftStdlibCurrentOS 5.7, *)
public static func + (_ lhs: Duration, _ rhs: Duration) -> Duration {
return Duration(_attoseconds: lhs._attoseconds + rhs._attoseconds)
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func - (_ lhs: Duration, _ rhs: Duration) -> Duration {
return Duration(_attoseconds: lhs._attoseconds - rhs._attoseconds)
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func += (_ lhs: inout Duration, _ rhs: Duration) {
lhs = lhs + rhs
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func -= (_ lhs: inout Duration, _ rhs: Duration) {
lhs = lhs - rhs
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration {
@available(SwiftStdlibCurrentOS 5.7, *)
public static func / (_ lhs: Duration, _ rhs: Double) -> Duration {
return Duration(_attoseconds:
_Int128(Double(lhs._attoseconds) / rhs))
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func /= (_ lhs: inout Duration, _ rhs: Double) {
lhs = lhs / rhs
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func / <T: BinaryInteger>(
_ lhs: Duration, _ rhs: T
) -> Duration {
Duration(_attoseconds: lhs._attoseconds / _Int128(rhs))
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func /= <T: BinaryInteger>(_ lhs: inout Duration, _ rhs: T) {
lhs = lhs / rhs
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func / (_ lhs: Duration, _ rhs: Duration) -> Double {
Double(lhs._attoseconds) / Double(rhs._attoseconds)
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func * (_ lhs: Duration, _ rhs: Double) -> Duration {
Duration(_attoseconds: _Int128(Double(lhs._attoseconds) * rhs))
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func * <T: BinaryInteger>(
_ lhs: Duration, _ rhs: T
) -> Duration {
Duration(_attoseconds: lhs._attoseconds * _Int128(rhs))
}
@available(SwiftStdlibCurrentOS 5.7, *)
public static func *= <T: BinaryInteger>(_ lhs: inout Duration, _ rhs: T) {
lhs = lhs * rhs
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
@_unavailableInEmbedded
extension Duration: CustomStringConvertible {
@available(SwiftStdlibCurrentOS 5.7, *)
public var description: String {
return (Double(_attoseconds) / 1e18).description + " seconds"
}
}
@available(SwiftStdlibCurrentOS 5.7, *)
extension Duration: DurationProtocol { }