diff --git a/Sources/FoundationEssentials/CMakeLists.txt b/Sources/FoundationEssentials/CMakeLists.txt index a5a1e9c79..36b6e34cb 100644 --- a/Sources/FoundationEssentials/CMakeLists.txt +++ b/Sources/FoundationEssentials/CMakeLists.txt @@ -21,6 +21,7 @@ add_library(FoundationEssentials ComparisonResult.swift Date.swift DateInterval.swift + DoubleDouble.swift FoundationEssentials.swift IndexPath.swift LockedState.swift diff --git a/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift b/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift index b7809b9b0..4f5cc4885 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift @@ -1448,30 +1448,39 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable } func dateInterval(of component: Calendar.Component, for date: Date) -> DateInterval? { - let time = date.timeIntervalSinceReferenceDate + let approximateTime = date._time.head var effectiveUnit = component switch effectiveUnit { case .calendar, .timeZone, .isLeapMonth, .isRepeatedDay: return nil case .era: - if time < -63113904000.0 { + if approximateTime < -63113904000.0 { return DateInterval(start: Date(timeIntervalSinceReferenceDate: -63113904000.0 - inf_ti), duration: inf_ti) } else { return DateInterval(start: Date(timeIntervalSinceReferenceDate: -63113904000.0), duration: inf_ti) } case .hour: - let ti = Double(timeZone.secondsFromGMT(for: date)) - var fixedTime = time + ti // compute local time - fixedTime = floor(fixedTime / 3600.0) * 3600.0 - fixedTime = fixedTime - ti // compute GMT - return DateInterval(start: Date(timeIntervalSinceReferenceDate: fixedTime), duration: 3600.0) + // Local hours may not be aligned to GMT hours, so we have to apply + // the time zone adjustment before rounding down, then unapply it. + let offset = Double(timeZone.secondsFromGMT(for: date)) + let start = ((date._time + offset)/3600).floor() * 3600 - offset + return DateInterval( + start: Date(start), + duration: 3600 + ) case .minute: - return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time / 60.0) * 60.0), duration: 60.0) + return DateInterval( + start: Date((date._time/60).floor() * 60), + duration: 60 + ) case .second: - return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time)), duration: 1.0) + return DateInterval(start: Date(date._time.floor()), duration: 1) case .nanosecond: - return DateInterval(start: Date(timeIntervalSinceReferenceDate: floor(time * 1.0e+9) * 1.0e-9), duration: 1.0e-9) + return DateInterval( + start: Date((date._time*1e9).floor() / 1e9), + duration: 1e-9 + ) case .year, .yearForWeekOfYear, .quarter, .month, .day, .dayOfYear, .weekOfMonth, .weekOfYear: // Continue to below break diff --git a/Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift b/Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift index dd8740236..8d9a32abc 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift @@ -93,8 +93,8 @@ extension Calendar { /// value is used as a lower bound for ``nextBaseRecurrenceDate()``. let rangeLowerBound: Date? - /// The start date's nanoseconds component - let startDateNanoseconds: TimeInterval + /// The start date's fractional seconds component + let fractionalSeconds: TimeInterval /// How many occurrences have been found so far var resultsFound = 0 @@ -131,7 +131,10 @@ extension Calendar { } self.recurrence = recurrence - self.start = start + // round start down to whole seconds, set aside fraction. + let wholeSeconds = start._time.floor() + fractionalSeconds = (start._time - wholeSeconds).head + self.start = Date(wholeSeconds) self.range = range let frequency = recurrence.frequency @@ -233,9 +236,7 @@ extension Calendar { case .monthly: [.second, .minute, .hour, .day] case .yearly: [.second, .minute, .hour, .day, .month, .isLeapMonth] } - var componentsForEnumerating = recurrence.calendar._dateComponents(components, from: start) - - startDateNanoseconds = start.timeIntervalSinceReferenceDate.truncatingRemainder(dividingBy: 1) + var componentsForEnumerating = recurrence.calendar._dateComponents(components, from: start) let expansionChangesDay = dayOfYearAction == .expand || dayOfMonthAction == .expand || weekAction == .expand || weekdayAction == .expand let expansionChangesMonth = dayOfYearAction == .expand || monthAction == .expand || weekAction == .expand @@ -427,11 +428,11 @@ extension Calendar { recurrence._limitTimeComponent(.second, dates: &dates, anchor: anchor) } - if startDateNanoseconds > 0 { + if fractionalSeconds != 0 { // `_dates(startingAfter:)` above returns whole-second dates, // so we need to restore the nanoseconds value present in the original start date. for idx in dates.indices { - dates[idx] += startDateNanoseconds + dates[idx] += fractionalSeconds } } dates = dates.filter { $0 >= self.start } diff --git a/Sources/FoundationEssentials/Date.swift b/Sources/FoundationEssentials/Date.swift index 4db2a7367..453b7853f 100644 --- a/Sources/FoundationEssentials/Date.swift +++ b/Sources/FoundationEssentials/Date.swift @@ -34,12 +34,64 @@ public typealias TimeInterval = Double 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`. */ @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) -public struct Date : Comparable, Hashable, Equatable, Sendable { - - internal var _time : TimeInterval +public struct Date: Comparable, Hashable, Equatable, Sendable { + /* Date is internally represented as a sum of two Doubles. + + Previously Date was backed by a single Double measuring time since + Jan 1 2001 in seconds. Because Double's precision is non-uniform, this + means that times within a hundred days of the epoch are represented + with approximately nanosecond precision, but as you get farther away + from that date the precision decreases. For times close to the time + at which this comment was written, accuracy has been reduced to about + 100ns. + + The obvious thing would be to adopt an integer-based representation + similar to C's timespec (32b nanoseconds, 64b seconds) or Swift's + Duration (128b attoseconds). These representations suffer from a few + difficulties: + + - Existing API on Date takes and produces `TimeInterval` (aka Double). + Making Date use an integer representation internally would mean that + existing users of the public API would suddently start getting + different results for computations that were previously exact; even + though we could add new API, and the overall system would be more + precise, this would be a surprisingly subtle change for users to + navigate. + + - We have been told that some software interprets the raw bytes of Date + as a Double for the purposes of fast serialization. These packages + formally violate Foundation's API boundaries, but that doesn't help + users of those packages who would abruptly be broken by switching to + an integer representation. + + Using DoubleDouble instead navigates these problems fairly elegantly. + + - Because DoubleDouble is still a floating-point type, it still suffers + from non-uniform precision. However, because DoubleDouble is so + fantastically precise, it can represent dates out to ±2.5 quadrillion + years at ~nanosecond or better precision, so in practice this won't + be much of an issue. + + - Existing API on Date will produce exactly the same result as it did + previously in cases where those results were exact, minimizing + surprises. In cases where the existing API was not exact, it will + produce much more accurate results, even if users do not adopt new + API, because its internal calculations are now more precise. + + - Software that (incorrectly) interprets the raw bytes of Date as a + Double will get at least as accurate of a value as it did previously + (and often a more accurate value). */ + internal var _time: DoubleDouble + + internal init(_ time: DoubleDouble) { + self._time = time + } +} +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +extension Date { /// The number of seconds from 1 January 1970 to the reference date, 1 January 2001. - public static let timeIntervalBetween1970AndReferenceDate : TimeInterval = 978307200.0 + public static let timeIntervalBetween1970AndReferenceDate: TimeInterval = 978307200.0 /// The number of seconds from 1 January 1601 to the reference date, 1 January 2001. internal static let timeIntervalBetween1601AndReferenceDate: TimeInterval = 12622780800.0 @@ -51,17 +103,23 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { /// Returns a `Date` initialized to the current date and time. public init() { - _time = Self.getCurrentAbsoluteTime() + _time = .init(uncheckedHead: Self.getCurrentAbsoluteTime(), tail: 0) } /// Returns a `Date` initialized relative to the current date and time by a given number of seconds. public init(timeIntervalSinceNow: TimeInterval) { - self.init(timeIntervalSinceReferenceDate: timeIntervalSinceNow + Self.getCurrentAbsoluteTime()) + self.init(.sum( + Self.getCurrentAbsoluteTime(), + timeIntervalSinceNow + )) } /// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 1970 by a given number of seconds. public init(timeIntervalSince1970: TimeInterval) { - self.init(timeIntervalSinceReferenceDate: timeIntervalSince1970 - Date.timeIntervalBetween1970AndReferenceDate) + self.init(.sum( + timeIntervalSince1970, + -Date.timeIntervalBetween1970AndReferenceDate + )) } /** @@ -71,12 +129,12 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { - Parameter date: The reference date. */ public init(timeInterval: TimeInterval, since date: Date) { - self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate + timeInterval) + self.init(date._time + timeInterval) } /// Returns a `Date` initialized relative to 00:00:00 UTC on 1 January 2001 by a given number of seconds. public init(timeIntervalSinceReferenceDate ti: TimeInterval) { - _time = ti + _time = .init(uncheckedHead: ti, tail: 0) } /** @@ -85,7 +143,7 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { 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). */ public var timeIntervalSinceReferenceDate: TimeInterval { - return _time + return _time.head } /** @@ -100,7 +158,7 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { - SeeAlso: `timeIntervalSinceReferenceDate` */ public func timeIntervalSince(_ date: Date) -> TimeInterval { - return self.timeIntervalSinceReferenceDate - date.timeIntervalSinceReferenceDate + return (self._time - date._time).head } /** @@ -173,9 +231,9 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { /// Compare two `Date` values. public func compare(_ other: Date) -> ComparisonResult { - if _time < other.timeIntervalSinceReferenceDate { + if _time < other._time { return .orderedAscending - } else if _time > other.timeIntervalSinceReferenceDate { + } else if _time > other._time { return .orderedDescending } else { return .orderedSame @@ -184,27 +242,27 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { /// Returns true if the two `Date` values represent the same point in time. public static func ==(lhs: Date, rhs: Date) -> Bool { - return lhs.timeIntervalSinceReferenceDate == rhs.timeIntervalSinceReferenceDate + return lhs._time == rhs._time } /// Returns true if the left hand `Date` is earlier in time than the right hand `Date`. public static func <(lhs: Date, rhs: Date) -> Bool { - return lhs.timeIntervalSinceReferenceDate < rhs.timeIntervalSinceReferenceDate + return lhs._time < rhs._time } /// Returns true if the left hand `Date` is later in time than the right hand `Date`. public static func >(lhs: Date, rhs: Date) -> Bool { - return lhs.timeIntervalSinceReferenceDate > rhs.timeIntervalSinceReferenceDate + return lhs._time > rhs._time } /// Returns a `Date` with a specified amount of time added to it. public static func +(lhs: Date, rhs: TimeInterval) -> Date { - return Date(timeIntervalSinceReferenceDate: lhs.timeIntervalSinceReferenceDate + rhs) + return Date(lhs._time + rhs) } /// Returns a `Date` with a specified amount of time subtracted from it. public static func -(lhs: Date, rhs: TimeInterval) -> Date { - return Date(timeIntervalSinceReferenceDate: lhs.timeIntervalSinceReferenceDate - rhs) + return Date(lhs._time - rhs) } /// Add a `TimeInterval` to a `Date`. @@ -220,7 +278,6 @@ public struct Date : Comparable, Hashable, Equatable, Sendable { public static func -=(lhs: inout Date, rhs: TimeInterval) { lhs = lhs - rhs } - } @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) diff --git a/Sources/FoundationEssentials/DateInterval.swift b/Sources/FoundationEssentials/DateInterval.swift index 04d2c55d1..835157808 100644 --- a/Sources/FoundationEssentials/DateInterval.swift +++ b/Sources/FoundationEssentials/DateInterval.swift @@ -12,30 +12,37 @@ /// 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. @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) -public struct DateInterval : Comparable, Hashable, Codable, Sendable { +public struct DateInterval: Comparable, Hashable, Codable, Sendable { /// The start date. - public var start : Date + public var start: Date + + /// Underlying storage for `duration` + internal var _duration: DoubleDouble /// The end date. /// /// - precondition: `end >= start` - public var end : Date { + public var end: Date { get { - return start + duration + return Date(start._time + _duration) } set { precondition(newValue >= start, "Reverse intervals are not allowed") - duration = newValue.timeIntervalSinceReferenceDate - start.timeIntervalSinceReferenceDate + _duration = (newValue._time - start._time) } } - - /// The duration. + + /// The duration /// /// - precondition: `duration >= 0` - public var duration : TimeInterval { - willSet { + public var duration: TimeInterval { + get { + _duration.head + } + set { precondition(newValue >= 0, "Negative durations are not allowed") + _duration = DoubleDouble(uncheckedHead: newValue, tail: 0) } } @@ -43,7 +50,7 @@ public struct DateInterval : Comparable, Hashable, Codable, Sendable { public init() { let d = Date() start = d - duration = 0 + _duration = .zero } /// Initialize a `DateInterval` with the specified start and end date. @@ -52,7 +59,7 @@ public struct DateInterval : Comparable, Hashable, Codable, Sendable { public init(start: Date, end: Date) { precondition(end >= start, "Reverse intervals are not allowed") self.start = start - duration = end.timeIntervalSince(start) + _duration = end._time - start._time } /// Initialize a `DateInterval` with the specified start date and duration. @@ -61,7 +68,7 @@ public struct DateInterval : Comparable, Hashable, Codable, Sendable { public init(start: Date, duration: TimeInterval) { precondition(duration >= 0, "Negative durations are not allowed") self.start = start - self.duration = duration + _duration = DoubleDouble(uncheckedHead: duration, tail: 0) } /** @@ -162,6 +169,24 @@ public struct DateInterval : Comparable, Hashable, Codable, Sendable { public static func <(lhs: DateInterval, rhs: DateInterval) -> Bool { return lhs.compare(rhs) == .orderedAscending } + + enum CodingKeys: String, CodingKey { + case start = "start" + case duration = "duration" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let start = try container.decode(Date.self, forKey: .start) + let duration = try container.decode(TimeInterval.self, forKey: .duration) + self.init(start: start, duration: duration) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(start, forKey: .start) + try container.encode(duration, forKey: .duration) + } } @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) diff --git a/Sources/FoundationEssentials/DoubleDouble.swift b/Sources/FoundationEssentials/DoubleDouble.swift new file mode 100644 index 000000000..b8d58ddf4 --- /dev/null +++ b/Sources/FoundationEssentials/DoubleDouble.swift @@ -0,0 +1,186 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 numeric type that uses two Double values as its representation, providing +/// about 106 bits of precision with the same exponent range as Double. +/// +/// This type conforms to AdditiveArithmetic, Hashable and Comparable, but does +/// not conform to FloatingPoint or Numeric; it implements only the API surface +/// that is necessary to serve as an internal implementation detail of Date. +internal struct DoubleDouble { + + private let storage: (Double, Double) + + /// A double-double value constructed by specifying the head and tail. + /// + /// This is an unchecked operation because it does not enforce the + /// invariant that head + tail == head in release builds, which is + /// necessary for subsequent arithmetic operations to behave correctly. + @_transparent + init(uncheckedHead head: Double, tail: Double) { + assert(!head.isFinite || head + tail == head) + storage = (head, tail) + } + + /// The high-order Double. + /// + /// This property does not have a setter because `head` should pretty much + /// never be set independently of `tail`, so as to maintain the invariant + /// that `head + tail == head`. You can use `init(uncheckedHead:tail:)` + /// to directly construct DoubleDouble values, which will enforce the + /// invariant in debug builds. + @_transparent + var head: Double { storage.0 } + + /// The low-order Double. + /// + /// This property does not have a setter because `tail` should pretty much + /// never be set independently of `head`, so as to maintain the invariant + /// that `head + tail == head`. You can use `init(uncheckedHead:tail:)` + /// to directly construct DoubleDouble values, which will enforce the + /// invariant in debug builds. + @_transparent + var tail: Double { storage.1 } + + /// `a + b` represented as a normalized DoubleDouble. + /// + /// Computed via the [2Sum algorithm](https://en.wikipedia.org/wiki/2Sum). + @inlinable + static func sum(_ a: Double, _ b: Double) -> DoubleDouble { + let head = a + b + let x = head - b + let y = head - x + let tail = (a - x) + (b - y) + return DoubleDouble(uncheckedHead: head, tail: tail) + } + + /// `a + b` represented as a normalized DoubleDouble. + /// + /// Computed via the [Fast2Sum algorithm](https://en.wikipedia.org/wiki/2Sum). + /// + /// - Precondition: + /// `large` and `small` must be such that `sum(large:small:)` + /// produces the same result as `sum(_:_:)` would. A sufficient condition + /// is that `|large| >= |small|`, but this is not necessary, so we do not + /// enforce it via an assert. Instead this function asserts that the result + /// is the same as that produced by `sum(_:_:)` in Debug builds. This is + /// unchecked in Release. + @inlinable + static func sum(large a: Double, small b: Double) -> DoubleDouble { + let head = a + b + let tail = a - head + b + let result = DoubleDouble(uncheckedHead: head, tail: tail) + assert(!head.isFinite || result == sum(a, b)) + return result + } + + /// `a * b` represented as a normalized DoubleDouble. + @inlinable + static func product(_ a: Double, _ b: Double) -> DoubleDouble { + let head = a * b + let tail = (-head).addingProduct(a, b) + return DoubleDouble(uncheckedHead: head, tail: tail) + } +} + +extension DoubleDouble: Comparable { + @_transparent + static func ==(a: Self, b: Self) -> Bool { + a.head == b.head && a.tail == b.tail + } + + @_transparent + static func <(a: Self, b: Self) -> Bool { + a.head < b.head || a.head == b.head && a.tail < b.tail + } +} + +extension DoubleDouble: Hashable { + @_transparent + func hash(into hasher: inout Hasher) { + hasher.combine(head) + hasher.combine(tail) + } +} + +extension DoubleDouble: AdditiveArithmetic { + @inlinable + static var zero: DoubleDouble { + Self(uncheckedHead: 0, tail: 0) + } + + @inlinable + static func +(a: DoubleDouble, b: DoubleDouble) -> DoubleDouble { + let heads = sum(a.head, b.head) + let tails = sum(a.tail, b.tail) + let first = sum(large: heads.head, small: heads.tail + tails.head) + return sum(large: first.head, small: first.tail + tails.tail) + } + + /// Equivalent to `a + DoubleDouble(uncheckedHead: b, tail: 0)` but + /// computed more efficiently. + @inlinable + static func +(a: DoubleDouble, b: Double) -> DoubleDouble { + let heads = sum(a.head, b) + let first = sum(large: heads.head, small: heads.tail + a.tail) + return sum(large: first.head, small: first.tail) + } + + @inlinable + prefix static func -(a: DoubleDouble) -> DoubleDouble { + DoubleDouble(uncheckedHead: -a.head, tail: -a.tail) + } + + @inlinable + static func -(a: DoubleDouble, b: DoubleDouble) -> DoubleDouble { + a + (-b) + } + + /// Equivalent to `a - DoubleDouble(uncheckedHead: b, tail: 0)` but + /// computed more efficiently. + @inlinable + static func -(a: DoubleDouble, b: Double) -> DoubleDouble { + a + (-b) + } +} + +extension DoubleDouble { + @inlinable + static func *(a: DoubleDouble, b: Double) -> DoubleDouble { + let tmp = product(a.head, b) + return DoubleDouble( + uncheckedHead: tmp.head, + tail: tmp.tail.addingProduct(a.tail, b) + ) + } + + @inlinable + static func /(a: DoubleDouble, b: Double) -> DoubleDouble { + let head = a.head/b + let residual = a.head.addingProduct(-head, b) + a.tail + return .sum(large: head, small: residual/b) + } +} + +extension DoubleDouble { + // This value rounded down to an integer. + @inlinable + func floor() -> DoubleDouble { + let approx = head.rounded(.down) + // If head was already an integer, round tail down and renormalize. + if approx == head { + return .sum(large: head, small: tail.rounded(.down)) + } + // Head was not an integer; we can simply discard tail. + return DoubleDouble(uncheckedHead: approx, tail: 0) + } +} diff --git a/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift b/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift index 9e3d3c05f..31377c924 100644 --- a/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift +++ b/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift @@ -277,7 +277,13 @@ private struct GregorianCalendarTests { func test(addField field: Calendar.Component, value: Int, to addingToDate: Date, wrap: Bool, expectedDate: Date, sourceLocation: SourceLocation = #_sourceLocation) { let components = DateComponents(component: field, value: value)! let result = gregorianCalendar.date(byAdding: components, to: addingToDate, wrappingComponents: wrap)! - #expect(result == expectedDate, sourceLocation: sourceLocation) + // These tests were written when Date used a 64b representation; + // we'll add new tests that validate the low-word of the 128b + // Date, but these old tests should continue passing if we only + // look at the high word as vended by tISRD. + #expect(result.timeIntervalSinceReferenceDate == + expectedDate.timeIntervalSinceReferenceDate, + sourceLocation: sourceLocation) } date = Date(timeIntervalSince1970: 825723300.0) @@ -399,7 +405,13 @@ private struct GregorianCalendarTests { func test(addField field: Calendar.Component, value: Int, to addingToDate: Date, wrap: Bool, expectedDate: Date, sourceLocation: SourceLocation = #_sourceLocation) { let components = DateComponents(component: field, value: value)! let result = gregorianCalendar.date(byAdding: components, to: addingToDate, wrappingComponents: wrap)! - #expect(result == expectedDate, sourceLocation: sourceLocation) + // These tests were written when Date used a 64b representation; + // we'll add new tests that validate the low-word of the 128b + // Date, but these old tests should continue passing if we only + // look at the high word as vended by tISRD. + #expect(result.timeIntervalSinceReferenceDate == + expectedDate.timeIntervalSinceReferenceDate, + sourceLocation: sourceLocation) } date = Date(timeIntervalSince1970: 62135596800.0) // 3939-01-01 @@ -826,7 +838,11 @@ private struct GregorianCalendarTests { let new_end = new?.end #expect(new_start == start, "interval start did not match", sourceLocation: sourceLocation) - #expect(new_end == end, "interval end did not match", sourceLocation: sourceLocation) + // These tests were written when Date used a 64b representation; + // we'll add new tests that validate the low-word of the 128b + // Date, but these old tests should continue passing if we only + // look at the high word as vended by tISRD. + #expect(new_end?.timeIntervalSinceReferenceDate == end?.timeIntervalSinceReferenceDate, "interval end did not match", sourceLocation: sourceLocation) } var date: Date @@ -838,7 +854,10 @@ private struct GregorianCalendarTests { test(.hour, date, expectedStart: Date(timeIntervalSince1970: 820454400.0), end: Date(timeIntervalSince1970: 820458000.0)) test(.minute, date, expectedStart: Date(timeIntervalSince1970: 820454400.0), end: Date(timeIntervalSince1970: 820454460.0)) test(.second, date, expectedStart: Date(timeIntervalSince1970: 820454400.0), end: Date(timeIntervalSince1970: 820454401.0)) + // Legacy test from 64b Date; expected end is the same as start due to rounding. test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: 820454400.0), end: Date(timeIntervalSince1970: 820454400.0)) + // Updated test for 128b Date to benefit from improved accuracy: + #expect(calendar.dateInterval(of: .nanosecond, for: date)?.end == Date(timeInterval: 1e-9, since: date)) test(.weekday, date, expectedStart: Date(timeIntervalSince1970: 820396800.0), end: Date(timeIntervalSince1970: 820483200.0)) test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: 820396800.0), end: Date(timeIntervalSince1970: 820483200.0)) test(.quarter, date, expectedStart: Date(timeIntervalSince1970: 812534400.0), end: Date(timeIntervalSince1970: 820483200.0)) @@ -869,7 +888,7 @@ private struct GregorianCalendarTests { test(.hour, date, expectedStart: Date(timeIntervalSince1970: -62135769600.0), end: Date(timeIntervalSince1970: -62135766000.0)) test(.minute, date, expectedStart: Date(timeIntervalSince1970: -62135769600.0), end: Date(timeIntervalSince1970: -62135769540.0)) test(.second, date, expectedStart: Date(timeIntervalSince1970: -62135769600.0), end: Date(timeIntervalSince1970: -62135769599.0)) - test(.nanosecond, date, expectedStart: Date(timeIntervalSince1970: -62135769600.00001), end: Date(timeIntervalSince1970: -62135769600.00001)) + test(.nanosecond, date, expectedStart: date, end: Date(timeInterval: 1e-9, since: date)) test(.weekday, date, expectedStart: Date(timeIntervalSince1970: -62135827200.0), end: Date(timeIntervalSince1970: -62135740800.0)) test(.weekdayOrdinal, date, expectedStart: Date(timeIntervalSince1970: -62135827200.0), end: Date(timeIntervalSince1970: -62135740800.0)) test(.quarter, date, expectedStart: Date(timeIntervalSince1970: -62143689600.0), end: Date(timeIntervalSince1970: -62135740800.0)) diff --git a/Tests/FoundationInternationalizationTests/CalendarTests.swift b/Tests/FoundationInternationalizationTests/CalendarTests.swift index 8f5b3db3a..a84a1022e 100644 --- a/Tests/FoundationInternationalizationTests/CalendarTests.swift +++ b/Tests/FoundationInternationalizationTests/CalendarTests.swift @@ -1419,7 +1419,7 @@ private struct CalendarTests { func test(_ start: Date, _ end: Date) throws { let components = c.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond, .weekOfMonth], from: start, to: end) let added = try #require(c.date(byAdding: components, to: start)) - #expect(added == end, "actual: \(s.format(added)), expected: \(s.format(end))") + #expect(added.timeIntervalSinceReferenceDate == end.timeIntervalSinceReferenceDate, "actual: \(s.format(added)), expected: \(s.format(end))") } // 2024-03-09T02:34:36-0800, 2024-03-17T03:34:36-0700, 10:34:36 UTC diff --git a/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift b/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift index 6e684a957..f9370c88d 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/ParseStrategy+RegexComponentTests.swift @@ -62,8 +62,6 @@ private struct ParseStrategyMatchTests { #expect(res.output.1 == expectedDate) } -// https://github.com/apple/swift-foundation/issues/60 -#if FOUNDATION_FRAMEWORK @Test func apiStatement() { let statement = """ @@ -213,9 +211,8 @@ DEBIT Mar 31/20 March Payment to BoA -USD 52,249.98 #expect(match.output.1 == "MergeableSetTests") #expect(match.output.2 == "started") // dateFormatter.date(from: "2021-07-08 10:19:35.418")! - #expect(match.output.3 == Date(timeIntervalSinceReferenceDate: 647432375.418)) + #expect(match.output.3.timeIntervalSinceReferenceDate == 647432375.418) } -#endif @Test func variousDatesAndTimes() { func verify(_ str: String, _ strategy: Date.ParseStrategy, _ expected: String?, sourceLocation: SourceLocation = #_sourceLocation) { diff --git a/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift b/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift index 195c18912..3d389281c 100644 --- a/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift +++ b/Tests/FoundationInternationalizationTests/GegorianCalendarInternationalizationTests.swift @@ -1461,8 +1461,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828262987.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 829468987.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828262987.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828867787.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828867787.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 828871387.0) // 1996-04-07T03:03:07-0700 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828871387.0)) @@ -1489,8 +1489,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828270187.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 829476187.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828270187.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828871387.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828871387.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 828874987.0) // 1996-04-07T04:03:07-0700 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828874987.0)) @@ -1517,8 +1517,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828273787.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 829479787.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828273787.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828874987.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828874987.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846403387.0) // 1996-10-27T01:03:07-0700 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846403387.0)) @@ -1547,8 +1547,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845798587.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 847011787.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845798587.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846403387.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846403387.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846406987.0) // 1996-10-27T01:03:07-0800 // Previously this returns 1996-10-27T01:03:07-0700 @@ -1578,8 +1578,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845798587.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 847011787.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845798587.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846406987.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846406987.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846410587.0) // 1996-10-27T02:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846410587.0)) @@ -1606,8 +1606,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845802187.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 847015387.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845802187.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846410587.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846410587.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846414187.0) // 1996-10-27T03:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846414187.0)) @@ -1634,8 +1634,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845805787.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 847018987.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845805787.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846414187.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846414187.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 814953787.0) // 1995-10-29T01:03:07-0700 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814953787.0)) @@ -1664,8 +1664,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814348987.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 815562187.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814348987.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814953787.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814953787.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 814957387.0) // 1995-10-29T01:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814957387.0)) @@ -1692,8 +1692,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814348987.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 815562187.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814348987.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814957387.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814957387.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 814960987.0) // 1995-10-29T02:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814960987.0)) @@ -1720,8 +1720,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814352587.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 815565787.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814352587.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814960987.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814960987.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 814964587.0) // 1995-10-29T03:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814964587.0)) @@ -1748,8 +1748,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814356187.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 815569387.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814356187.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 814964587.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 814964587.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) } @Test func add_Wrap_DST() { @@ -1789,8 +1789,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828262987.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 829468987.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 830851387.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828867787.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828867787.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 828871387.0) // 1996-04-07T03:03:07-0700 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828871387.0)) @@ -1817,8 +1817,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828270187.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 829476187.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 830858587.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828871387.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828871387.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 828874987.0) // 1996-04-07T04:03:07-0700 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828874987.0)) @@ -1845,8 +1845,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828273787.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 829479787.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 830862187.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 828874987.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 828874987.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846406987.0) // 1996-10-27T01:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846406987.0)) @@ -1873,8 +1873,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845798587.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846752587.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845798587.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846406987.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846406987.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846410587.0) // 1996-10-27T02:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846410587.0)) @@ -1901,8 +1901,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845802187.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846756187.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845802187.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846410587.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846410587.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) date = Date(timeIntervalSince1970: 846414187.0) // 1996-10-27T03:03:07-0800 test(addField: .era, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846414187.0)) @@ -1929,8 +1929,8 @@ private struct GregorianCalendarInternationalizationTests { test(addField: .weekOfYear, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845805787.0)) test(addField: .weekOfMonth, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846759787.0)) test(addField: .weekOfMonth, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 845805787.0)) - test(addField: .nanosecond, value: 1, to: date, expectedDate: Date(timeIntervalSince1970: 846414187.0)) - test(addField: .nanosecond, value: -1, to: date, expectedDate: Date(timeIntervalSince1970: 846414187.0)) + test(addField: .nanosecond, value: 1, to: date, expectedDate: date.addingTimeInterval(1e-9)) + test(addField: .nanosecond, value: -1, to: date, expectedDate: date.addingTimeInterval(-1e-9)) } @Test func ordinality_DST() { @@ -2448,21 +2448,21 @@ private struct GregorianCalendarInternationalizationTests { dc_customCalendarAndTimeZone.timeZone = .init(secondsFromGMT: 28800) // calendar.timeZone = UTC+0, dc.calendar.timeZone = UTC-7, dc.timeZone = UTC+8 // expect local time in dc.timeZone (UTC+8) - #expect(gregorianCalendar.date(from: dc_customCalendarAndTimeZone)! == Date(timeIntervalSinceReferenceDate: 679024975.891)) // 2022-07-09T02:02:55Z + #expect(gregorianCalendar.date(from: dc_customCalendarAndTimeZone)!.timeIntervalSinceReferenceDate == 679024975.891) // 2022-07-09T02:02:55Z var dc_customCalendar = dc dc_customCalendar.calendar = dcCalendar dc_customCalendar.timeZone = nil // calendar.timeZone = UTC+0, dc.calendar.timeZone = UTC-7, dc.timeZone = nil // expect local time in calendar.timeZone (UTC+0) - #expect(gregorianCalendar.date(from: dc_customCalendar)! == Date(timeIntervalSinceReferenceDate: 679053775.891)) // 2022-07-09T10:02:55Z + #expect(gregorianCalendar.date(from: dc_customCalendar)!.timeIntervalSinceReferenceDate == 679053775.891) // 2022-07-09T10:02:55Z var dc_customTimeZone = dc_customCalendarAndTimeZone dc_customTimeZone.calendar = nil dc_customTimeZone.timeZone = .init(secondsFromGMT: 28800) // calendar.timeZone = UTC+0, dc.calendar = nil, dc.timeZone = UTC+8 // expect local time in dc.timeZone (UTC+8) - #expect(gregorianCalendar.date(from: dc_customTimeZone)! == Date(timeIntervalSinceReferenceDate: 679024975.891)) // 2022-07-09T02:02:55Z + #expect(gregorianCalendar.date(from: dc_customTimeZone)!.timeIntervalSinceReferenceDate == 679024975.891) // 2022-07-09T02:02:55Z let dcCalendar_noTimeZone = Calendar(identifier: .japanese, locale: Locale(identifier: ""), timeZone: .gmt, firstWeekday: 1, minimumDaysInFirstWeek: 1, gregorianStartDate: nil) var dc_customCalendarNoTimeZone_customTimeZone = dc @@ -2470,7 +2470,7 @@ private struct GregorianCalendarInternationalizationTests { dc_customCalendarNoTimeZone_customTimeZone.timeZone = .init(secondsFromGMT: 28800) // calendar.timeZone = UTC+0, dc.calendar.timeZone = nil, dc.timeZone = UTC+8 // expect local time in dc.timeZone (UTC+8) - #expect(gregorianCalendar.date(from: dc_customCalendarNoTimeZone_customTimeZone)! == Date(timeIntervalSinceReferenceDate: 679024975.891)) // 2022-07-09T02:02:55Z + #expect(gregorianCalendar.date(from: dc_customCalendarNoTimeZone_customTimeZone)!.timeIntervalSinceReferenceDate == 679024975.891) // 2022-07-09T02:02:55Z } @Test func dateFromComponents_componentsTimeZoneConversion() {