mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-24 23:06:21 +08:00
298 lines
10 KiB
Swift
298 lines
10 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2021 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if canImport(FoundationEssentials)
|
|
import FoundationEssentials
|
|
#endif
|
|
|
|
#if canImport(Darwin)
|
|
import Darwin
|
|
#elseif canImport(Bionic)
|
|
@preconcurrency import Bionic
|
|
#elseif canImport(Glibc)
|
|
@preconcurrency import Glibc
|
|
#elseif canImport(Musl)
|
|
@preconcurrency import Musl
|
|
#elseif os(Windows)
|
|
import CRT
|
|
#elseif os(WASI)
|
|
@preconcurrency import WASILibc
|
|
#endif
|
|
|
|
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
|
|
extension Duration {
|
|
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
|
|
public func formatted<S: FormatStyle>(_ v: S) -> S.FormatOutput where S.FormatInput == Self {
|
|
v.format(self)
|
|
}
|
|
|
|
/// Formats `self` using the hour-minute-second time pattern
|
|
/// - Returns: A formatted string to describe the duration, such as "1:30:56" for a duration of 1 hour, 30 minutes, and 56 seconds
|
|
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
|
|
public func formatted() -> String {
|
|
return Self.TimeFormatStyle(pattern: .hourMinuteSecond).format(self)
|
|
}
|
|
}
|
|
|
|
extension Duration {
|
|
static func bound(
|
|
for input: Duration,
|
|
in interval: Duration,
|
|
countingDown: Bool,
|
|
roundingRule: FloatingPointRoundingRule
|
|
) -> (bound: Duration, includedInRangeOfInput: Bool) {
|
|
let (rounded, roundsToEven) = input.rounded(roundingRule, toMultipleOf: interval)
|
|
|
|
let shift: Duration
|
|
switch (roundingRule, input >= .zero) {
|
|
case (.toNearestOrAwayFromZero, _), (.toNearestOrEven, _):
|
|
shift = countingDown ? interval / -2 : interval / 2
|
|
case (.up, _):
|
|
shift = countingDown ? .zero - interval : .zero
|
|
case (.down, _):
|
|
shift = countingDown ? .zero : interval
|
|
case (.towardZero, let inputGeqZero):
|
|
let direction: Int
|
|
if rounded == .zero && countingDown == inputGeqZero {
|
|
direction = countingDown ? -1 : 1
|
|
} else if countingDown == inputGeqZero {
|
|
direction = 0
|
|
} else if inputGeqZero {
|
|
direction = 1
|
|
} else {
|
|
direction = -1
|
|
}
|
|
|
|
shift = interval * direction
|
|
case (.awayFromZero, _):
|
|
if input == .zero || countingDown != (input >= .zero) {
|
|
shift = .zero
|
|
} else {
|
|
shift = input >= .zero ? .zero - interval : interval
|
|
}
|
|
@unknown default:
|
|
fatalError("Unknown FloatingPointRoundingRule \(roundingRule)")
|
|
}
|
|
|
|
let bound = rounded + shift
|
|
|
|
let doesBoundRoundToInput: Bool
|
|
switch (roundingRule, input >= .zero) {
|
|
case (.down, _), (.awayFromZero, false):
|
|
doesBoundRoundToInput = countingDown
|
|
case (.up, _), (.awayFromZero, true):
|
|
doesBoundRoundToInput = !countingDown
|
|
case (.toNearestOrAwayFromZero, _):
|
|
doesBoundRoundToInput = countingDown && bound > .zero
|
|
|| !countingDown && bound < .zero
|
|
case (.toNearestOrEven, _):
|
|
doesBoundRoundToInput = roundsToEven
|
|
case (.towardZero, _):
|
|
doesBoundRoundToInput = (bound >= .zero) == countingDown
|
|
@unknown default:
|
|
fatalError("Unknown FloatingPointRoundingRule \(roundingRule)")
|
|
}
|
|
|
|
return (bound, doesBoundRoundToInput || input == bound)
|
|
}
|
|
|
|
// Returns an array of values corresponding to each unit in `units`
|
|
func valuesForUnits(
|
|
_ units: [UnitsFormatStyle.Unit],
|
|
trailingFractionalLength: Int,
|
|
smallestUnitRounding: FloatingPointRoundingRule,
|
|
roundingIncrement: Double?
|
|
) -> [Double] {
|
|
guard let smallestUnit = units.last?.unit else {
|
|
return []
|
|
}
|
|
|
|
let increment = Self.interval(
|
|
for: .init(unit: smallestUnit),
|
|
fractionalDigits: trailingFractionalLength,
|
|
roundingIncrement: roundingIncrement
|
|
)
|
|
|
|
let rounded: Duration
|
|
if increment != .zero {
|
|
rounded = self.rounded(increment: increment, rule: smallestUnitRounding)
|
|
} else {
|
|
rounded = self
|
|
}
|
|
|
|
var (values, remainder) = rounded.factor(intoUnits: units)
|
|
|
|
values[values.count-1] += TimeInterval(remainder) / Self.secondCoefficient(for: smallestUnit)
|
|
|
|
return values
|
|
}
|
|
|
|
static func interval(
|
|
for unit: Duration.UnitsFormatStyle.Unit,
|
|
fractionalDigits: Int = 0,
|
|
roundingIncrement: Double? = nil
|
|
) -> Duration {
|
|
let fractionalLengthBasedIncrement: Duration
|
|
if unit.unit >= .seconds {
|
|
fractionalLengthBasedIncrement = Self.interval(fractionalSecondsLength: fractionalDigits) * Self.secondCoefficient(for: unit.unit)!
|
|
} else {
|
|
let offset = Self.fractionalDigitOffsetToSecond(from: unit.unit)!
|
|
fractionalLengthBasedIncrement = Self.interval(fractionalSecondsLength: offset + Swift.min(fractionalDigits, Int.max - offset))
|
|
}
|
|
|
|
if let roundingIncrement {
|
|
let roundingIncrementBasedIncrement: Duration
|
|
if unit.unit >= .seconds {
|
|
roundingIncrementBasedIncrement = .seconds(Self.secondCoefficient(for: unit.unit)!) * roundingIncrement
|
|
} else {
|
|
roundingIncrementBasedIncrement = .nanoseconds(Self.nanosecondCoefficientsForSubsecondUnits(unit.unit)!) * roundingIncrement
|
|
}
|
|
|
|
return Swift.max(fractionalLengthBasedIncrement, roundingIncrementBasedIncrement)
|
|
} else {
|
|
return fractionalLengthBasedIncrement
|
|
}
|
|
}
|
|
|
|
private static func interval(fractionalSecondsLength: Int) -> Duration {
|
|
let intervalMod3: Int64
|
|
switch fractionalSecondsLength % 3 {
|
|
case 0:
|
|
intervalMod3 = 1
|
|
case 1:
|
|
intervalMod3 = 100
|
|
case 2:
|
|
intervalMod3 = 10
|
|
default:
|
|
fatalError("Int % 3 >= 3")
|
|
}
|
|
switch fractionalSecondsLength {
|
|
case ...0:
|
|
return .seconds(1)
|
|
case ...3:
|
|
return .milliseconds(intervalMod3)
|
|
case ...6:
|
|
return .microseconds(intervalMod3)
|
|
case ...9:
|
|
return .nanoseconds(intervalMod3)
|
|
default:
|
|
return .seconds(pow(0.1, Double(fractionalSecondsLength)))
|
|
}
|
|
}
|
|
|
|
func factor(intoUnits units: [UnitsFormatStyle.Unit]) -> (values: [Double], remainder: Duration) {
|
|
var value = self
|
|
var values = [Double]()
|
|
for unit in units {
|
|
if unit.unit >= .seconds {
|
|
guard let coefficient = Self.secondCoefficient(for: unit.unit) else {
|
|
values.append(0)
|
|
continue
|
|
}
|
|
|
|
let (quotient, remainder) = value.components.seconds.quotientAndRemainder(dividingBy: coefficient)
|
|
|
|
values.append(Double(quotient))
|
|
value = .init(secondsComponent: remainder, attosecondsComponent: value.components.attoseconds)
|
|
} else {
|
|
guard let coefficient = Self.attosecondCoefficient(for: unit.unit) else {
|
|
values.append(0)
|
|
continue
|
|
}
|
|
|
|
let (quotient, remainder) = value.components.attoseconds.quotientAndRemainder(dividingBy: coefficient)
|
|
|
|
var unitValue = Double(quotient)
|
|
|
|
if value.components.seconds != .zero {
|
|
unitValue += Double(value.components.seconds) * pow(10, Double(Self.fractionalDigitOffsetToSecond(from: unit.unit)!))
|
|
}
|
|
|
|
values.append(unitValue)
|
|
value = .init(secondsComponent: 0, attosecondsComponent: remainder)
|
|
}
|
|
}
|
|
return (values, value)
|
|
}
|
|
|
|
|
|
private static func secondCoefficient(for unit: UnitsFormatStyle.Unit._Unit) -> Double {
|
|
if let c: Int64 = secondCoefficient(for: unit) {
|
|
return Double(c)
|
|
} else {
|
|
return pow(0.1, Double(fractionalDigitOffsetToSecond(from: unit)!))
|
|
}
|
|
}
|
|
|
|
private static func secondCoefficient(for unit: UnitsFormatStyle.Unit._Unit) -> Int64? {
|
|
switch unit {
|
|
case .weeks:
|
|
return 604800
|
|
case .days:
|
|
return 86400
|
|
case .hours:
|
|
return 3600
|
|
case .minutes:
|
|
return 60
|
|
case .seconds:
|
|
return 1
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private static func fractionalDigitOffsetToSecond(from unit: UnitsFormatStyle.Unit._Unit) -> Int? {
|
|
switch unit {
|
|
case .seconds:
|
|
return 0
|
|
case .milliseconds:
|
|
return 3
|
|
case .microseconds:
|
|
return 6
|
|
case .nanoseconds:
|
|
return 9
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private static func attosecondCoefficient(for unit: UnitsFormatStyle.Unit._Unit) -> Int64? {
|
|
switch unit {
|
|
case .seconds:
|
|
return 1_000_000_000_000_000_000
|
|
case .milliseconds:
|
|
return 1_000_000_000_000_000
|
|
case .microseconds:
|
|
return 1_000_000_000_000
|
|
case .nanoseconds:
|
|
return 1_000_000_000
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private static func nanosecondCoefficientsForSubsecondUnits(_ unit: UnitsFormatStyle.Unit._Unit) -> Int64? {
|
|
switch unit {
|
|
case .seconds:
|
|
return 1_000_000_000
|
|
case .milliseconds:
|
|
return 1_000_000
|
|
case .microseconds:
|
|
return 1_000
|
|
case .nanoseconds:
|
|
return 1
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|