Skip to content

Commit b7be535

Browse files
authored
Revert "128-bit Date (#3475)" (#3549) (#1581)
Reverting this from main for now to figure out test breakages
1 parent 357befc commit b7be535

File tree

10 files changed

+97
-392
lines changed

10 files changed

+97
-392
lines changed

Sources/FoundationEssentials/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ add_library(FoundationEssentials
2121
ComparisonResult.swift
2222
Date.swift
2323
DateInterval.swift
24-
DoubleDouble.swift
2524
FoundationEssentials.swift
2625
IndexPath.swift
2726
LockedState.swift

Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,39 +1448,30 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable
14481448
}
14491449

14501450
func dateInterval(of component: Calendar.Component, for date: Date) -> DateInterval? {
1451-
let approximateTime = date._time.head
1451+
let time = date.timeIntervalSinceReferenceDate
14521452
var effectiveUnit = component
14531453
switch effectiveUnit {
14541454
case .calendar, .timeZone, .isLeapMonth, .isRepeatedDay:
14551455
return nil
14561456
case .era:
1457-
if approximateTime < -63113904000.0 {
1457+
if time < -63113904000.0 {
14581458
return DateInterval(start: Date(timeIntervalSinceReferenceDate: -63113904000.0 - inf_ti), duration: inf_ti)
14591459
} else {
14601460
return DateInterval(start: Date(timeIntervalSinceReferenceDate: -63113904000.0), duration: inf_ti)
14611461
}
14621462

14631463
case .hour:
1464-
// Local hours may not be aligned to GMT hours, so we have to apply
1465-
// the time zone adjustment before rounding down, then unapply it.
1466-
let offset = Double(timeZone.secondsFromGMT(for: date))
1467-
let start = ((date._time + offset)/3600).floor() * 3600 - offset
1468-
return DateInterval(
1469-
start: Date(start),
1470-
duration: 3600
1471-
)
1464+
let ti = Double(timeZone.secondsFromGMT(for: date))
1465+
var fixedTime = time + ti // compute local time
1466+
fixedTime = floor(fixedTime / 3600.0) * 3600.0
1467+
fixedTime = fixedTime - ti // compute GMT
1468+
return DateInterval(start: Date(timeIntervalSinceReferenceDate: fixedTime), duration: 3600.0)
14721469
case .minute:
1473-
return DateInterval(
1474-
start: Date((date._time/60).floor() * 60),
1475-
duration: 60
1476-
)
1470+
return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time / 60.0) * 60.0), duration: 60.0)
14771471
case .second:
1478-
return DateInterval(start: Date(date._time.floor()), duration: 1)
1472+
return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time)), duration: 1.0)
14791473
case .nanosecond:
1480-
return DateInterval(
1481-
start: Date((date._time*1e9).floor() / 1e9),
1482-
duration: 1e-9
1483-
)
1474+
return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time * 1.0e+9) * 1.0e-9), duration: 1.0e-9)
14841475
case .year, .yearForWeekOfYear, .quarter, .month, .day, .dayOfYear, .weekOfMonth, .weekOfYear:
14851476
// Continue to below
14861477
break

Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ extension Calendar {
9393
/// value is used as a lower bound for ``nextBaseRecurrenceDate()``.
9494
let rangeLowerBound: Date?
9595

96-
/// The start date's fractional seconds component
97-
let fractionalSeconds: TimeInterval
96+
/// The start date's nanoseconds component
97+
let startDateNanoseconds: TimeInterval
9898

9999
/// How many occurrences have been found so far
100100
var resultsFound = 0
@@ -131,10 +131,7 @@ extension Calendar {
131131
}
132132
self.recurrence = recurrence
133133

134-
// round start down to whole seconds, set aside fraction.
135-
let wholeSeconds = start._time.floor()
136-
fractionalSeconds = (start._time - wholeSeconds).head
137-
self.start = Date(wholeSeconds)
134+
self.start = start
138135
self.range = range
139136

140137
let frequency = recurrence.frequency
@@ -236,7 +233,9 @@ extension Calendar {
236233
case .monthly: [.second, .minute, .hour, .day]
237234
case .yearly: [.second, .minute, .hour, .day, .month, .isLeapMonth]
238235
}
239-
var componentsForEnumerating = recurrence.calendar._dateComponents(components, from: start)
236+
var componentsForEnumerating = recurrence.calendar._dateComponents(components, from: start)
237+
238+
startDateNanoseconds = start.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: 1)
240239

241240
let expansionChangesDay = dayOfYearAction == .expand || dayOfMonthAction == .expand || weekAction == .expand || weekdayAction == .expand
242241
let expansionChangesMonth = dayOfYearAction == .expand || monthAction == .expand || weekAction == .expand
@@ -428,11 +427,11 @@ extension Calendar {
428427
recurrence._limitTimeComponent(.second, dates: &dates, anchor: anchor)
429428
}
430429

431-
if fractionalSeconds != 0 {
430+
if startDateNanoseconds > 0 {
432431
// `_dates(startingAfter:)` above returns whole-second dates,
433432
// so we need to restore the nanoseconds value present in the original start date.
434433
for idx in dates.indices {
435-
dates[idx] += fractionalSeconds
434+
dates[idx] += startDateNanoseconds
436435
}
437436
}
438437
dates = dates.filter { $0 >= self.start }

Sources/FoundationEssentials/Date.swift

Lines changed: 20 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -34,64 +34,12 @@ public typealias TimeInterval = Double
3434
A `Date` is independent of a particular calendar or time zone. To represent a `Date` to a user, you must interpret it in the context of a `Calendar`.
3535
*/
3636
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
37-
public struct Date: Comparable, Hashable, Equatable, Sendable {
38-
/* Date is internally represented as a sum of two Doubles.
39-
40-
Previously Date was backed by a single Double measuring time since
41-
Jan 1 2001 in seconds. Because Double's precision is non-uniform, this
42-
means that times within a hundred days of the epoch are represented
43-
with approximately nanosecond precision, but as you get farther away
44-
from that date the precision decreases. For times close to the time
45-
at which this comment was written, accuracy has been reduced to about
46-
100ns.
47-
48-
The obvious thing would be to adopt an integer-based representation
49-
similar to C's timespec (32b nanoseconds, 64b seconds) or Swift's
50-
Duration (128b attoseconds). These representations suffer from a few
51-
difficulties:
52-
53-
- Existing API on Date takes and produces `TimeInterval` (aka Double).
54-
Making Date use an integer representation internally would mean that
55-
existing users of the public API would suddently start getting
56-
different results for computations that were previously exact; even
57-
though we could add new API, and the overall system would be more
58-
precise, this would be a surprisingly subtle change for users to
59-
navigate.
60-
61-
- We have been told that some software interprets the raw bytes of Date
62-
as a Double for the purposes of fast serialization. These packages
63-
formally violate Foundation's API boundaries, but that doesn't help
64-
users of those packages who would abruptly be broken by switching to
65-
an integer representation.
66-
67-
Using DoubleDouble instead navigates these problems fairly elegantly.
68-
69-
- Because DoubleDouble is still a floating-point type, it still suffers
70-
from non-uniform precision. However, because DoubleDouble is so
71-
fantastically precise, it can represent dates out to ±2.5 quadrillion
72-
years at ~nanosecond or better precision, so in practice this won't
73-
be much of an issue.
74-
75-
- Existing API on Date will produce exactly the same result as it did
76-
previously in cases where those results were exact, minimizing
77-
surprises. In cases where the existing API was not exact, it will
78-
produce much more accurate results, even if users do not adopt new
79-
API, because its internal calculations are now more precise.
80-
81-
- Software that (incorrectly) interprets the raw bytes of Date as a
82-
Double will get at least as accurate of a value as it did previously
83-
(and often a more accurate value). */
84-
internal var _time: DoubleDouble
85-
86-
internal init(_ time: DoubleDouble) {
87-
self._time = time
88-
}
89-
}
37+
public struct Date : Comparable, Hashable, Equatable, Sendable {
38+
39+
internal var _time : TimeInterval
9040

91-
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
92-
extension Date {
9341
/// The number of seconds from 1 January 1970 to the reference date, 1 January 2001.
94-
public static let timeIntervalBetween1970AndReferenceDate: TimeInterval = 978307200.0
42+
public static let timeIntervalBetween1970AndReferenceDate : TimeInterval = 978307200.0
9543

9644
/// The number of seconds from 1 January 1601 to the reference date, 1 January 2001.
9745
internal static let timeIntervalBetween1601AndReferenceDate: TimeInterval = 12622780800.0
@@ -103,23 +51,17 @@ extension Date {
10351

10452
/// Returns a `Date` initialized to the current date and time.
10553
public init() {
106-
_time = .init(uncheckedHead: Self.getCurrentAbsoluteTime(), tail: 0)
54+
_time = Self.getCurrentAbsoluteTime()
10755
}
10856

10957
/// Returns a `Date` initialized relative to the current date and time by a given number of seconds.
11058
public init(timeIntervalSinceNow: TimeInterval) {
111-
self.init(.sum(
112-
Self.getCurrentAbsoluteTime(),
113-
timeIntervalSinceNow
114-
))
59+
self.init(timeIntervalSinceReferenceDate: timeIntervalSinceNow + Self.getCurrentAbsoluteTime())
11560
}
11661

11762
/// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 1970 by a given number of seconds.
11863
public init(timeIntervalSince1970: TimeInterval) {
119-
self.init(.sum(
120-
timeIntervalSince1970,
121-
-Date.timeIntervalBetween1970AndReferenceDate
122-
))
64+
self.init(timeIntervalSinceReferenceDate: timeIntervalSince1970 - Date.timeIntervalBetween1970AndReferenceDate)
12365
}
12466

12567
/**
@@ -129,12 +71,12 @@ extension Date {
12971
- Parameter date: The reference date.
13072
*/
13173
public init(timeInterval: TimeInterval, since date: Date) {
132-
self.init(date._time + timeInterval)
74+
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate + timeInterval)
13375
}
13476

13577
/// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 2001 by a given number of seconds.
13678
public init(timeIntervalSinceReferenceDate ti: TimeInterval) {
137-
_time = .init(uncheckedHead: ti, tail: 0)
79+
_time = ti
13880
}
13981

14082
/**
@@ -143,7 +85,7 @@ extension Date {
14385
This property's value is negative if the date object is earlier than the system's absolute reference date (00:00:00 UTC on 1 January 2001).
14486
*/
14587
public var timeIntervalSinceReferenceDate: TimeInterval {
146-
return _time.head
88+
return _time
14789
}
14890

14991
/**
@@ -158,7 +100,7 @@ extension Date {
158100
- SeeAlso: `timeIntervalSinceReferenceDate`
159101
*/
160102
public func timeIntervalSince(_ date: Date) -> TimeInterval {
161-
return (self._time - date._time).head
103+
return self.timeIntervalSinceReferenceDate - date.timeIntervalSinceReferenceDate
162104
}
163105

164106
/**
@@ -231,9 +173,9 @@ extension Date {
231173

232174
/// Compare two `Date` values.
233175
public func compare(_ other: Date) -> ComparisonResult {
234-
if _time < other._time {
176+
if _time < other.timeIntervalSinceReferenceDate {
235177
return .orderedAscending
236-
} else if _time > other._time {
178+
} else if _time > other.timeIntervalSinceReferenceDate {
237179
return .orderedDescending
238180
} else {
239181
return .orderedSame
@@ -242,27 +184,27 @@ extension Date {
242184

243185
/// Returns true if the two `Date` values represent the same point in time.
244186
public static func ==(lhs: Date, rhs: Date) -> Bool {
245-
return lhs._time == rhs._time
187+
return lhs.timeIntervalSinceReferenceDate == rhs.timeIntervalSinceReferenceDate
246188
}
247189

248190
/// Returns true if the left hand `Date` is earlier in time than the right hand `Date`.
249191
public static func <(lhs: Date, rhs: Date) -> Bool {
250-
return lhs._time < rhs._time
192+
return lhs.timeIntervalSinceReferenceDate < rhs.timeIntervalSinceReferenceDate
251193
}
252194

253195
/// Returns true if the left hand `Date` is later in time than the right hand `Date`.
254196
public static func >(lhs: Date, rhs: Date) -> Bool {
255-
return lhs._time > rhs._time
197+
return lhs.timeIntervalSinceReferenceDate > rhs.timeIntervalSinceReferenceDate
256198
}
257199

258200
/// Returns a `Date` with a specified amount of time added to it.
259201
public static func +(lhs: Date, rhs: TimeInterval) -> Date {
260-
return Date(lhs._time + rhs)
202+
return Date(timeIntervalSinceReferenceDate: lhs.timeIntervalSinceReferenceDate + rhs)
261203
}
262204

263205
/// Returns a `Date` with a specified amount of time subtracted from it.
264206
public static func -(lhs: Date, rhs: TimeInterval) -> Date {
265-
return Date(lhs._time - rhs)
207+
return Date(timeIntervalSinceReferenceDate: lhs.timeIntervalSinceReferenceDate - rhs)
266208
}
267209

268210
/// Add a `TimeInterval` to a `Date`.
@@ -278,6 +220,7 @@ extension Date {
278220
public static func -=(lhs: inout Date, rhs: TimeInterval) {
279221
lhs = lhs - rhs
280222
}
223+
281224
}
282225

283226
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
@@ -395,7 +338,7 @@ extension Date : ReferenceConvertible, _ObjectiveCBridgeable {
395338

396339
@_semantics("convertToObjectiveC")
397340
public func _bridgeToObjectiveC() -> NSDate {
398-
return NSDate(timeIntervalSinceReferenceDate: _time.head)
341+
return NSDate(timeIntervalSinceReferenceDate: _time)
399342
}
400343

401344
public static func _forceBridgeFromObjectiveC(_ x: NSDate, result: inout Date?) {

Sources/FoundationEssentials/DateInterval.swift

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,38 @@
1212

1313
/// DateInterval represents a closed date interval in the form of [startDate, endDate]. It is possible for the start and end dates to be the same with a duration of 0. DateInterval does not support reverse intervals i.e. intervals where the duration is less than 0 and the end date occurs earlier in time than the start date.
1414
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
15-
public struct DateInterval: Comparable, Hashable, Codable, Sendable {
15+
public struct DateInterval : Comparable, Hashable, Codable, Sendable {
1616

1717
/// The start date.
18-
public var start: Date
19-
20-
/// Underlying storage for `duration`
21-
internal var _duration: DoubleDouble
18+
public var start : Date
2219

2320
/// The end date.
2421
///
2522
/// - precondition: `end >= start`
26-
public var end: Date {
23+
public var end : Date {
2724
get {
28-
return Date(start._time + _duration)
25+
return start + duration
2926
}
3027
set {
3128
precondition(newValue >= start, "Reverse intervals are not allowed")
32-
_duration = (newValue._time - start._time)
29+
duration = newValue.timeIntervalSinceReferenceDate - start.timeIntervalSinceReferenceDate
3330
}
3431
}
35-
36-
/// The duration
32+
33+
/// The duration.
3734
///
3835
/// - precondition: `duration >= 0`
39-
public var duration: TimeInterval {
40-
get {
41-
_duration.head
42-
}
43-
set {
36+
public var duration : TimeInterval {
37+
willSet {
4438
precondition(newValue >= 0, "Negative durations are not allowed")
45-
_duration = DoubleDouble(uncheckedHead: newValue, tail: 0)
4639
}
4740
}
4841

4942
/// Initializes a `DateInterval` with start and end dates set to the current date and the duration set to `0`.
5043
public init() {
5144
let d = Date()
5245
start = d
53-
_duration = .zero
46+
duration = 0
5447
}
5548

5649
/// Initialize a `DateInterval` with the specified start and end date.
@@ -59,7 +52,7 @@ public struct DateInterval: Comparable, Hashable, Codable, Sendable {
5952
public init(start: Date, end: Date) {
6053
precondition(end >= start, "Reverse intervals are not allowed")
6154
self.start = start
62-
_duration = end._time - start._time
55+
duration = end.timeIntervalSince(start)
6356
}
6457

6558
/// Initialize a `DateInterval` with the specified start date and duration.
@@ -68,7 +61,7 @@ public struct DateInterval: Comparable, Hashable, Codable, Sendable {
6861
public init(start: Date, duration: TimeInterval) {
6962
precondition(duration >= 0, "Negative durations are not allowed")
7063
self.start = start
71-
_duration = DoubleDouble(uncheckedHead: duration, tail: 0)
64+
self.duration = duration
7265
}
7366

7467
/**
@@ -169,24 +162,6 @@ public struct DateInterval: Comparable, Hashable, Codable, Sendable {
169162
public static func <(lhs: DateInterval, rhs: DateInterval) -> Bool {
170163
return lhs.compare(rhs) == .orderedAscending
171164
}
172-
173-
enum CodingKeys: String, CodingKey {
174-
case start = "start"
175-
case duration = "duration"
176-
}
177-
178-
public init(from decoder: Decoder) throws {
179-
let container = try decoder.container(keyedBy: CodingKeys.self)
180-
let start = try container.decode(Date.self, forKey: .start)
181-
let duration = try container.decode(TimeInterval.self, forKey: .duration)
182-
self.init(start: start, duration: duration)
183-
}
184-
185-
public func encode(to encoder: Encoder) throws {
186-
var container = encoder.container(keyedBy: CodingKeys.self)
187-
try container.encode(start, forKey: .start)
188-
try container.encode(duration, forKey: .duration)
189-
}
190165
}
191166

192167
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)

0 commit comments

Comments
 (0)