Skip to content

Commit dbc90a6

Browse files
committed
Add a Platform-Agnostic TimePoint Type
A TimePoint functions like a Duration, but for products that cannot yet use the niceties in Swift 5.7 like the Swift driver. This type carries a 64-bit second value and a 32-bit nanosecond value. Like Duration, it does not represent any one OS clock value, but is rather just a "point in time". Using this type, we can simplify and clean up quite a few places that were trafficking in Foundation.Date and prepare for a future world where we have Duration too. These simplifications come in the ensuing patches.
1 parent bee79b2 commit dbc90a6

File tree

1 file changed

+117
-17
lines changed

1 file changed

+117
-17
lines changed

Sources/SwiftDriver/Utilities/DateAdditions.swift

Lines changed: 117 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,127 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
import struct Foundation.Date
14-
import protocol Foundation.LocalizedError
15-
import func Foundation.floor
13+
#if os(Windows)
14+
import WinSDK
15+
#elseif os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
16+
import Darwin
17+
#else
18+
import Glibc
19+
#endif
1620

17-
public extension Date {
18-
init(legacyDriverSecsAndNanos secsAndNanos: [Int]) throws {
19-
enum Errors: LocalizedError {
20-
case needSecondsAndNanoseconds
21+
/// Represents a time point value with nanosecond precision.
22+
///
23+
/// - Warning: The accuracy of measured `TimePoint` values is an OS-dependent property.
24+
/// `TimePoint` does not correct for OS-level differences in e.g.
25+
/// clock epochs. This makes it unsuitable for serialization in
26+
/// products that are expected to transit between machines.
27+
public struct TimePoint: Equatable, Comparable, Hashable {
28+
public var seconds: UInt64
29+
public var nanoseconds: UInt32
30+
31+
public init(seconds: UInt64, nanoseconds: UInt32) {
32+
self.seconds = seconds
33+
self.nanoseconds = nanoseconds
34+
}
35+
36+
public static func < (lhs: TimePoint, rhs: TimePoint) -> Bool {
37+
return (lhs.seconds, lhs.nanoseconds) < (rhs.seconds, rhs.nanoseconds)
38+
}
39+
}
40+
41+
extension TimePoint {
42+
public static func seconds(_ value: Int) -> TimePoint {
43+
precondition(value >= 0,
44+
"Duration value in seconds is \(value), but cannot be negative")
45+
return TimePoint(seconds: UInt64(value), nanoseconds: 0)
2146
}
22-
guard secsAndNanos.count == 2 else {
23-
throw Errors.needSecondsAndNanoseconds
47+
48+
public static func nanoseconds(_ value: Int) -> TimePoint {
49+
precondition(value >= 0,
50+
"Duration value in nanoseconds is \(value), but cannot be negative")
51+
let (seconds, nanos) = value.quotientAndRemainder(dividingBy: TimePoint.nanosecondsPerSecond)
52+
return TimePoint(seconds: UInt64(seconds),
53+
nanoseconds: UInt32(nanos))
54+
}
55+
}
56+
57+
extension TimePoint: AdditiveArithmetic {
58+
public static var zero: TimePoint {
59+
return .seconds(0)
60+
}
61+
62+
public static func + (lhs: TimePoint, rhs: TimePoint) -> TimePoint {
63+
// Add raw operands
64+
var seconds = lhs.seconds + rhs.seconds
65+
var nanos = lhs.nanoseconds + rhs.nanoseconds
66+
// Normalize nanoseconds
67+
if nanos >= TimePoint.nanosecondsPerSecond {
68+
nanos -= UInt32(TimePoint.nanosecondsPerSecond)
69+
seconds += 1
2470
}
25-
self = Self(legacyDriverSecs: secsAndNanos[0], nanos: secsAndNanos[1])
71+
return TimePoint(seconds: seconds, nanoseconds: nanos)
2672
}
27-
init(legacyDriverSecs secs: Int, nanos: Int) {
28-
self = Date(timeIntervalSince1970: Double(secs) + Double(nanos) / 1e9)
73+
74+
public static func - (lhs: TimePoint, rhs: TimePoint) -> TimePoint {
75+
// Subtract raw operands
76+
var seconds = lhs.seconds - rhs.seconds
77+
// Normalize nanoseconds
78+
let nanos: UInt32
79+
if lhs.nanoseconds >= rhs.nanoseconds {
80+
nanos = lhs.nanoseconds - rhs.nanoseconds
81+
} else {
82+
// Subtract nanoseconds with carry - order of operations here
83+
// is important to avoid overflow.
84+
nanos = lhs.nanoseconds + UInt32(TimePoint.nanosecondsPerSecond) - rhs.nanoseconds
85+
seconds -= 1
86+
}
87+
return TimePoint(seconds: seconds, nanoseconds: nanos)
2988
}
30-
var legacyDriverSecsAndNanos: [Int] {
31-
let totalSecs = timeIntervalSince1970
32-
let secs = Int( floor(totalSecs))
33-
let nanos = Int((totalSecs - floor(totalSecs)) * 1e9)
34-
return [secs, nanos]
89+
}
90+
91+
extension TimePoint{
92+
public static func now() -> TimePoint {
93+
#if os(Windows)
94+
var ftTime: FILETIME = FILETIME()
95+
GetSystemTimePreciseAsFileTime(&ftTime)
96+
97+
let result: UInt64 = (UInt64(ftTime.dwLowDateTime) << 0)
98+
+ (UInt64(ftTime.dwHighDateTime) << 32)
99+
// Windows ticks in 100 nanosecond intervals.
100+
return .seconds(Int(result / 10_000_000))
101+
#else
102+
var tv = timeval()
103+
gettimeofday(&tv, nil)
104+
return TimePoint(seconds: UInt64(tv.tv_sec),
105+
nanoseconds: UInt32(tv.tv_usec) * UInt32(Self.nanosecondsPerMicrosecond))
106+
#endif
35107
}
108+
109+
public static var distantPast: TimePoint {
110+
return .zero
111+
}
112+
113+
public static var distantFuture: TimePoint {
114+
// N.B. This is the seconds value of `Foundation.Date.distantFuture.timeIntervalSince1970`.
115+
// At time of writing, this is January 1, 4001 at 12:00:00 AM GMT, which is
116+
// far enough in the future that it's a reasonable time point for us to
117+
// compare against.
118+
//
119+
// However, it is important to note that Foundation's value cannot be both
120+
// fixed in time AND correct because of leap seconds and other calendrical
121+
// oddities.
122+
//
123+
// Other candidates include std::chrono's std::time_point::max - which is
124+
// about 9223372036854775807 - enough to comfortably bound the age of the
125+
// universe.
126+
return .seconds(64_092_211_200)
127+
}
128+
}
129+
130+
extension TimePoint {
131+
fileprivate static let nanosecondsPerSecond: Int = 1_000_000_000
132+
fileprivate static let nanosecondsPerMillisecond: Int = 1_000_000
133+
fileprivate static let nanosecondsPerMicrosecond: Int = 1_000
134+
fileprivate static let millisecondsPerSecond: Int = 1_000
135+
fileprivate static let microsecondsPerSecond: Int = 1_000_000
36136
}

0 commit comments

Comments
 (0)