From ba396c0e342cf1594a2eaff5bd3bb8897b09c90e Mon Sep 17 00:00:00 2001 From: Jonathan Flat <50605158+jrflat@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:44:37 -0600 Subject: [PATCH 01/12] (154227191) Consolidate URL compatibility checks (#1392) --- Sources/FoundationEssentials/URL/URL_Swift.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/FoundationEssentials/URL/URL_Swift.swift b/Sources/FoundationEssentials/URL/URL_Swift.swift index 3b0559187..7da7365e2 100644 --- a/Sources/FoundationEssentials/URL/URL_Swift.swift +++ b/Sources/FoundationEssentials/URL/URL_Swift.swift @@ -864,7 +864,7 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable { /// `URL("/").deletingLastPathComponent == URL("/../")` /// `URL("/../").standardized == URL("")` #if FOUNDATION_FRAMEWORK - if URL.compatibility4 && path == "/" { + if URL.compatibility1 && path == "/" { components.percentEncodedPath = "/../" } else { components.percentEncodedPath = newPath @@ -915,7 +915,7 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable { /// `URL("/../").standardized == URL("")` #if FOUNDATION_FRAMEWORK guard isDecomposable else { return nil } - let newPath = if URL.compatibility4 && _parseInfo.path == "/../" { + let newPath = if URL.compatibility1 && _parseInfo.path == "/../" { "" } else { String(_parseInfo.path).removingDotSegments From 7a0e78f1c66199c876d31c77b2d9e1d2f91d326e Mon Sep 17 00:00:00 2001 From: Chloe Yeo Date: Mon, 30 Jun 2025 15:54:05 -0700 Subject: [PATCH 02/12] [Proposal] Update ProgressManager Proposal (#1335) * ProgressReporter proposal * reword future directions section + fix typo * fix spacing * refactor LocalizedDescriptionOptions * edit future directions * update ProgressReporter Pitch to V2 * update proposal to latest version * fix spacing * v3 updates * Update review status for SF-0023 to active review * Round 2 Pitch * fix typo * update proposal to v5 * add code documentation * Update status for SF-0023 to 2nd Review * Update 0023-progress-reporter.md * update name + add total and values to ProgressReporter * update proposal with minor updates * expand future directions * fix typo * update proposal * reformatting * expand alternatives considered + mark accepted * fix formatting * fix typos Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> * fix formatting * fix method description * remove implementation detail * add more discussions about additional properties --------- Co-authored-by: Charles Hu Co-authored-by: Charles Hu Co-authored-by: Tina L <49205802+itingliu@users.noreply.github.com> --- Proposals/0023-progress-reporter.md | 119 +++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 19 deletions(-) diff --git a/Proposals/0023-progress-reporter.md b/Proposals/0023-progress-reporter.md index e819b07e8..05dfbcfa9 100644 --- a/Proposals/0023-progress-reporter.md +++ b/Proposals/0023-progress-reporter.md @@ -1,13 +1,14 @@ -# `ProgressReporter`: Progress Reporting in Swift Concurrency +# `ProgressManager`: Progress Reporting in Swift Concurrency * Proposal: SF-0023 * Author(s): [Chloe Yeo](https://github.com/chloe-yeo) * Review Manager: [Charles Hu](https://github.com/iCharlesHu) -* Status: **2nd Review Jun. 3, 2025 ... Jun. 10, 2025** +* Status: **Accepted** * Review: * [Pitch](https://forums.swift.org/t/pitch-progress-reporting-in-swift-concurrency/78112/10) * [First Review](https://forums.swift.org/t/review-sf-0023-progress-reporting-in-swift-concurrency/79474) - + * [Second Pitch](https://forums.swift.org/t/pitch-2-progressmanager-progress-reporting-in-swift-concurrency/80024) + * [Second Review](https://forums.swift.org/t/review-2nd-sf-0023-progressreporter-progress-reporting-in-swift-concurrency/80284) ## Revision history @@ -27,12 +28,17 @@ - Introduced `ProgressReporter` type and `assign(count:to:)` for alternative use cases, including multi-parent support - Specified Behavior of `ProgressManager` for `Task` cancellation - Redesigned implementation of custom properties to support both holding values of custom property of `self` and of descendants, and multi-parent support + - Introduced `values(of:)` and `total(of:)` methods to dislay and aggregate values of custom properties in a subtree - Restructured examples in Proposed Solution to show the use of `Subprogress` and `ProgressReporter` in different cases and enforce use of `subprogress` as parameter label for methods reporting progress and use of `progressReporter` as property name when returning `ProgressReporter` from a library - Expanded Future Directions - Expanded Alternatives Considered - Moving `FormatStyle` to separate future proposal * **v5** Minor Updates: - - Renamed `manager(totalCount:)` to `start(totalCount)` + - Renamed `manager(totalCount:)` method to `start(totalCount)` + - Changed the return type of `values(of:)` to be an array of non-optional values + - Clarified cycle-detection behavior in `assign(count:to:)` at runtime + - Added `CustomStringConvertible` and `CustomDebugStringConvertible` conformance to `ProgressManager` and `ProgressReporter` + - Expanded Future Directions - Expanded Alternatives Considered ## Table of Contents @@ -56,7 +62,7 @@ This proposal aims to introduce a new Progress Reporting API —— `ProgressMan 1. **Swift Concurrency Integration**: This API enables smooth, incremental progress reporting within async/await code patterns. -2. **Self-Documenting Design**: The types introduced in this API clearly separate the composition from observation of progress and allow developers to make it obvious which methods report progress to clients. +2. **Self-Documenting Design**: The types introduced in this API clearly separate the composition of progress from observation of progress and allow developers to make it obvious which methods report progress to clients. 3. **Error-Resistant Architecture**: One common mistake/footgun when it comes to progress reporting is reusing the [same progress reporting instance](#advantages-of-using-subprogress-as-currency-type). This tends to lead to mistakenly overwriting its expected unit of work after previous caller has set it, or "over completing" / "double finishing" the report after it's been completed. This API prevents this by introducing strong types with different roles. Additionally, it handles progress delegation, accumulation, and nested reporting automatically, eliminating race conditions and progress calculation errors. @@ -153,12 +159,12 @@ Another recommended usage pattern of `Progress`, which involves the `ProgressRep ### `ProgressManager` API -We propose introducing a new progress reporting type called `ProgressManager`. `ProgressManager` is used to report progress. +We propose introducing a new progress reporting type called `ProgressManager`. `ProgressManager` is used to manage the composition of progress by either assigning it, or completing it. In order to compose progress into trees, we also introduce two more types: 1. `Subprogress`: A `~Copyable` type, used when a `ProgressManager` wishes to assign a portion of its total progress to an `async` function. -2. `ProgressReporter`: A class used to report progress to interested observers. This includes one or more other `ProgressManager`s, which may incorporate those updates into their own progress. +2. `ProgressReporter`: A class used to report progress of `ProgressManager` to interested observers. This includes one or more other `ProgressManager`s, which may incorporate those updates into their own progress. ```mermaid block-beta @@ -342,7 +348,6 @@ overall.assign(count: 3, to: examCountdown.progressReporter) // Add `ProgressReporter` to another parent `ProgressManager` with different assigned count let deadlineTracker = ProgressManager(totalCount: 2) overall.assign(count: 1, to: examCountdown, progressReporter) - ``` ### Reporting Progress With Type-Safe Custom Properties @@ -444,12 +449,12 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) ### `ProgressManager` -`ProgressManager` is an Observable and Sendable class that developers use to report progress. Specifically, an instance of `ProgressManager` can be used to either track progress of a single task, or track progress of a graph of `ProgressManager` instances. +`ProgressManager` is an `Observable` and `Sendable` class that developers use to report progress. Specifically, an instance of `ProgressManager` can be used to either track progress of a single task, or track progress of a graph of `ProgressManager` instances. ```swift /// An object that conveys ongoing progress to the user for a specified task. @available(FoundationPreview 6.2, *) -@Observable public final class ProgressManager : Sendable, Hashable, Equatable, CustomDebugStringConvertible { +@Observable public final class ProgressManager : Sendable, Hashable, Equatable, CustomStringConvertible, CustomDebugStringConvertible { /// The total units of work. public var totalCount: Int? { get } @@ -474,6 +479,9 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) /// A `ProgressReporter` instance, used for providing read-only observation of progress updates or composing into other `ProgressManager`s. public var reporter: ProgressReporter { get } + /// A description. + public var description: String { get } + /// A debug description. public var debugDescription: String { get } @@ -516,6 +524,8 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) /// Adds a `ProgressReporter` as a child, with its progress representing a portion of `self`'s progress. /// + /// If a cycle is detected, this will cause a crash at runtime. + /// /// - Parameters: /// - output: A `ProgressReporter` instance. /// - count: The portion of `totalCount` to be delegated to the `ProgressReporter`. @@ -534,7 +544,7 @@ overall.addChild(subprogressThree, withPendingUnitCount: 1) /// /// - Parameter property: Type of property. /// - Returns: Array of values for property. - public func values(of property: P.Type) -> [P.Value?] + public func values(of property: P.Type) -> [P.Value] /// Returns the aggregated result of values where type of property is `AdditiveArithmetic`. /// All values are added together. @@ -575,7 +585,7 @@ public struct Subprogress: ~Copyable, Sendable { /// ProgressReporter is used to observe progress updates from a `ProgressManager`. It may also be used to incorporate those updates into another `ProgressManager`. /// /// It is read-only and can be added as a child of another ProgressManager. -@Observable public final class ProgressReporter : Sendable { +@Observable public final class ProgressReporter : Sendable, CustomStringConvertible, CustomDebugStringConvertible { /// The total units of work. public var totalCount: Int? { get } @@ -596,11 +606,35 @@ public struct Subprogress: ~Copyable, Sendable { /// The state of completion of work. /// If `completedCount` >= `totalCount`, the value will be `true`. public var isFinished: Bool { get } + + /// A description. + public var description: String { get } + + /// A debug description. + public var debugDescription: String { get } /// Reads properties that convey additional information about progress. public func withProperties( _ closure: (sending ProgressManager.Values) throws(E) -> sending T ) throws(E) -> T + + /// Returns an array of values for specified additional property in subtree. + /// The specified property refers to a declared type representing additional progress-related properties + /// that conform to the `ProgressManager.Property` protocol. + /// + /// - Parameter property: Type of property. + /// - Returns: Array of values for property. + public func values(of property: P.Type) -> [P.Value] + + /// Returns the aggregated result of values for specified `AdditiveArithmetic` property in subtree. + /// The specified property refers to a declared type representing additional progress-related properties + /// that conform to the `ProgressManager.Property` protocol. + /// The specified property also has to be an `AdditiveArithmetic`. For non-`AdditiveArithmetic` types, you should + /// write your own method to aggregate values. + /// + /// - Parameters property: Type of property. + /// - Returns: Aggregated result of values for property. + public func total(of property: P.Type) -> P.Value where P.Value : AdditiveArithmetic } ``` @@ -612,6 +646,8 @@ We pre-declare some of these additional properties that are commonly desired in If you would like to report additional metadata or properties that are not part of the pre-declared additional properties, you can declare additional properties into `ProgressManager.Properties`, similar to how the pre-declared additional properties are declared. +Additionally, the additional metadata or properties of each `ProgressManager` can be read by calling the `values(of:)` method defined in `ProgressManager`. The `values(of:)` method returns an array of values for each specified property in a subtree. If you would like to get an aggregated value of a property that is an `AdditiveArithmetic` type, you can call the `total(of:)` method defined in `ProgressManager`. + ```swift @available(FoundationPreview 6.2, *) extension ProgressManager { @@ -780,6 +816,9 @@ To further safeguard developers from making mistakes of over-assigning or under- ### Support for Non-Integer Formats of Progress Updates To handle progress values from other sources that provide progress updates as non-integer formats such as `Double`, we can introduce a way for `ProgressManager` to either be instantiated with non-integer formats, or a peer instance of `ProgressManager` that works with `ProgressManager` to compose a progress graph. +### Support for Displaying Children of Progress Subtree + If there are greater demands of a functionality to display the children of a root `ProgressManager` or `ProgressReporter`, we can introduce additive changes to this API. + ## Alternatives considered ### Alternative Names @@ -852,6 +891,8 @@ func f() async { } ``` +Additionally, progress reporting being directly integrated into the structured concurrency model would also introduce a non-trivial trade-off. Supporting multi-parent use cases, or the ability to construct an acyclic graph for progress is a heavily-desired feature for this API, but structured concurrency, which assumes a tree structure, would inevitably break this use case. + ### Add Convenience Method to Existing `Progress` for Easier Instantiation of Child Progress While the explicit model has concurrency support via completion handlers, the usage pattern does not fit well with async/await, because which an instance of `Progress` returned by an asynchronous function would return after code is executed to completion. In the explicit model, to add a child to a parent progress, we pass an instantiated child progress object into the `addChild(child:withPendingUnitCount:)` method. In this alternative, we add a convenience method that bears the function signature `makeChild(pendingUnitCount:)` to the `Progress` class. This method instantiates an empty progress and adds itself as a child, allowing developers to add a child progress to a parent progress without having to instantiate a child progress themselves. The additional method reads as follows: @@ -886,24 +927,64 @@ We decided to replace the generic class implementation with `@dynamicMemberLooku ### Implement this Progress Reporting API as an actor We considered implementing `ProgressManager` as we want to maintain this API as a reference type that is safe to use in concurrent environments. However, if `ProgressManager` were to be implemented, `ProgressManager` will not be able to conform to `Observable` because actor-based keypaths do not exist as of now. Ensuring that `ProgressManager` is `Observable` is important to us, as we want to ensure that `ProgressManager` works well with UI components in UI frameworks. -### Make `ProgressManager` not @Observable -We considered making `ProgressManager` not @Observable, and make `ProgressReporter` the @Observable adapter instead. This would limit developers to have to do `manager.reporter` before binding it with a UI component. While this simplifies the case for integrating with UI components, it introduces more boilerplate to developers who may only have a `ProgressManager` to begin with. +### Make `ProgressManager` not `Observable` +We considered making `ProgressManager` not `Observable`, and make `ProgressReporter` the `Observable` adapter instead. This would limit developers to have to do `manager.reporter` before binding it with a UI component. While this simplifies the case for integrating with UI components, it introduces more boilerplate to developers who may only have a `ProgressManager` to begin with. -### Not exposing read-only variables in `ProgressReporter` -We initially considered not exposing get-only variables in `ProgressReporter`, which would work in cases where developers are composing `ProgressReporter` into multiple different `ProgressManager` parents. However, this would not work very well for cases where developers only want to observe values on the `ProgressReporter`, such as `fractionCompleted` because they would have to call `reporter.manager` just to get the properties. Thus we decided to introduce read-only properties on `ProgressReporter` as well. +### Support for Multi-parent Use Cases +We considered introducing only two types in this API, `ProgressManager` and `Subprogress`, which would enable developers to create a tree of `ProgressManager` to report progress. However, this has two limitations: + - It assumes a single-parent, tree-based structure. + - Developers would have to expose a mutable `ProgressManager` to its observers if they decide to have `ProgressManager` as a property on a class. For example: +```swift +class DownloadManager { + var progress: ProgressManager { get } // to be observed by developers using DownloadManager class +} + +let observedProgress = DownloadManager().progress +observedProgress.complete(count: 12) // ⚠️: ALLOWED, because `ProgressManager` is mutable!! +``` +To overcome the two limitations, we decided to introduce an additional type, `ProgressReporter`, which is a read-only representation of a `ProgressManager`, which would contain the calculations of progress within `ProgressManager`. The `ProgressReporter` can also be used to safely add the `ProgressManager` it wraps around as a child to more than one `ProgressManager` to support multi-parent use cases. This is written in code as follows: + +```swift +class DownloadManager { + var progressReporter: ProgressReporter { + get { + progressManager.reporter + } + } // wrapper for `ProgressManager` in `DownloadManager` class + + private let progressManager: ProgressManager // used to compose progress in DownloadManager class +} +``` + +Authors of DownloadManager can expose `ProgressReporter` to developers without allowing developers to mutate `ProgressManager`. Developers can also freely use `ProgressReporter` to construct a multi-parent acyclic graph of progress, as follows: +```swift +let myProgress = ProgressManager(totalCount: 2) + +let downloadManager = DownloadManager() +myProgress.assign(count: 1, to: downloadManager.progress) // add downloadManager.progress as a child -### Introduce Method to Generate Localized Description -We considered introducing a `localizedDescription(including:)` method, which returns a `LocalizedStringResource` for observers to get custom format descriptions for `ProgressManager`. In contrast, using a `FormatStyle` aligns more closely with Swift's API, and has more flexibility for developers to add custom `FormatStyle` to display localized descriptions for additional properties they may want to declare and use. +let observedProgress = downloadManager.progress +observedProgress.complete(count: 12) // ✅: NOT ALLOWED, `ProgressReporter` is read-only!! +``` + +### Not exposing read-only variables in `ProgressReporter` +We initially considered not exposing get-only variables in `ProgressReporter`, which would work in cases where developers are composing `ProgressReporter` into multiple different `ProgressManager` parents. However, this would not work very well for cases where developers only want to observe values on the `ProgressReporter`, such as `fractionCompleted` because they would have to call `reporter.manager` just to get the properties. Thus we decided to introduce read-only properties on `ProgressReporter` as well. ### Introduce Explicit Support for Cancellation, Pausing, and Resuming of this Progress Reporting API The existing `Progress` provides support for cancelling, pausing and resuming an ongoing operation tracked by an instance of `Progress`, and propagates these actions down to all of its children. We decided to not introduce support for this behavior as there is support in cancelling a `Task` via `Task.cancel()` in Swift structured concurrency. The absence of support for cancellation, pausing and resuming in `ProgressManager` helps to clarify the scope of responsibility of this API, which is to report progress, instead of owning a task and performing actions on it. ### Handling Cancellation by Checking Task Cancellation or Allowing Incomplete Progress after Task Cancellation -We considered adding a `Task.isCancelled` check in the `complete(count:)` method so that calls to `complete(count:)` from a `Task` that is cancelled becomes a no-op. We have also considered not completing the progress to reflect the fact that no futher calls to `complete(count:)` are made after a `Task` is cancelled. However, in order to make sure that there is always a consistent state for progress independent of state of task, the `ProgressManager` will always finish before being deinitialized. THROW ERROR INSTEAD; catch error use error handling, if want to provide metadata for the cancellation - use custom property +We considered adding a `Task.isCancelled` check in the `complete(count:)` method so that calls to `complete(count:)` from a `Task` that is cancelled becomes a no-op. We have also considered not completing the progress to reflect the fact that no futher calls to `complete(count:)` are made after a `Task` is cancelled. However, in order to make sure that there is always a consistent state for progress independent of state of task, the `ProgressManager` will always finish before being deinitialized. ### Introduce `totalCount` and `completedCount` properties as `UInt64` We considered using `UInt64` as the type for `totalCount` and `completedCount` to support the case where developers use `totalCount` and `completedCount` to track downloads of larger files on 32-bit platforms byte-by-byte. However, developers are not encouraged to update progress byte-by-byte, and should instead set the counts to the granularity at which they want progress to be visibly updated. For instance, instead of updating the download progress of a 10,000 bytes file in a byte-by-byte fashion, developers can instead update the count by 1 for every 1,000 bytes that has been downloaded. In this case, developers set the `totalCount` to 10 instead of 10,000. To account for cases in which developers may want to report the current number of bytes downloaded, we added `totalByteCount` and `completedByteCount` to `ProgressManager.Properties`, which developers can set and display using format style. +### Make `totalCount` a settable property on `ProgressManager` +We previously considered making `totalCount` a settable property on `ProgressManager`, but this would introduce a race condition that is common among cases in which `Sendable` types have settable properties. This is because two threads can try to mutate `totalCount` at the same time, but since `ProgressManager` is `Sendable`, we cannot guarantee the order of how the operations will interleave, thus creating a race condition. This results in `totalCount` either reflecting both the mutations, or one of the mutations indeterministically. Therefore, we changed it so that `totalCount` is a read-only property on `ProgressManager`, and is only mutable within the `withProperties` closure to prevent this race condition. + +### Representation of Indeterminate state in `ProgressManager` +There were discussions about representing indeterminate state in `ProgressManager` alternatively, for example, using enums. However, since `totalCount` is an optional and can be set to `nil` to represent indeterminate state, we think that this is straightforward and sufficient to represent indeterminate state for cases where developers do not know `totalCount` at the start of an operation they want to report progress for. A `ProgressManager` becomes determinate once its `totalCount` is set to an `Int`. + ## Acknowledgements Thanks to - [Tony Parker](https://github.com/parkera), From 7ab3fb090c13fb34004a2817d5292985b4f860ed Mon Sep 17 00:00:00 2001 From: Tina L <49205802+itingliu@users.noreply.github.com> Date: Tue, 1 Jul 2025 10:24:03 -0700 Subject: [PATCH 03/12] Swift URL and URLResourceValues have no method for changing tags on a file (#1396) Resolves 33478651 Co-authored-by: Aaron Burghardt --- Sources/FoundationEssentials/URL/URL.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/URL/URL.swift b/Sources/FoundationEssentials/URL/URL.swift index a188f5378..61285c4af 100644 --- a/Sources/FoundationEssentials/URL/URL.swift +++ b/Sources/FoundationEssentials/URL/URL.swift @@ -274,7 +274,11 @@ public struct URLResourceValues { #if os(macOS) /// The array of Tag names. - public var tagNames: [String]? { return _get(.tagNamesKey) } + public var tagNames: [String]? { + get { return _get(.tagNamesKey) } + @available(macOS 26.0, *) + set { _set(.tagNamesKey, newValue: newValue) } + } #endif /// The URL's path as a file system path. From 4e679a91afaacbc00474024323250dd88cffbe6a Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Tue, 1 Jul 2025 11:23:00 -0700 Subject: [PATCH 04/12] Ensure that subscripting AttributeSliceX behaves consistently regardless of index location (#1382) --- .../AttributedString+Runs.swift | 3 -- .../AttributedStringTests.swift | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift b/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift index 58fbc07ec..17fc34ab4 100644 --- a/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift +++ b/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift @@ -594,9 +594,6 @@ extension AttributedString.Runs { // _strBounds.range(containing:) below validates that i._value is within the bounds of this slice precondition(!attributeNames.isEmpty) let r = _guts.findRun(at: i._value) - if r.runIndex.offset == endIndex._runOffset { - return (i, r.runIndex) - } let currentRange = _strBounds.range(containing: i._value).range guard constraints.count != 1 || constraints.contains(nil) else { diff --git a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift index 670aabf97..36befc4e9 100644 --- a/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift +++ b/Tests/FoundationEssentialsTests/AttributedString/AttributedStringTests.swift @@ -1983,6 +1983,34 @@ E { } } } + + @Test func runSliceSubscripting() { + var str = AttributedString("Foo", attributes: .init().testInt(1)) + str += AttributedString("Bar", attributes: .init().testInt(2)) + str += AttributedString("Baz", attributes: .init().testInt(3)) + + do { + let runsSlice = str.runs[\.testInt] + for (value, range) in runsSlice { + for idx in str.utf8[range].indices { + let subscriptResult = runsSlice[idx] + #expect(subscriptResult.0 == value, "Subscript index \(idx) did not produce same value as runs slice") + #expect(subscriptResult.1 == range, "Subscript index \(idx) did not produce same range as runs slice") + } + } + } + + do { + let runsSlice = str[str.index(afterCharacter: str.startIndex) ..< str.index(beforeCharacter: str.endIndex)].runs[\.testInt] + for (value, range) in runsSlice { + for idx in str.utf8[range].indices { + let subscriptResult = runsSlice[idx] + #expect(subscriptResult.0 == value, "Subscript index \(idx) did not produce same value as runs slice") + #expect(subscriptResult.1 == range, "Subscript index \(idx) did not produce same range as runs slice") + } + } + } + } // MARK: - Other Tests From bca5d3d8939440c9f8dee078a4c3400557c98cb9 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Tue, 1 Jul 2025 11:26:58 -0700 Subject: [PATCH 05/12] Update availability macros for macOS 15 deployment target (#1397) --- CMakeLists.txt | 11 +-- Package.swift | 9 +- .../AttributedString+CharacterView.swift | 8 +- .../AttributedString+Runs.swift | 2 +- .../AttributedString+UnicodeScalarView.swift | 4 +- .../AttributedString/Conversion.swift | 2 +- .../FoundationAttributes.swift | 18 ++-- .../Calendar/Calendar.swift | 14 +-- .../Calendar/Calendar_Enumerate.swift | 2 +- .../Calendar/DateComponents.swift | 2 +- .../Calendar/RecurrenceRule.swift | 10 +- .../ComparisonResult.swift | 2 +- Sources/FoundationEssentials/Data/Data.swift | 2 +- .../Formatting/DiscreteFormatStyle.swift | 10 +- .../JSON/JSONDecoder.swift | 4 +- .../JSON/JSONEncoder.swift | 4 +- .../Locale/Locale+Components.swift | 36 +++---- ...codingContainers+PredicateExpression.swift | 16 ++-- .../Archiving/Predicate+Codable.swift | 8 +- .../PredicateCodableConfiguration.swift | 8 +- .../PredicateExpressionConstruction.swift | 14 +-- .../Predicate/Expression.swift | 4 +- .../Predicate/Expressions/Aggregate.swift | 20 ++-- .../Predicate/Expressions/Arithmetic.swift | 10 +- .../Predicate/Expressions/Collection.swift | 30 +++--- .../Predicate/Expressions/Comparison.swift | 10 +- .../Predicate/Expressions/Conditional.swift | 10 +- .../Predicate/Expressions/Conjunction.swift | 10 +- .../Predicate/Expressions/Dictionary.swift | 20 ++-- .../Predicate/Expressions/Disjunction.swift | 10 +- .../Predicate/Expressions/Division.swift | 26 ++--- .../Predicate/Expressions/Equality.swift | 10 +- .../Expressions/ExpressionEvaluation.swift | 14 +-- .../Predicate/Expressions/Filter.swift | 10 +- .../Predicate/Expressions/Inequality.swift | 10 +- .../Predicate/Expressions/Negation.swift | 10 +- .../Predicate/Expressions/Optional.swift | 28 +++--- .../Expressions/PredicateEvaluation.swift | 14 +-- .../Predicate/Expressions/Range.swift | 30 +++--- .../Predicate/Expressions/Regex.swift | 10 +- .../Predicate/Expressions/Sequence.swift | 32 +++---- .../Expressions/StringComparison.swift | 10 +- .../Predicate/Expressions/Types.swift | 26 ++--- .../Predicate/Expressions/UnaryMinus.swift | 10 +- .../Predicate/NSPredicateConversion.swift | 4 +- .../Predicate/Predicate+Description.swift | 96 +++++++++---------- .../Predicate/Predicate.swift | 10 +- .../Predicate/PredicateBindings.swift | 2 +- .../Predicate/PredicateExpression.swift | 28 +++--- .../PropertyList/PlistDecoder.swift | 8 +- .../PropertyList/PlistEncoder.swift | 4 +- .../FoundationEssentials/SortComparator.swift | 2 +- .../String/StringProtocol+Essentials.swift | 2 +- Sources/FoundationEssentials/UUID.swift | 4 +- .../Date+AnchoredRelativeFormatStyle.swift | 4 +- .../Date/Date+RelativeFormatStyle.swift | 6 +- .../Date/Date+VerbatimFormatStyle.swift | 6 +- .../Formatting/Date/DateFieldSymbol.swift | 32 +++---- .../Formatting/Date/DateFormatStyle.swift | 8 +- .../Formatting/Duration+TimeFormatStyle.swift | 10 +- .../Duration+UnitsFormatStyle.swift | 6 +- .../Number/Decimal+FormatStyle.swift | 2 +- .../Number/FloatingPointFormatStyle.swift | 2 +- .../Number/IntegerFormatStyle.swift | 2 +- .../NumberFormatStyleConfiguration.swift | 2 +- .../Predicate/LocalizedString.swift | 8 +- .../String/SortDescriptor.swift | 28 +++--- Sources/TestSupport/TestSupport.swift | 8 +- .../PredicateTests.swift | 5 - .../FoundationEssentialsTests/UUIDTests.swift | 1 - 70 files changed, 413 insertions(+), 427 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac49f5534..ffb0ea1ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,14 +94,11 @@ list(APPEND CMAKE_MODULE_PATH ${SwiftFoundation_SOURCE_DIR}/cmake/modules) # Availability Macros (only applies to FoundationEssentials and FoundationInternationalization) set(_SwiftFoundation_BaseAvailability "macOS 15, iOS 18, tvOS 18, watchOS 11") +set(_SwiftFoundation_macOS26Availability "macOS 26, iOS 26, tvOS 26, watchOS 26") set(_SwiftFoundation_FutureAvailability "macOS 10000, iOS 10000, tvOS 10000, watchOS 10000") # All versions to define for each availability name list(APPEND _SwiftFoundation_versions - "0.1" - "0.2" - "0.3" - "0.4" "6.0.2" "6.1" "6.2" @@ -110,16 +107,12 @@ list(APPEND _SwiftFoundation_versions # Each availability name to define list(APPEND _SwiftFoundation_availability_names "FoundationPreview" - "FoundationPredicate" - "FoundationPredicateRegex" "FoundationSpan") # The aligned availability for each name (in the same order) list(APPEND _SwiftFoundation_availability_releases ${_SwiftFoundation_BaseAvailability} - ${_SwiftFoundation_BaseAvailability} - ${_SwiftFoundation_BaseAvailability} - ${_SwiftFoundation_FutureAvailability}) + ${_SwiftFoundation_macOS26Availability}) foreach(version ${_SwiftFoundation_versions}) foreach(name release IN ZIP_LISTS _SwiftFoundation_availability_names _SwiftFoundation_availability_releases) diff --git a/Package.swift b/Package.swift index 4bd449156..cedb5599b 100644 --- a/Package.swift +++ b/Package.swift @@ -7,17 +7,16 @@ import CompilerPluginSupport // Availability Macros let availabilityTags: [_Availability] = [ - _Availability("FoundationPreview"), // Default FoundationPreview availability, - _Availability("FoundationPredicate"), // Predicate relies on pack parameter runtime support - _Availability("FoundationPredicateRegex"), // Predicate regexes rely on new stdlib APIs - _Availability("FoundationSpan", availability: .future), // Availability of Span types + _Availability("FoundationPreview"), // Default FoundationPreview availability + _Availability("FoundationSpan", availability: .macOS26), // Availability of Span types ] -let versionNumbers = ["0.1", "0.2", "0.3", "0.4", "6.0.2", "6.1", "6.2"] +let versionNumbers = ["6.0.2", "6.1", "6.2"] // Availability Macro Utilities enum _OSAvailability: String { case alwaysAvailable = "macOS 15, iOS 18, tvOS 18, watchOS 11" // This should match the package's deployment target + case macOS26 = "macOS 26, iOS 26, tvOS 26, watchOS 26" // Use 10000 for future availability to avoid compiler magic around the 9999 version number but ensure it is greater than 9999 case future = "macOS 10000, iOS 10000, tvOS 10000, watchOS 10000" } diff --git a/Sources/FoundationEssentials/AttributedString/AttributedString+CharacterView.swift b/Sources/FoundationEssentials/AttributedString/AttributedString+CharacterView.swift index 866c802f3..12af67060 100644 --- a/Sources/FoundationEssentials/AttributedString/AttributedString+CharacterView.swift +++ b/Sources/FoundationEssentials/AttributedString/AttributedString+CharacterView.swift @@ -123,7 +123,7 @@ extension AttributedString.CharacterView: BidirectionalCollection { return _defaultCount } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal var _count: Int { _characters.count @@ -153,7 +153,7 @@ extension AttributedString.CharacterView: BidirectionalCollection { return _defaultIndex(i, offsetBy: distance) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal func _index(_ i: AttributedString.Index, offsetBy distance: Int) -> AttributedString.Index { precondition(i >= startIndex && i <= endIndex, "AttributedString index out of bounds") @@ -176,7 +176,7 @@ extension AttributedString.CharacterView: BidirectionalCollection { return _defaultIndex(i, offsetBy: distance, limitedBy: limit) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal func _index( _ i: AttributedString.Index, @@ -207,7 +207,7 @@ extension AttributedString.CharacterView: BidirectionalCollection { return _defaultDistance(from: start, to: end) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal func _distance(from start: AttributedString.Index, to end: AttributedString.Index) -> Int { precondition(start >= startIndex && start <= endIndex, "AttributedString index out of bounds") diff --git a/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift b/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift index 17fc34ab4..f1c9c8abc 100644 --- a/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift +++ b/Sources/FoundationEssentials/AttributedString/AttributedString+Runs.swift @@ -366,7 +366,7 @@ extension AttributedString.Runs: BidirectionalCollection { #endif } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal func _index(_ index: Index, offsetBy distance: Int) -> Index { guard _isDiscontiguous else { diff --git a/Sources/FoundationEssentials/AttributedString/AttributedString+UnicodeScalarView.swift b/Sources/FoundationEssentials/AttributedString/AttributedString+UnicodeScalarView.swift index c7006ef89..a0f8ed991 100644 --- a/Sources/FoundationEssentials/AttributedString/AttributedString+UnicodeScalarView.swift +++ b/Sources/FoundationEssentials/AttributedString/AttributedString+UnicodeScalarView.swift @@ -124,7 +124,7 @@ extension AttributedString.UnicodeScalarView: BidirectionalCollection { return _defaultCount } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal var _count: Int { _unicodeScalars.count @@ -165,7 +165,7 @@ extension AttributedString.UnicodeScalarView: BidirectionalCollection { return _defaultIndex(i, offsetBy: distance, limitedBy: limit) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal func _index( _ i: AttributedString.Index, diff --git a/Sources/FoundationEssentials/AttributedString/Conversion.swift b/Sources/FoundationEssentials/AttributedString/Conversion.swift index fad6aba49..7b06e0476 100644 --- a/Sources/FoundationEssentials/AttributedString/Conversion.swift +++ b/Sources/FoundationEssentials/AttributedString/Conversion.swift @@ -41,7 +41,7 @@ extension String { } #endif - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @usableFromInline internal init(_characters: AttributedString.CharacterView) { self.init(_characters._characters) diff --git a/Sources/FoundationEssentials/AttributedString/FoundationAttributes.swift b/Sources/FoundationEssentials/AttributedString/FoundationAttributes.swift index 73bb2c478..c114f2158 100644 --- a/Sources/FoundationEssentials/AttributedString/FoundationAttributes.swift +++ b/Sources/FoundationEssentials/AttributedString/FoundationAttributes.swift @@ -40,13 +40,13 @@ extension AttributeScopes { public let writingDirection: WritingDirectionAttribute #if FOUNDATION_FRAMEWORK - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public let agreementConcept: AgreementConceptAttribute - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public let agreementArgument: AgreementArgumentAttribute - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public let referentConcept: ReferentConceptAttribute - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public let localizedNumberFormat: LocalizedNumberFormatAttribute // TODO: Support AttributedString markdown in FoundationPreview: https://github.com/apple/swift-foundation/issues/44 @@ -119,7 +119,7 @@ extension AttributeScopes.FoundationAttributes { #if FOUNDATION_FRAMEWORK @frozen - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public enum ReferentConceptAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { public typealias Value = Int public static let name = NSAttributedString.Key.referentConcept.rawValue @@ -127,7 +127,7 @@ extension AttributeScopes.FoundationAttributes { } @frozen - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public enum AgreementConceptAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { public typealias Value = Int public static let name = NSAttributedString.Key.agreeWithConcept.rawValue @@ -135,7 +135,7 @@ extension AttributeScopes.FoundationAttributes { } @frozen - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public enum AgreementArgumentAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { public typealias Value = Int public static let name = NSAttributedString.Key.agreeWithArgument.rawValue @@ -168,7 +168,7 @@ extension AttributeScopes.FoundationAttributes { } @frozen - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public enum LocalizedNumberFormatAttribute : CodableAttributedStringKey, MarkdownDecodableAttributedStringKey { public struct Value: Equatable, Hashable, Codable, Sendable { enum Format { @@ -851,7 +851,7 @@ extension AttributeScopes.FoundationAttributes.PersonNameComponentAttribute : Ob public typealias ObjectiveCValue = NSString } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension AttributeScopes.FoundationAttributes.LocalizedNumberFormatAttribute.Value: _ObjectiveCBridgeable { public func _bridgeToObjectiveC() -> __NSLocalizedNumberFormatRule { switch self.format { diff --git a/Sources/FoundationEssentials/Calendar/Calendar.swift b/Sources/FoundationEssentials/Calendar/Calendar.swift index 7f411420d..32f555bef 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar.swift @@ -323,10 +323,10 @@ public struct Calendar : Hashable, Equatable, Sendable { case nanosecond case calendar case timeZone - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) case isLeapMonth - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) case dayOfYear fileprivate var componentSetValue: ComponentSet.RawValue { @@ -620,7 +620,7 @@ public struct Calendar : Hashable, Equatable, Sendable { /// - parameter value: The value of the specified component to add or subtract. The default value is `1`. The value can be negative, which causes subtraction. /// - parameter wrappingComponents: If `true`, the component should be incremented and wrap around to zero/one on overflow, and should not cause higher components to be incremented. The default value is `false`. /// - returns: A `Sequence` of `Date` values, or an empty sequence if no addition could be performed. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public func dates(byAdding component: Calendar.Component, value: Int = 1, startingAt start: Date, @@ -641,7 +641,7 @@ public struct Calendar : Hashable, Equatable, Sendable { /// - parameter components: The components to add or subtract. /// - parameter wrappingComponents: If `true`, the component should be incremented and wrap around to zero/one on overflow, and should not cause higher components to be incremented. The default value is `false`. /// - returns: A `Sequence` of `Date` values, or an empty sequence if no addition could be performed. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public func dates(byAdding components: DateComponents, startingAt start: Date, in range: Range? = nil, @@ -1222,7 +1222,7 @@ public struct Calendar : Hashable, Equatable, Sendable { /// - parameter matchingPolicy: Determines the behavior of the search algorithm when the input produces an ambiguous result. /// - parameter repeatedTimePolicy: Determines the behavior of the search algorithm when the input produces a time that occurs twice on a particular day. /// - parameter direction: Which direction in time to search. The default value is `.forward`, which means later in time. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public func dates(byMatching components: DateComponents, startingAt start: Date, in range: Range? = nil, @@ -1617,7 +1617,7 @@ package struct WeekendRange: Equatable, Hashable { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Calendar.MatchingPolicy: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() @@ -1650,7 +1650,7 @@ extension Calendar.MatchingPolicy: Codable { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Calendar.RepeatedTimePolicy: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() diff --git a/Sources/FoundationEssentials/Calendar/Calendar_Enumerate.swift b/Sources/FoundationEssentials/Calendar/Calendar_Enumerate.swift index 52da07e4c..8e2a2a626 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar_Enumerate.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar_Enumerate.swift @@ -1580,7 +1580,7 @@ extension Calendar { return result } - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) internal func dateAfterMatchingDayOfYear(startingAt: Date, components: DateComponents, direction: SearchDirection) throws -> Date? { guard let dayOfYear = components.dayOfYear else { // Nothing to do diff --git a/Sources/FoundationEssentials/Calendar/DateComponents.swift b/Sources/FoundationEssentials/Calendar/DateComponents.swift index 50606302e..51c9519ae 100644 --- a/Sources/FoundationEssentials/Calendar/DateComponents.swift +++ b/Sources/FoundationEssentials/Calendar/DateComponents.swift @@ -271,7 +271,7 @@ public struct DateComponents : Hashable, Equatable, Sendable { /// A day of the year. /// For example, in the Gregorian calendar, can go from 1 to 365 or 1 to 366 in leap years. /// - note: This value is interpreted in the context of the calendar in which it is used. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public var dayOfYear: Int? { get { _dayOfYear } set { _dayOfYear = converted(newValue) } diff --git a/Sources/FoundationEssentials/Calendar/RecurrenceRule.swift b/Sources/FoundationEssentials/Calendar/RecurrenceRule.swift index 98fa4db53..d6050d155 100644 --- a/Sources/FoundationEssentials/Calendar/RecurrenceRule.swift +++ b/Sources/FoundationEssentials/Calendar/RecurrenceRule.swift @@ -12,7 +12,7 @@ extension Calendar { /// A rule which specifies how often an event should repeat in the future - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public struct RecurrenceRule: Sendable, Equatable { /// The calendar in which the recurrence occurs public var calendar: Calendar @@ -304,7 +304,7 @@ extension Calendar { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Calendar.RecurrenceRule.End: Codable { enum CodingKeys: String, CodingKey { case count @@ -332,7 +332,7 @@ extension Calendar.RecurrenceRule.End: Codable { } } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Calendar.RecurrenceRule.Weekday: Codable { enum CodingKeys: String, CodingKey { case weekday @@ -359,7 +359,7 @@ extension Calendar.RecurrenceRule.Weekday: Codable { } } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Calendar.RecurrenceRule.Month: Codable { enum CodingKeys: String, CodingKey { case month @@ -388,7 +388,7 @@ extension Calendar.RecurrenceRule.Month: Codable { } } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Calendar.RecurrenceRule: Codable { enum CodingKeys: String, CodingKey { case calendar diff --git a/Sources/FoundationEssentials/ComparisonResult.swift b/Sources/FoundationEssentials/ComparisonResult.swift index f6e6f8986..584dbd9ae 100644 --- a/Sources/FoundationEssentials/ComparisonResult.swift +++ b/Sources/FoundationEssentials/ComparisonResult.swift @@ -30,7 +30,7 @@ public enum ComparisonResult : Int, Sendable { #endif // !FOUNDATION_FRAMEWORK -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension ComparisonResult : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index c73d380a8..b6b73ed46 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -430,7 +430,7 @@ internal final class __DataStorage : @unchecked Sendable { } #if FOUNDATION_FRAMEWORK - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) #endif @usableFromInline // This is not @inlinable as it is a non-trivial, non-generic function. func replaceBytes(in range_: Range, with replacementBytes: UnsafeRawPointer?, length replacementLength: Int) { diff --git a/Sources/FoundationEssentials/Formatting/DiscreteFormatStyle.swift b/Sources/FoundationEssentials/Formatting/DiscreteFormatStyle.swift index 78b504a17..3aef61d5c 100644 --- a/Sources/FoundationEssentials/Formatting/DiscreteFormatStyle.swift +++ b/Sources/FoundationEssentials/Formatting/DiscreteFormatStyle.swift @@ -102,7 +102,7 @@ /// - the formatted output for `xA` and higher is **most likely** different from `format(y)` /// - the formatted output between `xB` and `zB`, as well as `zA` and `xA` (excluding bounds) cannot /// be predicted -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public protocol DiscreteFormatStyle : FormatStyle { /// The next discretization boundary before the given input. /// @@ -191,7 +191,7 @@ public protocol DiscreteFormatStyle : FormatStyle { func input(after input: FormatInput) -> FormatInput? } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension DiscreteFormatStyle where FormatInput : FloatingPoint { public func input(before input: FormatInput) -> FormatInput? { guard input > -FormatInput.infinity else { @@ -210,7 +210,7 @@ extension DiscreteFormatStyle where FormatInput : FloatingPoint { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension DiscreteFormatStyle where FormatInput : FixedWidthInteger { public func input(before input: FormatInput) -> FormatInput? { guard input > FormatInput.min else { @@ -229,7 +229,7 @@ extension DiscreteFormatStyle where FormatInput : FixedWidthInteger { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension DiscreteFormatStyle where FormatInput == Date { public func input(before input: FormatInput) -> FormatInput? { guard input > Date.distantPast else { @@ -258,7 +258,7 @@ extension Date { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension DiscreteFormatStyle where FormatInput == Duration { public func input(before input: FormatInput) -> FormatInput? { guard input > .seconds(Int64.min) else { diff --git a/Sources/FoundationEssentials/JSON/JSONDecoder.swift b/Sources/FoundationEssentials/JSON/JSONDecoder.swift index f3ede1670..b9e9a01cb 100644 --- a/Sources/FoundationEssentials/JSON/JSONDecoder.swift +++ b/Sources/FoundationEssentials/JSON/JSONDecoder.swift @@ -346,14 +346,14 @@ open class JSONDecoder { }, from: data) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func decode(_ type: T.Type, from data: Data, configuration: T.DecodingConfiguration) throws -> T { try _decode({ try $0.unwrap($1, as: type, configuration: configuration, for: .root, _CodingKey?.none) }, from: data) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func decode(_ type: T.Type, from data: Data, configuration: C.Type) throws -> T where T : DecodableWithConfiguration, C : DecodingConfigurationProviding, T.DecodingConfiguration == C.DecodingConfiguration { try decode(type, from: data, configuration: C.decodingConfiguration) } diff --git a/Sources/FoundationEssentials/JSON/JSONEncoder.swift b/Sources/FoundationEssentials/JSON/JSONEncoder.swift index f34bed668..84159bbd1 100644 --- a/Sources/FoundationEssentials/JSON/JSONEncoder.swift +++ b/Sources/FoundationEssentials/JSON/JSONEncoder.swift @@ -352,14 +352,14 @@ open class JSONEncoder { }, value: value) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func encode(_ value: T, configuration: T.EncodingConfiguration) throws -> Data { try _encode({ try $0.wrapGeneric(value, configuration: configuration) }, value: value) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func encode(_ value: T, configuration: C.Type) throws -> Data where T : EncodableWithConfiguration, C : EncodingConfigurationProviding, T.EncodingConfiguration == C.EncodingConfiguration { try encode(value, configuration: C.encodingConfiguration) } diff --git a/Sources/FoundationEssentials/Locale/Locale+Components.swift b/Sources/FoundationEssentials/Locale/Locale+Components.swift index 918100a8c..a740dd1be 100644 --- a/Sources/FoundationEssentials/Locale/Locale+Components.swift +++ b/Sources/FoundationEssentials/Locale/Locale+Components.swift @@ -124,31 +124,31 @@ extension Locale { } } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.LanguageCode : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.Script : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.Region : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.Currency : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.Collation : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.NumberingSystem : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.Subdivision : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.Variant : CustomDebugStringConvertible { } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Locale.MeasurementSystem : CustomDebugStringConvertible { } extension Locale { @@ -169,7 +169,7 @@ extension Locale { package var _identifier: String package var _normalizedIdentifier: String - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -254,7 +254,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -316,7 +316,7 @@ extension Locale { package var _identifier: String package var _normalizedIdentifier: String - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -395,7 +395,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -467,7 +467,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -533,7 +533,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -655,7 +655,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -734,7 +734,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } @@ -806,7 +806,7 @@ extension Locale { } } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var debugDescription: String { _normalizedIdentifier } diff --git a/Sources/FoundationEssentials/Predicate/Archiving/EncodingContainers+PredicateExpression.swift b/Sources/FoundationEssentials/Predicate/Archiving/EncodingContainers+PredicateExpression.swift index 77a813818..99747235e 100644 --- a/Sources/FoundationEssentials/Predicate/Archiving/EncodingContainers+PredicateExpression.swift +++ b/Sources/FoundationEssentials/Predicate/Archiving/EncodingContainers+PredicateExpression.swift @@ -14,7 +14,7 @@ // Initial API constrained to Output == Bool -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension KeyedEncodingContainer { public mutating func encodePredicateExpression(_ expression: T, forKey key: Self.Key, variable: repeat PredicateExpressions.Variable, predicateConfiguration: PredicateCodableConfiguration) throws where T.Output == Bool { var container = self.nestedContainer(keyedBy: PredicateExpressionCodingKeys.self, forKey: key) @@ -33,7 +33,7 @@ extension PredicateExpression { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension KeyedDecodingContainer { @_optimize(none) // Work around swift optimizer crash (rdar://124533887) public mutating func decodePredicateExpression(forKey key: Self.Key, input: repeat (each Input).Type, predicateConfiguration: PredicateCodableConfiguration) throws -> (expression: any PredicateExpression, variable: (repeat PredicateExpressions.Variable)) { @@ -48,7 +48,7 @@ extension KeyedDecodingContainer { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension UnkeyedEncodingContainer { public mutating func encodePredicateExpression(_ expression: T, variable: repeat PredicateExpressions.Variable, predicateConfiguration: PredicateCodableConfiguration) throws where T.Output == Bool { var container = self.nestedContainer(keyedBy: PredicateExpressionCodingKeys.self) @@ -64,7 +64,7 @@ extension UnkeyedEncodingContainer { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension UnkeyedDecodingContainer { @_optimize(none) // Work around swift optimizer crash (rdar://124533887) public mutating func decodePredicateExpression(input: repeat (each Input).Type, predicateConfiguration: PredicateCodableConfiguration) throws -> (expression: any PredicateExpression, variable: (repeat PredicateExpressions.Variable)) { @@ -87,7 +87,7 @@ extension UnkeyedDecodingContainer { // Added API without Output Constraint -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension KeyedEncodingContainer { @_disfavoredOverload public mutating func encodePredicateExpression(_ expression: T, forKey key: Self.Key, variable: repeat PredicateExpressions.Variable, predicateConfiguration: PredicateCodableConfiguration) throws { @@ -102,7 +102,7 @@ extension KeyedEncodingContainer { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension KeyedDecodingContainer { public mutating func decodePredicateExpression(forKey key: Self.Key, input: repeat (each Input).Type, output: Output.Type, predicateConfiguration: PredicateCodableConfiguration) throws -> (expression: any PredicateExpression, variable: (repeat PredicateExpressions.Variable)) { var container = try self.nestedContainer(keyedBy: PredicateExpressionCodingKeys.self, forKey: key) @@ -115,7 +115,7 @@ extension KeyedDecodingContainer { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension UnkeyedEncodingContainer { @_disfavoredOverload public mutating func encodePredicateExpression(_ expression: T, variable: repeat PredicateExpressions.Variable, predicateConfiguration: PredicateCodableConfiguration) throws { @@ -133,7 +133,7 @@ extension UnkeyedEncodingContainer { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension UnkeyedDecodingContainer { public mutating func decodePredicateExpression(input: repeat (each Input).Type, output: Output.Type, predicateConfiguration: PredicateCodableConfiguration) throws -> (expression: any PredicateExpression, variable: (repeat PredicateExpressions.Variable)) { var container = try self.nestedContainer(keyedBy: PredicateExpressionCodingKeys.self) diff --git a/Sources/FoundationEssentials/Predicate/Archiving/Predicate+Codable.swift b/Sources/FoundationEssentials/Predicate/Archiving/Predicate+Codable.swift index dab3d4858..0115f50ce 100644 --- a/Sources/FoundationEssentials/Predicate/Archiving/Predicate+Codable.swift +++ b/Sources/FoundationEssentials/Predicate/Archiving/Predicate+Codable.swift @@ -26,7 +26,7 @@ extension PredicateCodableConfiguration { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Predicate : Codable { public func encode(to encoder: Encoder) throws { try self.encode(to: encoder, configuration: .default) @@ -37,7 +37,7 @@ extension Predicate : Codable { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Expression : Codable { public func encode(to encoder: Encoder) throws { try self.encode(to: encoder, configuration: .default) @@ -48,7 +48,7 @@ extension Expression : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Predicate : CodableWithConfiguration { public typealias EncodingConfiguration = PredicateCodableConfiguration public typealias DecodingConfiguration = PredicateCodableConfiguration @@ -69,7 +69,7 @@ extension Predicate : CodableWithConfiguration { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Expression : CodableWithConfiguration { public typealias EncodingConfiguration = PredicateCodableConfiguration public typealias DecodingConfiguration = PredicateCodableConfiguration diff --git a/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift b/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift index 3bd02eef7..f788cea8d 100644 --- a/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift +++ b/Sources/FoundationEssentials/Predicate/Archiving/PredicateCodableConfiguration.swift @@ -15,13 +15,13 @@ #if canImport(ReflectionInternal) internal import ReflectionInternal -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public protocol PredicateCodableKeyPathProviding { @preconcurrency static var predicateCodableKeyPaths : [String : PartialKeyPath & Sendable] { get } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public struct PredicateCodableConfiguration: Sendable, CustomDebugStringConvertible { enum AllowListType : Equatable, Sendable { case concrete(Type) @@ -268,7 +268,7 @@ public struct PredicateCodableConfiguration: Sendable, CustomDebugStringConverti } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateCodableConfiguration { func _identifier(for keyPath: AnyKeyPath & Sendable) -> String? { let concreteIdentifier = allowedKeyPaths.first { @@ -342,7 +342,7 @@ extension PredicateCodableConfiguration { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateCodableConfiguration { public static let standardConfiguration: Self = { var configuration = Self() diff --git a/Sources/FoundationEssentials/Predicate/Archiving/PredicateExpressionConstruction.swift b/Sources/FoundationEssentials/Predicate/Archiving/PredicateExpressionConstruction.swift index c400b5173..c5b92ff00 100644 --- a/Sources/FoundationEssentials/Predicate/Archiving/PredicateExpressionConstruction.swift +++ b/Sources/FoundationEssentials/Predicate/Archiving/PredicateExpressionConstruction.swift @@ -14,7 +14,7 @@ internal import ReflectionInternal -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) enum PredicateCodableError : Error, CustomStringConvertible { case disallowedType(typeName: String, path: String) case disallowedIdentifier(String, path: String) @@ -38,7 +38,7 @@ enum PredicateCodableError : Error, CustomStringConvertible { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) private struct ExpressionStructure : Codable { private enum Argument : Codable { case scalar(ExpressionStructure) @@ -144,7 +144,7 @@ private struct ExpressionStructure : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) class PredicateArchivingState { var configuration: PredicateCodableConfiguration @@ -165,7 +165,7 @@ class PredicateArchivingState { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension _ThreadLocal.Key { static let predicateArchivingState = Self() } @@ -176,7 +176,7 @@ enum PredicateExpressionCodingKeys : CodingKey { case structure } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) fileprivate extension PredicateCodableConfiguration { mutating func allowInputs(_ input: repeat (each Input).Type) { guard self.shouldAddInputTypes else { return } @@ -202,7 +202,7 @@ private func _withPredicateArchivingState(_ configuration: PredicateCodableCo } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension KeyedEncodingContainer where Key == PredicateExpressionCodingKeys { mutating func _encode(_ expression: T, variable: repeat PredicateExpressions.Variable, predicateConfiguration: PredicateCodableConfiguration) throws { var predicateConfiguration = predicateConfiguration @@ -217,7 +217,7 @@ extension KeyedEncodingContainer where Key == PredicateExpressionCodingKeys { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension KeyedDecodingContainer where Key == PredicateExpressionCodingKeys { mutating func _decode(input: repeat (each Input).Type, output: Output.Type, predicateConfiguration: PredicateCodableConfiguration) throws -> (expression: any PredicateExpression, variable: (repeat PredicateExpressions.Variable)) { var predicateConfiguration = predicateConfiguration diff --git a/Sources/FoundationEssentials/Predicate/Expression.swift b/Sources/FoundationEssentials/Predicate/Expression.swift index 965f613d6..ec3966493 100644 --- a/Sources/FoundationEssentials/Predicate/Expression.swift +++ b/Sources/FoundationEssentials/Predicate/Expression.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public struct Expression : Sendable { public let expression : any StandardPredicateExpression public let variable: (repeat PredicateExpressions.Variable) @@ -29,6 +29,6 @@ public struct Expression : Sendable { #if hasFeature(Macros) @freestanding(expression) -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public macro Expression(_ body: (repeat each Input) -> Output) -> Expression = #externalMacro(module: "FoundationMacros", type: "ExpressionMacro") #endif diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Aggregate.swift b/Sources/FoundationEssentials/Predicate/Expressions/Aggregate.swift index e94bbd013..d215bccdc 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Aggregate.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Aggregate.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct SequenceMaximum< Elements : PredicateExpression @@ -36,23 +36,23 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceMaximum : StandardPredicateExpression where Elements : StandardPredicateExpression {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceMaximum : CustomStringConvertible { public var description: String { "SequenceMaximum(elements: \(elements))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceMaximum : Codable where Elements : Codable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceMaximum : Sendable where Elements : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct SequenceMinimum< Elements : PredicateExpression @@ -78,18 +78,18 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceMinimum : StandardPredicateExpression where Elements : StandardPredicateExpression {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceMinimum : CustomStringConvertible { public var description: String { "SequenceMinimum(elements: \(elements))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceMinimum : Codable where Elements : Codable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceMinimum : Sendable where Elements : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Arithmetic.swift b/Sources/FoundationEssentials/Predicate/Expressions/Arithmetic.swift index efec7e0c9..769b59e13 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Arithmetic.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Arithmetic.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public enum ArithmeticOperator: Codable, Sendable { case add, subtract, multiply @@ -53,17 +53,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Arithmetic : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Arithmetic : CustomStringConvertible { public var description: String { "Arithmetic(lhs: \(lhs), operator: \(op), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Arithmetic : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -80,5 +80,5 @@ extension PredicateExpressions.Arithmetic : Codable where LHS : Codable, RHS : C } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Arithmetic : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Collection.swift b/Sources/FoundationEssentials/Predicate/Expressions/Collection.swift index 7028f4b08..47afaf463 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Collection.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Collection.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct CollectionIndexSubscript< Wrapped : PredicateExpression, @@ -45,17 +45,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionIndexSubscript : Sendable where Wrapped : Sendable, Index : Sendable {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.CollectionIndexSubscript : CustomStringConvertible { public var description: String { "CollectionIndexSubscript(wrapped: \(wrapped), index: \(index))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionIndexSubscript : Codable where Wrapped : Codable, Index : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -70,10 +70,10 @@ extension PredicateExpressions.CollectionIndexSubscript : Codable where Wrapped } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionIndexSubscript : StandardPredicateExpression where Wrapped : StandardPredicateExpression, Index : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct CollectionRangeSubscript< Wrapped : PredicateExpression, @@ -113,17 +113,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.CollectionRangeSubscript : CustomStringConvertible { public var description: String { "CollectionRangeSubscript(wrapped: \(wrapped), range: \(range))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionRangeSubscript : Sendable where Wrapped : Sendable, Range : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionRangeSubscript : Codable where Wrapped : Codable, Range : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -138,10 +138,10 @@ extension PredicateExpressions.CollectionRangeSubscript : Codable where Wrapped } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionRangeSubscript : StandardPredicateExpression where Wrapped : StandardPredicateExpression, Range : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct CollectionContainsCollection< Base : PredicateExpression, @@ -174,17 +174,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.CollectionContainsCollection : CustomStringConvertible { public var description: String { "CollectionContainsCollection(base: \(base), other: \(other))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionContainsCollection : Sendable where Base : Sendable, Other : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionContainsCollection : Codable where Base : Codable, Other : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -199,5 +199,5 @@ extension PredicateExpressions.CollectionContainsCollection : Codable where Base } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.CollectionContainsCollection : StandardPredicateExpression where Base : StandardPredicateExpression, Other : StandardPredicateExpression {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Comparison.swift b/Sources/FoundationEssentials/Predicate/Expressions/Comparison.swift index 42dda257e..6a12d284f 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Comparison.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Comparison.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public enum ComparisonOperator: Codable, Sendable { case lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual @@ -54,17 +54,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Comparison : CustomStringConvertible { public var description: String { "Comparison(lhs: \(lhs), operator: \(op), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Comparison : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Comparison : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -81,5 +81,5 @@ extension PredicateExpressions.Comparison : Codable where LHS : Codable, RHS : C } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Comparison : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Conditional.swift b/Sources/FoundationEssentials/Predicate/Expressions/Conditional.swift index 3afb13d83..9b326907e 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Conditional.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Conditional.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Conditional< Test : PredicateExpression, @@ -48,17 +48,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Conditional : CustomStringConvertible { public var description: String { "Conditional(test: \(test), trueBranch: \(trueBranch), falseBranch: \(falseBranch))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Conditional : StandardPredicateExpression where Test : StandardPredicateExpression, If : StandardPredicateExpression, Else : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Conditional : Codable where Test : Codable, If : Codable, Else : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -75,5 +75,5 @@ extension PredicateExpressions.Conditional : Codable where Test : Codable, If : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Conditional : Sendable where Test : Sendable, If : Sendable, Else : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Conjunction.swift b/Sources/FoundationEssentials/Predicate/Expressions/Conjunction.swift index fef01e8d0..cae78bef2 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Conjunction.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Conjunction.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Conjunction< LHS : PredicateExpression, @@ -40,17 +40,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Conjunction : CustomStringConvertible { public var description: String { "Conjunction(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Conjunction : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Conjunction : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -65,5 +65,5 @@ extension PredicateExpressions.Conjunction : Codable where LHS : Codable, RHS : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Conjunction : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Dictionary.swift b/Sources/FoundationEssentials/Predicate/Expressions/Dictionary.swift index f4104f495..62e9395d1 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Dictionary.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Dictionary.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct DictionaryKeySubscript< Wrapped : PredicateExpression, @@ -40,17 +40,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.DictionaryKeySubscript : CustomStringConvertible { public var description: String { "DictionaryKeySubscript(wrapped: \(wrapped), key: \(key))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.DictionaryKeySubscript : Sendable where Wrapped : Sendable, Key : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.DictionaryKeySubscript : Codable where Wrapped : Codable, Key : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -65,10 +65,10 @@ extension PredicateExpressions.DictionaryKeySubscript : Codable where Wrapped : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.DictionaryKeySubscript : StandardPredicateExpression where Wrapped : StandardPredicateExpression, Key : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct DictionaryKeyDefaultValueSubscript< Wrapped : PredicateExpression, @@ -103,17 +103,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.DictionaryKeyDefaultValueSubscript : CustomStringConvertible { public var description: String { "DictionaryKeyDefaultValueSubscript(wrapped: \(wrapped), key: \(key), defaultValue: \(`default`))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.DictionaryKeyDefaultValueSubscript : Sendable where Wrapped : Sendable, Key : Sendable, Default : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.DictionaryKeyDefaultValueSubscript : Codable where Wrapped : Codable, Key : Codable, Default : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -130,5 +130,5 @@ extension PredicateExpressions.DictionaryKeyDefaultValueSubscript : Codable wher } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.DictionaryKeyDefaultValueSubscript : StandardPredicateExpression where Wrapped : StandardPredicateExpression, Key : StandardPredicateExpression, Default : StandardPredicateExpression {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Disjunction.swift b/Sources/FoundationEssentials/Predicate/Expressions/Disjunction.swift index 96c9829c2..7475de205 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Disjunction.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Disjunction.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Disjunction< LHS : PredicateExpression, @@ -40,17 +40,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Disjunction : CustomStringConvertible { public var description: String { "Disjunction(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Disjunction : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Disjunction : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -65,5 +65,5 @@ extension PredicateExpressions.Disjunction : Codable where LHS : Codable, RHS : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Disjunction : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Division.swift b/Sources/FoundationEssentials/Predicate/Expressions/Division.swift index d082902e3..c4675fdad 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Division.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Division.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct IntDivision< LHS : PredicateExpression, @@ -100,37 +100,37 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.FloatDivision : CustomStringConvertible { public var description: String { "FloatDivision(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.IntDivision : CustomStringConvertible { public var description: String { "IntDivision(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.IntRemainder : CustomStringConvertible { public var description: String { "IntRemainder(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.FloatDivision : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.IntRemainder : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.IntDivision : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.FloatDivision : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -145,7 +145,7 @@ extension PredicateExpressions.FloatDivision : Codable where LHS : Codable, RHS } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.IntRemainder : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -160,7 +160,7 @@ extension PredicateExpressions.IntRemainder : Codable where LHS : Codable, RHS : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.IntDivision : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -175,11 +175,11 @@ extension PredicateExpressions.IntDivision : Codable where LHS : Codable, RHS : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.FloatDivision : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.IntRemainder : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.IntDivision : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Equality.swift b/Sources/FoundationEssentials/Predicate/Expressions/Equality.swift index de996f015..7cbfd54bf 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Equality.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Equality.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Equal< LHS : PredicateExpression, @@ -40,17 +40,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Equal : CustomStringConvertible { public var description: String { "Equal(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Equal : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Equal : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -65,5 +65,5 @@ extension PredicateExpressions.Equal : Codable where LHS : Codable, RHS : Codabl } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Equal : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/ExpressionEvaluation.swift b/Sources/FoundationEssentials/Predicate/Expressions/ExpressionEvaluation.swift index f029a123d..f8d3b2519 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/ExpressionEvaluation.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/ExpressionEvaluation.swift @@ -10,9 +10,9 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { - @available(FoundationPredicate 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public struct ExpressionEvaluate< Transformation : PredicateExpression, each Input : PredicateExpression, @@ -35,23 +35,23 @@ extension PredicateExpressions { } } - @available(FoundationPredicate 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public static func build_evaluate(_ expression: Transformation, _ input: repeat each Input) -> ExpressionEvaluate { ExpressionEvaluate(expression: expression, input: repeat each input) } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.ExpressionEvaluate : CustomStringConvertible { public var description: String { "ExpressionEvaluate(expression: \(expression), input: \(input))" } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.ExpressionEvaluate : StandardPredicateExpression where Transformation : StandardPredicateExpression, repeat each Input : StandardPredicateExpression {} -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.ExpressionEvaluate : Codable where Transformation : Codable, repeat each Input : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -66,5 +66,5 @@ extension PredicateExpressions.ExpressionEvaluate : Codable where Transformation } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.ExpressionEvaluate : Sendable where Transformation : Sendable, repeat each Input : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Filter.swift b/Sources/FoundationEssentials/Predicate/Expressions/Filter.swift index b91f02819..288f6b979 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Filter.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Filter.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Filter< LHS : PredicateExpression, @@ -47,17 +47,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Filter : CustomStringConvertible { public var description: String { "Filter(sequence: \(sequence), variable: \(variable), filter: \(filter))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Filter : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Filter : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -74,5 +74,5 @@ extension PredicateExpressions.Filter : Codable where LHS : Codable, RHS : Codab } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Filter : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Inequality.swift b/Sources/FoundationEssentials/Predicate/Expressions/Inequality.swift index a0c258c97..5d8ca9b9e 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Inequality.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Inequality.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct NotEqual< LHS : PredicateExpression, @@ -40,17 +40,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.NotEqual : CustomStringConvertible { public var description: String { "NotEqual(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.NotEqual : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.NotEqual : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -65,5 +65,5 @@ extension PredicateExpressions.NotEqual : Codable where LHS : Codable, RHS : Cod } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.NotEqual : Sendable where LHS : Sendable, RHS : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Negation.swift b/Sources/FoundationEssentials/Predicate/Expressions/Negation.swift index c7f0ea341..dd73b85da 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Negation.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Negation.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Negation : PredicateExpression where Wrapped.Output == Bool { public typealias Output = Bool @@ -31,17 +31,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Negation : CustomStringConvertible { public var description: String { "Negation(wrapped: \(wrapped))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Negation : StandardPredicateExpression where Wrapped : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Negation : Codable where Wrapped : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -54,5 +54,5 @@ extension PredicateExpressions.Negation : Codable where Wrapped : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Negation : Sendable where Wrapped : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Optional.swift b/Sources/FoundationEssentials/Predicate/Expressions/Optional.swift index 55220b638..18327aa8d 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Optional.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Optional.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct OptionalFlatMap< LHS : PredicateExpression, @@ -111,37 +111,37 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.OptionalFlatMap : CustomStringConvertible { public var description: String { "OptionalFlatMap(wrapped: \(wrapped), variable: \(variable), transform: \(transform))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.NilCoalesce : CustomStringConvertible { public var description: String { "NilCoalesce(lhs: \(lhs), rhs: \(rhs))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ForcedUnwrap : CustomStringConvertible { public var description: String { "ForcedUnwrap(inner: \(inner))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.OptionalFlatMap : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.NilCoalesce : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ForcedUnwrap : StandardPredicateExpression where Inner : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.OptionalFlatMap : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -158,7 +158,7 @@ extension PredicateExpressions.OptionalFlatMap : Codable where LHS : Codable, RH } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.NilCoalesce : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -173,7 +173,7 @@ extension PredicateExpressions.NilCoalesce : Codable where LHS : Codable, RHS : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ForcedUnwrap : Codable where Inner : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -186,16 +186,16 @@ extension PredicateExpressions.ForcedUnwrap : Codable where Inner : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.OptionalFlatMap : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.NilCoalesce : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ForcedUnwrap : Sendable where Inner : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct NilLiteral : StandardPredicateExpression, Codable, Sendable { public typealias Output = Optional diff --git a/Sources/FoundationEssentials/Predicate/Expressions/PredicateEvaluation.swift b/Sources/FoundationEssentials/Predicate/Expressions/PredicateEvaluation.swift index c0d5e0a63..ed4155412 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/PredicateEvaluation.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/PredicateEvaluation.swift @@ -11,9 +11,9 @@ //===----------------------------------------------------------------------===// #if FOUNDATION_FRAMEWORK -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { - @available(FoundationPredicate 0.3, *) + @available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) public struct PredicateEvaluate< Condition : PredicateExpression, each Input : PredicateExpression @@ -37,23 +37,23 @@ extension PredicateExpressions { } } - @available(FoundationPredicate 0.3, *) + @available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) public static func build_evaluate(_ predicate: Condition, _ input: repeat each Input) -> PredicateEvaluate { PredicateEvaluate(predicate: predicate, input: repeat each input) } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.PredicateEvaluate : CustomStringConvertible { public var description: String { "PredicateEvaluate(predicate: \(predicate), input: \(input))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.PredicateEvaluate : StandardPredicateExpression where Condition : StandardPredicateExpression, repeat each Input : StandardPredicateExpression {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.PredicateEvaluate : Codable where Condition : Codable, repeat each Input : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -68,6 +68,6 @@ extension PredicateExpressions.PredicateEvaluate : Codable where Condition : Cod } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.PredicateEvaluate : Sendable where Condition : Sendable, repeat each Input : Sendable {} #endif diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Range.swift b/Sources/FoundationEssentials/Predicate/Expressions/Range.swift index 003773b8a..5ccfc7c87 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Range.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Range.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct Range< LHS : PredicateExpression, @@ -45,17 +45,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Range : CustomStringConvertible { public var description: String { "Range(lower: \(lower), upper: \(upper))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Range : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Range : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -70,10 +70,10 @@ extension PredicateExpressions.Range : Codable where LHS : Codable, RHS : Codabl } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Range : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct ClosedRange< LHS : PredicateExpression, @@ -105,17 +105,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ClosedRange : CustomStringConvertible { public var description: String { "ClosedRange(lower: \(lower), upper: \(upper))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ClosedRange : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ClosedRange : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -130,10 +130,10 @@ extension PredicateExpressions.ClosedRange : Codable where LHS : Codable, RHS : } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ClosedRange : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct RangeExpressionContains< RangeExpression : PredicateExpression, @@ -162,17 +162,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.RangeExpressionContains : CustomStringConvertible { public var description: String { "RangeExpressionContains(range: \(range), element: \(element))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.RangeExpressionContains : StandardPredicateExpression where RangeExpression : StandardPredicateExpression, Element : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.RangeExpressionContains : Codable where RangeExpression : Codable, Element : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -187,5 +187,5 @@ extension PredicateExpressions.RangeExpressionContains : Codable where RangeExpr } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.RangeExpressionContains : Sendable where RangeExpression : Sendable, Element : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Regex.swift b/Sources/FoundationEssentials/Predicate/Expressions/Regex.swift index 0bb87eda3..4ab7478f2 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Regex.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Regex.swift @@ -12,7 +12,7 @@ #if compiler(>=5.11) -@available(FoundationPredicateRegex 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions { public struct StringContainsRegex< Subject : PredicateExpression, @@ -48,7 +48,7 @@ extension PredicateExpressions { } } -@available(FoundationPredicateRegex 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions { public struct PredicateRegex: Sendable, Codable, RegexComponent, CustomStringConvertible { private struct _Storage: @unchecked Sendable { @@ -90,10 +90,10 @@ extension PredicateExpressions { } } -@available(FoundationPredicateRegex 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.StringContainsRegex : Sendable where Subject : Sendable, Regex : Sendable {} -@available(FoundationPredicateRegex 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.StringContainsRegex : Codable where Subject : Codable, Regex : Codable { public func encode(to encoder: any Encoder) throws { var container = encoder.unkeyedContainer() @@ -108,7 +108,7 @@ extension PredicateExpressions.StringContainsRegex : Codable where Subject : Cod } } -@available(FoundationPredicateRegex 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.StringContainsRegex : StandardPredicateExpression where Subject : StandardPredicateExpression, Regex : StandardPredicateExpression {} #endif diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift b/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift index 9ffbdc725..16bb79b52 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Sequence.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct SequenceContains< LHS : PredicateExpression, @@ -141,40 +141,40 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceContains : CustomStringConvertible { public var description: String { "SequenceContains(sequence: \(sequence), element: \(element))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceContainsWhere : CustomStringConvertible { public var description: String { "SequenceContainsWhere(sequence: \(sequence), variable: \(variable), test: \(test))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceAllSatisfy : CustomStringConvertible { public var description: String { "SequenceAllSatisfy(sequence: \(sequence), variable: \(variable), test: \(test))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceContains : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceContainsWhere : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceAllSatisfy : StandardPredicateExpression where LHS : StandardPredicateExpression, RHS : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceStartsWith : StandardPredicateExpression where Base : StandardPredicateExpression, Prefix : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceContains : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -189,7 +189,7 @@ extension PredicateExpressions.SequenceContains : Codable where LHS : Codable, R } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceContainsWhere : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -206,7 +206,7 @@ extension PredicateExpressions.SequenceContainsWhere : Codable where LHS : Codab } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceAllSatisfy : Codable where LHS : Codable, RHS : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -223,7 +223,7 @@ extension PredicateExpressions.SequenceAllSatisfy : Codable where LHS : Codable, } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceStartsWith : Codable where Base : Codable, Prefix : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -238,14 +238,14 @@ extension PredicateExpressions.SequenceStartsWith : Codable where Base : Codable } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceContains : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceContainsWhere : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceAllSatisfy : Sendable where LHS : Sendable, RHS : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.SequenceStartsWith : Sendable where Base : Sendable, Prefix : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/StringComparison.swift b/Sources/FoundationEssentials/Predicate/Expressions/StringComparison.swift index 69ed5d862..4cccfedbf 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/StringComparison.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/StringComparison.swift @@ -12,7 +12,7 @@ #if FOUNDATION_FRAMEWORK -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct StringCaseInsensitiveCompare< Root : PredicateExpression, @@ -41,17 +41,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.StringCaseInsensitiveCompare : CustomStringConvertible { public var description: String { "StringCaseInsensitiveCompare(root: \(root), other: \(other))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.StringCaseInsensitiveCompare : StandardPredicateExpression where Root : StandardPredicateExpression, Other : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.StringCaseInsensitiveCompare : Codable where Root : Codable, Other : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.unkeyedContainer() @@ -66,7 +66,7 @@ extension PredicateExpressions.StringCaseInsensitiveCompare : Codable where Root } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.StringCaseInsensitiveCompare : Sendable where Root : Sendable, Other : Sendable {} #endif diff --git a/Sources/FoundationEssentials/Predicate/Expressions/Types.swift b/Sources/FoundationEssentials/Predicate/Expressions/Types.swift index a091a9267..334e72616 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/Types.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/Types.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct ConditionalCast< Input : PredicateExpression, @@ -68,37 +68,37 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ConditionalCast : CustomStringConvertible { public var description: String { "ConditionalCast(input: \(input), desiredType: \(_typeName(Desired.self)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ForceCast : CustomStringConvertible { public var description: String { "ForceCast(input: \(input), desiredType: \(_typeName(Desired.self)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.TypeCheck : CustomStringConvertible { public var description: String { "TypeCheck(input: \(input), desiredType: \(_typeName(Desired.self)))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ConditionalCast : StandardPredicateExpression where Input : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ForceCast : StandardPredicateExpression where Input : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.TypeCheck : StandardPredicateExpression where Input : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ConditionalCast : Codable where Input : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -111,7 +111,7 @@ extension PredicateExpressions.ConditionalCast : Codable where Input : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ForceCast : Codable where Input : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -124,7 +124,7 @@ extension PredicateExpressions.ForceCast : Codable where Input : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.TypeCheck : Codable where Input : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -137,11 +137,11 @@ extension PredicateExpressions.TypeCheck : Codable where Input : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ConditionalCast : Sendable where Input : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.ForceCast : Sendable where Input : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.TypeCheck : Sendable where Input : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/Expressions/UnaryMinus.swift b/Sources/FoundationEssentials/Predicate/Expressions/UnaryMinus.swift index 3e0655439..d810491c4 100644 --- a/Sources/FoundationEssentials/Predicate/Expressions/UnaryMinus.swift +++ b/Sources/FoundationEssentials/Predicate/Expressions/UnaryMinus.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct UnaryMinus : PredicateExpression where Wrapped.Output: SignedNumeric { public typealias Output = Wrapped.Output @@ -31,17 +31,17 @@ extension PredicateExpressions { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.UnaryMinus : CustomStringConvertible { public var description: String { "UnaryMinus(wrapped: \(wrapped))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.UnaryMinus : StandardPredicateExpression where Wrapped : StandardPredicateExpression {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.UnaryMinus : Codable where Wrapped : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -54,5 +54,5 @@ extension PredicateExpressions.UnaryMinus : Codable where Wrapped : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.UnaryMinus : Sendable where Wrapped : Sendable {} diff --git a/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift b/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift index 1379b24b5..9e06056f5 100644 --- a/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift +++ b/Sources/FoundationEssentials/Predicate/NSPredicateConversion.swift @@ -562,7 +562,7 @@ extension OverwritingInitializable { extension NSPredicate : OverwritingInitializable {} extension NSExpression : OverwritingInitializable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension NSPredicate { public convenience init?(_ predicate: Predicate) where Input : NSObject { let variable = predicate.variable @@ -574,7 +574,7 @@ extension NSPredicate { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension NSExpression { public convenience init?(_ expression: Expression) where Input : NSObject { let variable = expression.variable diff --git a/Sources/FoundationEssentials/Predicate/Predicate+Description.swift b/Sources/FoundationEssentials/Predicate/Predicate+Description.swift index 031743f8b..7a5b3f193 100644 --- a/Sources/FoundationEssentials/Predicate/Predicate+Description.swift +++ b/Sources/FoundationEssentials/Predicate/Predicate+Description.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) package struct DebugStringConversionState { private var variables: [PredicateExpressions.VariableID : String] private var nextVariable = 1 @@ -78,61 +78,61 @@ extension AnyKeyPath { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) package protocol DebugStringConvertiblePredicateExpression : StandardPredicateExpression { func debugString(state: inout DebugStringConversionState) -> String } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Variable : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { state[self.key] } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.KeyPath : DebugStringConvertiblePredicateExpression where Root : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { root.debugString(state: &state) + keyPath.debugStringWithoutType } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Value : DebugStringConvertiblePredicateExpression where Self : StandardPredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { state.addCapture(value) } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Conjunction : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) && \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Disjunction : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) || \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Equal : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) == \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.NotEqual : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) != \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Arithmetic : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { let op = switch self.op { @@ -144,7 +144,7 @@ extension PredicateExpressions.Arithmetic : DebugStringConvertiblePredicateExpre } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Comparison : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { let op = switch self.op { @@ -157,42 +157,42 @@ extension PredicateExpressions.Comparison : DebugStringConvertiblePredicateExpre } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.UnaryMinus : DebugStringConvertiblePredicateExpression where Wrapped : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "-\(wrapped.debugString(state: &state))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceMinimum : DebugStringConvertiblePredicateExpression where Elements : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(elements.debugString(state: &state)).min()" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceMaximum : DebugStringConvertiblePredicateExpression where Elements : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(elements.debugString(state: &state)).max()" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ClosedRange : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lower.debugString(state: &state)) ... \(upper.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Range : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lower.debugString(state: &state)) ..< \(upper.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Conditional : DebugStringConvertiblePredicateExpression where Test : DebugStringConvertiblePredicateExpression, If : DebugStringConvertiblePredicateExpression, Else : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { """ @@ -205,56 +205,56 @@ extension PredicateExpressions.Conditional : DebugStringConvertiblePredicateExpr } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.CollectionIndexSubscript : DebugStringConvertiblePredicateExpression where Wrapped : DebugStringConvertiblePredicateExpression, Index : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(wrapped.debugString(state: &state))[\(index.debugString(state: &state))]" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.CollectionRangeSubscript : DebugStringConvertiblePredicateExpression where Wrapped : DebugStringConvertiblePredicateExpression, Range : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(wrapped.debugString(state: &state))[\(range.debugString(state: &state))]" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.CollectionContainsCollection : DebugStringConvertiblePredicateExpression where Base : DebugStringConvertiblePredicateExpression, Other : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(base.debugString(state: &state)).contains(\(other.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ConditionalCast : DebugStringConvertiblePredicateExpression where Input : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(input.debugString(state: &state)) as? \(_typeName(Desired.self)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ForceCast : DebugStringConvertiblePredicateExpression where Input : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(input.debugString(state: &state)) as! \(_typeName(Desired.self)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.TypeCheck : DebugStringConvertiblePredicateExpression where Input : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(input.debugString(state: &state)) is \(_typeName(Desired.self)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.ForcedUnwrap : DebugStringConvertiblePredicateExpression where Inner : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(inner.debugString(state: &state))!" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.OptionalFlatMap : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { state.setupVariable(variable.key) @@ -266,84 +266,84 @@ extension PredicateExpressions.OptionalFlatMap : DebugStringConvertiblePredicate } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.DictionaryKeySubscript : DebugStringConvertiblePredicateExpression where Wrapped : DebugStringConvertiblePredicateExpression, Key : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(wrapped.debugString(state: &state))[\(key.debugString(state: &state))]" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.DictionaryKeyDefaultValueSubscript : DebugStringConvertiblePredicateExpression where Wrapped : DebugStringConvertiblePredicateExpression, Key : DebugStringConvertiblePredicateExpression, Default : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(wrapped.debugString(state: &state))[\(key.debugString(state: &state)), default: \(self.default.debugString(state: &state))]" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.FloatDivision : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) / \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.IntDivision : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) / \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.IntRemainder : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) % \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Negation : DebugStringConvertiblePredicateExpression where Wrapped : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "!\(wrapped.debugString(state: &state))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.NilCoalesce : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS: DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "(\(lhs.debugString(state: &state)) ?? \(rhs.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.NilLiteral : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "nil" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.RangeExpressionContains : DebugStringConvertiblePredicateExpression where RangeExpression : DebugStringConvertiblePredicateExpression, Element : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(range.debugString(state: &state)).contains(\(element.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceContains : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS: DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(sequence.debugString(state: &state)).contains(\(element.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceStartsWith : DebugStringConvertiblePredicateExpression where Base : DebugStringConvertiblePredicateExpression, Prefix : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(base.debugString(state: &state)).starts(with: \(prefix.debugString(state: &state)))" } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceContainsWhere : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { state.setupVariable(variable.key) @@ -355,7 +355,7 @@ extension PredicateExpressions.SequenceContainsWhere : DebugStringConvertiblePre } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.SequenceAllSatisfy : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { state.setupVariable(variable.key) @@ -367,7 +367,7 @@ extension PredicateExpressions.SequenceAllSatisfy : DebugStringConvertiblePredic } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Filter : DebugStringConvertiblePredicateExpression where LHS : DebugStringConvertiblePredicateExpression, RHS : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { state.setupVariable(variable.key) @@ -380,7 +380,7 @@ extension PredicateExpressions.Filter : DebugStringConvertiblePredicateExpressio } #if compiler(>=5.11) -@available(FoundationPredicateRegex 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.StringContainsRegex : DebugStringConvertiblePredicateExpression where Subject : DebugStringConvertiblePredicateExpression, Regex : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(subject.debugString(state: &state)).contains(\(subject.debugString(state: &state)))" @@ -388,7 +388,7 @@ extension PredicateExpressions.StringContainsRegex : DebugStringConvertiblePredi } #endif -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension PredicateExpressions.ExpressionEvaluate : DebugStringConvertiblePredicateExpression where Transformation : DebugStringConvertiblePredicateExpression, repeat each Input : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { var inputStrings: [String] = [] @@ -399,7 +399,7 @@ extension PredicateExpressions.ExpressionEvaluate : DebugStringConvertiblePredic #if FOUNDATION_FRAMEWORK -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.PredicateEvaluate : DebugStringConvertiblePredicateExpression where Condition : DebugStringConvertiblePredicateExpression, repeat each Input : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { var inputStrings: [String] = [] @@ -408,7 +408,7 @@ extension PredicateExpressions.PredicateEvaluate : DebugStringConvertiblePredica } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.StringCaseInsensitiveCompare : DebugStringConvertiblePredicateExpression where Root : DebugStringConvertiblePredicateExpression, Other : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(root.debugString(state: &state)).caseInsensitiveCompare(\(other.debugString(state: &state)))" @@ -417,7 +417,7 @@ extension PredicateExpressions.StringCaseInsensitiveCompare : DebugStringConvert #endif -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) private func createDescription(variable: repeat PredicateExpressions.Variable, expression: some StandardPredicateExpression, typeName: String, outputType: Output.Type = Void.self) -> String { var variableIDs: [PredicateExpressions.VariableID] = [] repeat variableIDs.append((each variable).key) @@ -447,7 +447,7 @@ private func createDescription(variable: repeat PredicateExp return result } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension Predicate : CustomStringConvertible { @_optimize(none) // Work around swift optimizer crash (rdar://124533887) public var description: String { @@ -455,14 +455,14 @@ extension Predicate : CustomStringConvertible { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Expression : CustomStringConvertible { public var description: String { createDescription(variable: repeat each variable, expression: expression, typeName: "Expression", outputType: Output.self) } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension Predicate : CustomDebugStringConvertible { public var debugDescription: String { var variableDesc: [String] = [] @@ -471,7 +471,7 @@ extension Predicate : CustomDebugStringConvertible { } } -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Expression : CustomDebugStringConvertible { public var debugDescription: String { var variableDesc: [String] = [] diff --git a/Sources/FoundationEssentials/Predicate/Predicate.swift b/Sources/FoundationEssentials/Predicate/Predicate.swift index dda74f395..3a8c2a5f8 100644 --- a/Sources/FoundationEssentials/Predicate/Predicate.swift +++ b/Sources/FoundationEssentials/Predicate/Predicate.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public struct Predicate : Sendable { public let expression : any StandardPredicateExpression public let variable: (repeat PredicateExpressions.Variable) @@ -29,11 +29,11 @@ public struct Predicate : Sendable { #if hasFeature(Macros) @freestanding(expression) -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public macro Predicate(_ body: (repeat each Input) -> Bool) -> Predicate = #externalMacro(module: "FoundationMacros", type: "PredicateMacro") #endif -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Predicate { private init(value: Bool) { self.variable = (repeat PredicateExpressions.Variable()) @@ -51,7 +51,7 @@ extension Predicate { // Namespace for operator expressions -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) @frozen public enum PredicateExpressions {} @available(macOS, unavailable, introduced: 14.0) @@ -61,7 +61,7 @@ extension Predicate { @available(*, unavailable) extension PredicateExpressions : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension Sequence { public func filter(_ predicate: Predicate) throws -> [Element] { try self.filter { diff --git a/Sources/FoundationEssentials/Predicate/PredicateBindings.swift b/Sources/FoundationEssentials/Predicate/PredicateBindings.swift index 08ee68130..0ecca9f42 100644 --- a/Sources/FoundationEssentials/Predicate/PredicateBindings.swift +++ b/Sources/FoundationEssentials/Predicate/PredicateBindings.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public struct PredicateBindings { // Store as a values as an array instead of a dictionary (since it is almost always very few elements, this reduces heap allocation and hashing overhead) private var storage: [(id: PredicateExpressions.VariableID, value: Any)] diff --git a/Sources/FoundationEssentials/Predicate/PredicateExpression.swift b/Sources/FoundationEssentials/Predicate/PredicateExpression.swift index 160a00959..f2d7fad1f 100644 --- a/Sources/FoundationEssentials/Predicate/PredicateExpression.swift +++ b/Sources/FoundationEssentials/Predicate/PredicateExpression.swift @@ -14,7 +14,7 @@ internal import Synchronization #endif -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public protocol PredicateExpression { associatedtype Output @@ -22,10 +22,10 @@ public protocol PredicateExpression { } // Only Foundation should add conformances to this protocol -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public protocol StandardPredicateExpression : PredicateExpression, Codable, Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public struct PredicateError: Error, Hashable, CustomDebugStringConvertible { internal enum _Error: Hashable, Sendable { case undefinedVariable @@ -81,7 +81,7 @@ public struct PredicateError: Error, Hashable, CustomDebugStringConvertible { public static let invalidInput = Self(.invalidInput(nil)) } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions { public struct VariableID: Hashable, Codable, Sendable { let id: UInt @@ -203,7 +203,7 @@ extension AnyKeyPath { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.KeyPath : Codable where Root : Codable { private enum CodingKeys : CodingKey { case root @@ -240,20 +240,20 @@ extension PredicateExpressions.KeyPath : Codable where Root : Codable { #endif // FOUNDATION_FRAMEWORK } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.KeyPath : Sendable where Root : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.KeyPath : StandardPredicateExpression where Root : StandardPredicateExpression {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.KeyPath : CustomStringConvertible { public var description: String { "KeyPath(root: \(root), keyPath: \(keyPath.debugDescription))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Value : Codable where Output : Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() @@ -266,13 +266,13 @@ extension PredicateExpressions.Value : Codable where Output : Codable { } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Value : Sendable where Output : Sendable {} -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.Value : StandardPredicateExpression where Output : Codable /*, Output : Sendable*/ {} -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Value : CustomStringConvertible { public var description: String { var result = "Value<\(_typeName(Output.self))>(" @@ -281,14 +281,14 @@ extension PredicateExpressions.Value : CustomStringConvertible { } } -@available(FoundationPredicate 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.Variable : CustomStringConvertible { public var description: String { "Variable(\(key.id))" } } -@available(FoundationPredicate 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension PredicateExpressions.KeyPath { public enum CommonKeyPathKind : Hashable, Sendable { case collectionCount diff --git a/Sources/FoundationEssentials/PropertyList/PlistDecoder.swift b/Sources/FoundationEssentials/PropertyList/PlistDecoder.swift index 3b9c6a43a..79ec14087 100644 --- a/Sources/FoundationEssentials/PropertyList/PlistDecoder.swift +++ b/Sources/FoundationEssentials/PropertyList/PlistDecoder.swift @@ -103,23 +103,23 @@ open class PropertyListDecoder { }, from: data, format: &format) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func decode(_ type: T.Type, from data: Data, configuration: T.DecodingConfiguration) throws -> T { var format: PropertyListDecoder.PropertyListFormat = .binary return try decode(type, from: data, format: &format, configuration: configuration) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func decode(_ type: T.Type, from data: Data, configuration: C.Type) throws -> T where T : DecodableWithConfiguration, C : DecodingConfigurationProviding, T.DecodingConfiguration == C.DecodingConfiguration { try decode(type, from: data, configuration: C.decodingConfiguration) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func decode(_ type: T.Type, from data: Data, format: inout PropertyListDecoder.PropertyListFormat, configuration: C.Type) throws -> T where T : DecodableWithConfiguration, C: DecodingConfigurationProviding, T.DecodingConfiguration == C.DecodingConfiguration { try decode(type, from: data, format: &format, configuration: C.decodingConfiguration) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func decode(_ type: T.Type, from data: Data, format: inout PropertyListDecoder.PropertyListFormat, configuration: T.DecodingConfiguration) throws -> T { try _decode({ try $0.decode(type, configuration: configuration) diff --git a/Sources/FoundationEssentials/PropertyList/PlistEncoder.swift b/Sources/FoundationEssentials/PropertyList/PlistEncoder.swift index e9ee30678..3d5a3fcb1 100644 --- a/Sources/FoundationEssentials/PropertyList/PlistEncoder.swift +++ b/Sources/FoundationEssentials/PropertyList/PlistEncoder.swift @@ -168,7 +168,7 @@ open class PropertyListEncoder { return try writer.serializePlist(topLevel) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func encode(_ value: T, configuration: T.EncodingConfiguration) throws -> Data { let format = self.outputFormat do { @@ -202,7 +202,7 @@ open class PropertyListEncoder { return try writer.serializePlist(topLevel) } - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) open func encode(_ value: T, configuration: C.Type) throws -> Data where T : EncodableWithConfiguration, C : EncodingConfigurationProviding, T.EncodingConfiguration == C.EncodingConfiguration { try encode(value, configuration: C.encodingConfiguration) } diff --git a/Sources/FoundationEssentials/SortComparator.swift b/Sources/FoundationEssentials/SortComparator.swift index 91e0a3d69..8c7724896 100644 --- a/Sources/FoundationEssentials/SortComparator.swift +++ b/Sources/FoundationEssentials/SortComparator.swift @@ -143,7 +143,7 @@ public struct ComparableComparator: SortComparator, Sendab public var order: SortOrder #if FOUNDATION_FRAMEWORK - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(order: SortOrder = .forward) { self.order = order } diff --git a/Sources/FoundationEssentials/String/StringProtocol+Essentials.swift b/Sources/FoundationEssentials/String/StringProtocol+Essentials.swift index 1447385e0..34489bf00 100644 --- a/Sources/FoundationEssentials/String/StringProtocol+Essentials.swift +++ b/Sources/FoundationEssentials/String/StringProtocol+Essentials.swift @@ -98,7 +98,7 @@ dynamic package func _icuStringEncodingConvert(string: String, using encoding: S } #endif -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension String { public func data(using encoding: String.Encoding, allowLossyConversion: Bool = false) -> Data? { // allowLossyConversion is a no-op for UTF8 and UTF16. For UTF32, we fall back to NSString when lossy conversion is requested on Darwin platforms. diff --git a/Sources/FoundationEssentials/UUID.swift b/Sources/FoundationEssentials/UUID.swift index 23b69a115..2a5278219 100644 --- a/Sources/FoundationEssentials/UUID.swift +++ b/Sources/FoundationEssentials/UUID.swift @@ -125,9 +125,9 @@ extension UUID : Codable { } } -@available(FoundationPreview 0.1, *) +@available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) extension UUID : Comparable { - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public static func < (lhs: UUID, rhs: UUID) -> Bool { var leftUUID = lhs.uuid var rightUUID = rhs.uuid diff --git a/Sources/FoundationInternationalization/Formatting/Date/Date+AnchoredRelativeFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Date/Date+AnchoredRelativeFormatStyle.swift index f91f68809..d97aca5a3 100644 --- a/Sources/FoundationInternationalization/Formatting/Date/Date+AnchoredRelativeFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Date/Date+AnchoredRelativeFormatStyle.swift @@ -18,7 +18,7 @@ internal import _FoundationICU // MARK: Date.AnchoredRelativeFormatStyle -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date { /// A relative format style that is detached from the system time, and instead /// formats an anchor date relative to the format input. @@ -114,7 +114,7 @@ extension Date { // MARK: DiscreteFormatStyle Conformance -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.AnchoredRelativeFormatStyle : DiscreteFormatStyle { public func discreteInput(before input: Date) -> Date? { guard let (bound, isIncluded) = bound(for: input, relativeTo: anchor, movingDown: true, countingTowardZero: input > anchor) else { diff --git a/Sources/FoundationInternationalization/Formatting/Date/Date+RelativeFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Date/Date+RelativeFormatStyle.swift index 0d528960c..b65f6c53e 100644 --- a/Sources/FoundationInternationalization/Formatting/Date/Date+RelativeFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Date/Date+RelativeFormatStyle.swift @@ -22,7 +22,7 @@ extension Date { @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) public struct RelativeFormatStyle : Codable, Hashable, Sendable { - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public typealias Field = Date.ComponentsFormatStyle.Field public struct UnitsStyle : Codable, Hashable, Sendable { @@ -95,7 +95,7 @@ extension Date { public var calendar: Calendar /// The fields that can be used in the formatted output. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public var allowedFields: Set { get { _allowedFields @@ -125,7 +125,7 @@ extension Date { self._allowedFields = Set(Date.ComponentsFormatStyle.Field.Option.allCases.map { .init(option: $0) }) } - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public init(allowedFields: Set, presentation: Presentation = .numeric, unitsStyle: UnitsStyle = .wide, locale: Locale = .autoupdatingCurrent, calendar: Calendar = .autoupdatingCurrent, capitalizationContext: FormatStyleCapitalizationContext = .unknown) { self.presentation = presentation self.unitsStyle = unitsStyle diff --git a/Sources/FoundationInternationalization/Formatting/Date/Date+VerbatimFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Date/Date+VerbatimFormatStyle.swift index 807c3276d..26698f3b8 100644 --- a/Sources/FoundationInternationalization/Formatting/Date/Date+VerbatimFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Date/Date+VerbatimFormatStyle.swift @@ -78,7 +78,7 @@ extension Date.VerbatimFormatStyle: ParseableFormatStyle { // MARK: Typed Attributed Style -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.VerbatimFormatStyle { /// The type preserving attributed variant of this style. /// @@ -139,7 +139,7 @@ extension Date.VerbatimFormatStyle : CustomConsumingRegexComponent { // MARK: DiscreteFormatStyle Conformance -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.VerbatimFormatStyle : DiscreteFormatStyle { public func discreteInput(before input: Date) -> Date? { guard let (bound, isIncluded) = bound(for: input, isLower: true) else { @@ -176,7 +176,7 @@ extension Date.VerbatimFormatStyle : DiscreteFormatStyle { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.VerbatimFormatStyle.Attributed : DiscreteFormatStyle { public func discreteInput(before input: Date) -> Date? { base.discreteInput(before: input) diff --git a/Sources/FoundationInternationalization/Formatting/Date/DateFieldSymbol.swift b/Sources/FoundationInternationalization/Formatting/Date/DateFieldSymbol.swift index add48650f..1ff65c151 100644 --- a/Sources/FoundationInternationalization/Formatting/Date/DateFieldSymbol.swift +++ b/Sources/FoundationInternationalization/Formatting/Date/DateFieldSymbol.swift @@ -999,97 +999,97 @@ public extension Date.FormatStyle.Symbol.TimeZone { // MARK: Omitted Symbol Options -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Era { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Year { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.YearForWeekOfYear { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.CyclicYear { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Quarter { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Month { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Week { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Day { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.DayOfYear { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Weekday { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.DayPeriod { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Hour { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Minute { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.Second { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.SecondFraction { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Symbol.TimeZone { /// The option for not including the symbol in the formatted output. public static let omitted: Self = .init(option: nil) diff --git a/Sources/FoundationInternationalization/Formatting/Date/DateFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Date/DateFormatStyle.swift index b35fe56c1..5a25b2548 100644 --- a/Sources/FoundationInternationalization/Formatting/Date/DateFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Date/DateFormatStyle.swift @@ -346,7 +346,7 @@ extension Date.AttributedStyle : FormatStyle {} // MARK: Typed Attributed Style -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle { /// The type preserving attributed variant of this style. /// @@ -557,7 +557,7 @@ extension Date.FormatStyle { // MARK: Symbol Modifiers Attributed Style -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Attributed { /// Change the representation of the era in the format. /// @@ -817,7 +817,7 @@ public extension ParseStrategy where Self == Date.FormatStyle { // MARK: DiscreteFormatStyle Conformance -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle : DiscreteFormatStyle { public func discreteInput(before input: Date) -> Date? { guard let (bound, isIncluded) = bound(for: input, isLower: true) else { @@ -854,7 +854,7 @@ extension Date.FormatStyle : DiscreteFormatStyle { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Date.FormatStyle.Attributed : DiscreteFormatStyle { public func discreteInput(before input: Date) -> Date? { base.discreteInput(before: input) diff --git a/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift index 6b744be2c..2578c39d1 100644 --- a/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Duration+TimeFormatStyle.swift @@ -423,7 +423,7 @@ extension Duration.TimeFormatStyle { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.TimeFormatStyle { /// Returns a modified style that applies the given `grouping` rule to the highest field in the /// pattern. @@ -440,7 +440,7 @@ extension Duration.TimeFormatStyle { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.TimeFormatStyle.Attributed { /// Returns a modified style that applies the given `grouping` rule to the highest field in the /// pattern. @@ -453,7 +453,7 @@ extension Duration.TimeFormatStyle.Attributed { // MARK: Dynamic Member Lookup -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.TimeFormatStyle.Attributed { private var innerStyle: Duration.TimeFormatStyle { get { @@ -480,7 +480,7 @@ extension Duration.TimeFormatStyle.Attributed { // MARK: DiscreteFormatStyle Conformance -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.TimeFormatStyle.Attributed : DiscreteFormatStyle { public func discreteInput(before input: Duration) -> Duration? { Duration.TimeFormatStyle(pattern: pattern, locale: locale).discreteInput(before: input) @@ -491,7 +491,7 @@ extension Duration.TimeFormatStyle.Attributed : DiscreteFormatStyle { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.TimeFormatStyle : DiscreteFormatStyle { public func discreteInput(before input: Duration) -> Duration? { let (bound, isIncluded) = Duration.bound(for: input, in: interval(for: input), countingDown: true, roundingRule: self.pattern.roundingRule) diff --git a/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift index 2463070c2..f17ce7548 100644 --- a/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Duration+UnitsFormatStyle.swift @@ -632,7 +632,7 @@ extension Duration.UnitsFormatStyle { // MARK: Dynamic Member Lookup -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.UnitsFormatStyle.Attributed { public subscript(dynamicMember key: KeyPath) -> T { innerStyle[keyPath: key] @@ -650,7 +650,7 @@ extension Duration.UnitsFormatStyle.Attributed { // MARK: DiscreteFormatStyle Conformance -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.UnitsFormatStyle.Attributed : DiscreteFormatStyle { public func discreteInput(before input: Duration) -> Duration? { self.innerStyle.discreteInput(before: input) @@ -661,7 +661,7 @@ extension Duration.UnitsFormatStyle.Attributed : DiscreteFormatStyle { } } -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) extension Duration.UnitsFormatStyle : DiscreteFormatStyle { public func discreteInput(before input: Duration) -> Duration? { let (bound, isIncluded) = self.bound(for: input, countingDown: true) diff --git a/Sources/FoundationInternationalization/Formatting/Number/Decimal+FormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Number/Decimal+FormatStyle.swift index 74f12db46..cb2be15f3 100644 --- a/Sources/FoundationInternationalization/Formatting/Number/Decimal+FormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Number/Decimal+FormatStyle.swift @@ -239,7 +239,7 @@ extension Decimal.FormatStyle { /// /// - Parameter notation: The notation to apply to the format style. /// - Returns: A decimal currency format style modified to use the specified notation. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public func notation(_ notation: Configuration.Notation) -> Self { var new = self new.collection.notation = notation diff --git a/Sources/FoundationInternationalization/Formatting/Number/FloatingPointFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Number/FloatingPointFormatStyle.swift index 047efe4e6..249259e5d 100644 --- a/Sources/FoundationInternationalization/Formatting/Number/FloatingPointFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Number/FloatingPointFormatStyle.swift @@ -206,7 +206,7 @@ extension FloatingPointFormatStyle { /// /// - Parameter notation: The notation to apply to the format style. /// - Returns: A floating-point currency format style modified to use the specified notation. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public func notation(_ notation: Configuration.Notation) -> Self { var new = self new.collection.notation = notation diff --git a/Sources/FoundationInternationalization/Formatting/Number/IntegerFormatStyle.swift b/Sources/FoundationInternationalization/Formatting/Number/IntegerFormatStyle.swift index 23f9b7e0a..16f1d0142 100644 --- a/Sources/FoundationInternationalization/Formatting/Number/IntegerFormatStyle.swift +++ b/Sources/FoundationInternationalization/Formatting/Number/IntegerFormatStyle.swift @@ -206,7 +206,7 @@ extension IntegerFormatStyle { /// /// - Parameter notation: The notation to apply to the format style. /// - Returns: An integer currency format style modified to use the specified notation. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public func notation(_ notation: Configuration.Notation) -> Self { var new = self new.collection.notation = notation diff --git a/Sources/FoundationInternationalization/Formatting/Number/NumberFormatStyleConfiguration.swift b/Sources/FoundationInternationalization/Formatting/Number/NumberFormatStyleConfiguration.swift index 5898bf071..139ecf63f 100644 --- a/Sources/FoundationInternationalization/Formatting/Number/NumberFormatStyleConfiguration.swift +++ b/Sources/FoundationInternationalization/Formatting/Number/NumberFormatStyleConfiguration.swift @@ -318,7 +318,7 @@ public enum CurrencyFormatStyleConfiguration { public typealias DecimalSeparatorDisplayStrategy = NumberFormatStyleConfiguration.DecimalSeparatorDisplayStrategy public typealias RoundingRule = NumberFormatStyleConfiguration.RoundingRule /// The type used to configure notation for currency format styles. - @available(FoundationPreview 0.4, *) + @available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public typealias Notation = NumberFormatStyleConfiguration.Notation internal typealias RoundingIncrement = NumberFormatStyleConfiguration.RoundingIncrement diff --git a/Sources/FoundationInternationalization/Predicate/LocalizedString.swift b/Sources/FoundationInternationalization/Predicate/LocalizedString.swift index d704b053a..0d46f8cbd 100644 --- a/Sources/FoundationInternationalization/Predicate/LocalizedString.swift +++ b/Sources/FoundationInternationalization/Predicate/LocalizedString.swift @@ -41,14 +41,14 @@ extension PredicateExpressions { } } -@available(FoundationPreview 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.StringLocalizedStandardContains : CustomStringConvertible { public var description: String { "StringLocalizedStandardContains(root: \(root), other: \(other))" } } -@available(FoundationPreview 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.StringLocalizedStandardContains : DebugStringConvertiblePredicateExpression where Root : DebugStringConvertiblePredicateExpression, Other : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(root.debugString(state: &state)).localizedStandardContains(\(other.debugString(state: &state)))" @@ -105,14 +105,14 @@ extension PredicateExpressions { } } -@available(FoundationPreview 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.StringLocalizedCompare : CustomStringConvertible { public var description: String { "StringLocalizedCompare(root: \(root), other: \(other))" } } -@available(FoundationPreview 0.3, *) +@available(macOS 14.4, iOS 17.4, tvOS 17.4, watchOS 10.4, *) extension PredicateExpressions.StringLocalizedCompare : DebugStringConvertiblePredicateExpression where Root : DebugStringConvertiblePredicateExpression, Other : DebugStringConvertiblePredicateExpression { package func debugString(state: inout DebugStringConversionState) -> String { "\(root.debugString(state: &state)).localizedCompare(\(other.debugString(state: &state)))" diff --git a/Sources/FoundationInternationalization/String/SortDescriptor.swift b/Sources/FoundationInternationalization/String/SortDescriptor.swift index 6838c0863..b01608f13 100644 --- a/Sources/FoundationInternationalization/String/SortDescriptor.swift +++ b/Sources/FoundationInternationalization/String/SortDescriptor.swift @@ -182,7 +182,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// The key path to the field for comparison. /// /// This value is `nil` when `Compared` is not an NSObject - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var keyPath: PartialKeyPath? { switch comparison { case .comparable(_, let keyPath): @@ -202,7 +202,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// /// This property is non-`nil` when the `SortDescriptor` value is created /// with one. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public var stringComparator: String.StandardComparator? { var result: String.StandardComparator? switch comparison { @@ -237,38 +237,38 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { // A temporary workaround to a compiler bug that changes the ABI when adding the & Sendable constraint // Should be removed and the related functions should be made public when rdar://131764614 is resolved @_alwaysEmitIntoClient - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(_ keyPath: KeyPath & Sendable, order: SortOrder = .forward) where Value: Comparable { self.init(keyPath as KeyPath, order: order) } @_alwaysEmitIntoClient - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(_ keyPath: KeyPath & Sendable, order: SortOrder = .forward) where Value: Comparable { self.init(keyPath as KeyPath, order: order) } #if FOUNDATION_FRAMEWORK @_alwaysEmitIntoClient - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(_ keyPath: KeyPath & Sendable, comparator: String.StandardComparator = .localizedStandard) { self.init(keyPath as KeyPath, comparator: comparator) } @_alwaysEmitIntoClient - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(_ keyPath: KeyPath & Sendable, comparator: String.StandardComparator = .localizedStandard) { self.init(keyPath as KeyPath, comparator: comparator) } @_alwaysEmitIntoClient - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(_ keyPath: KeyPath & Sendable, comparator: String.StandardComparator = .localizedStandard, order: SortOrder) { self.init(keyPath as KeyPath, comparator: comparator, order: order) } @_alwaysEmitIntoClient - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public init(_ keyPath: KeyPath & Sendable, comparator: String.StandardComparator = .localizedStandard, order: SortOrder) { self.init(keyPath as KeyPath, comparator: comparator, order: order) } @@ -454,7 +454,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// - Parameters: /// - keyPath: The key path to the field to use for the comparison. /// - order: The initial order to use for comparison. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) /*public*/ @usableFromInline init(_ keyPath: KeyPath, order: SortOrder = .forward) where Value: Comparable { self.order = order self.keyString = nil @@ -476,7 +476,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// - Parameters: /// - keyPath: The key path to the field to use for the comparison. /// - order: The initial order to use for comparison. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) /*public*/ @usableFromInline init(_ keyPath: KeyPath, order: SortOrder = .forward) where Value: Comparable { self.order = order self.keyString = nil @@ -505,7 +505,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// - Parameters: /// - keyPath: The key path to the field to use for comparison. /// - comparator: The standard string comparator to use for comparison. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) /*public*/ @usableFromInline init(_ keyPath: KeyPath, comparator: String.StandardComparator = .localizedStandard) { self.order = comparator.order self.keyString = nil @@ -524,7 +524,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// - Parameters: /// - keyPath: The key path to the field to use for comparison. /// - comparator: The standard string comparator to use for comparison. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) /*public*/ @usableFromInline init(_ keyPath: KeyPath, comparator: String.StandardComparator = .localizedStandard) { self.order = comparator.order self.keyString = nil @@ -544,7 +544,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// - keyPath: The key path to the field to use for comparison. /// - comparator: The standard string comparator to use for comparison. /// - order: The initial order to use for comparison. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) /*public*/ @usableFromInline init(_ keyPath: KeyPath, comparator: String.StandardComparator = .localizedStandard, order: SortOrder) { self.order = order self.keyString = nil @@ -566,7 +566,7 @@ public struct SortDescriptor: SortComparator, Codable, Sendable { /// - keyPath: The key path to the field to use for comparison. /// - comparator: The standard string comparator to use for comparison. /// - order: The initial order to use for comparison. - @available(FoundationPreview 0.1, *) + @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) /*public*/ @usableFromInline init(_ keyPath: KeyPath, comparator: String.StandardComparator = .localizedStandard, order: SortOrder) { self.order = order self.keyString = nil diff --git a/Sources/TestSupport/TestSupport.swift b/Sources/TestSupport/TestSupport.swift index 780d0dc06..a8847500f 100644 --- a/Sources/TestSupport/TestSupport.swift +++ b/Sources/TestSupport/TestSupport.swift @@ -52,7 +52,7 @@ public typealias CurrencyFormatStyleConfiguration = Foundation.CurrencyFormatSty @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) public typealias IntegerParseStrategy = Foundation.IntegerParseStrategy -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public typealias DiscreteFormatStyle = Foundation.DiscreteFormatStyle @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) @@ -110,7 +110,7 @@ public typealias StandardPredicateExpression = Foundation.StandardPredicateExpre public typealias PredicateError = Foundation.PredicateError @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public typealias PredicateCodableConfiguration = Foundation.PredicateCodableConfiguration -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public typealias Expression = Foundation.Expression #else @@ -152,7 +152,7 @@ public typealias CurrencyFormatStyleConfiguration = FoundationInternationalizati @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) public typealias IntegerParseStrategy = FoundationInternationalization.IntegerParseStrategy -@available(FoundationPreview 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public typealias DiscreteFormatStyle = FoundationEssentials.DiscreteFormatStyle @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) @@ -213,7 +213,7 @@ public typealias PredicateExpressions = FoundationEssentials.PredicateExpression public typealias StandardPredicateExpression = FoundationEssentials.StandardPredicateExpression @available(macOS 14, iOS 17, tvOS 17, watchOS 10, *) public typealias PredicateError = FoundationEssentials.PredicateError -@available(FoundationPredicate 0.4, *) +@available(macOS 15, iOS 18, tvOS 18, watchOS 11, *) public typealias Expression = FoundationEssentials.Expression public typealias SortDescriptor = FoundationInternationalization.SortDescriptor diff --git a/Tests/FoundationEssentialsTests/PredicateTests.swift b/Tests/FoundationEssentialsTests/PredicateTests.swift index 28ee004f4..2a4a5541a 100644 --- a/Tests/FoundationEssentialsTests/PredicateTests.swift +++ b/Tests/FoundationEssentialsTests/PredicateTests.swift @@ -312,7 +312,6 @@ private struct PredicateTests { } @Test - @available(FoundationPredicateRegex 0.4, *) func regex() throws { let literalRegex = #/[AB0-9]\/?[^\n]+/# var predicate = #Predicate { @@ -329,7 +328,6 @@ private struct PredicateTests { #if canImport(RegexBuilder) @Test - @available(FoundationPredicateRegex 0.4, *) func regex_RegexBuilder() throws { let builtRegex = Regex { ChoiceOf { @@ -349,7 +347,6 @@ private struct PredicateTests { #endif @Test - @available(FoundationPredicate 0.3, *) func debugDescription() throws { let date = Date.now let predicate = #Predicate { @@ -385,7 +382,6 @@ private struct PredicateTests { #if FOUNDATION_FRAMEWORK @Test - @available(FoundationPredicate 0.3, *) func nested() throws { let predicateA = #Predicate { $0.a == 3 @@ -404,7 +400,6 @@ private struct PredicateTests { #endif @Test - @available(FoundationPredicate 0.4, *) func expression() throws { let expression = #Expression { $0 + 1 diff --git a/Tests/FoundationEssentialsTests/UUIDTests.swift b/Tests/FoundationEssentialsTests/UUIDTests.swift index 22ad824ce..0e9127621 100644 --- a/Tests/FoundationEssentialsTests/UUIDTests.swift +++ b/Tests/FoundationEssentialsTests/UUIDTests.swift @@ -99,7 +99,6 @@ private struct UUIDTests { #expect(String(reflecting: uuid) == "89E90DC6-5EBA-41A8-A64D-81D3576EE46E") } - @available(FoundationPreview 0.1, *) @Test func comparable() throws { var uuid1 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000001")) var uuid2 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000002")) From 6a7f64f21b4cf7ad5723a20f9cb21ef316ed3939 Mon Sep 17 00:00:00 2001 From: Tony Parker Date: Tue, 1 Jul 2025 15:20:40 -0500 Subject: [PATCH 06/12] Make sure to free the malloced pointer even in case of throwing an error. (#1394) Resolves rdar://154702045 --- .../Data/Data+Base64.swift | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 757edcb76..fb0960f96 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -490,15 +490,21 @@ extension Base64 { let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) let target = UnsafeMutableBufferPointer(start: other, count: outputLength) var length = outputLength - if options.contains(.ignoreUnknownCharacters) { - try Self._decodeIgnoringErrors(from: inBuffer, into: target, length: &length, options: options) - } else { - // for whatever reason I can see this being 10% faster for larger payloads. Maybe better - // branch prediction? - try self._decode(from: inBuffer, into: target, length: &length, options: options) + do { + if options.contains(.ignoreUnknownCharacters) { + try Self._decodeIgnoringErrors(from: inBuffer, into: target, length: &length, options: options) + } else { + // for whatever reason I can see this being 10% faster for larger payloads. Maybe better + // branch prediction? + try self._decode(from: inBuffer, into: target, length: &length, options: options) + } + + return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) + } catch { + // Do not leak the malloc on error + free(pointer) + throw error } - - return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) } static func _decode( From 45ba6682021e27e889f134f193badd55c1bc7166 Mon Sep 17 00:00:00 2001 From: Andreas Neusuess Date: Tue, 1 Jul 2025 22:57:18 +0200 Subject: [PATCH 07/12] Use __BundleLookupHelper.self when building mergable library (#1389) Co-authored-by: Andreas Neusuess --- Sources/FoundationMacros/BundleMacro.swift | 2 ++ Tests/FoundationMacrosTests/BundleMacroTests.swift | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/Sources/FoundationMacros/BundleMacro.swift b/Sources/FoundationMacros/BundleMacro.swift index e919fba1b..867938887 100644 --- a/Sources/FoundationMacros/BundleMacro.swift +++ b/Sources/FoundationMacros/BundleMacro.swift @@ -22,6 +22,8 @@ public struct BundleMacro: SwiftSyntaxMacros.ExpressionMacro, Sendable { return Bundle.module #elseif SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE #error("No resource bundle is available for this module. If resources are included elsewhere, specify the bundle manually.") + #elseif SWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE + return Bundle(for: __BundleLookupHelper.self) #else return Bundle(_dsoHandle: #dsohandle) ?? .main #endif diff --git a/Tests/FoundationMacrosTests/BundleMacroTests.swift b/Tests/FoundationMacrosTests/BundleMacroTests.swift index b9c1add51..ea3a51777 100644 --- a/Tests/FoundationMacrosTests/BundleMacroTests.swift +++ b/Tests/FoundationMacrosTests/BundleMacroTests.swift @@ -28,6 +28,8 @@ private struct BundleMacroTests { return Bundle.module #elseif SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE #error("No resource bundle is available for this module. If resources are included elsewhere, specify the bundle manually.") + #elseif SWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE + return Bundle(for: __BundleLookupHelper.self) #else return Bundle(_dsoHandle: #dsohandle) ?? .main #endif @@ -48,6 +50,8 @@ private struct BundleMacroTests { return Bundle.module #elseif SWIFT_MODULE_RESOURCE_BUNDLE_UNAVAILABLE #error("No resource bundle is available for this module. If resources are included elsewhere, specify the bundle manually.") + #elseif SWIFT_BUNDLE_LOOKUP_HELPER_AVAILABLE + return Bundle(for: __BundleLookupHelper.self) #else return Bundle(_dsoHandle: #dsohandle) ?? .main #endif From 7828da46f5bd800a3a648a81765796310261001b Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Tue, 1 Jul 2025 15:54:41 -0700 Subject: [PATCH 08/12] Continue to move FoundationInternationalization tests to swift-testing (#1391) * Convert CalendarRecurrenceRule tests * Convert date tests * Convert predicate tests * Convert sort descriptor tests * Convert Locale tests * Convert Calendar tests * Convert formatting tests * Ensure current calendar is reset after mutating current locale * Fix build failures * Fix test failures --- .../CalendarRecurrenceRuleTests.swift | 65 +- .../CalendarTests.swift | 1197 ++++++++--------- ...InternationalizationPreferencesActor.swift | 16 +- .../DateComponentsTests.swift | 127 +- .../DateTests+Locale.swift | 58 +- .../ByteCountFormatStyleTests.swift | 86 +- .../Formatting/DateFormatStyleTests.swift | 732 +++++----- .../DateIntervalFormatStyleTests.swift | 3 + .../DateRelativeFormatStyleTests.swift | 251 ++-- .../DiscreteFormatStyleTestUtilities.swift | 72 +- .../DurationTimeFormatStyleTests.swift | 206 +-- .../DurationUnitsFormatStyleTests.swift | 349 +++-- .../LocaleTests.swift | 613 ++++----- .../PredicateInternationalizationTests.swift | 54 +- .../SortDescriptorConversionTests.swift | 443 +++--- .../SortDescriptorTests.swift | 261 ++-- .../StringSortComparatorTests.swift | 13 +- 17 files changed, 2246 insertions(+), 2300 deletions(-) diff --git a/Tests/FoundationInternationalizationTests/CalendarRecurrenceRuleTests.swift b/Tests/FoundationInternationalizationTests/CalendarRecurrenceRuleTests.swift index b220ea709..4422e570b 100644 --- a/Tests/FoundationInternationalizationTests/CalendarRecurrenceRuleTests.swift +++ b/Tests/FoundationInternationalizationTests/CalendarRecurrenceRuleTests.swift @@ -10,9 +10,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -21,7 +19,8 @@ import TestSupport @testable import FoundationEssentials #endif // FOUNDATION_FRAMEWORK -final class CalendarRecurrenceRuleTests: XCTestCase { +@Suite("Calendar RecurrenceRule") +private struct CalendarRecurrenceRuleTests { /// A Gregorian calendar with a time zone set to California var gregorian: Calendar = { var gregorian = Calendar(identifier: .gregorian) @@ -29,7 +28,7 @@ final class CalendarRecurrenceRuleTests: XCTestCase { return gregorian }() - func testYearlyRecurrenceInLunarCalendar() { + @Test func yearlyRecurrenceInLunarCalendar() { // Find the first day of the lunar new year let start = Date(timeIntervalSince1970: 1726876800.0) // 2024-09-21T00:00:00-0000 let end = Date(timeIntervalSince1970: 1855699200.0) // 2028-10-21T00:00:00-0000 @@ -50,10 +49,10 @@ final class CalendarRecurrenceRuleTests: XCTestCase { Date(timeIntervalSince1970: 1832508000.0), // 2028-01-26T14:00:00-0000 ] - XCTAssertEqual(results, expectedResults) + #expect(results == expectedResults) } - func testExpandToLeapMonths() { + @Test func expandToLeapMonths() { var lunarCalendar = Calendar(identifier: .chinese) lunarCalendar.timeZone = .gmt @@ -64,14 +63,14 @@ final class CalendarRecurrenceRuleTests: XCTestCase { rule.daysOfTheMonth = [1] var sequence = rule.recurrences(of: start).makeIterator() - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1753401600.0)) // 2025-07-25T00:00:00-0000 (Sixth leap month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1786579200.0)) // 2026-08-13T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1817164800.0)) // 2027-08-02T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1850342400.0)) // 2028-08-20T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1881014400.0)) // 2029-08-10T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1753401600.0)) // 2025-07-25T00:00:00-0000 (Sixth leap month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1786579200.0)) // 2026-08-13T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1817164800.0)) // 2027-08-02T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1850342400.0)) // 2028-08-20T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1881014400.0)) // 2029-08-10T00:00:00-0000 (Seventh month) } - func testStartFromLeapMonth() { + @Test func startFromLeapMonth() { var lunarCalendar = Calendar(identifier: .chinese) lunarCalendar.timeZone = .gmt @@ -82,29 +81,29 @@ final class CalendarRecurrenceRuleTests: XCTestCase { let rule = Calendar.RecurrenceRule(calendar: lunarCalendar, frequency: .yearly, matchingPolicy: .nextTimePreservingSmallerComponents) var sequence = rule.recurrences(of: start).makeIterator() - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1753401600.0)) // 2025-07-25T00:00:00-0000 (Sixth leap month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1786579200.0)) // 2026-08-13T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1817164800.0)) // 2027-08-02T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1850342400.0)) // 2028-08-20T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1881014400.0)) // 2029-08-10T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1911600000.0)) // 2030-07-30T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1944777600.0)) // 2031-08-18T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 1975363200.0)) // 2032-08-06T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 2005948800.0)) // 2033-07-26T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 2039126400.0)) // 2034-08-14T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 2069798400.0)) // 2035-08-04T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 2100384000.0)) // 2036-07-23T00:00:00-0000 (Sixth leap month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 2133561600.0)) // 2037-08-11T00:00:00-0000 (Seventh month) - XCTAssertEqual(sequence.next(), Date(timeIntervalSince1970: 2164233600.0)) // 2038-08-01T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1753401600.0)) // 2025-07-25T00:00:00-0000 (Sixth leap month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1786579200.0)) // 2026-08-13T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1817164800.0)) // 2027-08-02T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1850342400.0)) // 2028-08-20T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1881014400.0)) // 2029-08-10T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1911600000.0)) // 2030-07-30T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1944777600.0)) // 2031-08-18T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 1975363200.0)) // 2032-08-06T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 2005948800.0)) // 2033-07-26T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 2039126400.0)) // 2034-08-14T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 2069798400.0)) // 2035-08-04T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 2100384000.0)) // 2036-07-23T00:00:00-0000 (Sixth leap month) + #expect(sequence.next() == Date(timeIntervalSince1970: 2133561600.0)) // 2037-08-11T00:00:00-0000 (Seventh month) + #expect(sequence.next() == Date(timeIntervalSince1970: 2164233600.0)) // 2038-08-01T00:00:00-0000 (Seventh month) // A strict recurrence only matches in years with leap months let strictRule = Calendar.RecurrenceRule(calendar: lunarCalendar, frequency: .yearly, matchingPolicy: .strict) var strictSequence = strictRule.recurrences(of: start).makeIterator() - XCTAssertEqual(strictSequence.next(), Date(timeIntervalSince1970: 1753401600.0)) // 2025-07-25T00:00:00-0000 (Sixth leap month) - XCTAssertEqual(strictSequence.next(), Date(timeIntervalSince1970: 2100384000.0)) // 2036-07-23T00:00:00-0000 (Sixth leap month) + #expect(strictSequence.next() == Date(timeIntervalSince1970: 1753401600.0)) // 2025-07-25T00:00:00-0000 (Sixth leap month) + #expect(strictSequence.next() == Date(timeIntervalSince1970: 2100384000.0)) // 2036-07-23T00:00:00-0000 (Sixth leap month) } - func testDaylightSavingsRepeatedTimePolicyFirst() { + @Test func daylightSavingsRepeatedTimePolicyFirst() { let start = Date(timeIntervalSince1970: 1730535600.0) // 2024-11-02T01:20:00-0700 var rule = Calendar.RecurrenceRule(calendar: gregorian, frequency: .daily) rule.repeatedTimePolicy = .first @@ -117,10 +116,10 @@ final class CalendarRecurrenceRuleTests: XCTestCase { /// 02:00 PDT) Date(timeIntervalSince1970: 1730712000.0), // 2024-11-04T01:20:00-0800 ] - XCTAssertEqual(results, expectedResults) + #expect(results == expectedResults) } - func testDaylightSavingsRepeatedTimePolicyLast() { + @Test func daylightSavingsRepeatedTimePolicyLast() { let start = Date(timeIntervalSince1970: 1730535600.0) // 2024-11-02T01:20:00-0700 var rule = Calendar.RecurrenceRule(calendar: gregorian, frequency: .daily) rule.repeatedTimePolicy = .last @@ -133,6 +132,6 @@ final class CalendarRecurrenceRuleTests: XCTestCase { Date(timeIntervalSince1970: 1730625600.0), // 2024-11-03T01:20:00-0800 Date(timeIntervalSince1970: 1730712000.0), // 2024-11-04T01:20:00-0800 ] - XCTAssertEqual(results, expectedResults) + #expect(results == expectedResults) } } diff --git a/Tests/FoundationInternationalizationTests/CalendarTests.swift b/Tests/FoundationInternationalizationTests/CalendarTests.swift index 14727011f..8c8cf0a6a 100644 --- a/Tests/FoundationInternationalizationTests/CalendarTests.swift +++ b/Tests/FoundationInternationalizationTests/CalendarTests.swift @@ -10,6 +10,8 @@ // //===----------------------------------------------------------------------===// +import Testing + #if canImport(TestSupport) import TestSupport #endif @@ -21,6 +23,40 @@ import TestSupport @testable import FoundationEssentials #endif // FOUNDATION_FRAMEWORK +// Compare two date components like the original equality, but compares nanosecond within a reasonable epsilon, and optionally ignores quarter and calendar equality since they were often not supported in the original implementation +private func expectEqual(_ first: DateComponents, _ second: DateComponents, within nanosecondAccuracy: Int = 5000, expectQuarter: Bool = true, expectCalendar: Bool = true, _ message: @autoclosure () -> Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(first.era == second.era, message(), sourceLocation: sourceLocation) + #expect(first.year == second.year, message(), sourceLocation: sourceLocation) + #expect(first.month == second.month, message(), sourceLocation: sourceLocation) + #expect(first.day == second.day, message(), sourceLocation: sourceLocation) + #expect(first.dayOfYear == second.dayOfYear, message(), sourceLocation: sourceLocation) + #expect(first.hour == second.hour, message(), sourceLocation: sourceLocation) + #expect(first.minute == second.minute, message(), sourceLocation: sourceLocation) + #expect(first.second == second.second, message(), sourceLocation: sourceLocation) + #expect(first.weekday == second.weekday, message(), sourceLocation: sourceLocation) + #expect(first.weekdayOrdinal == second.weekdayOrdinal, message(), sourceLocation: sourceLocation) + #expect(first.weekOfMonth == second.weekOfMonth, message(), sourceLocation: sourceLocation) + #expect(first.weekOfYear == second.weekOfYear, message(), sourceLocation: sourceLocation) + #expect(first.yearForWeekOfYear == second.yearForWeekOfYear, message(), sourceLocation: sourceLocation) + if expectQuarter { + #expect(first.quarter == second.quarter, message(), sourceLocation: sourceLocation) + } + + if let ns = first.nanosecond, let otherNS = second.nanosecond { + #expect(abs(ns - otherNS) <= nanosecondAccuracy, message(), sourceLocation: sourceLocation) + } else { + #expect(first.nanosecond == second.nanosecond, message(), sourceLocation: sourceLocation) + } + + #expect(first.isLeapMonth == second.isLeapMonth, message(), sourceLocation: sourceLocation) + + if expectCalendar { + #expect(first.calendar == second.calendar, message(), sourceLocation: sourceLocation) + } + + #expect(first.timeZone == second.timeZone, message(), sourceLocation: sourceLocation) +} + extension DateComponents { fileprivate static func differenceBetween(_ d1: DateComponents?, _ d2: DateComponents?, compareQuarter: Bool, within nanosecondAccuracy: Int = 5000) -> String? { let components: [Calendar.Component] = [.era, .year, .month, .day, .dayOfYear, .hour, .minute, .second, .weekday, .weekdayOrdinal, .weekOfYear, .yearForWeekOfYear, .weekOfMonth, .timeZone, .isLeapMonth, .calendar, .quarter, .nanosecond] @@ -59,53 +95,34 @@ extension DateComponents { } } -final class CalendarTests : XCTestCase { - - var allCalendars: [Calendar] = [ - Calendar(identifier: .gregorian), - Calendar(identifier: .buddhist), - Calendar(identifier: .chinese), - Calendar(identifier: .coptic), - Calendar(identifier: .ethiopicAmeteMihret), - Calendar(identifier: .ethiopicAmeteAlem), - Calendar(identifier: .hebrew), - Calendar(identifier: .iso8601), - Calendar(identifier: .indian), - Calendar(identifier: .islamic), - Calendar(identifier: .islamicCivil), - Calendar(identifier: .japanese), - Calendar(identifier: .persian), - Calendar(identifier: .republicOfChina), - Calendar(identifier: .islamicTabular), - Calendar(identifier: .islamicUmmAlQura) - ] - - func test_localeIsCached() { +@Suite("Calendar") +private struct CalendarTests { + @Test func localeIsCached() { let c = Calendar(identifier: .gregorian) let defaultLocale = Locale(identifier: "") - XCTAssertEqual(c.locale, defaultLocale) - XCTAssertIdentical(c.locale?._locale, defaultLocale._locale) + #expect(c.locale == defaultLocale) + #expect(c.locale?._locale === defaultLocale._locale) } - func test_copyOnWrite() { + @Test func copyOnWrite() { var c = Calendar(identifier: .gregorian) let c2 = c - XCTAssertEqual(c, c2) + #expect(c == c2) // Change the weekday and check result let firstWeekday = c.firstWeekday let newFirstWeekday = firstWeekday < 7 ? firstWeekday + 1 : firstWeekday - 1 c.firstWeekday = newFirstWeekday - XCTAssertEqual(newFirstWeekday, c.firstWeekday) - XCTAssertEqual(c2.firstWeekday, firstWeekday) + #expect(newFirstWeekday == c.firstWeekday) + #expect(c2.firstWeekday == firstWeekday) - XCTAssertNotEqual(c, c2) + #expect(c != c2) // Change the time zone and check result let c3 = c - XCTAssertEqual(c, c3) + #expect(c == c3) let tz = c.timeZone // Use two different identifiers so we don't fail if the current time zone happens to be the one returned @@ -119,23 +136,22 @@ final class CalendarTests : XCTestCase { // Do it again! Now it's unique c.timeZone = newTz - XCTAssertNotEqual(c, c3) - + #expect(c != c3) } - func test_equality() { + @Test func equality() { let autoupdating = Calendar.autoupdatingCurrent let autoupdating2 = Calendar.autoupdatingCurrent - XCTAssertEqual(autoupdating, autoupdating2) + #expect(autoupdating == autoupdating2) let current = Calendar.current - XCTAssertNotEqual(autoupdating, current) + #expect(autoupdating != current) // Make a copy of current var current2 = current - XCTAssertEqual(current, current2) + #expect(current == current2) // Mutate something (making sure we don't use the current time zone) if current2.timeZone.identifier == "America/Los_Angeles" { @@ -143,17 +159,17 @@ final class CalendarTests : XCTestCase { } else { current2.timeZone = TimeZone(identifier: "America/Los_Angeles")! } - XCTAssertNotEqual(current, current2) + #expect(current != current2) // Mutate something else current2 = current - XCTAssertEqual(current, current2) + #expect(current == current2) current2.locale = Locale(identifier: "MyMadeUpLocale") - XCTAssertNotEqual(current, current2) + #expect(current != current2) } - func test_hash() { + @Test func hash() { let calendars: [Calendar] = [ Calendar.autoupdatingCurrent, Calendar(identifier: .buddhist), @@ -161,7 +177,7 @@ final class CalendarTests : XCTestCase { Calendar(identifier: .islamic), Calendar(identifier: .iso8601), ] - XCTCheckHashable(calendars, equalityOracle: { $0 == $1 }) + checkHashable(calendars, equalityOracle: { $0 == $1 }) // autoupdating calendar isn't equal to the current, even though it's // likely to be the same. @@ -169,53 +185,55 @@ final class CalendarTests : XCTestCase { Calendar.autoupdatingCurrent, Calendar.current, ] - XCTCheckHashable(calendars2, equalityOracle: { $0 == $1 }) + checkHashable(calendars2, equalityOracle: { $0 == $1 }) } - func test_AnyHashableContainingCalendar() { + @Test func anyHashableContainingCalendar() { let values: [Calendar] = [ Calendar(identifier: .gregorian), Calendar(identifier: .japanese), Calendar(identifier: .japanese) ] let anyHashables = values.map(AnyHashable.init) - expectEqual(Calendar.self, type(of: anyHashables[0].base)) - expectEqual(Calendar.self, type(of: anyHashables[1].base)) - expectEqual(Calendar.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(Calendar.self == type(of: anyHashables[0].base)) + #expect(Calendar.self == type(of: anyHashables[1].base)) + #expect(Calendar.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func decodeHelper(_ l: Calendar) -> Calendar { + func decodeHelper(_ l: Calendar) throws -> Calendar { let je = JSONEncoder() - let data = try! je.encode(l) + let data = try je.encode(l) let jd = JSONDecoder() - return try! jd.decode(Calendar.self, from: data) + return try jd.decode(Calendar.self, from: data) } - func test_serializationOfCurrent() { - let current = Calendar.current - let decodedCurrent = decodeHelper(current) - XCTAssertEqual(decodedCurrent, current) - - let autoupdatingCurrent = Calendar.autoupdatingCurrent - let decodedAutoupdatingCurrent = decodeHelper(autoupdatingCurrent) - XCTAssertEqual(decodedAutoupdatingCurrent, autoupdatingCurrent) - - XCTAssertNotEqual(decodedCurrent, decodedAutoupdatingCurrent) - XCTAssertNotEqual(current, autoupdatingCurrent) - XCTAssertNotEqual(decodedCurrent, autoupdatingCurrent) - XCTAssertNotEqual(current, decodedAutoupdatingCurrent) - - // Calendar, unlike TimeZone and Locale, has some mutable properties - var modified = Calendar.autoupdatingCurrent - modified.firstWeekday = 6 - let decodedModified = decodeHelper(modified) - XCTAssertNotEqual(decodedModified, autoupdatingCurrent) - XCTAssertEqual(modified, decodedModified) + @Test func serializationOfCurrent() async throws { + try await usingCurrentInternationalizationPreferences { + let current = Calendar.current + let decodedCurrent = try decodeHelper(current) + #expect(decodedCurrent == current) + + let autoupdatingCurrent = Calendar.autoupdatingCurrent + let decodedAutoupdatingCurrent = try decodeHelper(autoupdatingCurrent) + #expect(decodedAutoupdatingCurrent == autoupdatingCurrent) + + #expect(decodedCurrent != decodedAutoupdatingCurrent) + #expect(current != autoupdatingCurrent) + #expect(decodedCurrent != autoupdatingCurrent) + #expect(current != decodedAutoupdatingCurrent) + + // Calendar, unlike TimeZone and Locale, has some mutable properties + var modified = Calendar.autoupdatingCurrent + modified.firstWeekday = 6 + let decodedModified = try decodeHelper(modified) + #expect(decodedModified != autoupdatingCurrent) + #expect(modified == decodedModified) + } } - static func validateOrdinality(_ expected: Array>, calendar: Calendar, date: Date) { + static func validateOrdinality(_ expected: Array>, calendar: Calendar, date: Date, sourceLocation: SourceLocation = #_sourceLocation) { let units: [Calendar.Component] = [.era, .year, .month, .day, .hour, .minute, .second, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .nanosecond] var smallerIndex = 0 @@ -224,14 +242,14 @@ final class CalendarTests : XCTestCase { for larger in units { let ordinality = calendar.ordinality(of: smaller, in: larger, for: date) let expected = expected[largerIndex][smallerIndex] - XCTAssertEqual(ordinality, expected, "Unequal for \(smaller) in \(larger)") + #expect(ordinality == expected, "Unequal for \(smaller) in \(larger)", sourceLocation: sourceLocation) largerIndex += 1 } smallerIndex += 1 } } - func validateRange(_ expected: Array?>>, calendar: Calendar, date: Date) { + func validateRange(_ expected: Array?>>, calendar: Calendar, date: Date, sourceLocation: SourceLocation = #_sourceLocation) { let units: [Calendar.Component] = [.era, .year, .month, .day, .hour, .minute, .second, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .nanosecond] var smallerIndex = 0 @@ -240,7 +258,7 @@ final class CalendarTests : XCTestCase { for larger in units { let range = calendar.range(of: smaller, in: larger, for: date) let expected = expected[largerIndex][smallerIndex] - XCTAssertEqual(range, expected, "Unequal for \(smaller) in \(larger)") + #expect(range == expected, "Unequal for \(smaller) in \(larger)", sourceLocation: sourceLocation) largerIndex += 1 } smallerIndex += 1 @@ -267,8 +285,8 @@ final class CalendarTests : XCTestCase { } // This test requires 64-bit integers - #if arch(x86_64) || arch(arm64) - func test_ordinality() { + #if _pointerBitWidth(_64) + @Test func ordinality() { let expected: Array> = [ /* [era, year, month, day, hour, minute, second, weekday, weekdayOrdinal, quarter, weekOfMonth, weekOfYear, yearForWeekOfYear, nanosecond] */ /* era */ [nil, 2022, 24260, 738389, 17721328, 1063279623, 63796777359, 105484, 105484, 8087, 105485, 105485, 2022, nil], @@ -294,7 +312,7 @@ final class CalendarTests : XCTestCase { Self.validateOrdinality(expected, calendar: calendar, date: Date(timeIntervalSinceReferenceDate: 682898558.712307)) } - func test_ordinality_dst() { + @Test func ordinality_dst() { let expected: Array> = [ /* [era, year, month, day, hour, minute, second, weekday, weekdayOrdinal, quarter, weekOfMonth, weekOfYear, yearForWeekOfYear, nanosecond] */ /* era */ [nil, 2022, 24255, 738227, 17717428, 1063045623, 63782737329, 105461, 105461, 8085, 105461, 105461, 2022, nil], @@ -319,11 +337,12 @@ final class CalendarTests : XCTestCase { calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! Self.validateOrdinality(expected, calendar: calendar, date: Date(timeIntervalSinceReferenceDate: 668858528.712)) } - #endif // arch(x86_64) || arch(arm64) + #endif // _pointerBitWidth(_64) // This test requires 64-bit integers - #if (arch(x86_64) || arch(arm64)) && FOUNDATION_FRAMEWORK - func test_multithreadedCalendarAccess() { + #if _pointerBitWidth(_64) && FOUNDATION_FRAMEWORK + @Test(.timeLimit(.minutes(1))) + func multithreadedCalendarAccess() async { let expected: Array> = [ /* [era, year, month, day, hour, minute, second, weekday, weekdayOrdinal, quarter, weekOfMonth, weekOfYear, yearForWeekOfYear, nanosecond] */ /* era */ [nil, 2022, 24260, 738389, 17721328, 1063279623, 63796777359, 105484, 105484, 8087, 105485, 105485, 2022, nil], @@ -351,18 +370,19 @@ final class CalendarTests : XCTestCase { calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! let immutableCalendar = calendar - let group = DispatchGroup() - let queue = DispatchQueue(label: "calendar test", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem) - for _ in 1..<10 { - queue.async(group: group) { - Self.validateOrdinality(expected, calendar: immutableCalendar, date: date) + await withDiscardingTaskGroup { group in + for _ in 1 ..< 10 { + group.addTask { + autoreleasepool { + Self.validateOrdinality(expected, calendar: immutableCalendar, date: date) + } + } } } - XCTAssertEqual(.success, group.wait(timeout: .now().advanced(by: .seconds(3)))) } - #endif // (arch(x86_64) || arch(arm64)) && FOUNDATION_FRAMEWORK + #endif // _pointerBitWidth(_64) && FOUNDATION_FRAMEWORK - func test_range() { + @Test func range() { let expected : [[Range?]] = [[nil, 1..<144684, 1..<13, 1..<32, 0..<24, 0..<60, 0..<60, 1..<8, 1..<6, 1..<5, 1..<7, 1..<54, nil, 0..<1_000_000_000], [nil, nil, 1..<13, 1..<366, 0..<24, 0..<60, 0..<60, 1..<8, 1..<60, 1..<5, 1..<64, 1..<54, nil, 0..<1_000_000_000], @@ -382,12 +402,14 @@ final class CalendarTests : XCTestCase { // An arbitrary date, for which we know the answers // August 22, 2022 at 3:02:38 PM PDT let date = Date(timeIntervalSinceReferenceDate: 682898558.712307) - let calendar = Calendar(identifier: .gregorian) + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt + calendar.locale = Locale(identifier: "en_US") validateRange(expected, calendar: calendar, date: date) } - func test_range_dst() { + @Test func range_dst() { let expected : [[Range?]] = [[nil, 1..<144684, 1..<13, 1..<32, 0..<24, 0..<60, 0..<60, 1..<8, 1..<6, 1..<5, 1..<7, 1..<54, nil, 0..<1_000_000_000], [nil, nil, 1..<13, 1..<366, 0..<24, 0..<60, 0..<60, 1..<8, 1..<60, 1..<5, 1..<64, 1..<54, nil, 0..<1_000_000_000], @@ -404,22 +426,24 @@ final class CalendarTests : XCTestCase { [nil, nil, nil, 1..<397, 0..<24, 0..<60, 0..<60, 1..<8, 1..<65, nil, nil, 1..<54, nil, 0..<1_000_000_000], [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]] + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! // A date which corresponds to a DST transition in Pacific Time // let d = try! Date("2022-03-13T03:02:08.712-07:00", strategy: .iso8601) - validateRange(expected, calendar: Calendar(identifier: .gregorian), date: Date(timeIntervalSinceReferenceDate: 668858528.712)) + validateRange(expected, calendar: calendar, date: Date(timeIntervalSinceReferenceDate: 668858528.712)) } // This test requires 64-bit integers - #if arch(x86_64) || arch(arm64) - func test_addingLargeValues() { + #if _pointerBitWidth(_64) + @Test func addingLargeValues() { let dc = DateComponents(month: 3, day: Int(Int32.max) + 10) let date = Date.now let result = Calendar(identifier: .gregorian).date(byAdding: dc, to: date) - XCTAssertNotNil(result) + #expect(result != nil) } - #endif // arch(x86_64) || arch(arm64) + #endif - func test_chineseYearlessBirthdays() { + @Test func chineseYearlessBirthdays() { var gregorian = Calendar(identifier: .gregorian) gregorian.timeZone = TimeZone(identifier: "UTC")! let threshold = gregorian.date(from: DateComponents(era: 1, year: 1605, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0))! @@ -446,37 +470,36 @@ final class CalendarTests : XCTestCase { } } - XCTAssertFalse(loopedForever) - XCTAssertNotNil(foundDate) + #expect(!loopedForever) // Expected 1126-10-18 07:52:58 +0000 - XCTAssertEqual(foundDate!.timeIntervalSinceReferenceDate, -27586714022) + #expect(foundDate?.timeIntervalSinceReferenceDate == -27586714022) } - func test_dateFromComponentsNearDSTTransition() { + @Test func dateFromComponentsNearDSTTransition() { let comps = DateComponents(year: 2021, month: 11, day: 7, hour: 1, minute: 45) var cal = Calendar(identifier: .gregorian) cal.timeZone = TimeZone(abbreviation: "PDT")! let result = cal.date(from: comps) - XCTAssertEqual(result?.timeIntervalSinceReferenceDate, 657967500) + #expect(result?.timeIntervalSinceReferenceDate == 657967500) } - func test_dayInWeekOfMonth() { + @Test func dayInWeekOfMonth() { let cal = Calendar(identifier: .chinese) // A very specific date for which we know a call into ICU produces an unusual result let date = Date(timeIntervalSinceReferenceDate: 1790212894.000224) let result = cal.range(of: .day, in: .weekOfMonth, for: date) - XCTAssertNotNil(result) + #expect(result != nil) } - func test_dateBySettingNearDSTTransition() { + @Test func dateBySettingNearDSTTransition() { let cal = Calendar(identifier: .gregorian) let midnightDate = Date(timeIntervalSinceReferenceDate: 689673600.0) // 2022-11-09 08:00:00 +0000 // A compatibility behavior of `DateComponents` interop with `NSDateComponents` is that it must accept `Int.max` (NSNotFound) the same as `nil`. let result = cal.date(bySettingHour: 15, minute: 6, second: Int.max, of: midnightDate) - XCTAssertNotNil(result) + #expect(result != nil) } - func test_properties() { + @Test func properties() { var c = Calendar(identifier: .gregorian) // Use english localization c.locale = Locale(identifier: "en_US") @@ -488,68 +511,68 @@ final class CalendarTests : XCTestCase { let d = Date(timeIntervalSince1970: 1468705593.2533731) let earlierD = c.date(byAdding: DateComponents(day: -10), to: d)! - XCTAssertEqual(1..<29, c.minimumRange(of: .day)) - XCTAssertEqual(1..<54, c.maximumRange(of: .weekOfYear)) - XCTAssertEqual(0..<60, c.range(of: .second, in: .minute, for: d)) + #expect(1..<29 == c.minimumRange(of: .day)) + #expect(1..<54 == c.maximumRange(of: .weekOfYear)) + #expect(0..<60 == c.range(of: .second, in: .minute, for: d)) var d1 = Date() var ti : TimeInterval = 0 - XCTAssertTrue(c.dateInterval(of: .day, start: &d1, interval: &ti, for: d)) - XCTAssertEqual(Date(timeIntervalSince1970: 1468652400.0), d1) - XCTAssertEqual(86400, ti) + #expect(c.dateInterval(of: .day, start: &d1, interval: &ti, for: d)) + #expect(Date(timeIntervalSince1970: 1468652400.0) == d1) + #expect(86400 == ti) let dateInterval = c.dateInterval(of: .day, for: d) - XCTAssertEqual(DateInterval(start: d1, duration: ti), dateInterval) + #expect(DateInterval(start: d1, duration: ti) == dateInterval) - XCTAssertEqual(15, c.ordinality(of: .hour, in: .day, for: d)) + #expect(15 == c.ordinality(of: .hour, in: .day, for: d)) - XCTAssertEqual(Date(timeIntervalSince1970: 1468791993.2533731), c.date(byAdding: .day, value: 1, to: d)) - XCTAssertEqual(Date(timeIntervalSince1970: 1468791993.2533731), c.date(byAdding: DateComponents(day: 1), to: d)) + #expect(Date(timeIntervalSince1970: 1468791993.2533731) == c.date(byAdding: .day, value: 1, to: d)) + #expect(Date(timeIntervalSince1970: 1468791993.2533731) == c.date(byAdding: DateComponents(day: 1), to: d)) - XCTAssertEqual(Date(timeIntervalSince1970: 946627200.0), c.date(from: DateComponents(year: 1999, month: 12, day: 31))) + #expect(Date(timeIntervalSince1970: 946627200.0) == c.date(from: DateComponents(year: 1999, month: 12, day: 31))) let comps = c.dateComponents([.year, .month, .day], from: Date(timeIntervalSince1970: 946627200.0)) - XCTAssertEqual(1999, comps.year) - XCTAssertEqual(12, comps.month) - XCTAssertEqual(31, comps.day) + #expect(1999 == comps.year) + #expect(12 == comps.month) + #expect(31 == comps.day) - XCTAssertEqual(10, c.dateComponents([.day], from: d, to: c.date(byAdding: DateComponents(day: 10), to: d)!).day) + #expect(10 == c.dateComponents([.day], from: d, to: c.date(byAdding: DateComponents(day: 10), to: d)!).day) - XCTAssertEqual(30, c.dateComponents([.day], from: DateComponents(year: 1999, month: 12, day: 1), to: DateComponents(year: 1999, month: 12, day: 31)).day) + #expect(30 == c.dateComponents([.day], from: DateComponents(year: 1999, month: 12, day: 1), to: DateComponents(year: 1999, month: 12, day: 31)).day) - XCTAssertEqual(2016, c.component(.year, from: d)) + #expect(2016 == c.component(.year, from: d)) - XCTAssertEqual(Date(timeIntervalSince1970: 1468652400.0), c.startOfDay(for: d)) + #expect(Date(timeIntervalSince1970: 1468652400.0) == c.startOfDay(for: d)) // Mac OS X 10.9 and iOS 7 had a bug in NSCalendar for hour, minute, and second granularities. - XCTAssertEqual(.orderedSame, c.compare(d, to: d + 10, toGranularity: .minute)) + #expect(.orderedSame == c.compare(d, to: d + 10, toGranularity: .minute)) - XCTAssertFalse(c.isDate(d, equalTo: d + 10, toGranularity: .second)) - XCTAssertTrue(c.isDate(d, equalTo: d + 10, toGranularity: .day)) + #expect(!c.isDate(d, equalTo: d + 10, toGranularity: .second)) + #expect(c.isDate(d, equalTo: d + 10, toGranularity: .day)) - XCTAssertFalse(c.isDate(earlierD, inSameDayAs: d)) - XCTAssertTrue(c.isDate(d, inSameDayAs: d)) + #expect(!c.isDate(earlierD, inSameDayAs: d)) + #expect(c.isDate(d, inSameDayAs: d)) - XCTAssertFalse(c.isDateInToday(earlierD)) - XCTAssertFalse(c.isDateInYesterday(earlierD)) - XCTAssertFalse(c.isDateInTomorrow(earlierD)) + #expect(!c.isDateInToday(earlierD)) + #expect(!c.isDateInYesterday(earlierD)) + #expect(!c.isDateInTomorrow(earlierD)) - XCTAssertTrue(c.isDateInWeekend(d)) // 😢 + #expect(c.isDateInWeekend(d)) // 😢 - XCTAssertTrue(c.dateIntervalOfWeekend(containing: d, start: &d1, interval: &ti)) + #expect(c.dateIntervalOfWeekend(containing: d, start: &d1, interval: &ti)) let thisWeekend = DateInterval(start: Date(timeIntervalSince1970: 1468652400.0), duration: 172800.0) - XCTAssertEqual(thisWeekend, DateInterval(start: d1, duration: ti)) - XCTAssertEqual(thisWeekend, c.dateIntervalOfWeekend(containing: d)) + #expect(thisWeekend == DateInterval(start: d1, duration: ti)) + #expect(thisWeekend == c.dateIntervalOfWeekend(containing: d)) - XCTAssertTrue(c.nextWeekend(startingAfter: d, start: &d1, interval: &ti)) + #expect(c.nextWeekend(startingAfter: d, start: &d1, interval: &ti)) let nextWeekend = DateInterval(start: Date(timeIntervalSince1970: 1469257200.0), duration: 172800.0) - XCTAssertEqual(nextWeekend, DateInterval(start: d1, duration: ti)) - XCTAssertEqual(nextWeekend, c.nextWeekend(startingAfter: d)) + #expect(nextWeekend == DateInterval(start: d1, duration: ti)) + #expect(nextWeekend == c.nextWeekend(startingAfter: d)) // Enumeration @@ -580,22 +603,22 @@ final class CalendarTests : XCTestCase { Optional(2017-07-31 07:00:00 +0000) */ - XCTAssertEqual(count, 13) - XCTAssertEqual(exactCount, 8) + #expect(count == 13) + #expect(exactCount == 8) - XCTAssertEqual(Date(timeIntervalSince1970: 1469948400.0), c.nextDate(after: d, matching: DateComponents(day: 31), matchingPolicy: .nextTime)) + #expect(Date(timeIntervalSince1970: 1469948400.0) == c.nextDate(after: d, matching: DateComponents(day: 31), matchingPolicy: .nextTime)) - XCTAssertEqual(Date(timeIntervalSince1970: 1468742400.0), c.date(bySetting: .hour, value: 1, of: d)) + #expect(Date(timeIntervalSince1970: 1468742400.0) == c.date(bySetting: .hour, value: 1, of: d)) - XCTAssertEqual(Date(timeIntervalSince1970: 1468656123.0), c.date(bySettingHour: 1, minute: 2, second: 3, of: d, matchingPolicy: .nextTime)) + #expect(Date(timeIntervalSince1970: 1468656123.0) == c.date(bySettingHour: 1, minute: 2, second: 3, of: d, matchingPolicy: .nextTime)) - XCTAssertTrue(c.date(d, matchesComponents: DateComponents(month: 7))) - XCTAssertFalse(c.date(d, matchesComponents: DateComponents(month: 7, day: 31))) + #expect(c.date(d, matchesComponents: DateComponents(month: 7))) + #expect(!c.date(d, matchesComponents: DateComponents(month: 7, day: 31))) } - func test_leapMonthProperty() throws { + @Test func leapMonthProperty() throws { let c = Calendar(identifier: .chinese) /// 2023-02-20 08:00:00 +0000 -- non-leap month in the Chinese calendar let d1 = Date(timeIntervalSinceReferenceDate: 698572800.0) @@ -604,18 +627,18 @@ final class CalendarTests : XCTestCase { var components = DateComponents() components.isLeapMonth = true - XCTAssertFalse(c.date(d1, matchesComponents: components)) - XCTAssertTrue(c.date(d2, matchesComponents: components)) + #expect(!c.date(d1, matchesComponents: components)) + #expect(c.date(d2, matchesComponents: components)) components.isLeapMonth = false - XCTAssertTrue(c.date(d1, matchesComponents: components)) - XCTAssertFalse(c.date(d2, matchesComponents: components)) + #expect(c.date(d1, matchesComponents: components)) + #expect(!c.date(d2, matchesComponents: components)) components.day = 1 components.isLeapMonth = true - XCTAssertFalse(c.date(d1, matchesComponents: components)) - XCTAssertTrue(c.date(d2, matchesComponents: components)) + #expect(!c.date(d1, matchesComponents: components)) + #expect(c.date(d2, matchesComponents: components)) } - func test_addingDeprecatedWeek() throws { + @Test func addingDeprecatedWeek() throws { let date = try Date("2024-02-24 01:00:00 UTC", strategy: .iso8601.dateTimeSeparator(.space)) var dc = DateComponents() dc.week = 1 @@ -624,87 +647,87 @@ final class CalendarTests : XCTestCase { let oneWeekAfter = calendar.date(byAdding: dc, to: date) let expected = date.addingTimeInterval(86400*7) - XCTAssertEqual(oneWeekAfter, expected) + #expect(oneWeekAfter == expected) } - func test_symbols() { + @Test func symbols() { var c = Calendar(identifier: .gregorian) // Use english localization c.locale = Locale(identifier: "en_US") c.timeZone = TimeZone(identifier: "America/Los_Angeles")! - XCTAssertEqual("AM", c.amSymbol) - XCTAssertEqual("PM", c.pmSymbol) - XCTAssertEqual(["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"], c.quarterSymbols) - XCTAssertEqual(["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"], c.standaloneQuarterSymbols) - XCTAssertEqual(["BC", "AD"], c.eraSymbols) - XCTAssertEqual(["Before Christ", "Anno Domini"], c.longEraSymbols) - XCTAssertEqual(["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"], c.veryShortMonthSymbols) - XCTAssertEqual(["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"], c.veryShortStandaloneMonthSymbols) - XCTAssertEqual(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], c.shortMonthSymbols) - XCTAssertEqual(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], c.shortStandaloneMonthSymbols) - XCTAssertEqual(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], c.monthSymbols) - XCTAssertEqual(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], c.standaloneMonthSymbols) - XCTAssertEqual(["Q1", "Q2", "Q3", "Q4"], c.shortQuarterSymbols) - XCTAssertEqual(["Q1", "Q2", "Q3", "Q4"], c.shortStandaloneQuarterSymbols) - XCTAssertEqual(["S", "M", "T", "W", "T", "F", "S"], c.veryShortStandaloneWeekdaySymbols) - XCTAssertEqual(["S", "M", "T", "W", "T", "F", "S"], c.veryShortWeekdaySymbols) - XCTAssertEqual(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], c.shortStandaloneWeekdaySymbols) - XCTAssertEqual(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], c.shortWeekdaySymbols) - XCTAssertEqual(["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], c.standaloneWeekdaySymbols) - XCTAssertEqual(["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], c.weekdaySymbols) - } - - func test_symbols_not_gregorian() { + #expect("AM" == c.amSymbol) + #expect("PM" == c.pmSymbol) + #expect(["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"] == c.quarterSymbols) + #expect(["1st quarter", "2nd quarter", "3rd quarter", "4th quarter"] == c.standaloneQuarterSymbols) + #expect(["BC", "AD"] == c.eraSymbols) + #expect(["Before Christ", "Anno Domini"] == c.longEraSymbols) + #expect(["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"] == c.veryShortMonthSymbols) + #expect(["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"] == c.veryShortStandaloneMonthSymbols) + #expect(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] == c.shortMonthSymbols) + #expect(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] == c.shortStandaloneMonthSymbols) + #expect(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] == c.monthSymbols) + #expect(["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] == c.standaloneMonthSymbols) + #expect(["Q1", "Q2", "Q3", "Q4"] == c.shortQuarterSymbols) + #expect(["Q1", "Q2", "Q3", "Q4"] == c.shortStandaloneQuarterSymbols) + #expect(["S", "M", "T", "W", "T", "F", "S"] == c.veryShortStandaloneWeekdaySymbols) + #expect(["S", "M", "T", "W", "T", "F", "S"] == c.veryShortWeekdaySymbols) + #expect(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] == c.shortStandaloneWeekdaySymbols) + #expect(["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] == c.shortWeekdaySymbols) + #expect(["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] == c.standaloneWeekdaySymbols) + #expect(["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] == c.weekdaySymbols) + } + + @Test func symbols_not_gregorian() { var c = Calendar(identifier: .hebrew) c.locale = Locale(identifier: "en_US") c.timeZone = TimeZone(identifier: "America/Los_Angeles")! - XCTAssertEqual("AM", c.amSymbol) - XCTAssertEqual("PM", c.pmSymbol) - XCTAssertEqual( [ "1st quarter", "2nd quarter", "3rd quarter", "4th quarter" ], c.quarterSymbols) - XCTAssertEqual( [ "1st quarter", "2nd quarter", "3rd quarter", "4th quarter" ], c.standaloneQuarterSymbols) - XCTAssertEqual( [ "AM" ], c.eraSymbols) - XCTAssertEqual( [ "AM" ], c.longEraSymbols) - XCTAssertEqual( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ], c.veryShortMonthSymbols) - XCTAssertEqual( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ], c.veryShortStandaloneMonthSymbols) - XCTAssertEqual( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ], c.shortMonthSymbols) - XCTAssertEqual( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ], c.shortStandaloneMonthSymbols) - XCTAssertEqual( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ], c.monthSymbols) - XCTAssertEqual( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ], c.standaloneMonthSymbols) - XCTAssertEqual( [ "Q1", "Q2", "Q3", "Q4" ], c.shortQuarterSymbols) - XCTAssertEqual( [ "Q1", "Q2", "Q3", "Q4" ], c.shortStandaloneQuarterSymbols) - XCTAssertEqual( [ "S", "M", "T", "W", "T", "F", "S" ], c.veryShortStandaloneWeekdaySymbols) - XCTAssertEqual( [ "S", "M", "T", "W", "T", "F", "S" ], c.veryShortWeekdaySymbols) - XCTAssertEqual( [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], c.shortStandaloneWeekdaySymbols) - XCTAssertEqual( [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], c.shortWeekdaySymbols) - XCTAssertEqual( [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], c.standaloneWeekdaySymbols) - XCTAssertEqual( [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], c.weekdaySymbols) + #expect("AM" == c.amSymbol) + #expect("PM" == c.pmSymbol) + #expect( [ "1st quarter", "2nd quarter", "3rd quarter", "4th quarter" ] == c.quarterSymbols) + #expect( [ "1st quarter", "2nd quarter", "3rd quarter", "4th quarter" ] == c.standaloneQuarterSymbols) + #expect( [ "AM" ] == c.eraSymbols) + #expect( [ "AM" ] == c.longEraSymbols) + #expect( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ] == c.veryShortMonthSymbols) + #expect( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ] == c.veryShortStandaloneMonthSymbols) + #expect( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ] == c.shortMonthSymbols) + #expect( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ] == c.shortStandaloneMonthSymbols) + #expect( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ] == c.monthSymbols) + #expect( [ "Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "Adar I", "Adar", "Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul", "Adar II" ] == c.standaloneMonthSymbols) + #expect( [ "Q1", "Q2", "Q3", "Q4" ] == c.shortQuarterSymbols) + #expect( [ "Q1", "Q2", "Q3", "Q4" ] == c.shortStandaloneQuarterSymbols) + #expect( [ "S", "M", "T", "W", "T", "F", "S" ] == c.veryShortStandaloneWeekdaySymbols) + #expect( [ "S", "M", "T", "W", "T", "F", "S" ] == c.veryShortWeekdaySymbols) + #expect( [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] == c.shortStandaloneWeekdaySymbols) + #expect( [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ] == c.shortWeekdaySymbols) + #expect( [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] == c.standaloneWeekdaySymbols) + #expect( [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ] == c.weekdaySymbols) c.locale = Locale(identifier: "es_ES") - XCTAssertEqual("a.\u{202f}m.", c.amSymbol) - XCTAssertEqual("p.\u{202f}m.", c.pmSymbol) - XCTAssertEqual( [ "1.er trimestre", "2.\u{00ba} trimestre", "3.er trimestre", "4.\u{00ba} trimestre" ], c.quarterSymbols) - XCTAssertEqual( [ "1.er trimestre", "2.\u{00ba} trimestre", "3.er trimestre", "4.\u{00ba} trimestre" ], c.standaloneQuarterSymbols) - XCTAssertEqual( [ "AM" ], c.eraSymbols) - XCTAssertEqual( [ "AM" ], c.longEraSymbols) - XCTAssertEqual( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ], c.veryShortMonthSymbols) - XCTAssertEqual( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ], c.veryShortStandaloneMonthSymbols) - XCTAssertEqual( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ], c.shortMonthSymbols) - XCTAssertEqual( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ], c.shortStandaloneMonthSymbols) - XCTAssertEqual( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ], c.monthSymbols) - XCTAssertEqual( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ], c.standaloneMonthSymbols) - XCTAssertEqual( [ "T1", "T2", "T3", "T4" ], c.shortQuarterSymbols) - XCTAssertEqual( [ "T1", "T2", "T3", "T4" ], c.shortStandaloneQuarterSymbols) - XCTAssertEqual( [ "D", "L", "M", "X", "J", "V", "S" ], c.veryShortStandaloneWeekdaySymbols) - XCTAssertEqual( [ "D", "L", "M", "X", "J", "V", "S" ], c.veryShortWeekdaySymbols) - XCTAssertEqual( [ "dom", "lun", "mar", "mi\u{00e9}", "jue", "vie", "s\u{00e1}b" ], c.shortStandaloneWeekdaySymbols) - XCTAssertEqual( [ "dom", "lun", "mar", "mi\u{00e9}", "jue", "vie", "s\u{00e1}b" ], c.shortWeekdaySymbols) - XCTAssertEqual( [ "domingo", "lunes", "martes", "mi\u{00e9}rcoles", "jueves", "viernes", "s\u{00e1}bado" ], c.standaloneWeekdaySymbols) - XCTAssertEqual( [ "domingo", "lunes", "martes", "mi\u{00e9}rcoles", "jueves", "viernes", "s\u{00e1}bado" ], c.weekdaySymbols) + #expect("a.\u{202f}m." == c.amSymbol) + #expect("p.\u{202f}m." == c.pmSymbol) + #expect( [ "1.er trimestre", "2.\u{00ba} trimestre", "3.er trimestre", "4.\u{00ba} trimestre" ] == c.quarterSymbols) + #expect( [ "1.er trimestre", "2.\u{00ba} trimestre", "3.er trimestre", "4.\u{00ba} trimestre" ] == c.standaloneQuarterSymbols) + #expect( [ "AM" ] == c.eraSymbols) + #expect( [ "AM" ] == c.longEraSymbols) + #expect( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ] == c.veryShortMonthSymbols) + #expect( [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "7" ] == c.veryShortStandaloneMonthSymbols) + #expect( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ] == c.shortMonthSymbols) + #expect( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ] == c.shortStandaloneMonthSymbols) + #expect( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ] == c.monthSymbols) + #expect( [ "tishri", "heshvan", "kislev", "tevet", "shevat", "adar I", "adar", "nisan", "iyar", "sivan", "tamuz", "av", "elul", "adar II" ] == c.standaloneMonthSymbols) + #expect( [ "T1", "T2", "T3", "T4" ] == c.shortQuarterSymbols) + #expect( [ "T1", "T2", "T3", "T4" ] == c.shortStandaloneQuarterSymbols) + #expect( [ "D", "L", "M", "X", "J", "V", "S" ] == c.veryShortStandaloneWeekdaySymbols) + #expect( [ "D", "L", "M", "X", "J", "V", "S" ] == c.veryShortWeekdaySymbols) + #expect( [ "dom", "lun", "mar", "mi\u{00e9}", "jue", "vie", "s\u{00e1}b" ] == c.shortStandaloneWeekdaySymbols) + #expect( [ "dom", "lun", "mar", "mi\u{00e9}", "jue", "vie", "s\u{00e1}b" ] == c.shortWeekdaySymbols) + #expect( [ "domingo", "lunes", "martes", "mi\u{00e9}rcoles", "jueves", "viernes", "s\u{00e1}bado" ] == c.standaloneWeekdaySymbols) + #expect( [ "domingo", "lunes", "martes", "mi\u{00e9}rcoles", "jueves", "viernes", "s\u{00e1}bado" ] == c.weekdaySymbols) } - func test_weekOfMonthLoop() { + @Test func weekOfMonthLoop() { // This test simply needs to not hang or crash let date = Date(timeIntervalSinceReferenceDate: 2.4499581972890255e+18) let calendar = Calendar(identifier: .gregorian) @@ -714,7 +737,7 @@ final class CalendarTests : XCTestCase { _ = calendar.nextDate(after: date, matching: components, matchingPolicy: .previousTimePreservingSmallerComponents) } - func test_weekendRangeNilLocale() { + @Test func weekendRangeNilLocale() { var c = Calendar(identifier: .gregorian) c.locale = Locale(identifier: "en_001") @@ -724,11 +747,11 @@ final class CalendarTests : XCTestCase { let date = Date(timeIntervalSince1970: 0) let weekend = c.nextWeekend(startingAfter: date) let weekendForNilLocale = c_nilLocale.nextWeekend(startingAfter: date) - XCTAssertNotNil(weekend) - XCTAssertEqual(weekend, weekendForNilLocale) + #expect(weekend != nil) + #expect(weekend == weekendForNilLocale) } - func test_datesAdding_range() { + @Test func datesAdding_range() { let startDate = Date(timeIntervalSinceReferenceDate: 689292158.712307) // 2022-11-04 22:02:38 UTC let endDate = startDate + (86400 * 3) + (3600 * 2) // 3 days + 2 hours later - cross a DST boundary which adds a day with an additional hour in it var cal = Calendar(identifier: .gregorian) @@ -737,10 +760,10 @@ final class CalendarTests : XCTestCase { // Purpose of this test is not to test the addition itself (we have others for that), but to smoke test the wrapping API let numberOfDays = Array(cal.dates(byAdding: .day, startingAt: startDate, in: startDate.. unboundedBackward.last!) + #expect(unboundedBackward.first! > unboundedBackward.last!) } - func test_dayOfYear_bounds() { + @Test func dayOfYear_bounds() { let date = Date(timeIntervalSinceReferenceDate: 682898558.712307) // 2022-08-22 22:02:38 UTC, day 234 var cal = Calendar(identifier: .gregorian) let tz = TimeZone.gmt @@ -810,32 +833,32 @@ final class CalendarTests : XCTestCase { var dayOfYearComps = DateComponents() dayOfYearComps.dayOfYear = 0 let zeroDay = cal.nextDate(after: date, matching: dayOfYearComps, matchingPolicy: .previousTimePreservingSmallerComponents) - XCTAssertNil(zeroDay) + #expect(zeroDay == nil) dayOfYearComps.dayOfYear = 400 let futureDay = cal.nextDate(after: date, matching: dayOfYearComps, matchingPolicy: .nextTime) - XCTAssertNil(futureDay) + #expect(futureDay == nil) // Test subtraction over a year boundary dayOfYearComps.dayOfYear = 1 let firstDay = cal.nextDate(after: date, matching: dayOfYearComps, matchingPolicy: .nextTime, direction: .backward) - XCTAssertNotNil(firstDay) + #expect(firstDay != nil) let firstDayComps = cal.dateComponents([.year], from: firstDay!) let expectationComps = DateComponents(year: 2022) - XCTAssertEqual(firstDayComps, expectationComps) + #expect(firstDayComps == expectationComps) var subtractMe = DateComponents() subtractMe.dayOfYear = -1 let previousDay = cal.date(byAdding: subtractMe, to: firstDay!) - XCTAssertNotNil(previousDay) + #expect(previousDay != nil) let previousDayComps = cal.dateComponents([.year, .dayOfYear], from: previousDay!) var previousDayExpectationComps = DateComponents() previousDayExpectationComps.year = 2021 previousDayExpectationComps.dayOfYear = 365 - XCTAssertEqual(previousDayComps, previousDayExpectationComps) + #expect(previousDayComps == previousDayExpectationComps) } - func test_dayOfYear() { + @Test func dayOfYear() { // An arbitrary date, for which we know the answers let date = Date(timeIntervalSinceReferenceDate: 682898558.712307) // 2022-08-22 22:02:38 UTC, day 234 let leapYearDate = Date(timeIntervalSinceReferenceDate: 745891200) // 2024-08-21 00:00:00 UTC, day 234 @@ -844,22 +867,22 @@ final class CalendarTests : XCTestCase { cal.timeZone = tz // Ordinality - XCTAssertEqual(cal.ordinality(of: .dayOfYear, in: .year, for: date), 234) - XCTAssertEqual(cal.ordinality(of: .hour, in: .dayOfYear, for: date), 23) - XCTAssertEqual(cal.ordinality(of: .minute, in: .dayOfYear, for: date), 1323) - XCTAssertEqual(cal.ordinality(of: .second, in: .dayOfYear, for: date), 79359) + #expect(cal.ordinality(of: .dayOfYear, in: .year, for: date) == 234) + #expect(cal.ordinality(of: .hour, in: .dayOfYear, for: date) == 23) + #expect(cal.ordinality(of: .minute, in: .dayOfYear, for: date) == 1323) + #expect(cal.ordinality(of: .second, in: .dayOfYear, for: date) == 79359) // Nonsense ordinalities. Since day of year is already relative, we don't count the Nth day of year in an era. - XCTAssertEqual(cal.ordinality(of: .dayOfYear, in: .era, for: date), nil) - XCTAssertEqual(cal.ordinality(of: .year, in: .dayOfYear, for: date), nil) + #expect(cal.ordinality(of: .dayOfYear, in: .era, for: date) == nil) + #expect(cal.ordinality(of: .year, in: .dayOfYear, for: date) == nil) // Interval let interval = cal.dateInterval(of: .dayOfYear, for: date) - XCTAssertEqual(interval, DateInterval(start: Date(timeIntervalSinceReferenceDate: 682819200), duration: 86400)) + #expect(interval == DateInterval(start: Date(timeIntervalSinceReferenceDate: 682819200), duration: 86400)) // Specific component values - XCTAssertEqual(cal.dateComponents(in: .gmt, from: date).dayOfYear, 234) - XCTAssertEqual(cal.component(.dayOfYear, from: date), 234) + #expect(cal.dateComponents(in: .gmt, from: date).dayOfYear == 234) + #expect(cal.component(.dayOfYear, from: date) == 234) // Enumeration let beforeDate = date - (86400 * 3) @@ -868,10 +891,10 @@ final class CalendarTests : XCTestCase { var matchingComps = DateComponents(); matchingComps.dayOfYear = 234 var foundDate = cal.nextDate(after: beforeDate, matching: matchingComps, matchingPolicy: .nextTime) - XCTAssertEqual(foundDate, startOfDate) + #expect(foundDate == startOfDate) foundDate = cal.nextDate(after: afterDate, matching: matchingComps, matchingPolicy: .nextTime, direction: .backward) - XCTAssertEqual(foundDate, startOfDate) + #expect(foundDate == startOfDate) // Go over a leap year let nextFive = Array(cal.dates(byMatching: matchingComps, startingAt: beforeDate).prefix(5)) @@ -882,27 +905,27 @@ final class CalendarTests : XCTestCase { Date(timeIntervalSinceReferenceDate: 777513600), // 2025-08-22 00:00:00 +0000 Date(timeIntervalSinceReferenceDate: 809049600), // 2026-08-22 00:00:00 +0000 ] - XCTAssertEqual(nextFive, expected) + #expect(nextFive == expected) // Ranges let min = cal.minimumRange(of: .dayOfYear) let max = cal.maximumRange(of: .dayOfYear) - XCTAssertEqual(min, 1..<366) // hard coded for gregorian - XCTAssertEqual(max, 1..<367) + #expect(min == 1..<366) // hard coded for gregorian + #expect(max == 1..<367) - XCTAssertEqual(cal.range(of: .dayOfYear, in: .year, for: date), 1..<366) - XCTAssertEqual(cal.range(of: .dayOfYear, in: .year, for: leapYearDate), 1..<367) + #expect(cal.range(of: .dayOfYear, in: .year, for: date) == 1..<366) + #expect(cal.range(of: .dayOfYear, in: .year, for: leapYearDate) == 1..<367) // Addition let d1 = cal.date(byAdding: .dayOfYear, value: 1, to: date) - XCTAssertEqual(d1, date + 86400) + #expect(d1 == date + 86400) // Using setting to go to Jan 1 let jan1 = cal.date(bySetting: .dayOfYear, value: 1, of: date)! let jan1Comps = cal.dateComponents([.year, .month, .day], from: jan1) - XCTAssertEqual(jan1Comps.year, 2023) - XCTAssertEqual(jan1Comps.day, 1) - XCTAssertEqual(jan1Comps.month, 1) + #expect(jan1Comps.year == 2023) + #expect(jan1Comps.day == 1) + #expect(jan1Comps.month == 1) // Using setting to go to Jan 1 let whatDay = cal.date(bySetting: .dayOfYear, value: 100, of: Date.now)! @@ -911,24 +934,24 @@ final class CalendarTests : XCTestCase { // Comparison - XCTAssertEqual(cal.compare(date, to: beforeDate, toGranularity: .dayOfYear), .orderedDescending) - XCTAssertEqual(cal.compare(date, to: afterDate, toGranularity: .dayOfYear), .orderedAscending) - XCTAssertEqual(cal.compare(date + 10, to: date, toGranularity: .dayOfYear), .orderedSame) + #expect(cal.compare(date, to: beforeDate, toGranularity: .dayOfYear) == .orderedDescending) + #expect(cal.compare(date, to: afterDate, toGranularity: .dayOfYear) == .orderedAscending) + #expect(cal.compare(date + 10, to: date, toGranularity: .dayOfYear) == .orderedSame) // Nonsense day-of-year var nonsenseDayOfYear = DateComponents() nonsenseDayOfYear.dayOfYear = 500 let shouldBeEmpty = Array(cal.dates(byMatching: nonsenseDayOfYear, startingAt: beforeDate)) - XCTAssertTrue(shouldBeEmpty.isEmpty) + #expect(shouldBeEmpty.isEmpty) } - func test_dateComponentsFromFarDateCrash() { + @Test func dateComponentsFromFarDateCrash() { // Calling dateComponents(:from:) on a remote date should not crash let c = Calendar(identifier: .gregorian) _ = c.dateComponents([.month], from: Date(timeIntervalSinceReferenceDate: 7.968993439840418e+23)) } - func test_dateBySettingDay() { + @Test func dateBySettingDay() { func firstDayOfMonth(_ calendar: Calendar, for date: Date) -> Date? { var startOfCurrentMonthComponents = calendar.dateComponents(in: calendar.timeZone, from: date) startOfCurrentMonthComponents.day = 1 @@ -943,31 +966,31 @@ final class CalendarTests : XCTestCase { gregorianCalendar.timeZone = .gmt let date = Date(timeIntervalSince1970: 1609459199) // 2020-12-31T23:59:59Z - XCTAssertEqual(firstDayOfMonth(iso8601calendar, for: date), Date(timeIntervalSinceReferenceDate: 628559999.0)) // 2020-12-01T23:59:59Z - XCTAssertEqual(firstDayOfMonth(gregorianCalendar, for: date), Date(timeIntervalSinceReferenceDate: 628559999.0)) // 2020-12-01T23:59:59Z + #expect(firstDayOfMonth(iso8601calendar, for: date) == Date(timeIntervalSinceReferenceDate: 628559999.0)) // 2020-12-01T23:59:59Z + #expect(firstDayOfMonth(gregorianCalendar, for: date) == Date(timeIntervalSinceReferenceDate: 628559999.0)) // 2020-12-01T23:59:59Z let date2 = Date(timeIntervalSinceReferenceDate: 730860719) // 2024-02-29T00:51:59Z - XCTAssertEqual(firstDayOfMonth(iso8601calendar, for: date2), Date(timeIntervalSinceReferenceDate: 728441519)) // 2024-02-01T00:51:59Z - XCTAssertEqual(firstDayOfMonth(gregorianCalendar, for: date2), Date(timeIntervalSinceReferenceDate: 728441519.0)) // 2024-02-01T00:51:59Z + #expect(firstDayOfMonth(iso8601calendar, for: date2) == Date(timeIntervalSinceReferenceDate: 728441519)) // 2024-02-01T00:51:59Z + #expect(firstDayOfMonth(gregorianCalendar, for: date2) == Date(timeIntervalSinceReferenceDate: 728441519.0)) // 2024-02-01T00:51:59Z } - func test_dateFromComponents_componentsTimeZoneConversion() { + @Test func dateFromComponents_componentsTimeZoneConversion() { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = .gmt let startOfYearGMT = Date(timeIntervalSince1970: 1577836800) // January 1, 2020 00:00:00 GMT var components = calendar.dateComponents([.era, .year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .dayOfYear, .calendar, .timeZone], from: startOfYearGMT) let roundtrip = calendar.date(from: components) - XCTAssertEqual(roundtrip, startOfYearGMT) + #expect(roundtrip == startOfYearGMT) components.timeZone = TimeZone(abbreviation: "EST")! let startOfYearEST = calendar.date(from: components) let expected = startOfYearGMT + 3600 * 5 // January 1, 2020 05:00:00 GMT, Jan 1, 2020 00:00:00 EST - XCTAssertEqual(startOfYearEST, expected) + #expect(startOfYearEST == expected) } - func test_dateComponentsFromDate_componentsTimeZoneConversion2() { + @Test func dateComponentsFromDate_componentsTimeZoneConversion2() throws { let gmtDate = Date(timeIntervalSinceReferenceDate: 441907261) // "2015-01-03T01:01:01+0900" let localDate = Date(timeIntervalSinceReferenceDate: 441939661) // "2015-01-03T01:01:01+0000" @@ -975,29 +998,29 @@ final class CalendarTests : XCTestCase { calendar.timeZone = .gmt let timeZoneOffset = localDate.timeIntervalSince(gmtDate) - let nearestTimeZone = TimeZone(secondsFromGMT: Int(timeZoneOffset))! + let nearestTimeZone = try #require(TimeZone(secondsFromGMT: Int(timeZoneOffset))) let dateComponents = calendar.dateComponents(in: nearestTimeZone, from: gmtDate) - XCTAssertEqual(dateComponents.month, 1) - XCTAssertEqual(dateComponents.day, 3) - XCTAssertEqual(dateComponents.year, 2015) + #expect(dateComponents.month == 1) + #expect(dateComponents.day == 3) + #expect(dateComponents.year == 2015) - let date = calendar.date(from: dateComponents)! + let date = try #require(calendar.date(from: dateComponents)) let regeneratedDateComponents = calendar.dateComponents(in: nearestTimeZone, from: date) - XCTAssertEqual(dateComponents.month, regeneratedDateComponents.month) - XCTAssertEqual(dateComponents.day, regeneratedDateComponents.day) - XCTAssertEqual(dateComponents.year, regeneratedDateComponents.year) + #expect(dateComponents.month == regeneratedDateComponents.month) + #expect(dateComponents.day == regeneratedDateComponents.day) + #expect(dateComponents.year == regeneratedDateComponents.year) } - func test_dateFromComponents() { + @Test func dateFromComponents() { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = .gmt calendar.minimumDaysInFirstWeek = 1 calendar.firstWeekday = 1 - func test(_ dc: DateComponents, _ expectation: Date, file: StaticString = #filePath, line: UInt = #line) { + func test(_ dc: DateComponents, _ expectation: Date, sourceLocation: SourceLocation = #_sourceLocation) { let date = calendar.date(from: dc)! - XCTAssertEqual(date, expectation, "expect: \(date.timeIntervalSinceReferenceDate)", file: file, line: line) + #expect(date == expectation, "expect: \(date.timeIntervalSinceReferenceDate)", sourceLocation: sourceLocation) } // The first week of year 2000 is Dec 26, 1999...Jan 1, 2000 @@ -1086,47 +1109,47 @@ final class CalendarTests : XCTestCase { test(.init(year: 1995, weekday: 1, weekdayOrdinal: 3, weekOfMonth: 2, weekOfYear: 4, yearForWeekOfYear: 1995), Date(timeIntervalSinceReferenceDate: -188179200.0)) // 1995-01-15T00:00:00Z } - func test_firstWeekday() { + @Test func firstWeekday() { var calendar = Calendar(identifier: .gregorian) calendar.locale = Locale(identifier: "en_US") - XCTAssertEqual(calendar.firstWeekday, 1) + #expect(calendar.firstWeekday == 1) calendar.locale = Locale(identifier: "en_GB") - XCTAssertEqual(calendar.firstWeekday, 2) + #expect(calendar.firstWeekday == 2) var calendarWithCustomLocale = Calendar(identifier: .gregorian) calendarWithCustomLocale.locale = Locale(identifier: "en_US", preferences: .init(firstWeekday: [.gregorian: 3])) - XCTAssertEqual(calendarWithCustomLocale.firstWeekday, 3) + #expect(calendarWithCustomLocale.firstWeekday == 3) calendarWithCustomLocale.firstWeekday = 5 - XCTAssertEqual(calendarWithCustomLocale.firstWeekday, 5) // Returns the one set directly on Calendar + #expect(calendarWithCustomLocale.firstWeekday == 5) // Returns the one set directly on Calendar var calendarWithCustomLocaleAndCustomWeekday = Calendar(identifier: .gregorian) calendarWithCustomLocaleAndCustomWeekday.firstWeekday = 2 calendarWithCustomLocaleAndCustomWeekday.locale = Locale(identifier: "en_US", preferences: .init(firstWeekday: [.gregorian: 3])) - XCTAssertEqual(calendarWithCustomLocaleAndCustomWeekday.firstWeekday, 2) // Returns the one set directly on Calendar even if `.locale` is set later + #expect(calendarWithCustomLocaleAndCustomWeekday.firstWeekday == 2) // Returns the one set directly on Calendar even if `.locale` is set later } - func test_minDaysInFirstWeek() { + @Test func minDaysInFirstWeek() { var calendar = Calendar(identifier: .gregorian) calendar.locale = Locale(identifier: "en_GB") - XCTAssertEqual(calendar.minimumDaysInFirstWeek, 4) + #expect(calendar.minimumDaysInFirstWeek == 4) calendar.minimumDaysInFirstWeek = 5 - XCTAssertEqual(calendar.minimumDaysInFirstWeek, 5) + #expect(calendar.minimumDaysInFirstWeek == 5) var calendarWithCustomLocale = Calendar(identifier: .gregorian) calendarWithCustomLocale.locale = Locale(identifier: "en_US", preferences: .init(minDaysInFirstWeek: [.gregorian: 6])) - XCTAssertEqual(calendarWithCustomLocale.minimumDaysInFirstWeek, 6) + #expect(calendarWithCustomLocale.minimumDaysInFirstWeek == 6) var calendarWithCustomLocaleAndCustomMinDays = Calendar(identifier: .gregorian) calendarWithCustomLocaleAndCustomMinDays.minimumDaysInFirstWeek = 2 calendarWithCustomLocaleAndCustomMinDays.locale = Locale(identifier: "en_US", preferences: .init(minDaysInFirstWeek: [.gregorian: 6])) - XCTAssertEqual(calendarWithCustomLocaleAndCustomMinDays.minimumDaysInFirstWeek, 2) + #expect(calendarWithCustomLocaleAndCustomMinDays.minimumDaysInFirstWeek == 2) } - func test_addingZeroComponents() { + @Test func addingZeroComponents() { var calendar = Calendar(identifier: .gregorian) let timeZone = TimeZone(identifier: "America/Los_Angeles")! calendar.timeZone = timeZone @@ -1135,17 +1158,17 @@ final class CalendarTests : XCTestCase { let date = Date(timeIntervalSinceReferenceDate: 657966600) let dateComponents = DateComponents(era: 0, year: 0, month: 0, day: 0, hour: 0, minute: 0, second: 0) let result = calendar.date(byAdding: dateComponents, to: date) - XCTAssertEqual(date, result) + #expect(date == result) let allComponents : [Calendar.Component] = [.era, .year, .month, .day, .hour, .minute, .second] for component in allComponents { let res = calendar.date(byAdding: component, value: 0, to: date) - XCTAssertEqual(res, date, "component: \(component)") + #expect(res == date, "component: \(component)") } } - func test_addingDaysAndWeeks() throws { + @Test func addingDaysAndWeeks() throws { let timeZone = TimeZone(identifier: "America/Los_Angeles")! var c = Calendar(identifier: .gregorian) c.timeZone = timeZone @@ -1154,22 +1177,22 @@ final class CalendarTests : XCTestCase { let a = Date(timeIntervalSinceReferenceDate: 731673276) // "2024-03-09T02:34:36-0800", 10:34:36 UTC let d1_w1 = c.date(byAdding: .init(day: 1, weekOfMonth: 1), to: a)! let exp = try Date("2024-03-17T02:34:36-0700", strategy: s) - XCTAssertEqual(d1_w1, exp) + #expect(d1_w1 == exp) let d8 = c.date(byAdding: .init(day: 8), to: a)! - XCTAssertEqual(d8, exp) + #expect(d8 == exp) } - func test_addingDifferencesRoundtrip() throws { + @Test func addingDifferencesRoundtrip() throws { let timeZone = TimeZone(identifier: "America/Los_Angeles")! var c = Calendar(identifier: .gregorian) c.timeZone = timeZone let s = Date.ISO8601FormatStyle(timeZone: timeZone) func test(_ start: Date, _ end: Date) throws { - let components = try XCTUnwrap(c.dateComponents([.year, .month, .day, .hour, .minute, .second, .nanosecond, .weekOfMonth], from: start, to: end)) - let added = try XCTUnwrap(c.date(byAdding: components, to: start)) - XCTAssertEqual(added, end, "actual: \(s.format(added)), expected: \(s.format(end))") + 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))") } // 2024-03-09T02:34:36-0800, 2024-03-17T03:34:36-0700, 10:34:36 UTC @@ -1186,43 +1209,43 @@ final class CalendarTests : XCTestCase { } #if _pointerBitWidth(_64) // These tests assumes Int is Int64 - func test_dateFromComponentsOverflow() { + @Test func dateFromComponentsOverflow() { let calendar = Calendar(identifier: .gregorian) do { let components = DateComponents(year: -1157442765409226769, month: -1157442765409226769, day: -1157442765409226769) let date = calendar.date(from: components) - XCTAssertNil(date) + #expect(date == nil) } do { let components = DateComponents(year: -8935141660703064064, month: -8897841259083430780, day: -8897841259083430780) let date = calendar.date(from: components) - XCTAssertNil(date) + #expect(date == nil) } do { let components = DateComponents(era: 3475652213542486016, year: -1, month: 72056757140062316, day: 7812738666521952255) let date = calendar.date(from: components) - XCTAssertNil(date) + #expect(date == nil) } do { let components = DateComponents(weekOfYear: -5280832742222096118, yearForWeekOfYear: 182) let date = calendar.date(from: components) - XCTAssertNil(date) + #expect(date == nil) } } - func test_addDateOverflow() throws { + @Test func addDateOverflow() throws { do { let date = Date(timeIntervalSinceReferenceDate: 964779243.351134) let calendar = Calendar(identifier: .gregorian) let components = DateComponents(year: 788960010015224562) let added = calendar.date(byAdding: components, to: date) - XCTAssertNil(added) + #expect(added == nil) } do { @@ -1230,7 +1253,7 @@ final class CalendarTests : XCTestCase { let calendar = Calendar(identifier: .gregorian) let components = DateComponents(year: 9223372036854775556) let added = calendar.date(byAdding: components, to: date) - XCTAssertNil(added) + #expect(added == nil) } do { @@ -1238,11 +1261,11 @@ final class CalendarTests : XCTestCase { let calendar = Calendar(identifier: .gregorian) let value = 9223372036854775806 let added = calendar.date(byAdding: .month, value: value, to: date) - XCTAssertNil(added) + #expect(added == nil) } } - func test_dateComponentsFromDateOverflow() { + @Test func dateComponentsFromDateOverflow() { let calendar = Calendar(identifier: .gregorian) do { let dc = calendar.dateComponents([.year], from: Date(timeIntervalSinceReferenceDate: Double(Int64.max))) @@ -1261,36 +1284,37 @@ final class CalendarTests : XCTestCase { // MARK: - Bridging Tests #if FOUNDATION_FRAMEWORK -final class CalendarBridgingTests : XCTestCase { - func test_AnyHashableCreatedFromNSCalendar() { +@Suite("Calendar Bridging") +private struct CalendarBridgingTests { + @Test func AnyHashableCreatedFromNSCalendar() { let values: [NSCalendar] = [ NSCalendar(identifier: .gregorian)!, NSCalendar(identifier: .japanese)!, NSCalendar(identifier: .japanese)!, ] let anyHashables = values.map(AnyHashable.init) - expectEqual(Calendar.self, type(of: anyHashables[0].base)) - expectEqual(Calendar.self, type(of: anyHashables[1].base)) - expectEqual(Calendar.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(Calendar.self == type(of: anyHashables[0].base)) + #expect(Calendar.self == type(of: anyHashables[1].base)) + #expect(Calendar.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } } #endif // This test validates the results against FoundationInternationalization's calendar implementation temporarily until we completely ported the calendar -#if false // Disabled because these tests are extensive and have long runtimes to validate full compatibility, they can be enabled locally to validate changes -final class GregorianCalendarCompatibilityTests: XCTestCase { +@Suite("GregorianCalendar Compatibility", .disabled("These tests are extensive and have long runtimes to validate full compatibility, they can be enabled locally to validate changes")) +private struct GregorianCalendarCompatibilityTests { - func testDateFromComponentsCompatibility() { + @Test func dateFromComponentsCompatibility() { let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: .gmt, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: .gmt, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) - func test(_ dateComponents: DateComponents, file: StaticString = #filePath, line: UInt = #line) { + func test(_ dateComponents: DateComponents, sourceLocation: SourceLocation = #_sourceLocation) { let date_new = gregorianCalendar.date(from: dateComponents)! let date_old = icuCalendar.date(from: dateComponents)! - expectEqual(date_new, date_old) + #expect(date_new == date_old, sourceLocation: sourceLocation) } test(.init(year: 1996, month: 3)) @@ -1396,13 +1420,11 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { } - func testDateFromComponentsCompatibilityCustom() { - - self.continueAfterFailure = false - func test(_ dateComponents: DateComponents, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, file: StaticString = #filePath, line: UInt = #line) { + @Test func dateFromComponentsCompatibilityCustom() { + func test(_ dateComponents: DateComponents, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, sourceLocation: SourceLocation = #_sourceLocation) { let date_new = gregorianCalendar.date(from: dateComponents)! let date_old = icuCalendar.date(from: dateComponents)! - expectEqual(date_new, date_old, "dateComponents: \(dateComponents), first weekday: \(gregorianCalendar.firstWeekday), minimumDaysInFirstWeek: \(gregorianCalendar.minimumDaysInFirstWeek)") + #expect(date_new == date_old, "dateComponents: \(dateComponents), first weekday: \(gregorianCalendar.firstWeekday), minimumDaysInFirstWeek: \(gregorianCalendar.minimumDaysInFirstWeek)", sourceLocation: sourceLocation) } // first weekday, min days in first week @@ -1458,19 +1480,19 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { } } - func testDateFromComponentsCompatibility_DaylightSavingTimeZone() { + @Test func dateFromComponentsCompatibility_DaylightSavingTimeZone() { let tz = TimeZone(identifier: "America/Los_Angeles")! let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: tz, locale: nil, firstWeekday: 1, minimumDaysInFirstWeek: 4, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: tz, locale: nil, firstWeekday: 1, minimumDaysInFirstWeek: 4, gregorianStartDate: nil) - func test(_ dateComponents: DateComponents, file: StaticString = #filePath, line: UInt = #line) { + func test(_ dateComponents: DateComponents, sourceLocation: SourceLocation = #_sourceLocation) { let date_new = gregorianCalendar.date(from: dateComponents)! let date_old = icuCalendar.date(from: dateComponents)! - expectEqual(date_new, date_old, "dateComponents: \(dateComponents)") + #expect(date_new == date_old, "dateComponents: \(dateComponents)", sourceLocation: sourceLocation) let roundtrip_new = gregorianCalendar.dateComponents([.hour], from: date_new) let roundtrip_old = icuCalendar.dateComponents([.hour], from: date_new) - XCTAssertEqual(roundtrip_new.hour, roundtrip_old.hour, "dateComponents: \(dateComponents)") + #expect(roundtrip_new.hour == roundtrip_old.hour, "dateComponents: \(dateComponents)", sourceLocation: sourceLocation) } // In daylight saving time @@ -1498,16 +1520,16 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { test(.init(year: 2023, month: 11, day: 5, hour: 3, minute: 34, second: 52)) } - func testDateFromComponents_componentsTimeZone() { + @Test func dateFromComponents_componentsTimeZone() { let timeZone = TimeZone.gmt let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) - func test(_ dateComponents: DateComponents, file: StaticString = #filePath, line: UInt = #line) { + func test(_ dateComponents: DateComponents, sourceLocation: SourceLocation = #_sourceLocation) { let date_new = gregorianCalendar.date(from: dateComponents)! let date_old = icuCalendar.date(from: dateComponents)! - expectEqual(date_new, date_old, "dateComponents: \(dateComponents)") + #expect(date_new == date_old, "dateComponents: \(dateComponents)", sourceLocation: sourceLocation) } let dcCalendar = Calendar(identifier: .japanese, locale: Locale(identifier: ""), timeZone: .init(secondsFromGMT: -25200), firstWeekday: 1, minimumDaysInFirstWeek: 1, gregorianStartDate: nil) @@ -1534,39 +1556,38 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { test(dc_customCalendarNoTimeZone_customTimeZone) // calendar.timeZone = .gmt, dc.calendar.timeZone = nil, dc.timeZone = UTC+8 } - func testDateFromComponentsCompatibility_RemoveDates() { + @Test func dateFromComponentsCompatibility_RemoveDates() { let tz = TimeZone(identifier: "America/Los_Angeles")! let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: tz, locale: nil, firstWeekday: 1, minimumDaysInFirstWeek: 1, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: tz, locale: nil, firstWeekday: 1, minimumDaysInFirstWeek: 1, gregorianStartDate: nil) - func test(_ dateComponents: DateComponents, file: StaticString = #filePath, line: UInt = #line) { + func test(_ dateComponents: DateComponents, sourceLocation: SourceLocation = #_sourceLocation) { let date_new = gregorianCalendar.date(from: dateComponents)! let date_old = icuCalendar.date(from: dateComponents)! - expectEqual(date_new, date_old, "dateComponents: \(dateComponents)") + #expect(date_new == date_old, "dateComponents: \(dateComponents)", sourceLocation: sourceLocation) let roundtrip_new = gregorianCalendar.dateComponents([.hour], from: date_new) let roundtrip_old = icuCalendar.dateComponents([.hour], from: date_new) - XCTAssertEqual(roundtrip_new.hour, roundtrip_old.hour, "dateComponents: \(dateComponents)") + #expect(roundtrip_new.hour == roundtrip_old.hour, "dateComponents: \(dateComponents)", sourceLocation: sourceLocation) } test(.init(year: 4713, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0, weekday: 2)) test(.init(year: 4713, month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0)) } - func testDateComponentsFromDateCompatibility() { + @Test func dateComponentsFromDateCompatibility() throws { let componentSet = Calendar.ComponentSet([.era, .year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .calendar]) let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) - func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, timeZone: TimeZone = .gmt, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, timeZone: TimeZone = .gmt, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { let gregResult = gregorianCalendar.dateComponents(componentSet, from: date, in: timeZone) let icuResult = icuCalendar.dateComponents(componentSet, from: date, in: timeZone) // The original implementation does not set quarter - expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, message().appending("\ndate: \(date.timeIntervalSinceReferenceDate), \(date.formatted(.iso8601))\nnew:\n\(gregResult)\nold:\n\(icuResult)"), file: file, line: line) + expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, "\(message())\ndate: \(date.timeIntervalSinceReferenceDate), \(date.formatted(.iso8601))\nnew:\n\(gregResult)\nold:\n\(icuResult)", sourceLocation: sourceLocation) } - self.continueAfterFailure = false let testStrides = stride(from: -864000, to: 864000, by: 100) let gmtPlusOne = TimeZone(secondsFromGMT: 3600)! @@ -1574,7 +1595,7 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { for ti in testStrides { let date = Date(timeIntervalSince1970: TimeInterval(ti)) if let timeZone = TimeZone(secondsFromGMT: timeZoneOffset) { - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: timeZone) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: timeZone) } } @@ -1586,19 +1607,19 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { for ti in testStrides { let date = Date(timeInterval: TimeInterval(ti), since: ref) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) } } // test day light saving time do { let tz = TimeZone(identifier: "America/Los_Angeles")! - XCTAssert(tz.nextDaylightSavingTimeTransition(after: Date(timeIntervalSinceReferenceDate: 0)) != nil) + #expect(tz.nextDaylightSavingTimeTransition(after: Date(timeIntervalSinceReferenceDate: 0)) != nil) let intervalsAroundDSTTransition = [41418000.0, 41425200.0, 25689600.0, 73476000.0, 89197200.0, 57747600.0, 57744000.0, 9972000.0, 25693200.0, 9975600.0, 57751200.0, 25696800.0, 89193600.0, 41421600.0, 73479600.0, 89200800.0, 73472400.0, 9968400.0] for ti in intervalsAroundDSTTransition { let date = Date(timeIntervalSince1970: TimeInterval(ti)) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: tz) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: tz) } } @@ -1610,12 +1631,12 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { for ti in testStrides { let date = Date(timeIntervalSince1970: TimeInterval(ti)) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne, "firstweekday: \(firstWeekday)") - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne, "firstweekday: \(firstWeekday)") + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) } } } @@ -1627,29 +1648,29 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: nil, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: minDaysInFirstWeek, gregorianStartDate: nil) for ti in testStrides { let date = Date(timeIntervalSince1970: TimeInterval(ti)) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) - test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) + try test(date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, timeZone: gmtPlusOne) } } } } - func testDateComponentsFromDateCompatibility_DST() { + @Test func dateComponentsFromDateCompatibility_DST() { let componentSet = Calendar.ComponentSet([.era, .year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .calendar]) let tz = TimeZone(identifier: "America/Los_Angeles")! let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: tz, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: tz, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) - func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) { let gregResult = gregorianCalendar.dateComponents(componentSet, from: date, in: tz) let icuResult = icuCalendar.dateComponents(componentSet, from: date, in: tz) // The original implementation does not set quarter - expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, message().appending("\ndate: \(date.timeIntervalSinceReferenceDate), \(date.formatted(.iso8601))\nnew:\n\(gregResult)\nold:\n\(icuResult)"), file: file, line: line) + expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, "\(message())\ndate: \(date.timeIntervalSinceReferenceDate), \(date.formatted(.iso8601))\nnew:\n\(gregResult)\nold:\n\(icuResult)", sourceLocation: sourceLocation) } let testStrides = stride(from: -864000, to: 864000, by: 100) @@ -1715,14 +1736,14 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { } - func testDateComponentsFromDate_distantDates() { + @Test func dateComponentsFromDate_distantDates() { let componentSet = Calendar.ComponentSet([.era, .year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .calendar]) - func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) { let gregResult = gregorianCalendar.dateComponents(componentSet, from: date, in: gregorianCalendar.timeZone) let icuResult = icuCalendar.dateComponents(componentSet, from: date, in: icuCalendar.timeZone) // The original implementation does not set quarter - expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, message().appending("\ndate: \(date.timeIntervalSince1970), \(date.formatted(Date.ISO8601FormatStyle(timeZone: gregorianCalendar.timeZone)))\nnew:\n\(gregResult)\nold:\n\(icuResult)\ndiff:\n\(DateComponents.differenceBetween(gregResult, icuResult, compareQuarter: false) ?? "nil")"), file: file, line: line) + expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, "\(message())\ndate: \(date.timeIntervalSince1970), \(date.formatted(Date.ISO8601FormatStyle(timeZone: gregorianCalendar.timeZone)))\nnew:\n\(gregResult)\nold:\n\(icuResult)\ndiff:\n\(DateComponents.differenceBetween(gregResult, icuResult, compareQuarter: false) ?? "nil")", sourceLocation: sourceLocation) } do { @@ -1747,14 +1768,14 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { } - func testDateComponentsFromDate() { + @Test func dateComponentsFromDate() { let componentSet = Calendar.ComponentSet([.era, .year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .calendar]) - func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + func test(_ date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) { let gregResult = gregorianCalendar.dateComponents(componentSet, from: date, in: gregorianCalendar.timeZone) let icuResult = icuCalendar.dateComponents(componentSet, from: date, in: icuCalendar.timeZone) // The original implementation does not set quarter - expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, message().appending("\ndate: \(date.timeIntervalSince1970), \(date.formatted(Date.ISO8601FormatStyle(timeZone: gregorianCalendar.timeZone)))\nnew:\n\(gregResult)\nold:\n\(icuResult)\ndiff:\n\(DateComponents.differenceBetween(gregResult, icuResult, compareQuarter: false) ?? "")"), file: file, line: line) + expectEqual(gregResult, icuResult, expectQuarter: false, expectCalendar: false, "\(message())\ndate: \(date.timeIntervalSince1970), \(date.formatted(Date.ISO8601FormatStyle(timeZone: gregorianCalendar.timeZone)))\nnew:\n\(gregResult)\nold:\n\(icuResult)\ndiff:\n\(DateComponents.differenceBetween(gregResult, icuResult, compareQuarter: false) ?? "")", sourceLocation: sourceLocation) } do { @@ -1773,39 +1794,33 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { } // MARK: - adding - func verifyAdding(_ components: DateComponents, to date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, wrap: Bool = false, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { - let added_icu = icuCalendar.date(byAdding: components, to: date, wrappingComponents: wrap) - let added_greg = gregorianCalendar.date(byAdding: components, to: date, wrappingComponents: wrap) - guard let added_icu, let added_greg else { - XCTFail("\(message())", file: file, line: line) - return - } + func verifyAdding(_ components: DateComponents, to date: Date, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, wrap: Bool = false, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { + let added_icu = try #require(icuCalendar.date(byAdding: components, to: date, wrappingComponents: wrap), "\(message())", sourceLocation: sourceLocation) + let added_greg = try #require(gregorianCalendar.date(byAdding: components, to: date, wrappingComponents: wrap), "\(message())", sourceLocation: sourceLocation) let tz = icuCalendar.timeZone assert(icuCalendar.timeZone == gregorianCalendar.timeZone) let dsc_greg = added_greg.formatted(Date.ISO8601FormatStyle(timeZone: tz)) let dsc_icu = added_icu.formatted(Date.ISO8601FormatStyle(timeZone: tz)) - expectEqual(added_greg, added_icu, message().appending("components:\(components), greg: \(dsc_greg), icu: \(dsc_icu)"), file: file, line: line) + try #require(added_greg == added_icu, "\(message()) components:\(components), greg: \(dsc_greg), icu: \(dsc_icu)", sourceLocation: sourceLocation) } - func testAddComponentsCompatibility_singleField() { - - self.continueAfterFailure = false - func verify(_ date: Date, wrap: Bool, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + @Test func addComponentsCompatibility_singleField() throws { + func verify(_ date: Date, wrap: Bool, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { for v in stride(from: -100, through: 100, by: 3) { - verifyAdding(DateComponents(component: .era, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .year, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .month, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .day, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .hour, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .minute, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .second, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekday, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekdayOrdinal, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekOfMonth, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .yearForWeekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .nanosecond, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) + try verifyAdding(DateComponents(component: .era, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .year, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .month, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .day, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .hour, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .minute, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .second, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekday, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekdayOrdinal, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekOfMonth, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .yearForWeekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .nanosecond, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) } } @@ -1816,52 +1831,50 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: nil) // Wrap - verify(Date(timeIntervalSince1970: 825638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 00:00 - verify(Date(timeIntervalSince1970: 825721200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:00 - verify(Date(timeIntervalSince1970: 825723300), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:35 - verify(Date(timeIntervalSince1970: 825638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 5, Tue - verify(Date(timeIntervalSince1970: 826588800), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 12, Tue + try verify(Date(timeIntervalSince1970: 825638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 00:00 + try verify(Date(timeIntervalSince1970: 825721200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:00 + try verify(Date(timeIntervalSince1970: 825723300), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:35 + try verify(Date(timeIntervalSince1970: 825638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 5, Tue + try verify(Date(timeIntervalSince1970: 826588800), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 12, Tue // Dates close to Gregorian cutover - verify(Date(timeIntervalSince1970: -12219638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 1 - verify(Date(timeIntervalSince1970: -12218515200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 14 - verify(Date(timeIntervalSince1970: -12219292800), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 15 - verify(Date(timeIntervalSince1970: -12219206400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 16 - verify(Date(timeIntervalSince1970: -62130067200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // long time ago + try verify(Date(timeIntervalSince1970: -12219638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 1 + try verify(Date(timeIntervalSince1970: -12218515200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 14 + try verify(Date(timeIntervalSince1970: -12219292800), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 15 + try verify(Date(timeIntervalSince1970: -12219206400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 16 + try verify(Date(timeIntervalSince1970: -62130067200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // long time ago // No wrap - verify(Date(timeIntervalSince1970: 825638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 00:00 - verify(Date(timeIntervalSince1970: 825721200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:00 - verify(Date(timeIntervalSince1970: 825723300), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:35 - verify(Date(timeIntervalSince1970: 825638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 5, Tue - verify(Date(timeIntervalSince1970: 826588800), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 12, Tue + try verify(Date(timeIntervalSince1970: 825638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 00:00 + try verify(Date(timeIntervalSince1970: 825721200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:00 + try verify(Date(timeIntervalSince1970: 825723300), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 1, Fri 23:35 + try verify(Date(timeIntervalSince1970: 825638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 5, Tue + try verify(Date(timeIntervalSince1970: 826588800), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1996 Mar 12, Tue // Dates close to Gregorian cutover - verify(Date(timeIntervalSince1970: -12219638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 1 - verify(Date(timeIntervalSince1970: -12218515200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 14 - verify(Date(timeIntervalSince1970: -12219292800), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 15 - verify(Date(timeIntervalSince1970: -12219206400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 16 - verify(Date(timeIntervalSince1970: -62130067200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // long time ago + try verify(Date(timeIntervalSince1970: -12219638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 1 + try verify(Date(timeIntervalSince1970: -12218515200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 14 + try verify(Date(timeIntervalSince1970: -12219292800), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 15 + try verify(Date(timeIntervalSince1970: -12219206400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // 1582 Oct 16 + try verify(Date(timeIntervalSince1970: -62130067200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar) // long time ago } - func testAddComponentsCompatibility_singleField_custom() { - - self.continueAfterFailure = false - func verify(_ date: Date, wrap: Bool, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { + @Test func addComponentsCompatibility_singleField_custom() throws { + func verify(_ date: Date, wrap: Bool, icuCalendar: _CalendarICU, gregorianCalendar: _CalendarGregorian, _ message: @autoclosure () -> String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { for v in stride(from: -100, through: 100, by: 23) { - verifyAdding(DateComponents(component: .era, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .year, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .month, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .day, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .hour, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .minute, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .second, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekday, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekdayOrdinal, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .weekOfMonth, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .yearForWeekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) - verifyAdding(DateComponents(component: .nanosecond, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), file: file, line: line) + try verifyAdding(DateComponents(component: .era, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .year, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .month, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .day, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .hour, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .minute, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .second, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekday, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekdayOrdinal, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .weekOfMonth, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .yearForWeekOfYear, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) + try verifyAdding(DateComponents(component: .nanosecond, value: v)!, to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: wrap, message(), sourceLocation: sourceLocation) } } @@ -1873,13 +1886,13 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: nil) // Wrap - verify(Date(timeIntervalSince1970: 825723300), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 1, Fri 23:35 - verify(Date(timeIntervalSince1970: 826588800), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 12, Tue + try verify(Date(timeIntervalSince1970: 825723300), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 1, Fri 23:35 + try verify(Date(timeIntervalSince1970: 826588800), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 12, Tue // Dates close to Gregorian cutover - verify(Date(timeIntervalSince1970: -12219638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 1 - verify(Date(timeIntervalSince1970: -12218515200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 14 - verify(Date(timeIntervalSince1970: -12219206400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 16 + try verify(Date(timeIntervalSince1970: -12219638400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 1 + try verify(Date(timeIntervalSince1970: -12218515200), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 14 + try verify(Date(timeIntervalSince1970: -12219206400), wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 16 // Far dates // FIXME: This is failing @@ -1887,13 +1900,13 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { // verify(Date.distantFuture, wrap: true, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // No Wrap - verify(Date(timeIntervalSince1970: 825723300), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 1, Fri 23:35 - verify(Date(timeIntervalSince1970: 826588800), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 12, Tue + try verify(Date(timeIntervalSince1970: 825723300), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 1, Fri 23:35 + try verify(Date(timeIntervalSince1970: 826588800), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1996 Mar 12, Tue // Dates close to Gregorian cutover - verify(Date(timeIntervalSince1970: -12219638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 1 - verify(Date(timeIntervalSince1970: -12218515200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 14 - verify(Date(timeIntervalSince1970: -12219206400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 16 + try verify(Date(timeIntervalSince1970: -12219638400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 1 + try verify(Date(timeIntervalSince1970: -12218515200), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 14 + try verify(Date(timeIntervalSince1970: -12219206400), wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) // 1582 Oct 16 // Far dates // verify(Date.distantPast, wrap: false, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, msg) @@ -1903,7 +1916,7 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { } } - func testAddComponentsCompatibility() { + @Test func addComponentsCompatibility() throws { let firstWeekday = 2 let minimumDaysInFirstWeek = 4 let timeZone = TimeZone(secondsFromGMT: -3600 * 8)! @@ -1912,30 +1925,28 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let march1_1996 = Date(timeIntervalSince1970: 825723300) // 1996 Mar 1, Fri 23:35 - verifyAdding(.init(day: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: -1, day: 30), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(year: 4, day: -1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -1, hour: 24), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -1, weekday: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -7, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -7, weekOfMonth: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - - verifyAdding(.init(day: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: -1, day: 30), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(year: 4, day: -1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -1, hour: 24), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -1, weekday: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -7, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -7, weekOfMonth: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - } - - func testAddComponentsCompatibility_DST() { - - + try verifyAdding(.init(day: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: -1, day: 30), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(year: 4, day: -1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -1, hour: 24), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -1, weekday: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -7, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -7, weekOfMonth: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + + try verifyAdding(.init(day: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: -1, hour: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: -1, day: 30), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(year: 4, day: -1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -1, hour: 24), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -1, weekday: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -7, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -7, weekOfMonth: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: march1_1996, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + } + + @Test func addComponentsCompatibility_DST() throws { let firstWeekday = 3 let minimumDaysInFirstWeek = 5 let timeZone = TimeZone(identifier: "America/Los_Angeles")! @@ -1944,72 +1955,72 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { var date = Date(timeIntervalSince1970: 846403387.0) // 1996-10-27T01:03:07-0700 - verifyAdding(.init(day: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: -1, day: 30), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(year: 4, day: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -1, hour: 24), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -1, weekday: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -7, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -7, weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(hour: 1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(hour: -1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // result is also DST transition day - verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: -12, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - - verifyAdding(.init(day: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: -1, day: 30), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(year: 4, day: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // result is also DST transition day - verifyAdding(.init(day: -1, hour: 24), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -1, weekday: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -7, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -7, weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(hour: 1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(hour: -1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: -12, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Also DST + try verifyAdding(.init(day: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: -1, day: 30), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(year: 4, day: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -1, hour: 24), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -1, weekday: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -7, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -7, weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(hour: 1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(hour: -1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // result is also DST transition day + try verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: -12, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + + try verifyAdding(.init(day: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: -1, hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: -1, day: 30), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(year: 4, day: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // result is also DST transition day + try verifyAdding(.init(day: -1, hour: 24), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -1, weekday: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -7, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -7, weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: -7, weekOfMonth: 1, weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(hour: 1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(hour: -1, yearForWeekOfYear: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: -12, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Also DST date = Date(timeIntervalSince1970: 814953787.0) // 1995-10-29T01:03:07-0700 - verifyAdding(.init(year: 1, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // result is also DST transition day - verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(weekOfYear: 43), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // Also DST - - verifyAdding(.init(year: 1, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // result is also DST transition day - verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(weekOfYear: 43), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Also DST + try verifyAdding(.init(year: 1, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // result is also DST transition day + try verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(weekOfYear: 43), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // Also DST + + try verifyAdding(.init(year: 1, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // result is also DST transition day + try verifyAdding(.init(hour: 1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(hour: -1, yearForWeekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(weekOfYear: 43), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Also DST date = Date(timeIntervalSince1970: 846406987.0) // 1996-10-27T01:03:07-0800 - verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // result is also DST transition day - verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) - verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // Also DST + try verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // result is also DST transition day + try verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) // Also DST - verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // result is also DST transition day - verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Also DST + try verifyAdding(.init(year: -1, day: 2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // result is also DST transition day + try verifyAdding(.init(weekOfMonth: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(weekOfYear: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(month: 12, day: -2), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Also DST } - func testAddComponents() { + @Test func addComponents() throws { let firstWeekday = 1 let minimumDaysInFirstWeek = 1 let timeZone = TimeZone(identifier: "America/Edmonton")! @@ -2017,19 +2028,19 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: firstWeekday, minimumDaysInFirstWeek: minimumDaysInFirstWeek, gregorianStartDate: nil) var date = Date(timeIntervalSinceReferenceDate: -2976971168) // Some remote dates - verifyAdding(.init(weekday: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(weekday: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) date = Date(timeIntervalSinceReferenceDate: -2977057568.0) - verifyAdding(.init(day: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) + try verifyAdding(.init(day: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: false) } - func testAddComponentsWrap() { + @Test func addComponentsWrap() throws { let timeZone = TimeZone(identifier: "Europe/Rome")! let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let date = Date(timeIntervalSinceReferenceDate: -702180000) // 1978-10-01T23:00:00+0100 - verifyAdding(.init(hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(hour: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) // Expected // 10-01 23:00 +0100 @@ -2038,32 +2049,32 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { // -> 10-01 00:00 +0100 (DST, rewinds back) } - func testAddComponentsWrap2() { + @Test func addComponentsWrap2() throws { let timeZone = TimeZone(identifier: "America/Los_Angeles")! let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) var date = Date(timeIntervalSince1970: 814950000.0) // 1995-10-29T00:00:00-0700 - verifyAdding(.init(minute: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) - verifyAdding(.init(second: 60), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(minute: -1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(second: 60), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) date = Date(timeIntervalSince1970: 814953599.0) // 1995-10-29T00:59:59-0700 - verifyAdding(.init(minute: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(minute: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) } - func testAddComponentsWrap3_GMT() { + @Test func addComponentsWrap3_GMT() throws { let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: .gmt, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: .gmt, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let date = Date(timeIntervalSinceReferenceDate: 2557249259.5) // 2082-1-13 19:00:59.5 +0000 - verifyAdding(.init(day: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) + try verifyAdding(.init(day: 1), to: date, icuCalendar: icuCalendar, gregorianCalendar: gregorianCalendar, wrap: true) } // MARK: DateInterval - func testDateIntervalCompatibility() { + @Test func dateIntervalCompatibility() throws { let firstWeekday = 2 let minimumDaysInFirstWeek = 4 let timeZone = TimeZone(secondsFromGMT: -3600 * 8)! @@ -2080,19 +2091,18 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { Date(timeIntervalSince1970: -12218515200.0), // 1582-10-14 ] - self.continueAfterFailure = false for date in dates { for unit in units { let old = icuCalendar.dateInterval(of: unit, for: date) let new = gregorianCalendar.dateInterval(of: unit, for: date) - let msg = "unit: \(unit), date: \(date)" - XCTAssertEqual(old?.start, new?.start, msg) - XCTAssertEqual(old?.end, new?.end, msg) + let msg: Comment = "unit: \(unit), date: \(date)" + try #require(old?.start == new?.start, msg) + try #require(old?.end == new?.end, msg) } } } - func testDateIntervalCompatibility_DST() { + @Test func dateIntervalCompatibility_DST() throws { let firstWeekday = 2 let minimumDaysInFirstWeek = 4 let timeZone = TimeZone(identifier: "America/Los_Angeles")! @@ -2109,19 +2119,18 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { Date(timeIntervalSince1970: 846410587.0), // 1996-10-27T02:03:07-0800 ] - self.continueAfterFailure = false for date in dates { for unit in units { let old = icuCalendar.dateInterval(of: unit, for: date) let new = gregorianCalendar.dateInterval(of: unit, for: date) - let msg = "unit: \(unit), date: \(date)" - XCTAssertEqual(old?.start, new?.start, msg) - XCTAssertEqual(old?.end, new?.end, msg) + let msg: Comment = "unit: \(unit), date: \(date)" + try #require(old?.start == new?.start, msg) + try #require(old?.end == new?.end, msg) } } } - func testDateInterval() { + @Test func dateInterval() { let firstWeekday = 1 let minimumDaysInFirstWeek = 1 let timeZone = TimeZone(identifier: "America/Edmonton")! @@ -2132,12 +2141,12 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let date = Date(timeIntervalSinceReferenceDate: -2976971169.0) let old = icuCalendar.dateInterval(of: unit, for: date) let new = gregorianCalendar.dateInterval(of: unit, for: date) - let msg = "unit: \(unit), date: \(date)" - XCTAssertEqual(old?.start, new?.start, msg) - XCTAssertEqual(old?.end, new?.end, msg) + let msg: Comment = "unit: \(unit), date: \(date)" + #expect(old?.start == new?.start, msg) + #expect(old?.end == new?.end, msg) } - func testDateIntervalRemoteDates() { + @Test func dateIntervalRemoteDates() { let firstWeekday = 2 let minimumDaysInFirstWeek = 4 let timeZone = TimeZone.gmt @@ -2159,19 +2168,17 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let c1 = icuCalendar.dateInterval(of: component, for: date) let c2 = gregorianCalendar.dateInterval(of: component, for: date) guard let c1, let c2 else { - if c1 != c2 { - XCTFail("c1: \(String(describing: c1)), c2: \(String(describing: c2)), component: \(component)") - } + #expect(c1 == c2, "component: \(component)") return } - XCTAssertEqual(c1.start, c2.start, "\(component), start diff c1: \(c1.start.timeIntervalSince(date)), c2: \(c2.start.timeIntervalSince(date))") - XCTAssertEqual(c1.end, c2.end, "\(component), end diff c1: \(c1.end.timeIntervalSince(date)) c2: \(c2.end.timeIntervalSince(date))") + #expect(c1.start == c2.start, "\(component), start diff c1: \(c1.start.timeIntervalSince(date)), c2: \(c2.start.timeIntervalSince(date))") + #expect(c1.end == c2.end, "\(component), end diff c1: \(c1.end.timeIntervalSince(date)) c2: \(c2.end.timeIntervalSince(date))") } } } - func testDateInterval_cappedDate_nonGMT() { + @Test func dateInterval_cappedDate_nonGMT() { let firstWeekday = 2 let minimumDaysInFirstWeek = 4 let timeZone = TimeZone(identifier: "America/Los_Angeles")! @@ -2191,14 +2198,14 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { for component in allComponents { let c1 = icuCalendar.dateInterval(of: component, for: date) let c2 = gregorianCalendar.dateInterval(of: component, for: date) - XCTAssertEqual(c1, c2, "\(i)") + #expect(c1 == c2, "\(i)") } } } // MARK: - First instant - func testFirstInstant() { + @Test func firstInstant() throws { let firstWeekday = 1 let minimumDaysInFirstWeek = 1 let timeZone = TimeZone.gmt @@ -2212,17 +2219,13 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { for date in dates { for component in allComponents { let c1 = icuCalendar.firstInstant(of: component, at: date) - let c2 = gregorianCalendar.firstInstant(of: component, at: date) - guard let c2 else { - XCTFail("unexpected nil first instant") - continue - } - XCTAssertEqual(c1, c2, "c1: \(c1.timeIntervalSinceReferenceDate), c2: \(c2.timeIntervalSinceReferenceDate), \(date.timeIntervalSinceReferenceDate)") + let c2 = try #require(gregorianCalendar.firstInstant(of: component, at: date)) + #expect(c1 == c2, "c1: \(c1.timeIntervalSinceReferenceDate), c2: \(c2.timeIntervalSinceReferenceDate), \(date.timeIntervalSinceReferenceDate)") } } } - func testFirstInstantDST() { + @Test func firstInstantDST() { let timeZone = TimeZone(identifier: "Europe/Rome")! let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) @@ -2232,11 +2235,11 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { for component in allComponents { let c1 = icuCalendar.firstInstant(of: component, at: date) let c2 = gregorianCalendar.firstInstant(of: component, at: date) - XCTAssertEqual(c1, c2, "\(date.timeIntervalSinceReferenceDate)") + #expect(c1 == c2, "\(date.timeIntervalSinceReferenceDate)") } } - func testDateComponentsFromTo() { + @Test func dateComponentsFromTo() { let timeZone = TimeZone(secondsFromGMT: -8*3600) let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) @@ -2245,19 +2248,19 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let d2 = Date(timeIntervalSinceReferenceDate: 5458822.0) // 2001-03-04 20:20:22 PT let a = icuCalendar.dateComponents(allComponents, from: d1, to: d2) let b = gregorianCalendar.dateComponents(allComponents, from: d1, to: d2) - expectEqual(a, b) + #expect(a == b) } - func testDifference() throws { + @Test func difference() throws { let timeZone = TimeZone(secondsFromGMT: -8*3600) let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let d1 = Date(timeIntervalSinceReferenceDate: 0) // 2000-12-31 16:00:00 PT let d2 = Date(timeIntervalSinceReferenceDate: 5458822.0) // 2001-03-04 20:20:22 PT let (_, newStart) = try gregorianCalendar.difference(inComponent: .month, from: d1, to: d2) - XCTAssertEqual(newStart.timeIntervalSince1970, 983404800) // 2001-03-01 00:00:00 UTC + #expect(newStart.timeIntervalSince1970 == 983404800) // 2001-03-01 00:00:00 UTC } - func testAdd() throws { + @Test func add() throws { let timeZone = TimeZone(secondsFromGMT: -8*3600)! let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) @@ -2265,19 +2268,19 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let dc = DateComponents(year: 2000, month: 14, day: 28, hour: 16, minute: 0, second: 0) let old = icuCalendar.date(from: dc)! let new = gregorianCalendar.date(from: dc)! - XCTAssertEqual(old, new) - XCTAssertEqual(old.timeIntervalSince1970, 983404800) + #expect(old == new) + #expect(old.timeIntervalSince1970 == 983404800) let d1 = Date(timeIntervalSinceReferenceDate: 0) // 2000-12-31 16:00:00 PT let added = try gregorianCalendar.add(.month, to: d1, amount: 2, inTimeZone: timeZone) let gregResult = gregorianCalendar.date(byAdding: .init(month: 2), to: d1, wrappingComponents: false)! let icuResult = icuCalendar.date(byAdding: .init(month: 2), to: d1, wrappingComponents: false)! - XCTAssertEqual(gregResult, icuResult) - XCTAssertEqual(added, icuResult) - XCTAssertEqual(icuResult.timeIntervalSince1970, 983404800) // 2001-03-01 00:00:00 UTC, 2001-02-28 16:00:00 PT + #expect(gregResult == icuResult) + #expect(added == icuResult) + #expect(icuResult.timeIntervalSince1970 == 983404800) // 2001-03-01 00:00:00 UTC, 2001-02-28 16:00:00 PT } - func testAdd_precision() throws { + @Test func add_precision() throws { let timeZone = TimeZone.gmt let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) @@ -2290,21 +2293,21 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { gregResult = gregorianCalendar.date(byAdding: .init(month: -277), to: d1, wrappingComponents: false)! icuResult = icuCalendar.date(byAdding: .init(month: -277), to: d1, wrappingComponents: false)! - XCTAssertEqual(gregResult, icuResult, "greg: \(gregResult.timeIntervalSinceReferenceDate), icu: \(icuResult.timeIntervalSinceReferenceDate)") - XCTAssertEqual(added, icuResult) + #expect(gregResult == icuResult, "greg: \(gregResult.timeIntervalSinceReferenceDate), icu: \(icuResult.timeIntervalSinceReferenceDate)") + #expect(added == icuResult) let d2 = Date(timeIntervalSinceReferenceDate: -0.4525610214656613) gregResult = gregorianCalendar.date(byAdding: .init(nanosecond: 500000000), to: d2, wrappingComponents: false)! icuResult = icuCalendar.date(byAdding: .init(nanosecond: 500000000), to: d2, wrappingComponents: false)! - XCTAssertEqual(gregResult, icuResult, "greg: \(gregResult.timeIntervalSinceReferenceDate), icu: \(icuResult.timeIntervalSinceReferenceDate)") + #expect(gregResult == icuResult, "greg: \(gregResult.timeIntervalSinceReferenceDate), icu: \(icuResult.timeIntervalSinceReferenceDate)") let d3 = Date(timeIntervalSinceReferenceDate: 729900523.547439) gregResult = gregorianCalendar.date(byAdding: .init(year: -60), to: d3, wrappingComponents: false)! icuResult = icuCalendar.date(byAdding: .init(year: -60), to: d3, wrappingComponents: false)! - XCTAssertEqual(gregResult, icuResult, "greg: \(gregResult.timeIntervalSinceReferenceDate), icu: \(icuResult.timeIntervalSinceReferenceDate)") + #expect(gregResult == icuResult, "greg: \(gregResult.timeIntervalSinceReferenceDate), icu: \(icuResult.timeIntervalSinceReferenceDate)") } - func testDateComponentsFromTo_precision() { + @Test func dateComponentsFromTo_precision() { let timeZone = TimeZone.gmt let gregorianCalendar = _CalendarGregorian(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) let icuCalendar = _CalendarICU(identifier: .gregorian, timeZone: timeZone, locale: nil, firstWeekday: nil, minimumDaysInFirstWeek: nil, gregorianStartDate: nil) @@ -2326,9 +2329,7 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let d2 = Date(timeIntervalSinceReferenceDate: ti2) a = icuCalendar.dateComponents(allComponents, from: d1, to: d2) b = gregorianCalendar.dateComponents(allComponents, from: d1, to: d2) - XCTAssertEqual(a, b, "test: \(i)") - - expectEqual(a, b, "test: \(i)") + #expect(a == b, "test: \(i)") } for (i, (ti1, ti2)) in tests.enumerated() { @@ -2336,16 +2337,14 @@ final class GregorianCalendarCompatibilityTests: XCTestCase { let d2 = Date(timeIntervalSinceReferenceDate: ti2) a = icuCalendar.dateComponents([.nanosecond], from: d1, to: d2) b = gregorianCalendar.dateComponents([.nanosecond], from: d1, to: d2) - XCTAssertEqual(a, b, "test: \(i)") + #expect(a == b, "test: \(i)") if ti1 < ti2 { - XCTAssertGreaterThanOrEqual(b.nanosecond!, 0, "test: \(i)") + #expect(b.nanosecond! >= 0, "test: \(i)") } else { - XCTAssertLessThanOrEqual(b.nanosecond!, 0, "test: \(i)") + #expect(b.nanosecond! <= 0, "test: \(i)") } - expectEqual(a, b, "test: \(i)") } } } -#endif diff --git a/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift b/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift index 5cb85ac7d..a469d1029 100644 --- a/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift +++ b/Tests/FoundationInternationalizationTests/CurrentInternationalizationPreferencesActor.swift @@ -25,17 +25,25 @@ private actor CurrentInternationalizationPreferencesActor: GlobalActor { private init() {} + private static func resetCaches() { + LocaleCache.cache.reset() + CalendarCache.cache.reset() + _ = TimeZoneCache.cache.reset() + _ = TimeZone.resetSystemTimeZone() + TimeZoneCache.cache.setDefault(nil) + } + @CurrentInternationalizationPreferencesActor static func usingCurrentInternationalizationPreferences( body: () throws -> Void // Must be synchronous to prevent suspension points within body which could introduce a change in the preferences ) rethrows { + // Reset caches before the test to clean up any lingering, eagerly realized values + resetCaches() + try body() // Reset everything after the test runs to ensure custom values don't persist - LocaleCache.cache.reset() - CalendarCache.cache.reset() - _ = TimeZoneCache.cache.reset() - _ = TimeZone.resetSystemTimeZone() + resetCaches() } } diff --git a/Tests/FoundationInternationalizationTests/DateComponentsTests.swift b/Tests/FoundationInternationalizationTests/DateComponentsTests.swift index 14456e5e2..edba87f9f 100644 --- a/Tests/FoundationInternationalizationTests/DateComponentsTests.swift +++ b/Tests/FoundationInternationalizationTests/DateComponentsTests.swift @@ -10,56 +10,77 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationInternationalization) +import FoundationEssentials +import FoundationInternationalization +#elseif FOUNDATION_FRAMEWORK +import Foundation +#endif + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(WASI) +import WASILibc +#elseif os(Windows) +import CRT #endif -final class DateComponentsTests : XCTestCase { +@Suite("DateComponents") +private struct DateComponentsTests { - func test_isValidDate() { + @Test func isValidDate() { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! + let dc = DateComponents(year: 2022, month: 11, day: 1) - XCTAssertTrue(dc.isValidDate(in: Calendar(identifier: .gregorian))) + #expect(dc.isValidDate(in: calendar)) let dc2 = DateComponents(year: 2022, month: 11, day: 32) - XCTAssertFalse(dc2.isValidDate(in: Calendar(identifier: .gregorian))) + #expect(!dc2.isValidDate(in: calendar)) } - func test_leapMonth() { + @Test func leapMonth() { var components = DateComponents() components.month = 1 - XCTAssertFalse(components.isLeapMonth ?? true == false) + #expect(components.isLeapMonth == nil) components.isLeapMonth = true - XCTAssertEqual(components.month, 1) - XCTAssertTrue(components.isLeapMonth ?? false == true) + #expect(components.month == 1) + #expect(components.isLeapMonth == true) } - func test_valueForComponent() { + @Test func valueForComponent() { let comps = DateComponents(calendar: nil, timeZone: nil, era: 1, year: 2013, month: 4, day: 2, hour: 20, minute: 33, second: 49, nanosecond: 192837465, weekday: 3, weekdayOrdinal: 1, quarter: nil, weekOfMonth: 1, weekOfYear: 14, yearForWeekOfYear: 2013) - XCTAssertEqual(comps.value(for: .calendar), nil) - XCTAssertEqual(comps.value(for: .timeZone), nil) - XCTAssertEqual(comps.value(for: .era), 1) - XCTAssertEqual(comps.value(for: .year), 2013) - XCTAssertEqual(comps.value(for: .month), 4) - XCTAssertEqual(comps.value(for: .day), 2) - XCTAssertEqual(comps.value(for: .hour), 20) - XCTAssertEqual(comps.value(for: .minute), 33) - XCTAssertEqual(comps.value(for: .second), 49) - XCTAssertEqual(comps.value(for: .nanosecond), 192837465) - XCTAssertEqual(comps.value(for: .weekday), 3) - XCTAssertEqual(comps.value(for: .weekdayOrdinal), 1) - XCTAssertEqual(comps.value(for: .quarter), nil) - XCTAssertEqual(comps.value(for: .weekOfMonth), 1) - XCTAssertEqual(comps.value(for: .weekOfYear), 14) - XCTAssertEqual(comps.value(for: .yearForWeekOfYear), 2013) + #expect(comps.value(for: .calendar) == nil) + #expect(comps.value(for: .timeZone) == nil) + #expect(comps.value(for: .era) == 1) + #expect(comps.value(for: .year) == 2013) + #expect(comps.value(for: .month) == 4) + #expect(comps.value(for: .day) == 2) + #expect(comps.value(for: .hour) == 20) + #expect(comps.value(for: .minute) == 33) + #expect(comps.value(for: .second) == 49) + #expect(comps.value(for: .nanosecond) == 192837465) + #expect(comps.value(for: .weekday) == 3) + #expect(comps.value(for: .weekdayOrdinal) == 1) + #expect(comps.value(for: .quarter) == nil) + #expect(comps.value(for: .weekOfMonth) == 1) + #expect(comps.value(for: .weekOfYear) == 14) + #expect(comps.value(for: .yearForWeekOfYear) == 2013) } - func test_nanosecond() { + @Test func nanosecond() throws { var comps = DateComponents(nanosecond: 123456789) - XCTAssertEqual(comps.nanosecond, 123456789) + #expect(comps.nanosecond == 123456789) comps.year = 2013 comps.month = 12 @@ -69,51 +90,51 @@ final class DateComponentsTests : XCTestCase { comps.second = 45 var cal = Calendar(identifier: .gregorian) - cal.timeZone = TimeZone(identifier: "UTC")! + cal.timeZone = try #require(TimeZone(identifier: "UTC")) - let dateWithNS = cal.date(from: comps)! + let dateWithNS = try #require(cal.date(from: comps)) let newComps = cal.dateComponents([.nanosecond], from: dateWithNS) - let nanosecondsApproximatelyEqual = labs(CLong(newComps.nanosecond!) - 123456789) <= 500 - XCTAssertTrue(nanosecondsApproximatelyEqual) + let nano = try #require(newComps.nanosecond) + #expect(labs(CLong(nano) - 123456789) <= 500) } - func testDateComponents() { + @Test func dateComponents() { // Make sure the optional init stuff works let dc = DateComponents() - XCTAssertNil(dc.year) + #expect(dc.year == nil) let dc2 = DateComponents(year: 1999) - XCTAssertNil(dc2.day) - XCTAssertEqual(1999, dc2.year) + #expect(dc2.day == nil) + #expect(1999 == dc2.year) } - func test_AnyHashableContainingDateComponents() { + @Test func anyHashableContainingDateComponents() { let values: [DateComponents] = [ DateComponents(year: 2016), DateComponents(year: 1995), DateComponents(year: 1995), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(DateComponents.self, type(of: anyHashables[0].base)) - expectEqual(DateComponents.self, type(of: anyHashables[1].base)) - expectEqual(DateComponents.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(DateComponents.self == type(of: anyHashables[0].base)) + #expect(DateComponents.self == type(of: anyHashables[1].base)) + #expect(DateComponents.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func test_weekComponent() { + @Test func weekComponent() { var calendar = Calendar(identifier: .gregorian) calendar.timeZone = .gmt // date(from: "2010-09-08 07:59:54 +0000") let date = Date(timeIntervalSinceReferenceDate: 305625594.0) let comps = calendar.dateComponents([.weekOfYear], from: date) - XCTAssertEqual(comps.weekOfYear, 37) + #expect(comps.weekOfYear == 37) } - func test_components_fromDate_toDate_options_withEraChange() { + @Test func components_fromDate_toDate_options_withEraChange() { // date(from: "1900-01-01 01:23:34 +0000") let fromDate = Date(timeIntervalSinceReferenceDate: -3187290986.0) // date(from: "2010-09-08 07:59:54 +0000") @@ -126,13 +147,13 @@ final class DateComponentsTests : XCTestCase { let comps = calendar.dateComponents(units, from: fromDate, to: toDate) - XCTAssertEqual(comps.era, 3) - XCTAssertEqual(comps.year, -10) - XCTAssertEqual(comps.month, -3) - XCTAssertEqual(comps.day, -22) - XCTAssertEqual(comps.hour, -17) - XCTAssertEqual(comps.minute, -23) - XCTAssertEqual(comps.second, -40) + #expect(comps.era == 3) + #expect(comps.year == -10) + #expect(comps.month == -3) + #expect(comps.day == -22) + #expect(comps.hour == -17) + #expect(comps.minute == -23) + #expect(comps.second == -40) } } diff --git a/Tests/FoundationInternationalizationTests/DateTests+Locale.swift b/Tests/FoundationInternationalizationTests/DateTests+Locale.swift index df2c58181..0e27c78ef 100644 --- a/Tests/FoundationInternationalizationTests/DateTests+Locale.swift +++ b/Tests/FoundationInternationalizationTests/DateTests+Locale.swift @@ -10,58 +10,66 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationInternationalization) +import FoundationEssentials +import FoundationInternationalization +#elseif FOUNDATION_FRAMEWORK +import Foundation #endif // TODO: Reenable these tests once DateFormatStyle has been ported -final class DateLocaleTests : XCTestCase { +@Suite("Date (Locale)") +private struct DateLocaleTests { #if FOUNDATION_FRAMEWORK func dateWithString(_ str: String) -> Date { let formatter = DateFormatter() // Note: Calendar(identifier:) is OSX 10.9+ and iOS 8.0+ whereas the CF version has always been available - formatter.calendar = Calendar(identifier: .gregorian) + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt + formatter.calendar = calendar formatter.locale = Locale(identifier: "en_US") formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" return formatter.date(from: str)! as Date } - func testEquality() { + @Test func equality() { let date = dateWithString("2010-05-17 14:49:47 -0700") let sameDate = dateWithString("2010-05-17 14:49:47 -0700") - XCTAssertEqual(date, sameDate) - XCTAssertEqual(sameDate, date) + #expect(date == sameDate) + #expect(sameDate == date) let differentDate = dateWithString("2010-05-17 14:49:46 -0700") - XCTAssertNotEqual(date, differentDate) - XCTAssertNotEqual(differentDate, date) + #expect(date != differentDate) + #expect(differentDate != date) let sameDateByTimeZone = dateWithString("2010-05-17 13:49:47 -0800") - XCTAssertEqual(date, sameDateByTimeZone) - XCTAssertEqual(sameDateByTimeZone, date) + #expect(date == sameDateByTimeZone) + #expect(sameDateByTimeZone == date) let differentDateByTimeZone = dateWithString("2010-05-17 14:49:47 -0800") - XCTAssertNotEqual(date, differentDateByTimeZone) - XCTAssertNotEqual(differentDateByTimeZone, date) + #expect(date != differentDateByTimeZone) + #expect(differentDateByTimeZone != date) } - func testTimeIntervalSinceDate() { + @Test func timeIntervalSinceDate() { let referenceDate = dateWithString("1900-01-01 00:00:00 +0000") let sameDate = dateWithString("1900-01-01 00:00:00 +0000") let laterDate = dateWithString("2010-05-17 14:49:47 -0700") let earlierDate = dateWithString("1810-05-17 14:49:47 -0700") let laterSeconds = laterDate.timeIntervalSince(referenceDate) - XCTAssertEqual(laterSeconds, 3483121787.0) + #expect(laterSeconds == 3483121787.0) let earlierSeconds = earlierDate.timeIntervalSince(referenceDate) - XCTAssertEqual(earlierSeconds, -2828311813.0) + #expect(earlierSeconds == -2828311813.0) let sameSeconds = sameDate.timeIntervalSince(referenceDate) - XCTAssertEqual(sameSeconds, 0.0) + #expect(sameSeconds == 0.0) } - func test_DateHashing() { + @Test func hashing() { let values: [Date] = [ dateWithString("2010-05-17 14:49:47 -0700"), dateWithString("2011-05-17 14:49:47 -0700"), @@ -71,21 +79,21 @@ final class DateLocaleTests : XCTestCase { dateWithString("2010-05-17 14:50:47 -0700"), dateWithString("2010-05-17 14:49:48 -0700"), ] - XCTCheckHashable(values, equalityOracle: { $0 == $1 }) + checkHashable(values, equalityOracle: { $0 == $1 }) } - func test_AnyHashableContainingDate() { + @Test func anyHashableContainingDate() { let values: [Date] = [ dateWithString("2016-05-17 14:49:47 -0700"), dateWithString("2010-05-17 14:49:47 -0700"), dateWithString("2010-05-17 14:49:47 -0700"), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(Date.self, type(of: anyHashables[0].base)) - expectEqual(Date.self, type(of: anyHashables[1].base)) - expectEqual(Date.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(Date.self == type(of: anyHashables[0].base)) + #expect(Date.self == type(of: anyHashables[1].base)) + #expect(Date.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } #endif } diff --git a/Tests/FoundationInternationalizationTests/Formatting/ByteCountFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/ByteCountFormatStyleTests.swift index 3b6822383..40bea6531 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/ByteCountFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/ByteCountFormatStyleTests.swift @@ -5,19 +5,22 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport +import Testing + +#if canImport(FoundationInternationalization) +import FoundationEssentials +import FoundationInternationalization +#else +import Foundation #endif -final class ByteCountFormatStyleTests : XCTestCase { - let locales = [Locale(identifier: "en_US"), .init(identifier: "fr_FR"), .init(identifier: "zh_TW"), .init(identifier: "zh_CN"), .init(identifier: "ar")] +@Suite("ByteCountFormatStyle") +private struct ByteCountFormatStyleTests { + static let locales = [Locale(identifier: "en_US"), .init(identifier: "fr_FR"), .init(identifier: "zh_TW"), .init(identifier: "zh_CN"), .init(identifier: "ar")] - func test_zeroSpelledOutKb() { + @Test(arguments: locales) + func zeroSpelledOutKb(locale: Locale) { let localizedZerosSpelledOutKb: [Locale: String] = [ Locale(identifier: "en_US"): "Zero kB", Locale(identifier: "fr_FR"): "Zéro ko", @@ -26,12 +29,11 @@ final class ByteCountFormatStyleTests : XCTestCase { Locale(identifier: "ar"): "صفر كيلوبايت", ] - for locale in locales { - XCTAssertEqual(0.formatted(.byteCount(style: .memory, spellsOutZero: true).locale(locale)), localizedZerosSpelledOutKb[locale], "locale: \(locale.identifier) failed expectation" ) - } + #expect(0.formatted(.byteCount(style: .memory, spellsOutZero: true).locale(locale)) == localizedZerosSpelledOutKb[locale]) } - func test_zeroSpelledOutBytes() { + @Test(arguments: locales) + func zeroSpelledOutBytes(locale: Locale) { let localizedZerosSpelledOutBytes: [Locale: String] = [ Locale(identifier: "en_US"): "Zero bytes", Locale(identifier: "fr_FR"): "Zéro octet", @@ -40,9 +42,7 @@ final class ByteCountFormatStyleTests : XCTestCase { Locale(identifier: "ar"): "صفر بايت", ] - for locale in locales { - XCTAssertEqual(0.formatted(.byteCount(style: .memory, allowedUnits: .bytes, spellsOutZero: true).locale(locale)), localizedZerosSpelledOutBytes[locale], "locale: \(locale.identifier) failed expectation") - } + #expect(0.formatted(.byteCount(style: .memory, allowedUnits: .bytes, spellsOutZero: true).locale(locale)) == localizedZerosSpelledOutBytes[locale], "locale: \(locale.identifier) failed expectation") } let localizedSingular: [Locale: [String]] = [ @@ -89,40 +89,38 @@ final class ByteCountFormatStyleTests : XCTestCase { ] #if FIXED_86386674 - func test_singularUnitsBinary() { - for locale in locales { - for i in 0...5 { - let value: Int64 = (1 << (i*10)) - XCTAssertEqual((value).formatted(.byteCount(style: .memory).locale(locale)), localizedSingular[locale]![i]) - } + @Test(arguments: locales) + func singularUnitsBinary(locale: Locale) { + for i in 0...5 { + let value: Int64 = (1 << (i*10)) + #expect((value).formatted(.byteCount(style: .memory).locale(locale)) == localizedSingular[locale]![i]) } } #endif #if FIXED_86386684 - func test_singularUnitsDecimal() { - for locale in locales { - for i in 0...5 { - XCTAssertEqual(Int64(pow(10.0, Double(i*3))).formatted(.byteCount(style: .file).locale(locale)), localizedSingular[locale]![i]) - } + @Test(arguments: locales) + func singularUnitsDecimal(locale: Locale) { + for i in 0...5 { + #expect(Int64(pow(10.0, Double(i*3))).formatted(.byteCount(style: .file).locale(locale)) == localizedSingular[locale]![i]) } } #endif - func test_localizedParens() { - XCTAssertEqual(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.binary, includesActualByteCount: true).locale(.init(identifier: "zh_TW"))), "1 kB(1,024 byte)") - XCTAssertEqual(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.binary, includesActualByteCount: true).locale(.init(identifier: "en_US"))), "1 kB (1,024 bytes)") + @Test func localizedParens() { + #expect(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.binary, includesActualByteCount: true).locale(.init(identifier: "zh_TW"))) == "1 kB(1,024 byte)") + #expect(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.binary, includesActualByteCount: true).locale(.init(identifier: "en_US"))) == "1 kB (1,024 bytes)") } - func testActualByteCount() { - XCTAssertEqual(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.file, includesActualByteCount: true)), "1 kB (1,024 bytes)") + @Test func actualByteCount() { + #expect(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.file, includesActualByteCount: true).locale(.init(identifier: "en_US"))) == "1 kB (1,024 bytes)") } - func test_RTL() { - XCTAssertEqual(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.binary, includesActualByteCount: true).locale(.init(identifier: "ar_SA"))), "١ كيلوبايت (١٬٠٢٤ بايت)") + @Test func rtl() { + #expect(1024.formatted(.byteCount(style: ByteCountFormatStyle.Style.binary, includesActualByteCount: true).locale(.init(identifier: "ar_SA"))) == "١ كيلوبايت (١٬٠٢٤ بايت)") } - func testAttributed() { + @Test func attributed() { var expected: [Segment] // Zero kB @@ -130,14 +128,14 @@ final class ByteCountFormatStyleTests : XCTestCase { .init(string: "Zero", number: nil, symbol: nil, byteCount: .spelledOutValue), .space, .init(string: "kB", number: nil, symbol: nil, byteCount: .unit(.kb))] - XCTAssertEqual(0.formatted(.byteCount(style: .file, spellsOutZero: true).attributed), expected.attributedString) + #expect(0.formatted(.byteCount(style: .file, spellsOutZero: true).locale(.init(identifier: "en_US")).attributed) == expected.attributedString) // 1 byte expected = [ .init(string: "1", number: .integer, symbol: nil, byteCount: .value), .space, .init(string: "byte", number: nil, symbol: nil, byteCount: .unit(.byte))] - XCTAssertEqual(1.formatted(.byteCount(style: .file).attributed), expected.attributedString) + #expect(1.formatted(.byteCount(style: .file).locale(.init(identifier: "en_US")).attributed) == expected.attributedString) // 1,000 bytes expected = [ @@ -146,7 +144,7 @@ final class ByteCountFormatStyleTests : XCTestCase { .init(string: "000", number: .integer, symbol: nil, byteCount: .value), .space, .init(string: "bytes", number: nil, symbol: nil, byteCount: .unit(.byte))] - XCTAssertEqual(1000.formatted(.byteCount(style: .memory).attributed), expected.attributedString) + #expect(1000.formatted(.byteCount(style: .memory).locale(.init(identifier: "en_US")).attributed) == expected.attributedString) // 1,016 kB expected = [ @@ -155,7 +153,7 @@ final class ByteCountFormatStyleTests : XCTestCase { .init(string: "016", number: .integer, symbol: nil, byteCount: .value), .space, .init(string: "kB", number: nil, symbol: nil, byteCount: .unit(.kb))] - XCTAssertEqual(1_040_000.formatted(.byteCount(style: .memory).attributed), expected.attributedString) + #expect(1_040_000.formatted(.byteCount(style: .memory).locale(.init(identifier: "en_US")).attributed) == expected.attributedString) // 1.1 MB expected = [ @@ -164,7 +162,7 @@ final class ByteCountFormatStyleTests : XCTestCase { .init(string: "1", number: .fraction, symbol: nil, byteCount: .value), .space, .init(string: "MB", number: nil, symbol: nil, byteCount: .unit(.mb))] - XCTAssertEqual(1_100_000.formatted(.byteCount(style: .file).attributed), expected.attributedString) + #expect(1_100_000.formatted(.byteCount(style: .file).locale(.init(identifier: "en_US")).attributed) == expected.attributedString) // 4.2 GB (4,200,000 bytes) expected = [ @@ -185,11 +183,11 @@ final class ByteCountFormatStyleTests : XCTestCase { .space, .init(string: "bytes", number: nil, symbol: nil, byteCount: .unit(.byte)), .closedParen] - XCTAssertEqual(Int64(4_200_000_000).formatted(.byteCount(style: .file, includesActualByteCount: true).attributed), expected.attributedString) + #expect(Int64(4_200_000_000).formatted(.byteCount(style: .file, includesActualByteCount: true).locale(.init(identifier: "en_US")).attributed) == expected.attributedString) } -#if !os(watchOS) - func testEveryAllowedUnit() { +#if !_pointerBitWidth(_32) + @Test func testEveryAllowedUnit() { // 84270854: The largest unit supported currently is pb let expectations: [ByteCountFormatStyle.Units: String] = [ .bytes: "10,000,000,000,000,000 bytes", @@ -204,7 +202,7 @@ final class ByteCountFormatStyleTests : XCTestCase { ] for (units, expectation) in expectations { - XCTAssertEqual(10_000_000_000_000_000.formatted(.byteCount(style: .file, allowedUnits: units).locale(Locale(identifier: "en_US"))), expectation) + #expect(10_000_000_000_000_000.formatted(.byteCount(style: .file, allowedUnits: units).locale(Locale(identifier: "en_US"))) == expectation) } } #endif diff --git a/Tests/FoundationInternationalizationTests/Formatting/DateFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DateFormatStyleTests.swift index 807248f67..83dabb19f 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DateFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DateFormatStyleTests.swift @@ -5,14 +5,8 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials @@ -23,46 +17,47 @@ import TestSupport @testable import Foundation #endif -final class DateFormatStyleTests : XCTestCase { +@Suite("Date.FormatStyle") +private struct DateFormatStyleTests { let referenceDate = Date(timeIntervalSinceReferenceDate: 0) - func test_constructorSyntax() { + @Test func constructorSyntax() { let style = Date.FormatStyle(locale: .init(identifier: "en_US"), calendar: .init(identifier: .gregorian), timeZone: TimeZone(identifier: "America/Los_Angeles")!) .year(.defaultDigits) .month(.abbreviated) .day(.twoDigits) .hour(.twoDigits(amPM: .omitted)) .minute(.defaultDigits) - XCTAssertEqual(referenceDate.formatted(style), "Dec 31, 2000 at 04:00") + #expect(referenceDate.formatted(style) == "Dec 31, 2000 at 04:00") } - func test_era() { + @Test func era() { let abbreviatedStyle = Date.FormatStyle(locale: .init(identifier: "en_US"), calendar: .init(identifier: .gregorian), timeZone: TimeZone(identifier: "America/Los_Angeles")!) .era(.abbreviated) - XCTAssertEqual(referenceDate.formatted(abbreviatedStyle), "AD") + #expect(referenceDate.formatted(abbreviatedStyle) == "AD") let narrowStyle = Date.FormatStyle(locale: .init(identifier: "en_US"), calendar: .init(identifier: .gregorian), timeZone: TimeZone(identifier: "America/Los_Angeles")!) .era(.narrow) - XCTAssertEqual(referenceDate.formatted(narrowStyle), "A") + #expect(referenceDate.formatted(narrowStyle) == "A") let wideStyle = Date.FormatStyle(locale: .init(identifier: "en_US"), calendar: .init(identifier: .gregorian), timeZone: TimeZone(identifier: "America/Los_Angeles")!) .era(.wide) - XCTAssertEqual(referenceDate.formatted(wideStyle), "Anno Domini") + #expect(referenceDate.formatted(wideStyle) == "Anno Domini") } - func test_dateFormatString() { + @Test func dateFormatString() { // dateFormatter.date(from: "2021-04-12 15:04:32")! let date = Date(timeIntervalSinceReferenceDate: 639932672.0) - func _verify(_ format: Date.FormatString, rawExpectation: String, formattedExpectation: String, line: UInt = #line) { - XCTAssertEqual(format.rawFormat, rawExpectation, "raw expectation failed", line: line) - XCTAssertEqual( + func _verify(_ format: Date.FormatString, rawExpectation: String, formattedExpectation: String, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(format.rawFormat == rawExpectation, "raw expectation failed", sourceLocation: sourceLocation) + #expect( Date.VerbatimFormatStyle(format: format, timeZone: .gmt, calendar: .init(identifier: .gregorian)) .locale(.init(identifier: "en_US")) - .format(date), + .format(date) == formattedExpectation, "formatted expectation failed", - line: line + sourceLocation: sourceLocation ) } @@ -88,25 +83,26 @@ final class DateFormatStyleTests : XCTestCase { _verify("Day:\(day: .defaultDigits) Month:\(month: .abbreviated) Year:\(year: .padded(4))", rawExpectation: "'Day:'d' Month:'MMM' Year:'yyyy", formattedExpectation: "Day:12 Month:Apr Year:2021") } - func test_parsingThrows() { + @Test(arguments: [ + ("ddMMyy", "010599"), + ("dd/MM/yy", "01/05/99"), + ("d/MMM/yyyy", "1/Sep/1999"), + ]) + func parsingThrows(format: Date.FormatString, dateString: String) { // Literal symbols are treated as literals, so they won't parse when parsing strictly - let invalidFormats: [(Date.FormatString, String)] = [ - ("ddMMyy", "010599"), - ("dd/MM/yy", "01/05/99"), - ("d/MMM/yyyy", "1/Sep/1999"), - ] - let locale = Locale(identifier: "en_US") let timeZone = TimeZone(secondsFromGMT: 0)! - for (format, dateString) in invalidFormats { - let parseStrategy = Date.ParseStrategy(format: format, locale: locale, timeZone: timeZone, isLenient: false) - XCTAssertThrowsError(try parseStrategy.parse(dateString), "Date string: \(dateString); Format: \(format.rawFormat)") + let parseStrategy = Date.ParseStrategy(format: format, locale: locale, timeZone: timeZone, isLenient: false) + #expect(throws: (any Error).self) { + try parseStrategy.parse(dateString) } } - func test_codable() { - let style = Date.FormatStyle(date: .long, time: .complete, capitalizationContext: .unknown) + @Test func codable() throws { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt + let style = Date.FormatStyle(date: .long, time: .complete, locale: Locale(identifier: "en_US"), calendar: calendar, timeZone: .gmt, capitalizationContext: .unknown) .era() .year() .quarter() @@ -121,18 +117,16 @@ final class DateFormatStyleTests : XCTestCase { .secondFraction(.milliseconds(2)) .timeZone() let jsonEncoder = JSONEncoder() - let encodedStyle = try? jsonEncoder.encode(style) - XCTAssertNotNil(encodedStyle) + let encodedStyle = try jsonEncoder.encode(style) let jsonDecoder = JSONDecoder() - let decodedStyle = try? jsonDecoder.decode(Date.FormatStyle.self, from: encodedStyle!) - XCTAssertNotNil(decodedStyle) + let decodedStyle = try jsonDecoder.decode(Date.FormatStyle.self, from: encodedStyle) - XCTAssert(referenceDate.formatted(decodedStyle!) == referenceDate.formatted(style), "\(referenceDate.formatted(decodedStyle!)) should be \(referenceDate.formatted(style))") + #expect(referenceDate.formatted(decodedStyle) == referenceDate.formatted(style), "\(referenceDate.formatted(decodedStyle)) should be \(referenceDate.formatted(style))") } - func test_createFormatStyleMultithread() { - let group = DispatchGroup() + @Test(.timeLimit(.minutes(2))) + func createFormatStyleMultithread() async throws { let testLocales: [String] = [ "en_US", "en_US", "en_GB", "es_SP", "zh_TW", "fr_FR", "en_US", "en_GB", "fr_FR"] let expectations: [String : String] = [ "en_US": "Dec 31, 1969", @@ -143,30 +137,25 @@ final class DateFormatStyleTests : XCTestCase { ] let date = Date(timeIntervalSince1970: 0) - for localeIdentifier in testLocales { - DispatchQueue.global(qos: .userInitiated).async(group:group) { - let locale = Locale(identifier: localeIdentifier) - XCTAssertNotNil(locale) - let timeZone = TimeZone(secondsFromGMT: -3600)! - - let formatStyle = Date.FormatStyle(date: .abbreviated, locale: locale, timeZone: timeZone) - guard let formatterFromCache = ICUDateFormatter.cachedFormatter(for: formatStyle) else { - XCTFail("Unexpected nil formatter") - return + try await withThrowingDiscardingTaskGroup { group in + for localeIdentifier in testLocales { + group.addTask { + let locale = Locale(identifier: localeIdentifier) + let timeZone = try #require(TimeZone(secondsFromGMT: -3600)) + + let formatStyle = Date.FormatStyle(date: .abbreviated, locale: locale, timeZone: timeZone) + let formatterFromCache = try #require(ICUDateFormatter.cachedFormatter(for: formatStyle)) + + let expected = try #require(expectations[localeIdentifier]) + let result = formatterFromCache.format(date) + #expect(result == expected) } - - let expected = expectations[localeIdentifier]! - let result = formatterFromCache.format(date) - XCTAssertEqual(result, expected) } } - - let result = group.wait(timeout: DispatchTime.now() + DispatchTimeInterval.seconds(105)) - XCTAssertEqual(result, .success) } - func test_createPatternMultithread() { - let group = DispatchGroup() + @Test(.timeLimit(.minutes(2))) + func createPatternMultithread() async { let testLocales = [ "en_US", "en_US", "en_GB", "es_SP", "zh_TW", "fr_FR", "en_US", "en_GB", "fr_FR"].map { Locale(identifier: $0) } let expectations: [String : String] = [ "en_US": "MMM d, y", @@ -178,169 +167,171 @@ final class DateFormatStyleTests : XCTestCase { let gregorian = Calendar(identifier: .gregorian) let symbols = Date.FormatStyle.DateFieldCollection(year: .defaultDigits, month: .abbreviated, day: .defaultDigits) - for testLocale in testLocales { - DispatchQueue.global(qos: .userInitiated).async(group:group) { - let pattern = ICUPatternGenerator.localizedPattern(symbols: symbols, locale: testLocale, calendar: gregorian) - - let expected = expectations[testLocale.identifier] - XCTAssertEqual(pattern, expected) + await withDiscardingTaskGroup { group in + for testLocale in testLocales { + group.addTask { + let pattern = ICUPatternGenerator.localizedPattern(symbols: symbols, locale: testLocale, calendar: gregorian) + + let expected = expectations[testLocale.identifier] + #expect(pattern == expected) + } } } - - let result = group.wait(timeout: DispatchTime.now() + DispatchTimeInterval.seconds(105)) - XCTAssertEqual(result, .success) } - func test_roundtrip() { + @Test func roundtrip() throws { let date = Date.now - let style = Date.FormatStyle(date: .numeric, time: .shortened) + let style = Date.FormatStyle(date: .numeric, time: .shortened, locale: Locale(identifier: "en_US"), calendar: Calendar(identifier: .gregorian), timeZone: .gmt) let format = date.formatted(style) - let parsed = try? Date(format, strategy: style.parseStrategy) - XCTAssertNotNil(parsed) - XCTAssertEqual(parsed?.formatted(style), format) + let parsed = try Date(format, strategy: style.parseStrategy) + #expect(parsed.formatted(style) == format) } - func testLeadingDotSyntax() { - let date = Date.now - XCTAssertEqual(date.formatted(date: .long, time: .complete), date.formatted(Date.FormatStyle(date: .long, time: .complete))) - XCTAssertEqual( - date.formatted( - .dateTime - .day() - .month() - .year() - ), - date.formatted( - Date.FormatStyle() - .day() - .month() - .year() + @Test func leadingDotSyntax() async { + await usingCurrentInternationalizationPreferences { + let date = Date.now + #expect(date.formatted(date: .long, time: .complete) == date.formatted(Date.FormatStyle(date: .long, time: .complete))) + #expect( + date.formatted( + .dateTime + .day() + .month() + .year() + ) == + date.formatted( + Date.FormatStyle() + .day() + .month() + .year() + ) ) - ) + } } - func testDateFormatStyleIndividualFields() { + @Test func dateFormatStyleIndividualFields() { let date = Date(timeIntervalSince1970: 0) let style = Date.FormatStyle(date: nil, time: nil, locale: Locale(identifier: "en_US"), calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(abbreviation: "UTC")!, capitalizationContext: .unknown) - XCTAssertEqual(date.formatted(style.era(.abbreviated)), "AD") - XCTAssertEqual(date.formatted(style.era(.wide)), "Anno Domini") - XCTAssertEqual(date.formatted(style.era(.narrow)), "A") - - XCTAssertEqual(date.formatted(style.year(.defaultDigits)), "1970") - XCTAssertEqual(date.formatted(style.year(.twoDigits)), "70") - XCTAssertEqual(date.formatted(style.year(.padded(0))), "1970") - XCTAssertEqual(date.formatted(style.year(.padded(1))), "1970") - XCTAssertEqual(date.formatted(style.year(.padded(2))), "70") - XCTAssertEqual(date.formatted(style.year(.padded(3))), "1970") - XCTAssertEqual(date.formatted(style.year(.padded(999))), "0000001970") - - XCTAssertEqual(date.formatted(style.year(.relatedGregorian(minimumLength: 0))), "1970") - XCTAssertEqual(date.formatted(style.year(.relatedGregorian(minimumLength: 999))), "0000001970") - - XCTAssertEqual(date.formatted(style.year(.extended(minimumLength: 0))), "1970") - XCTAssertEqual(date.formatted(style.year(.extended(minimumLength: 999))), "0000001970") - - XCTAssertEqual(date.formatted(style.quarter(.oneDigit)), "1") - XCTAssertEqual(date.formatted(style.quarter(.twoDigits)), "01") - XCTAssertEqual(date.formatted(style.quarter(.abbreviated)), "Q1") - XCTAssertEqual(date.formatted(style.quarter(.wide)), "1st quarter") - XCTAssertEqual(date.formatted(style.quarter(.narrow)), "1") - - XCTAssertEqual(date.formatted(style.month(.defaultDigits)), "1") - XCTAssertEqual(date.formatted(style.month(.twoDigits)), "01") - XCTAssertEqual(date.formatted(style.month(.abbreviated)), "Jan") - XCTAssertEqual(date.formatted(style.month(.wide)), "January") - XCTAssertEqual(date.formatted(style.month(.narrow)), "J") - - XCTAssertEqual(date.formatted(style.week(.defaultDigits)), "1") - XCTAssertEqual(date.formatted(style.week(.twoDigits)), "01") - XCTAssertEqual(date.formatted(style.week(.weekOfMonth)), "1") - - XCTAssertEqual(date.formatted(style.day(.defaultDigits)), "1") - XCTAssertEqual(date.formatted(style.day(.twoDigits)), "01") - XCTAssertEqual(date.formatted(style.day(.ordinalOfDayInMonth)), "1") - - XCTAssertEqual(date.formatted(style.day(.julianModified(minimumLength: 0))), "2440588") - XCTAssertEqual(date.formatted(style.day(.julianModified(minimumLength: 999))), "0002440588") - - XCTAssertEqual(date.formatted(style.dayOfYear(.defaultDigits)), "1") - XCTAssertEqual(date.formatted(style.dayOfYear(.twoDigits)), "01") - XCTAssertEqual(date.formatted(style.dayOfYear(.threeDigits)), "001") - - XCTAssertEqual(date.formatted(style.weekday(.oneDigit)), "5") - XCTAssertEqual(date.formatted(style.weekday(.twoDigits)), "5") // This is an ICU bug - XCTAssertEqual(date.formatted(style.weekday(.abbreviated)), "Thu") - XCTAssertEqual(date.formatted(style.weekday(.wide)), "Thursday") - XCTAssertEqual(date.formatted(style.weekday(.narrow)), "T") - XCTAssertEqual(date.formatted(style.weekday(.short)), "Th") - - XCTAssertEqual(date.formatted(style.hour(.defaultDigits(amPM: .omitted))), "12") - XCTAssertEqual(date.formatted(style.hour(.defaultDigits(amPM: .narrow))), "12 a") - XCTAssertEqual(date.formatted(style.hour(.defaultDigits(amPM: .abbreviated))), "12 AM") - XCTAssertEqual(date.formatted(style.hour(.defaultDigits(amPM: .wide))), "12 AM") - - XCTAssertEqual(date.formatted(style.hour(.twoDigits(amPM: .omitted))), "12") - XCTAssertEqual(date.formatted(style.hour(.twoDigits(amPM: .narrow))), "12 a") - XCTAssertEqual(date.formatted(style.hour(.twoDigits(amPM: .abbreviated))), "12 AM") - XCTAssertEqual(date.formatted(style.hour(.twoDigits(amPM: .wide))), "12 AM") + #expect(date.formatted(style.era(.abbreviated)) == "AD") + #expect(date.formatted(style.era(.wide)) == "Anno Domini") + #expect(date.formatted(style.era(.narrow)) == "A") + + #expect(date.formatted(style.year(.defaultDigits)) == "1970") + #expect(date.formatted(style.year(.twoDigits)) == "70") + #expect(date.formatted(style.year(.padded(0))) == "1970") + #expect(date.formatted(style.year(.padded(1))) == "1970") + #expect(date.formatted(style.year(.padded(2))) == "70") + #expect(date.formatted(style.year(.padded(3))) == "1970") + #expect(date.formatted(style.year(.padded(999))) == "0000001970") + + #expect(date.formatted(style.year(.relatedGregorian(minimumLength: 0))) == "1970") + #expect(date.formatted(style.year(.relatedGregorian(minimumLength: 999))) == "0000001970") + + #expect(date.formatted(style.year(.extended(minimumLength: 0))) == "1970") + #expect(date.formatted(style.year(.extended(minimumLength: 999))) == "0000001970") + + #expect(date.formatted(style.quarter(.oneDigit)) == "1") + #expect(date.formatted(style.quarter(.twoDigits)) == "01") + #expect(date.formatted(style.quarter(.abbreviated)) == "Q1") + #expect(date.formatted(style.quarter(.wide)) == "1st quarter") + #expect(date.formatted(style.quarter(.narrow)) == "1") + + #expect(date.formatted(style.month(.defaultDigits)) == "1") + #expect(date.formatted(style.month(.twoDigits)) == "01") + #expect(date.formatted(style.month(.abbreviated)) == "Jan") + #expect(date.formatted(style.month(.wide)) == "January") + #expect(date.formatted(style.month(.narrow)) == "J") + + #expect(date.formatted(style.week(.defaultDigits)) == "1") + #expect(date.formatted(style.week(.twoDigits)) == "01") + #expect(date.formatted(style.week(.weekOfMonth)) == "1") + + #expect(date.formatted(style.day(.defaultDigits)) == "1") + #expect(date.formatted(style.day(.twoDigits)) == "01") + #expect(date.formatted(style.day(.ordinalOfDayInMonth)) == "1") + + #expect(date.formatted(style.day(.julianModified(minimumLength: 0))) == "2440588") + #expect(date.formatted(style.day(.julianModified(minimumLength: 999))) == "0002440588") + + #expect(date.formatted(style.dayOfYear(.defaultDigits)) == "1") + #expect(date.formatted(style.dayOfYear(.twoDigits)) == "01") + #expect(date.formatted(style.dayOfYear(.threeDigits)) == "001") + + #expect(date.formatted(style.weekday(.oneDigit)) == "5") + #expect(date.formatted(style.weekday(.twoDigits)) == "5") // This is an ICU bug + #expect(date.formatted(style.weekday(.abbreviated)) == "Thu") + #expect(date.formatted(style.weekday(.wide)) == "Thursday") + #expect(date.formatted(style.weekday(.narrow)) == "T") + #expect(date.formatted(style.weekday(.short)) == "Th") + + #expect(date.formatted(style.hour(.defaultDigits(amPM: .omitted))) == "12") + #expect(date.formatted(style.hour(.defaultDigits(amPM: .narrow))) == "12 a") + #expect(date.formatted(style.hour(.defaultDigits(amPM: .abbreviated))) == "12 AM") + #expect(date.formatted(style.hour(.defaultDigits(amPM: .wide))) == "12 AM") + + #expect(date.formatted(style.hour(.twoDigits(amPM: .omitted))) == "12") + #expect(date.formatted(style.hour(.twoDigits(amPM: .narrow))) == "12 a") + #expect(date.formatted(style.hour(.twoDigits(amPM: .abbreviated))) == "12 AM") + #expect(date.formatted(style.hour(.twoDigits(amPM: .wide))) == "12 AM") } - func testFormattingWithHourCycleOverrides() throws { + @Test func formattingWithHourCycleOverrides() throws { let date = Date(timeIntervalSince1970: 0) let enUS = "en_US" let esES = "es_ES" let style = Date.FormatStyle(date: .omitted, time: .standard, calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(identifier: "PST")!, capitalizationContext: .standalone) - XCTAssertEqual(date.formatted(style.locale(Locale.localeAsIfCurrent(name: enUS, overrides: .init()))), "4:00:00 PM") - XCTAssertEqual(date.formatted(style.locale(Locale.localeAsIfCurrent(name: enUS, overrides: .init(force12Hour: true)))), "4:00:00 PM") - XCTAssertEqual(date.formatted(style.locale(Locale.localeAsIfCurrent(name: enUS, overrides: .init(force24Hour: true)))), "16:00:00") + #expect(date.formatted(style.locale(Locale.localeAsIfCurrent(name: enUS, overrides: .init()))) == "4:00:00 PM") + #expect(date.formatted(style.locale(Locale.localeAsIfCurrent(name: enUS, overrides: .init(force12Hour: true)))) == "4:00:00 PM") + #expect(date.formatted(style.locale(Locale.localeAsIfCurrent(name: enUS, overrides: .init(force24Hour: true)))) == "16:00:00") - XCTAssertEqual(date.formatted(style.locale(Locale.localeAsIfCurrent(name: esES, overrides: .init()))), "16:00:00") - XCTAssertEqual(date.formatted(style.locale(Locale.localeAsIfCurrent(name: esES, overrides: .init(force12Hour: true)))), "4:00:00 p. m.") - XCTAssertEqual(date.formatted(style.locale(Locale.localeAsIfCurrent(name: esES, overrides: .init(force24Hour: true)))), "16:00:00") + #expect(date.formatted(style.locale(Locale.localeAsIfCurrent(name: esES, overrides: .init()))) == "16:00:00") + #expect(date.formatted(style.locale(Locale.localeAsIfCurrent(name: esES, overrides: .init(force12Hour: true)))) == "4:00:00 p. m.") + #expect(date.formatted(style.locale(Locale.localeAsIfCurrent(name: esES, overrides: .init(force24Hour: true)))) == "16:00:00") } - + #if !os(watchOS) // 99504292 - func testNSICUDateFormatterCache() throws { - guard Locale.autoupdatingCurrent.language.isEquivalent(to: Locale.Language(identifier: "en_US")) else { - throw XCTSkip("This test can only be run with the system set to the en_US language") + @Test func nsICUDateFormatterCache() async throws { + await usingCurrentInternationalizationPreferences { + // This test can only be run with the system set to the en_US language + var prefs = LocalePreferences() + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + CalendarCache.cache.reset() // The current calendar caches the current locale + + let fixedTimeZone = TimeZone(identifier: TimeZone.current.identifier)! + let fixedCalendar = Calendar(identifier: Calendar.current.identifier) + + let dateStyle = Date.FormatStyle.DateStyle.complete + let timeStyle = Date.FormatStyle.TimeStyle.standard + + let style = Date.FormatStyle(date: dateStyle, time: timeStyle) + let styleUsingFixedTimeZone = Date.FormatStyle(date: dateStyle, time: timeStyle, timeZone: fixedTimeZone) + let styleUsingFixedCalendar = Date.FormatStyle(date: dateStyle, time: timeStyle, calendar: fixedCalendar) + + #expect(ICUDateFormatter.cachedFormatter(for: style) === ICUDateFormatter.cachedFormatter(for: styleUsingFixedTimeZone)) + #expect(ICUDateFormatter.cachedFormatter(for: style) === ICUDateFormatter.cachedFormatter(for: styleUsingFixedCalendar)) } - - let fixedTimeZone = TimeZone(identifier: TimeZone.current.identifier)! - let fixedCalendar = Calendar(identifier: Calendar.current.identifier) - - let dateStyle = Date.FormatStyle.DateStyle.complete - let timeStyle = Date.FormatStyle.TimeStyle.standard - - let style = Date.FormatStyle(date: dateStyle, time: timeStyle) - let styleUsingFixedTimeZone = Date.FormatStyle(date: dateStyle, time: timeStyle, timeZone: fixedTimeZone) - let styleUsingFixedCalendar = Date.FormatStyle(date: dateStyle, time: timeStyle, calendar: fixedCalendar) - - XCTAssertTrue(ICUDateFormatter.cachedFormatter(for: style) === ICUDateFormatter.cachedFormatter(for: styleUsingFixedTimeZone)) - XCTAssertTrue(ICUDateFormatter.cachedFormatter(for: style) === ICUDateFormatter.cachedFormatter(for: styleUsingFixedCalendar)) } #endif // Only Foundation framework supports the DateStyle override #if FOUNDATION_FRAMEWORK - func testFormattingWithPrefsOverride() { + @Test func formattingWithPrefsOverride() throws { let date = Date(timeIntervalSince1970: 0) let enUS = "en_US" - func test(dateStyle: Date.FormatStyle.DateStyle, timeStyle: Date.FormatStyle.TimeStyle, dateFormatOverride: [Date.FormatStyle.DateStyle: String], expected: String, file: StaticString = #filePath, line: UInt = #line) { + func test(dateStyle: Date.FormatStyle.DateStyle, timeStyle: Date.FormatStyle.TimeStyle, dateFormatOverride: [Date.FormatStyle.DateStyle: String], expected: String, sourceLocation: SourceLocation = #_sourceLocation) throws { let locale = Locale.localeAsIfCurrent(name: enUS, overrides: .init(dateFormats: dateFormatOverride)) let style = Date.FormatStyle(date: dateStyle, time: timeStyle, locale: locale, calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(identifier: "PST")!, capitalizationContext: .standalone) let formatted = style.format(date) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) - guard let parsed = try? Date(formatted, strategy: style) else { - XCTFail("Parsing failed", file: file, line: line) - return - } + let parsed = try Date(formatted, strategy: style) let parsedStr = style.format(parsed) - XCTAssertEqual(parsedStr, expected, "round trip formatting failed", file: file, line: line) + #expect(parsedStr == expected, "round trip formatting failed", sourceLocation: sourceLocation) } let dateFormatOverride: [Date.FormatStyle.DateStyle: String] = [ @@ -358,70 +349,65 @@ final class DateFormatStyleTests : XCTestCase { let expectedShortTimeString = "4:00 PM" #endif - test(dateStyle: .omitted, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: "12/31/1969, \(expectedShortTimeString)") // Ignoring override since there's no match for the specific style - test(dateStyle: .abbreviated, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") - test(dateStyle: .numeric, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") - test(dateStyle: .long, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") - test(dateStyle: .complete, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") + try test(dateStyle: .omitted, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: "12/31/1969, \(expectedShortTimeString)") // Ignoring override since there's no match for the specific style + try test(dateStyle: .abbreviated, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") + try test(dateStyle: .numeric, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") + try test(dateStyle: .long, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") + try test(dateStyle: .complete, timeStyle: .omitted, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31") - test(dateStyle: .omitted, timeStyle: .standard, dateFormatOverride: dateFormatOverride, expected: expectTimeString) - test(dateStyle: .abbreviated, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31 at \(expectTimeString) PST") - test(dateStyle: .numeric, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31, \(expectTimeString) PST") - test(dateStyle: .long, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31 at \(expectTimeString) PST") - test(dateStyle: .complete, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31 at \(expectTimeString) PST") + try test(dateStyle: .omitted, timeStyle: .standard, dateFormatOverride: dateFormatOverride, expected: expectTimeString) + try test(dateStyle: .abbreviated, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31 at \(expectTimeString) PST") + try test(dateStyle: .numeric, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31, \(expectTimeString) PST") + try test(dateStyle: .long, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31 at \(expectTimeString) PST") + try test(dateStyle: .complete, timeStyle: .complete, dateFormatOverride: dateFormatOverride, expected: " 1969-Dec-31 at \(expectTimeString) PST") } #endif - func testFormattingWithPrefsOverride_firstweekday() { + @Test func formattingWithPrefsOverride_firstweekday() { let date = Date(timeIntervalSince1970: 0) let locale = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(firstWeekday: [.gregorian : 5])) let style = Date.FormatStyle(date: .complete, time: .omitted, locale: locale, calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(identifier: "PST")!, capitalizationContext: .standalone).week() - XCTAssertEqual(style.format(date), "Wednesday, December 31, 1969 (week: 53)") // First day is Thursday, so `date`, which is Wednesday, falls into the 53th week of the previous year. + #expect(style.format(date) == "Wednesday, December 31, 1969 (week: 53)") // First day is Thursday, so `date`, which is Wednesday, falls into the 53th week of the previous year. } #if FOUNDATION_FRAMEWORK - func testEncodingDecodingWithPrefsOverride() { + @Test func encodingDecodingWithPrefsOverride() throws { let date = Date(timeIntervalSince1970: 0) let dateFormatOverride: [Date.FormatStyle.DateStyle: String] = [ .complete: "'' yyyy-MMM-dd" ] let localeWithOverride = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(dateFormats: dateFormatOverride)) - let style = Date.FormatStyle(date: .complete, time: .omitted, locale: localeWithOverride, calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(identifier: "PST")!, capitalizationContext: .standalone) - XCTAssertEqual(style.format(date), " 1969-Dec-31") - - guard let encoded = try? JSONEncoder().encode(style) else { - XCTFail("Encoding Date.FormatStyle failed") - return - } + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = TimeZone(identifier: "PST")! + let style = Date.FormatStyle(date: .complete, time: .omitted, locale: localeWithOverride, calendar: calendar, timeZone: TimeZone(identifier: "PST")!, capitalizationContext: .standalone) + #expect(style.format(date) == " 1969-Dec-31") - guard var decoded = try? JSONDecoder().decode(Date.FormatStyle.self, from: encoded) else { - XCTFail("Decoding failed") - return - } + let encoded = try JSONEncoder().encode(style) + var decoded = try JSONDecoder().decode(Date.FormatStyle.self, from: encoded) - XCTAssertEqual(decoded._dateStyle, .complete) + #expect(decoded._dateStyle == .complete) decoded.locale = localeWithOverride - XCTAssertEqual(decoded.format(date), " 1969-Dec-31") + #expect(decoded.format(date) == " 1969-Dec-31") } #endif - func testConversationalDayPeriodsOverride() { - let middleOfNight = try! Date("2001-01-01T03:50:00Z", strategy: .iso8601) - let earlyMorning = try! Date("2001-01-01T06:50:00Z", strategy: .iso8601) - let morning = try! Date("2001-01-01T09:50:00Z", strategy: .iso8601) - let noon = try! Date("2001-01-01T12:50:00Z", strategy: .iso8601) - let afternoon = try! Date("2001-01-01T15:50:00Z", strategy: .iso8601) - let evening = try! Date("2001-01-01T21:50:00Z", strategy: .iso8601) + @Test func conversationalDayPeriodsOverride() throws { + let middleOfNight = try Date("2001-01-01T03:50:00Z", strategy: .iso8601) + let earlyMorning = try Date("2001-01-01T06:50:00Z", strategy: .iso8601) + let morning = try Date("2001-01-01T09:50:00Z", strategy: .iso8601) + let noon = try Date("2001-01-01T12:50:00Z", strategy: .iso8601) + let afternoon = try Date("2001-01-01T15:50:00Z", strategy: .iso8601) + let evening = try Date("2001-01-01T21:50:00Z", strategy: .iso8601) var locale: Locale var format: Date.FormatStyle - func verifyWithFormat(_ date: Date, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func verifyWithFormat(_ date: Date, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let fmt = format.locale(locale) let formatted = fmt.format(date) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } do { @@ -583,11 +569,11 @@ final class DateFormatStyleTests : XCTestCase { } } - func testRemovingFields() { + @Test func removingFields() { var format: Date.FormatStyle = .init(calendar: .init(identifier: .gregorian), timeZone: .gmt).locale(Locale(identifier: "en_US")) - func verifyWithFormat(_ date: Date, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func verifyWithFormat(_ date: Date, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let formatted = format.format(date) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } let date = Date(timeIntervalSince1970: 0) @@ -619,12 +605,13 @@ extension Sequence where Element == (String, AttributeScopes.FoundationAttribute } } -final class DateAttributedFormatStyleTests : XCTestCase { +@Suite("Attributed Date.FormatStyle") +private struct DateAttributedFormatStyleTests { var enUSLocale = Locale(identifier: "en_US") var gmtTimeZone = TimeZone(secondsFromGMT: 0)! typealias Segment = (String, AttributeScopes.FoundationAttributes.DateFieldAttribute.Field?) - func testAttributedFormatStyle() throws { + @Test func attributedFormatStyle() throws { let baseStyle = Date.FormatStyle(locale: enUSLocale, timeZone: gmtTimeZone) // dateFormatter.date(from: "2021-04-12 15:04:32")! let date = Date(timeIntervalSinceReferenceDate: 639932672.0) @@ -643,10 +630,11 @@ final class DateAttributedFormatStyleTests : XCTestCase { for (style, expectation) in expectations { let formatted = style.attributedStyle.format(date) - XCTAssertEqual(formatted, expectation.attributedString) + #expect(formatted == expectation.attributedString) } } - func testIndividualFields() throws { + + @Test func individualFields() throws { let baseStyle = Date.FormatStyle(locale: enUSLocale, timeZone: gmtTimeZone) // dateFormatter.date(from: "2021-04-12 15:04:32")! let date = Date(timeIntervalSinceReferenceDate: 639932672.0) @@ -668,31 +656,30 @@ final class DateAttributedFormatStyleTests : XCTestCase { for (style, expectation) in expectations { let formatted = style.attributedStyle.format(date) - XCTAssertEqual(formatted, expectation.attributedString) + #expect(formatted == expectation.attributedString) } } - func testCodable() throws { + @Test func codable() throws { let encoder = JSONEncoder() let decoder = JSONDecoder() let fields: [AttributeScopes.FoundationAttributes.DateFieldAttribute.Field] = [.era, .year, .relatedGregorianYear, .quarter, .month, .weekOfYear, .weekOfMonth, .weekday, .weekdayOrdinal, .day, .dayOfYear, .amPM, .hour, .minute, .second, .secondFraction, .timeZone] for field in fields { - let encoded = try? encoder.encode(field) - XCTAssertNotNil(encoded) + let encoded = try encoder.encode(field) - let decoded = try? decoder.decode(AttributeScopes.FoundationAttributes.DateFieldAttribute.Field.self, from: encoded!) - XCTAssertEqual(decoded, field) + let decoded = try decoder.decode(AttributeScopes.FoundationAttributes.DateFieldAttribute.Field.self, from: encoded) + #expect(decoded == field) } } - func testSettingLocale() throws { + @Test func settingLocale() throws { // dateFormatter.date(from: "2021-04-12 15:04:32")! let date = Date(timeIntervalSinceReferenceDate: 639932672.0) let zhTW = Locale(identifier: "zh_TW") - func test(_ attributedResult: AttributedString, _ expected: [Segment], file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(attributedResult, expected.attributedString, file: file, line: line) + func test(_ attributedResult: AttributedString, _ expected: [Segment], sourceLocation: SourceLocation = #_sourceLocation) { + #expect(attributedResult == expected.attributedString, sourceLocation: sourceLocation) } var format = Date.FormatStyle.dateTime @@ -706,14 +693,14 @@ final class DateAttributedFormatStyleTests : XCTestCase { } #if FOUNDATION_FRAMEWORK - func testFormattingWithPrefsOverride() { + @Test func formattingWithPrefsOverride() { let date = Date(timeIntervalSince1970: 0) let enUS = "en_US" - func test(dateStyle: Date.FormatStyle.DateStyle, timeStyle: Date.FormatStyle.TimeStyle, dateFormatOverride: [Date.FormatStyle.DateStyle: String], expected: [Segment], file: StaticString = #filePath, line: UInt = #line) { + func test(dateStyle: Date.FormatStyle.DateStyle, timeStyle: Date.FormatStyle.TimeStyle, dateFormatOverride: [Date.FormatStyle.DateStyle: String], expected: [Segment], sourceLocation: SourceLocation = #_sourceLocation) { let locale = Locale.localeAsIfCurrent(name: enUS, overrides: .init(dateFormats: dateFormatOverride)) let style = Date.FormatStyle(date: dateStyle, time: timeStyle, locale: locale, calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(identifier: "PST")!, capitalizationContext: .standalone).attributedStyle - XCTAssertEqual(style.format(date), expected.attributedString, file: file, line: line) + #expect(style.format(date) == expected.attributedString, sourceLocation: sourceLocation) } let dateFormatOverride: [Date.FormatStyle.DateStyle: String] = [ @@ -864,16 +851,17 @@ final class DateAttributedFormatStyleTests : XCTestCase { #endif } -final class DateVerbatimFormatStyleTests : XCTestCase { +@Suite("Verbatim Date.FormatStyle") +private struct DateVerbatimFormatStyleTests { var utcTimeZone = TimeZone(identifier: "UTC")! - func testFormats() throws { + @Test func formats() throws { // dateFormatter.date(from: "2021-01-23 14:51:20")! let date = Date(timeIntervalSinceReferenceDate: 633106280.0) - func verify(_ f: Date.FormatString, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ f: Date.FormatString, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let s = date.formatted(Date.VerbatimFormatStyle.verbatim(f, timeZone: utcTimeZone, calendar: Calendar(identifier: .gregorian))) - XCTAssertEqual(s, expected, file: file, line: line) + #expect(s == expected, sourceLocation: sourceLocation) } verify("\(month: .wide)", expected: "M01") verify("\(month: .narrow)", expected: "1") @@ -889,42 +877,41 @@ final class DateVerbatimFormatStyleTests : XCTestCase { verify("\(hour: .defaultDigits(clock: .twelveHour, hourCycle: .zeroBased)) heures et \(minute: .twoDigits) minutes", expected: "2 heures et 51 minutes") } - func testParseable() throws { + @Test func parseable() throws { // dateFormatter.date(from: "2021-01-23 14:51:20")! let date = Date(timeIntervalSinceReferenceDate: 633106280.0) - func verify(_ f: Date.FormatString, expectedString: String, expectedDate: Date, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ f: Date.FormatString, expectedString: String, expectedDate: Date, sourceLocation: SourceLocation = #_sourceLocation) throws { let style = Date.VerbatimFormatStyle.verbatim(f, timeZone: utcTimeZone, calendar: Calendar(identifier: .gregorian)) let s = date.formatted(style) - XCTAssertEqual(s, expectedString) + #expect(s == expectedString, sourceLocation: sourceLocation) - let d = try? Date(s, strategy: style.parseStrategy) - XCTAssertNotNil(d) - XCTAssertEqual(d, expectedDate) + let d = try Date(s, strategy: style.parseStrategy) + #expect(d == expectedDate, sourceLocation: sourceLocation) } // dateFormatter.date(from: "2021-01-23 00:00:00")! - verify("\(year: .twoDigits)_\(month: .defaultDigits)_\(day: .defaultDigits)", expectedString: "21_1_23", expectedDate: Date(timeIntervalSinceReferenceDate: 633052800.0)) + try verify("\(year: .twoDigits)_\(month: .defaultDigits)_\(day: .defaultDigits)", expectedString: "21_1_23", expectedDate: Date(timeIntervalSinceReferenceDate: 633052800.0)) // dateFormatter.date(from: "2021-01-23 02:00:00")! - verify("\(year: .defaultDigits)_\(month: .defaultDigits)_\(day: .defaultDigits) at \(hour: .defaultDigits(clock: .twelveHour, hourCycle: .zeroBased)) o'clock", expectedString: "2021_1_23 at 2 o'clock", expectedDate: Date(timeIntervalSinceReferenceDate: 633060000.0)) + try verify("\(year: .defaultDigits)_\(month: .defaultDigits)_\(day: .defaultDigits) at \(hour: .defaultDigits(clock: .twelveHour, hourCycle: .zeroBased)) o'clock", expectedString: "2021_1_23 at 2 o'clock", expectedDate: Date(timeIntervalSinceReferenceDate: 633060000.0)) // dateFormatter.date(from: "2021-01-23 14:00:00")! - verify("\(year: .defaultDigits)_\(month: .defaultDigits)_\(day: .defaultDigits) at \(hour: .defaultDigits(clock: .twentyFourHour, hourCycle: .zeroBased))", expectedString: "2021_1_23 at 14", expectedDate: Date(timeIntervalSinceReferenceDate: 633103200.0)) + try verify("\(year: .defaultDigits)_\(month: .defaultDigits)_\(day: .defaultDigits) at \(hour: .defaultDigits(clock: .twentyFourHour, hourCycle: .zeroBased))", expectedString: "2021_1_23 at 14", expectedDate: Date(timeIntervalSinceReferenceDate: 633103200.0)) } // Test parsing strings containing `abbreviated` names - func testNonLenientParsingAbbreviatedNames() throws { + @Test func nonLenientParsingAbbreviatedNames() throws { // dateFormatter.date(from: "1970-01-01 00:00:00")! let date = Date(timeIntervalSinceReferenceDate: -978307200.0) - func verify(_ f: Date.FormatString, localeID: String, calendarID: Calendar.Identifier, expectedString: String, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ f: Date.FormatString, localeID: String, calendarID: Calendar.Identifier, expectedString: String, sourceLocation: SourceLocation = #_sourceLocation) { let style = Date.VerbatimFormatStyle.verbatim(f, locale: Locale(identifier: localeID), timeZone: .gmt, calendar: Calendar(identifier: calendarID)) let s = date.formatted(style) - XCTAssertEqual(s, expectedString, file: file, line: line) + #expect(s == expectedString, sourceLocation: sourceLocation) var strategy = style.parseStrategy strategy.isLenient = false let parsed = try? Date(s, strategy: strategy) - XCTAssertEqual(parsed, date, file: file, line: line) + #expect(parsed == date, sourceLocation: sourceLocation) } // Era: formatting @@ -954,7 +941,7 @@ final class DateVerbatimFormatStyleTests : XCTestCase { #endif // FIXED_ICU_74_DAYPERIOD } - func test_95845290() throws { + @Test func issue95845290() throws { let formatString: Date.FormatString = "\(weekday: .abbreviated) \(month: .abbreviated) \(day: .twoDigits) \(hour: .twoDigits(clock: .twentyFourHour, hourCycle: .zeroBased)):\(minute: .twoDigits):\(second: .twoDigits) \(timeZone: .iso8601(.short)) \(year: .defaultDigits)" let enGB = Locale(identifier: "en_GB") let verbatim = Date.VerbatimFormatStyle(format: formatString, locale: enGB, timeZone: .init(secondsFromGMT: .zero)!, calendar: Calendar(identifier: .gregorian)) @@ -962,24 +949,24 @@ final class DateVerbatimFormatStyleTests : XCTestCase { do { let date = try Date("Sat Jun 18 16:10:00 +0000 2022", strategy: verbatim.parseStrategy) // dateFormatter.date(from: "2022-06-18 16:10:00")! - XCTAssertEqual(date, Date(timeIntervalSinceReferenceDate: 677261400.0)) + #expect(date == Date(timeIntervalSinceReferenceDate: 677261400.0)) } do { let date = try Date("Sat Jun 18 16:10:00 +0000 2022", strategy: .fixed(format: formatString, timeZone: .gmt, locale: enGB)) // dateFormatter.date(from: "2022-06-18 16:10:00")! - XCTAssertEqual(date, Date(timeIntervalSinceReferenceDate: 677261400.0)) + #expect(date == Date(timeIntervalSinceReferenceDate: 677261400.0)) } } typealias Segment = (String, AttributeScopes.FoundationAttributes.DateFieldAttribute.Field?) - func testAttributedString() throws { + @Test func attributedString() throws { // dateFormatter.date(from: "2021-01-23 14:51:20")! let date = Date(timeIntervalSinceReferenceDate: 633106280.0) - func verify(_ f: Date.FormatString, expected: [Segment], file: StaticString = #filePath, line: UInt = #line) { + func verify(_ f: Date.FormatString, expected: [Segment], file: StaticString = #filePath, sourceLocation: SourceLocation = #_sourceLocation) { let s = date.formatted(Date.VerbatimFormatStyle.verbatim(f, locale:Locale(identifier: "en_US"), timeZone: utcTimeZone, calendar: Calendar(identifier: .gregorian)).attributedStyle) - XCTAssertEqual(s, expected.attributedString, file: file, line: line) + #expect(s == expected.attributedString, sourceLocation: sourceLocation) } verify("\(year: .twoDigits)_\(month: .defaultDigits)_\(day: .defaultDigits)", expected: [("21", .year), @@ -997,21 +984,21 @@ final class DateVerbatimFormatStyleTests : XCTestCase { ("20", .second)]) } - func test_storedVar() { + @Test func storedVar() { _ = Date.FormatStyle.dateTime _ = Date.ISO8601FormatStyle.iso8601 } - func testAllIndividualFields() { + @Test func allIndividualFields() { // dateFormatter.date(from: "2021-01-23 14:51:20")! let date = Date(timeIntervalSinceReferenceDate: 633106280.0) let gregorian = Calendar(identifier: .gregorian) let enUS = Locale(identifier: "en_US") - func _verify(_ f: Date.FormatString, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func _verify(_ f: Date.FormatString, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let s = date.formatted(Date.VerbatimFormatStyle.verbatim(f, locale: enUS, timeZone: utcTimeZone, calendar: gregorian)) - XCTAssertEqual(s, expected, file: file, line: line) + #expect(s == expected, sourceLocation: sourceLocation) } _verify("\(era: .abbreviated)", expected: "AD") @@ -1066,20 +1053,21 @@ final class DateVerbatimFormatStyleTests : XCTestCase { } } -final class MatchConsumerAndSearcherTests : XCTestCase { +@Suite("Match Consumer and Searcher") +private struct MatchConsumerAndSearcherTests { let enUS = Locale(identifier: "en_US") let utcTimeZone = TimeZone(identifier: "UTC")! let gregorian = Calendar(identifier: .gregorian) - func _verifyUTF16String(_ string: String, matches format: Date.FormatString, in range: Range, expectedUpperBound: Int?, expectedDate: Date?, file: StaticString = #filePath, line: UInt = #line) { + func _verifyUTF16String(_ string: String, matches format: Date.FormatString, in range: Range, expectedUpperBound: Int?, expectedDate: Date?, sourceLocation: SourceLocation = #_sourceLocation) { let lower = string.index(string.startIndex, offsetBy: range.lowerBound) let upper = string.index(string.startIndex, offsetBy: range.upperBound) - _verifyString(string, matches: format, start: lower, in: lower.., expectedUpperBound: String.Index?, expectedDate: Date?, file: StaticString = #filePath, line: UInt = #line) { + func _verifyString(_ string: String, matches format: Date.FormatString, start: String.Index, in range: Range, expectedUpperBound: String.Index?, expectedDate: Date?, sourceLocation: SourceLocation = #_sourceLocation) { let style = Date.VerbatimFormatStyle(format: format, locale: enUS, timeZone: utcTimeZone, calendar: gregorian) let m = try? style.consuming(string, startingAt: start, in: range) @@ -1088,14 +1076,14 @@ final class MatchConsumerAndSearcherTests : XCTestCase { let upperBoundDescription = matchedUpper?.utf16Offset(in: string) let expectedUpperBoundDescription = expectedUpperBound?.utf16Offset(in: string) - XCTAssertEqual(matchedUpper, expectedUpperBound, "matched upperBound: \(String(describing: upperBoundDescription)), expected: \(String(describing: expectedUpperBoundDescription))", file: file, line: line) - XCTAssertEqual(match, expectedDate, file: file, line: line) + #expect(matchedUpper == expectedUpperBound, "matched upperBound: \(String(describing: upperBoundDescription)), expected: \(String(describing: expectedUpperBoundDescription))", sourceLocation: sourceLocation) + #expect(match == expectedDate, sourceLocation: sourceLocation) } - func testMatchFullRanges() { - func verify(_ string: String, matches format: Date.FormatString, expectedDate: TimeInterval?, file: StaticString = #filePath, line: UInt = #line) { + @Test func matchFullRanges() { + func verify(_ string: String, matches format: Date.FormatString, expectedDate: TimeInterval?, sourceLocation: SourceLocation = #_sourceLocation) { let targetDate: Date? = (expectedDate != nil) ? Date(timeIntervalSinceReferenceDate: expectedDate!) : nil - _verifyString(string, matches: format, start: string.startIndex, in: string.startIndex.., matches format: Date.FormatString, expectedDate: TimeInterval, file: StaticString = #filePath, line: UInt = #line) { - _verifyUTF16String(string, matches: format, in: range, expectedUpperBound: range.upperBound, expectedDate: Date(timeIntervalSinceReferenceDate: expectedDate), file: file, line: line) + @Test func matchPartialRangesWithinLegitimateString() { + func verify(_ string: String, in range: Range, matches format: Date.FormatString, expectedDate: TimeInterval, sourceLocation: SourceLocation = #_sourceLocation) { + _verifyUTF16String(string, matches: format, in: range, expectedUpperBound: range.upperBound, expectedDate: Date(timeIntervalSinceReferenceDate: expectedDate), sourceLocation: sourceLocation) } // Match only up to "2022-2-1" though "2022-2-12" is also a legitimate date @@ -1165,10 +1153,10 @@ final class MatchConsumerAndSearcherTests : XCTestCase { verify("2020218", in: 0..<6, matches: "\(year: .padded(4))\(month: .defaultDigits)\(day: .defaultDigits)", expectedDate: 602208000.0) // "2020-02-01 00:00:00" } - func testDateFormatStyleMatchRoundtrip() { + @Test func dateFormatStyleMatchRoundtrip() { // dateFormatter.date(from: "2021-01-23 14:51:20")! let date = Date(timeIntervalSinceReferenceDate: 633106280.0) - func verify(_ formatStyle: Date.FormatStyle, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ formatStyle: Date.FormatStyle, sourceLocation: SourceLocation = #_sourceLocation) { var format = formatStyle format.calendar = gregorian format.timeZone = utcTimeZone @@ -1184,9 +1172,9 @@ final class MatchConsumerAndSearcherTests : XCTestCase { let m = try? format.consuming(embeddedDate, startingAt: embeddedDate.startIndex, in: embeddedDate.startIndex..", file: file, line: line) - XCTAssertEqual(match, date, "cannot find match in: <\(embeddedDate)>", file: file, line: line) + let expectedUpperBound = embeddedDate.firstRange(of: formattedDate)?.upperBound + #expect(foundUpperBound == expectedUpperBound, "cannot find match in: <\(embeddedDate)>", sourceLocation: sourceLocation) + #expect(match == date, "cannot find match in: <\(embeddedDate)>", sourceLocation: sourceLocation) } @@ -1201,23 +1189,22 @@ final class MatchConsumerAndSearcherTests : XCTestCase { let m = try? format.consuming(embeddedDate, startingAt: start, in: embeddedDate.startIndex..", file: file, line: line) - XCTAssertEqual(match, date, "cannot find match in: <\(embeddedDate)>", file: file, line: line) + let expectedUpperBound = embeddedDate.firstRange(of: formattedDate)?.upperBound + #expect(foundUpperBound == expectedUpperBound, "cannot find match in: <\(embeddedDate)>", sourceLocation: sourceLocation) + #expect(match == date, "cannot find match in: <\(embeddedDate)>", sourceLocation: sourceLocation) } } - verify(Date.FormatStyle(date: .complete, time: .standard)) - verify(Date.FormatStyle(date: .complete, time: .complete)) + verify(Date.FormatStyle(date: .complete, time: .standard, locale: Locale(identifier: "zh_TW"))) verify(Date.FormatStyle(date: .complete, time: .complete, locale: Locale(identifier: "zh_TW"))) verify(Date.FormatStyle(date: .omitted, time: .complete, locale: enUS).year().month(.abbreviated).day(.twoDigits)) verify(Date.FormatStyle(date: .omitted, time: .complete).year().month(.wide).day(.twoDigits).locale(Locale(identifier: "zh_TW"))) } - func testMatchPartialRangesFromMiddle() { - func verify(_ string: String, matches format: Date.FormatString, expectedMatch: String, expectedDate: TimeInterval, file: StaticString = #filePath, line: UInt = #line) { - let occurrenceRange = string.range(of: expectedMatch)! - _verifyString(string, matches: format, start: occurrenceRange.lowerBound, in: string.startIndex.., includes expectedExcerpts: [String]..., - file: StaticString = #filePath, - line: UInt = #line) { + sourceLocation: SourceLocation = #_sourceLocation) { var style = style.locale(Locale(identifier: "en_US")) style.calendar = calendar style.timeZone = calendar.timeZone @@ -1403,8 +1389,7 @@ final class TestDateStyleDiscreteConformance : XCTestCase { }.lazy.map(\.output), contains: expectedExcerpts, "(lowerbound to upperbound)", - file: file, - line: line) + sourceLocation: sourceLocation) verify( sequence: style.evaluate(from: range.upperBound, to: range.lowerBound) { prev, bound in @@ -1418,8 +1403,7 @@ final class TestDateStyleDiscreteConformance : XCTestCase { .reversed() .map { $0.reversed() }, "(upperbound to lowerbound)", - file: file, - line: line) + sourceLocation: sourceLocation) } let now = date("2023-05-15 08:47:20Z") @@ -1557,52 +1541,41 @@ final class TestDateStyleDiscreteConformance : XCTestCase { ]) } - func testRegressions() throws { + @Test func regressions() throws { var style: Date.FormatStyle style = .init(date: .complete, time: .complete).secondFraction(.fractional(2)) style.timeZone = .gmt - XCTAssertLessThan(try XCTUnwrap(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 15538915.899999967))), Date(timeIntervalSinceReferenceDate: 15538915.899999967)) + #expect(try #require(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 15538915.899999967))) < Date(timeIntervalSinceReferenceDate: 15538915.899999967)) style = .init(date: .complete, time: .complete).secondFraction(.fractional(2)) style.timeZone = .gmt - XCTAssertNotNil(style.input(after: Date(timeIntervalSinceReferenceDate: 1205656112.7299998))) + #expect(style.input(after: Date(timeIntervalSinceReferenceDate: 1205656112.7299998)) != nil) } - func testRandomSamples() throws { - var style: Date.FormatStyle - - style = .init(date: .complete, time: .complete).secondFraction(.fractional(3)) - style.timeZone = .gmt - try verifyDiscreteFormatStyleConformance(style, samples: 100) - - style = .init(date: .complete, time: .complete).secondFraction(.fractional(2)) - style.timeZone = .gmt - try verifyDiscreteFormatStyleConformance(style, samples: 100) - - style = .init(date: .complete, time: .complete) - style.timeZone = .gmt - try verifyDiscreteFormatStyleConformance(style, samples: 100) - - style = .init().hour(.twoDigits(amPM: .abbreviated)).minute() - style.timeZone = .gmt - try verifyDiscreteFormatStyleConformance(style, samples: 100) - - style = .init(date: .omitted, time: .omitted).year().month() - style.timeZone = .gmt - try verifyDiscreteFormatStyleConformance(style, samples: 100) - - style = .init(date: .omitted, time: .omitted).year().month().era() + @Test(arguments: [ + Date.FormatStyle(date: .complete, time: .complete).secondFraction(.fractional(3)), + Date.FormatStyle(date: .complete, time: .complete).secondFraction(.fractional(2)), + Date.FormatStyle(date: .complete, time: .complete), + Date.FormatStyle().hour(.twoDigits(amPM: .abbreviated)).minute(), + Date.FormatStyle(date: .omitted, time: .omitted).year().month(), + Date.FormatStyle(date: .omitted, time: .omitted).year().month().era() + ]) + func randomSamples(style: Date.FormatStyle) throws { + var style = style + style.locale = Locale(identifier: "en_US") + style.calendar = Calendar(identifier: .gregorian) style.timeZone = .gmt try verifyDiscreteFormatStyleConformance(style, samples: 100) } } -final class TestDateVerbatimStyleDiscreteConformance : XCTestCase { +@Suite("Verbatime Date.FormatStyle Discrete Conformance") +private struct TestDateVerbatimStyleDiscreteConformance { let enUSLocale = Locale(identifier: "en_US") var calendar = Calendar(identifier: .gregorian) - override func setUp() { + init() { calendar.timeZone = TimeZone(abbreviation: "GMT")! } @@ -1610,30 +1583,29 @@ final class TestDateVerbatimStyleDiscreteConformance : XCTestCase { try! Date.ISO8601FormatStyle(dateSeparator: .dash, dateTimeSeparator: .space, timeZoneSeparator: .omitted, timeZone: .gmt).locale(enUSLocale).parse(string) } - func testExamples() throws { + @Test func examples() throws { let style = Date.VerbatimFormatStyle( format: "\(year: .extended())-\(month: .twoDigits)-\(day: .twoDigits) \(hour: .twoDigits(clock: .twentyFourHour, hourCycle: .oneBased)):\(minute: .twoDigits):\(second: .twoDigits)", timeZone: Calendar.current.timeZone, calendar: .current) let date = date("2021-05-05 16:00:00Z") - XCTAssertEqual(style.discreteInput(after: date.addingTimeInterval(1)), date.addingTimeInterval(2)) - XCTAssertEqual(style.discreteInput(before: date.addingTimeInterval(1)), date.addingTimeInterval(1).nextDown) - XCTAssertEqual(style.discreteInput(after: date.addingTimeInterval(0.5)), date.addingTimeInterval(1)) - XCTAssertEqual(style.discreteInput(before: date.addingTimeInterval(0.5)), date.addingTimeInterval(0).nextDown) - XCTAssertEqual(style.discreteInput(after: date.addingTimeInterval(0)), date.addingTimeInterval(1)) - XCTAssertEqual(style.discreteInput(before: date.addingTimeInterval(0)), date.addingTimeInterval(0).nextDown) - XCTAssertEqual(style.discreteInput(after: date.addingTimeInterval(-0.5)), date.addingTimeInterval(0)) - XCTAssertEqual(style.discreteInput(before: date.addingTimeInterval(-0.5)), date.addingTimeInterval(-1).nextDown) - XCTAssertEqual(style.discreteInput(after: date.addingTimeInterval(-1)), date.addingTimeInterval(0)) - XCTAssertEqual(style.discreteInput(before: date.addingTimeInterval(-1)), date.addingTimeInterval(-1).nextDown) + #expect(style.discreteInput(after: date.addingTimeInterval(1)) == date.addingTimeInterval(2)) + #expect(style.discreteInput(before: date.addingTimeInterval(1)) == date.addingTimeInterval(1).nextDown) + #expect(style.discreteInput(after: date.addingTimeInterval(0.5)) == date.addingTimeInterval(1)) + #expect(style.discreteInput(before: date.addingTimeInterval(0.5)) == date.addingTimeInterval(0).nextDown) + #expect(style.discreteInput(after: date.addingTimeInterval(0)) == date.addingTimeInterval(1)) + #expect(style.discreteInput(before: date.addingTimeInterval(0)) == date.addingTimeInterval(0).nextDown) + #expect(style.discreteInput(after: date.addingTimeInterval(-0.5)) == date.addingTimeInterval(0)) + #expect(style.discreteInput(before: date.addingTimeInterval(-0.5)) == date.addingTimeInterval(-1).nextDown) + #expect(style.discreteInput(after: date.addingTimeInterval(-1)) == date.addingTimeInterval(0)) + #expect(style.discreteInput(before: date.addingTimeInterval(-1)) == date.addingTimeInterval(-1).nextDown) } - func testCounting() { + @Test func counting() { func assertEvaluation(of style: Date.VerbatimFormatStyle, in range: ClosedRange, includes expectedExcerpts: [String]..., - file: StaticString = #filePath, - line: UInt = #line) { + sourceLocation: SourceLocation = #_sourceLocation) { var style = style.locale(enUSLocale) style.calendar = calendar style.timeZone = calendar.timeZone @@ -1648,8 +1620,7 @@ final class TestDateVerbatimStyleDiscreteConformance : XCTestCase { }.lazy.map(\.output), contains: expectedExcerpts, "(lowerbound to upperbound)", - file: file, - line: line) + sourceLocation: sourceLocation) verify( sequence: style.evaluate(from: range.upperBound, to: range.lowerBound) { prev, bound in @@ -1663,8 +1634,7 @@ final class TestDateVerbatimStyleDiscreteConformance : XCTestCase { .reversed() .map { $0.reversed() }, "(upperbound to lowerbound)", - file: file, - line: line) + sourceLocation: sourceLocation) } let now = date("2023-05-15 08:47:20Z") @@ -1774,7 +1744,7 @@ final class TestDateVerbatimStyleDiscreteConformance : XCTestCase { ]) assertEvaluation( - of: .init(format: "\(month: .abbreviated)''ss''' \(year: .extended()) \(era: .abbreviated) (week: \(week: .defaultDigits))", timeZone: calendar.timeZone, calendar: calendar), + of: Date.VerbatimFormatStyle(format: "\(month: .abbreviated)''ss''' \(year: .extended()) \(era: .abbreviated) (week: \(week: .defaultDigits))", timeZone: calendar.timeZone, calendar: calendar), in: now.addingTimeInterval(-8 * 31 * 24 * 3600)...now.addingTimeInterval(-4 * 31 * 24 * 3600), includes: [ "Sep''ss''' 2022 AD (week: 37)", diff --git a/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift index 6ed9c6129..a519a9f5f 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DateIntervalFormatStyleTests.swift @@ -210,16 +210,19 @@ private struct DateIntervalFormatStyleTests { prefs.languages = ["es-ES"] prefs.locale = "es_ES" LocaleCache.cache.resetCurrent(to: prefs) + CalendarCache.cache.reset() let formattedSpanish = range.formatted(.interval.locale(locale)) // Get a formatted result from en-US prefs.languages = ["en-US"] prefs.locale = "en_US" LocaleCache.cache.resetCurrent(to: prefs) + CalendarCache.cache.reset() let formattedEnglish = range.formatted(.interval.locale(locale)) // Reset to current preferences before any possibility of failing this test LocaleCache.cache.reset() + CalendarCache.cache.reset() // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. #expect(formattedSpanish != formattedEnglish) diff --git a/Tests/FoundationInternationalizationTests/Formatting/DateRelativeFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DateRelativeFormatStyleTests.swift index 2c3e82f14..eb80026d3 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DateRelativeFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DateRelativeFormatStyleTests.swift @@ -5,73 +5,68 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_intero -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class DateRelativeFormatStyleTests: XCTestCase { +@Suite("Date.RelativeFormatStyle") +private struct DateRelativeFormatStyleTests { let oneHour: TimeInterval = 60 * 60 let oneDay: TimeInterval = 60 * 60 * 24 let enUSLocale = Locale(identifier: "en_US") - var calendar = Calendar(identifier: .gregorian) + let calendar: Calendar - override func setUp() { - calendar.timeZone = TimeZone(abbreviation: "GMT")! + init() { + var c = Calendar(identifier: .gregorian) + c.timeZone = TimeZone(abbreviation: "GMT")! + self.calendar = c } - func testDefaultStyle() throws { + @Test func defaultStyle() throws { let style = Date.RelativeFormatStyle(locale: enUSLocale, calendar: calendar) - XCTAssertEqual(style.format(Date()), "in 0 seconds") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: oneHour)), "in 1 hour") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: oneHour * 2)), "in 2 hours") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: oneDay)), "in 1 day") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: oneDay * 2)), "in 2 days") + #expect(style.format(Date()) == "in 0 seconds") + #expect(style.format(Date(timeIntervalSinceNow: oneHour)) == "in 1 hour") + #expect(style.format(Date(timeIntervalSinceNow: oneHour * 2)) == "in 2 hours") + #expect(style.format(Date(timeIntervalSinceNow: oneDay)) == "in 1 day") + #expect(style.format(Date(timeIntervalSinceNow: oneDay * 2)) == "in 2 days") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: -oneHour)), "1 hour ago") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: -oneHour * 2)), "2 hours ago") + #expect(style.format(Date(timeIntervalSinceNow: -oneHour)) == "1 hour ago") + #expect(style.format(Date(timeIntervalSinceNow: -oneHour * 2)) == "2 hours ago") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: -oneHour * 1.5)), "2 hours ago") - XCTAssertEqual(style.format(Date(timeIntervalSinceNow: oneHour * 1.5)), "in 2 hours") + #expect(style.format(Date(timeIntervalSinceNow: -oneHour * 1.5)) == "2 hours ago") + #expect(style.format(Date(timeIntervalSinceNow: oneHour * 1.5)) == "in 2 hours") } - func testDateRelativeFormatConvenience() throws { + @Test func dateRelativeFormatConvenience() throws { let now = Date() let tomorrow = Date(timeInterval: oneDay + oneHour * 2, since: now) let future = Date(timeInterval: oneDay * 14 + oneHour * 3, since: now) let past = Date(timeInterval: -(oneDay * 14 + oneHour * 2), since: now) - XCTAssertEqual(past.formatted(.relative(presentation: .named)), Date.RelativeFormatStyle(presentation: .named, unitsStyle: .wide).format(past)) - XCTAssertEqual(tomorrow.formatted(.relative(presentation: .numeric)), Date.RelativeFormatStyle(presentation: .numeric, unitsStyle: .wide).format(tomorrow)) - XCTAssertEqual(tomorrow.formatted(Date.RelativeFormatStyle(presentation: .named)), Date.RelativeFormatStyle(presentation: .named).format(tomorrow)) + #expect(past.formatted(.relative(presentation: .named).locale(enUSLocale)) == Date.RelativeFormatStyle(presentation: .named, unitsStyle: .wide).locale(enUSLocale).format(past)) + #expect(tomorrow.formatted(.relative(presentation: .numeric).locale(enUSLocale)) == Date.RelativeFormatStyle(presentation: .numeric, unitsStyle: .wide).locale(enUSLocale).format(tomorrow)) + #expect(tomorrow.formatted(Date.RelativeFormatStyle(presentation: .named).locale(enUSLocale)) == Date.RelativeFormatStyle(presentation: .named).locale(enUSLocale).format(tomorrow)) - XCTAssertEqual(past.formatted(Date.RelativeFormatStyle(unitsStyle: .spellOut, capitalizationContext: .beginningOfSentence)), Date.RelativeFormatStyle(unitsStyle: .spellOut, capitalizationContext: .beginningOfSentence).format(past)) - XCTAssertEqual(future.formatted(.relative(presentation: .numeric, unitsStyle: .abbreviated)), Date.RelativeFormatStyle(unitsStyle: .abbreviated).format(future)) + #expect(past.formatted(Date.RelativeFormatStyle(unitsStyle: .spellOut, capitalizationContext: .beginningOfSentence).locale(enUSLocale)) == Date.RelativeFormatStyle(unitsStyle: .spellOut, capitalizationContext: .beginningOfSentence).locale(enUSLocale).format(past)) + #expect(future.formatted(.relative(presentation: .numeric, unitsStyle: .abbreviated).locale(enUSLocale)) == Date.RelativeFormatStyle(unitsStyle: .abbreviated).locale(enUSLocale).format(future)) } - func testNamedStyleRounding() throws { + @Test func namedStyleRounding() throws { let named = Date.RelativeFormatStyle(presentation: .named, locale: enUSLocale, calendar: calendar) - func _verifyStyle(_ dateValue: TimeInterval, relativeTo: TimeInterval, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func _verifyStyle(_ dateValue: TimeInterval, relativeTo: TimeInterval, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let date = Date(timeIntervalSinceReferenceDate: dateValue) let refDate = Date(timeIntervalSinceReferenceDate: relativeTo) let formatted = named._format(date, refDate: refDate) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } // Within a day @@ -186,14 +181,14 @@ final class DateRelativeFormatStyleTests: XCTestCase { _verifyStyle(725759999.0, relativeTo: 645019200.0, expected: "in 2 years") } - func testNumericStyleRounding() throws { + @Test func numericStyleRounding() throws { let numeric = Date.RelativeFormatStyle(presentation: .numeric, locale: enUSLocale, calendar: calendar) - func _verifyStyle(_ dateValue: TimeInterval, relativeTo: TimeInterval, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func _verifyStyle(_ dateValue: TimeInterval, relativeTo: TimeInterval, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let date = Date(timeIntervalSinceReferenceDate: dateValue) let refDate = Date(timeIntervalSinceReferenceDate: relativeTo) let formatted = numeric._format(date, refDate: refDate) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } // Within a day @@ -304,38 +299,43 @@ final class DateRelativeFormatStyleTests: XCTestCase { } - func testAutoupdatingCurrentChangesFormatResults() { - let locale = Locale.autoupdatingCurrent - let date = Date.now + 3600 - - // Get a formatted result from es-ES - var prefs = LocalePreferences() - prefs.languages = ["es-ES"] - prefs.locale = "es_ES" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedSpanish = date.formatted(.relative(presentation: .named).locale(locale)) - - // Get a formatted result from en-US - prefs.languages = ["en-US"] - prefs.locale = "en_US" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedEnglish = date.formatted(.relative(presentation: .named).locale(locale)) - - // Reset to current preferences before any possibility of failing this test - LocaleCache.cache.reset() - - // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. - XCTAssertNotEqual(formattedSpanish, formattedEnglish) + @Test func autoupdatingCurrentChangesFormatResults() async { + await usingCurrentInternationalizationPreferences { + let locale = Locale.autoupdatingCurrent + let date = Date.now + 3600 + + // Get a formatted result from es-ES + var prefs = LocalePreferences() + prefs.languages = ["es-ES"] + prefs.locale = "es_ES" + LocaleCache.cache.resetCurrent(to: prefs) + CalendarCache.cache.reset() + let formattedSpanish = date.formatted(.relative(presentation: .named).locale(locale)) + + // Get a formatted result from en-US + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + CalendarCache.cache.reset() + let formattedEnglish = date.formatted(.relative(presentation: .named).locale(locale)) + + // Reset to current preferences before any possibility of failing this test + LocaleCache.cache.reset() + CalendarCache.cache.reset() + + // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. + #expect(formattedSpanish != formattedEnglish) + } } - func testAllowedFieldsNamed() throws { + @Test func allowedFieldsNamed() throws { var named = Date.RelativeFormatStyle(presentation: .named, locale: enUSLocale, calendar: calendar) - func _verifyStyle(_ dateStr: String, relativeTo: String, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func _verifyStyle(_ dateStr: String, relativeTo: String, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let date = try! Date.ISO8601FormatStyle().parse(dateStr) let refDate = try! Date.ISO8601FormatStyle().parse(relativeTo) let formatted = named._format(date, refDate: refDate) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } named.allowedFields = [.year] @@ -361,14 +361,14 @@ final class DateRelativeFormatStyleTests: XCTestCase { _verifyStyle("2021-06-11T07:00:00Z", relativeTo: "2021-06-10T12:00:00Z", expected: "tomorrow") } - func testAllowedFieldsNumeric() throws { + @Test func allowedFieldsNumeric() throws { var named = Date.RelativeFormatStyle(presentation: .numeric, locale: enUSLocale, calendar: calendar) - func _verifyStyle(_ dateStr: String, relativeTo: String, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func _verifyStyle(_ dateStr: String, relativeTo: String, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let date = try! Date.ISO8601FormatStyle().parse(dateStr) let refDate = try! Date.ISO8601FormatStyle().parse(relativeTo) let formatted = named._format(date, refDate: refDate) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } named.allowedFields = [.year] @@ -393,11 +393,12 @@ final class DateRelativeFormatStyleTests: XCTestCase { // MARK: DiscreteFormatStyle conformance test -final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { +@Suite("Date.AnchoredRelativeFormatStyle Discrete Conformance") +private struct TestDateAnchoredRelativeDiscreteConformance { let enUSLocale = Locale(identifier: "en_US") var calendar = Calendar(identifier: .gregorian) - override func setUp() { + init() { calendar.timeZone = TimeZone(abbreviation: "GMT")! } @@ -405,65 +406,64 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { try! Date.ISO8601FormatStyle(dateSeparator: .dash, dateTimeSeparator: .space, timeZoneSeparator: .omitted, timeZone: .gmt).locale(enUSLocale).parse(string) } - func testExamples() throws { + @Test func examples() throws { var now = Date.now var style = Date.AnchoredRelativeFormatStyle(anchor: now) .locale(enUSLocale) - XCTAssertEqual(style.discreteInput(after: now.addingTimeInterval(1)), now.addingTimeInterval(1.5)) - XCTAssertEqual(style.discreteInput(before: now.addingTimeInterval(1)), now.addingTimeInterval(0.5).nextDown) - XCTAssertEqual(style.discreteInput(after: now.addingTimeInterval(0.5)), now.addingTimeInterval(1.5)) - XCTAssertEqual(style.discreteInput(before: now.addingTimeInterval(0.5)), now.addingTimeInterval(0.5).nextDown) - XCTAssertEqual(style.discreteInput(after: now.addingTimeInterval(0)), now.addingTimeInterval(0.5)) - XCTAssertEqual(style.discreteInput(before: now.addingTimeInterval(0)), now.addingTimeInterval(-0.5)) - XCTAssertEqual(style.discreteInput(after: now.addingTimeInterval(-0.5)), now.addingTimeInterval(-0.5).nextUp) - XCTAssertEqual(style.discreteInput(before: now.addingTimeInterval(-0.5)), now.addingTimeInterval(-1.5)) - XCTAssertEqual(style.discreteInput(after: now.addingTimeInterval(-1)), now.addingTimeInterval(-0.5).nextUp) - XCTAssertEqual(style.discreteInput(before: now.addingTimeInterval(-1)), now.addingTimeInterval(-1.5)) + #expect(style.discreteInput(after: now.addingTimeInterval(1)) == now.addingTimeInterval(1.5)) + #expect(style.discreteInput(before: now.addingTimeInterval(1)) == now.addingTimeInterval(0.5).nextDown) + #expect(style.discreteInput(after: now.addingTimeInterval(0.5)) == now.addingTimeInterval(1.5)) + #expect(style.discreteInput(before: now.addingTimeInterval(0.5)) == now.addingTimeInterval(0.5).nextDown) + #expect(style.discreteInput(after: now.addingTimeInterval(0)) == now.addingTimeInterval(0.5)) + #expect(style.discreteInput(before: now.addingTimeInterval(0)) == now.addingTimeInterval(-0.5)) + #expect(style.discreteInput(after: now.addingTimeInterval(-0.5)) == now.addingTimeInterval(-0.5).nextUp) + #expect(style.discreteInput(before: now.addingTimeInterval(-0.5)) == now.addingTimeInterval(-1.5)) + #expect(style.discreteInput(after: now.addingTimeInterval(-1)) == now.addingTimeInterval(-0.5).nextUp) + #expect(style.discreteInput(before: now.addingTimeInterval(-1)) == now.addingTimeInterval(-1.5)) now = date("2021-06-10 12:00:00Z") style.anchor = now - XCTAssertEqual(style.discreteInput(before: date("2021-06-10 11:58:30Z").addingTimeInterval(0.5).nextUp), date("2021-06-10 11:58:30Z").addingTimeInterval(0.5)) - XCTAssertEqual(style.discreteInput(after: date("2021-06-10 11:58:30Z").addingTimeInterval(0.5)), date("2021-06-10 11:58:30Z").addingTimeInterval(0.5).nextUp) - XCTAssertEqual(style.format(date("2021-06-10 11:58:30Z").addingTimeInterval(0.5).nextUp), "in 1 minute") - XCTAssertEqual(style.format(date("2021-06-10 11:58:30Z").addingTimeInterval(0.5)), "in 2 minutes") + #expect(style.discreteInput(before: date("2021-06-10 11:58:30Z").addingTimeInterval(0.5).nextUp) == date("2021-06-10 11:58:30Z").addingTimeInterval(0.5)) + #expect(style.discreteInput(after: date("2021-06-10 11:58:30Z").addingTimeInterval(0.5)) == date("2021-06-10 11:58:30Z").addingTimeInterval(0.5).nextUp) + #expect(style.format(date("2021-06-10 11:58:30Z").addingTimeInterval(0.5).nextUp) == "in 1 minute") + #expect(style.format(date("2021-06-10 11:58:30Z").addingTimeInterval(0.5)) == "in 2 minutes") - XCTAssertEqual(style.discreteInput(before: date("2021-06-10 11:57:30Z").addingTimeInterval(0.5).nextUp), date("2021-06-10 11:57:30Z").addingTimeInterval(0.5)) - XCTAssertEqual(style.discreteInput(after: date("2021-06-10 11:57:30Z").addingTimeInterval(0.5)), date("2021-06-10 11:57:30Z").addingTimeInterval(0.5).nextUp) - XCTAssertEqual(style.format(date("2021-06-10 11:57:30Z").addingTimeInterval(0.5).nextUp), "in 2 minutes") - XCTAssertEqual(style.format(date("2021-06-10 11:57:30Z").addingTimeInterval(0.5)), "in 3 minutes") + #expect(style.discreteInput(before: date("2021-06-10 11:57:30Z").addingTimeInterval(0.5).nextUp) == date("2021-06-10 11:57:30Z").addingTimeInterval(0.5)) + #expect(style.discreteInput(after: date("2021-06-10 11:57:30Z").addingTimeInterval(0.5)) == date("2021-06-10 11:57:30Z").addingTimeInterval(0.5).nextUp) + #expect(style.format(date("2021-06-10 11:57:30Z").addingTimeInterval(0.5).nextUp) == "in 2 minutes") + #expect(style.format(date("2021-06-10 11:57:30Z").addingTimeInterval(0.5)) == "in 3 minutes") - XCTAssertEqual(style.discreteInput(before: date("2021-06-10 11:56:30Z").addingTimeInterval(0.5).nextUp), date("2021-06-10 11:56:30Z").addingTimeInterval(0.5)) - XCTAssertEqual(style.discreteInput(after: date("2021-06-10 11:56:30Z").addingTimeInterval(0.5)), date("2021-06-10 11:56:30Z").addingTimeInterval(0.5).nextUp) - XCTAssertEqual(style.format(date("2021-06-10 11:56:30Z").addingTimeInterval(0.5).nextUp), "in 3 minutes") - XCTAssertEqual(style.format(date("2021-06-10 11:56:30Z").addingTimeInterval(0.5)), "in 4 minutes") + #expect(style.discreteInput(before: date("2021-06-10 11:56:30Z").addingTimeInterval(0.5).nextUp) == date("2021-06-10 11:56:30Z").addingTimeInterval(0.5)) + #expect(style.discreteInput(after: date("2021-06-10 11:56:30Z").addingTimeInterval(0.5)) == date("2021-06-10 11:56:30Z").addingTimeInterval(0.5).nextUp) + #expect(style.format(date("2021-06-10 11:56:30Z").addingTimeInterval(0.5).nextUp) == "in 3 minutes") + #expect(style.format(date("2021-06-10 11:56:30Z").addingTimeInterval(0.5)) == "in 4 minutes") - XCTAssertEqual(style.discreteInput(before: date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5)), date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5).nextDown) - XCTAssertEqual(style.discreteInput(after: date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5).nextDown), date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5)) - XCTAssertEqual(style.format(date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5).nextDown), "1 minute ago") - XCTAssertEqual(style.format(date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5)), "2 minutes ago") + #expect(style.discreteInput(before: date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5)) == date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5).nextDown) + #expect(style.discreteInput(after: date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5).nextDown) == date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5)) + #expect(style.format(date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5).nextDown) == "1 minute ago") + #expect(style.format(date("2021-06-10 12:01:30Z").addingTimeInterval(-0.5)) == "2 minutes ago") - XCTAssertEqual(style.discreteInput(before: date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5)), date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5).nextDown) - XCTAssertEqual(style.discreteInput(after: date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5).nextDown), date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5)) - XCTAssertEqual(style.format(date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5).nextDown), "2 minutes ago") - XCTAssertEqual(style.format(date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5)), "3 minutes ago") + #expect(style.discreteInput(before: date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5)) == date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5).nextDown) + #expect(style.discreteInput(after: date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5).nextDown) == date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5)) + #expect(style.format(date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5).nextDown) == "2 minutes ago") + #expect(style.format(date("2021-06-10 12:02:30Z").addingTimeInterval(-0.5)) == "3 minutes ago") - XCTAssertEqual(style.discreteInput(before: date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5)), date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5).nextDown) - XCTAssertEqual(style.discreteInput(after: date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5).nextDown), date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5)) - XCTAssertEqual(style.format(date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5).nextDown), "3 minutes ago") - XCTAssertEqual(style.format(date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5)), "4 minutes ago") + #expect(style.discreteInput(before: date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5)) == date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5).nextDown) + #expect(style.discreteInput(after: date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5).nextDown) == date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5)) + #expect(style.format(date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5).nextDown) == "3 minutes ago") + #expect(style.format(date("2021-06-10 12:03:30Z").addingTimeInterval(-0.5)) == "4 minutes ago") } - func testCounting() { + @Test func counting() { func assertEvaluation(of style: Date.AnchoredRelativeFormatStyle, in range: ClosedRange, includes expectedExcerpts: [String]..., - file: StaticString = #filePath, - line: UInt = #line) { + sourceLocation: SourceLocation = #_sourceLocation) { var style = style .locale(enUSLocale) style.calendar = calendar @@ -472,8 +472,7 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { sequence: style.evaluate(from: range.lowerBound, to: range.upperBound).lazy.map(\.output), contains: expectedExcerpts, "(lowerbound to upperbound)", - file: file, - line: line) + sourceLocation: sourceLocation) verify( sequence: style.evaluate(from: range.upperBound, to: range.lowerBound).lazy.map(\.output), @@ -481,11 +480,10 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { .reversed() .map { $0.reversed() }, "(upperbound to lowerbound)", - file: file, - line: line) + sourceLocation: sourceLocation) } - var now = date("2021-06-10 12:00:00Z") + var now = try date("2021-06-10 12:00:00Z") assertEvaluation( of: .init(anchor: now, presentation: .numeric, unitsStyle: .abbreviated), @@ -565,7 +563,7 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { "8 mo. ago", ]) - now = date("2023-05-15 08:47:20Z") + now = try date("2023-05-15 08:47:20Z") assertEvaluation( of: .init(anchor: now, allowedFields: [.month, .week], presentation: .numeric, unitsStyle: .abbreviated), @@ -685,7 +683,7 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { "8 mo. ago", ]) - now = date("2019-06-03 09:41:00Z") + now = try date("2019-06-03 09:41:00Z") assertEvaluation( of: .init(anchor: now, allowedFields: [.year, .month, .day, .hour, .minute], presentation: .named, unitsStyle: .wide), @@ -863,31 +861,31 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { ]) } - func testRegressions() throws { + @Test func regressions() throws { var style: Date.AnchoredRelativeFormatStyle var now: Date now = Date(timeIntervalSinceReferenceDate: 724685580.417914) style = .init(anchor: now, allowedFields: [.minute], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar - XCTAssertGreaterThan(try XCTUnwrap(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 12176601839.415668))), Date(timeIntervalSinceReferenceDate: 12176601839.415668)) + #expect(try #require(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 12176601839.415668))) > Date(timeIntervalSinceReferenceDate: 12176601839.415668)) now = Date(timeIntervalSinceReferenceDate: 724686086.706003) style = .init(anchor: now, allowedFields: [.minute], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar - XCTAssertLessThan(try XCTUnwrap(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: -24141834543.08099))), Date(timeIntervalSinceReferenceDate: -24141834543.08099)) + #expect(try #require(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: -24141834543.08099))) < Date(timeIntervalSinceReferenceDate: -24141834543.08099)) now = Date(timeIntervalSinceReferenceDate: 724688507.315708) style = .init(anchor: now, allowedFields: [.minute, .second], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar - XCTAssertGreaterThan(try XCTUnwrap(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 6013270816.926929))), Date(timeIntervalSinceReferenceDate: 6013270816.926929)) + #expect(try #require(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 6013270816.926929))) > Date(timeIntervalSinceReferenceDate: 6013270816.926929)) now = Date(timeIntervalSinceReferenceDate: 724689590.234374) style = .init(anchor: now, allowedFields: [.month, .week], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar print(style.format(Date(timeIntervalSinceReferenceDate: 722325435.4645464))) - XCTAssertGreaterThan(try XCTUnwrap(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 722325435.4645464))), Date(timeIntervalSinceReferenceDate: 722325435.4645464)) + #expect(try #require(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 722325435.4645464))) > Date(timeIntervalSinceReferenceDate: 722325435.4645464)) now = Date(timeIntervalSinceReferenceDate: 724701229.591328) style = .init(anchor: now, presentation: .numeric, unitsStyle: .abbreviated) @@ -895,7 +893,7 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { /// style.discreteInput(before: Date(timeIntervalSinceReferenceDate: -7256167.2374657225)) returned Date(timeIntervalSinceReferenceDate: -31622400.5), but /// Date(timeIntervalSinceReferenceDate: -31622400.49), which is a valid input, because style.input(after: Date(timeIntervalSinceReferenceDate: -31622400.5)) = Date(timeIntervalSinceReferenceDate: -31622400.49), /// already produces a different formatted output 'in 24 yr' compared to style.format(Date(timeIntervalSinceReferenceDate: -7256167.2374657225)), which is 'in 23 yr' - XCTAssertGreaterThanOrEqual(try XCTUnwrap(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: -7256167.2374657225))), Date(timeIntervalSinceReferenceDate: -31622400.49)) + #expect(try #require(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: -7256167.2374657225))) >= Date(timeIntervalSinceReferenceDate: -31622400.49)) now = Date(timeIntervalSinceReferenceDate: 724707086.436074) @@ -904,13 +902,13 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { /// style.discreteInput(after: Date(timeIntervalSinceReferenceDate: -728.7911686889214)) returned Date(timeIntervalSinceReferenceDate: 0.9360740142747098), but /// Date(timeIntervalSinceReferenceDate: 0.9260740142747098), which is a valid input, because style.input(before: Date(timeIntervalSinceReferenceDate: 0.9360740142747098)) = Date(timeIntervalSinceReferenceDate: 0.9260740142747098), /// already produces a different formatted output 'in 22 yr' compared to style.format(Date(timeIntervalSinceReferenceDate: -728.7911686889214)), which is 'in 23 yr' - XCTAssertLessThanOrEqual(try XCTUnwrap(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: -728.7911686889214))), Date(timeIntervalSinceReferenceDate: 0.9260740142747098)) + #expect(try #require(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: -728.7911686889214))) <= Date(timeIntervalSinceReferenceDate: 0.9260740142747098)) now = Date(timeIntervalSinceReferenceDate: 724707983.332096) style = .init(anchor: now, allowedFields: [.year, .month, .day, .hour, .minute], presentation: .named, unitsStyle: .wide) style.calendar = self.calendar - XCTAssertGreaterThan(try XCTUnwrap(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 722086631.228182))), Date(timeIntervalSinceReferenceDate: 722086631.228182)) + #expect(try #require(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 722086631.228182))) > Date(timeIntervalSinceReferenceDate: 722086631.228182)) now = Date(timeIntervalSinceReferenceDate: 725887340.112405) style = .init(anchor: now, allowedFields: [.month, .week, .day, .hour], presentation: .numeric, unitsStyle: .abbreviated) @@ -918,7 +916,7 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { /// style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 728224511.9413433)) returned Date(timeIntervalSinceReferenceDate: 727487999.6124048), but /// Date(timeIntervalSinceReferenceDate: 727487999.6224048), which is a valid input, because style.input(after: Date(timeIntervalSinceReferenceDate: 727487999.6124048)) = Date(timeIntervalSinceReferenceDate: 727487999.6224048), /// already produces a different formatted output '3 wk ago' compared to style.format(Date(timeIntervalSinceReferenceDate: 728224511.9413433)), which is '1 mo ago' - XCTAssertGreaterThanOrEqual(try XCTUnwrap(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 728224511.9413433))), Date(timeIntervalSinceReferenceDate: 727487999.6224048)) + #expect(try #require(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 728224511.9413433))) >= Date(timeIntervalSinceReferenceDate: 727487999.6224048)) now = Date(timeIntervalSinceReferenceDate: 725895690.016681) style = .init(anchor: now, presentation: .numeric, unitsStyle: .abbreviated) @@ -926,7 +924,7 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { /// style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 726561180.513301)) returned Date(timeIntervalSinceReferenceDate: 726364799.5166808), but /// Date(timeIntervalSinceReferenceDate: 726364799.5266808), which is a valid input, because style.input(after: Date(timeIntervalSinceReferenceDate: 726364799.5166808)) = Date(timeIntervalSinceReferenceDate: 726364799.5266808), /// already produces a different formatted output '6 days ago' compared to style.format(Date(timeIntervalSinceReferenceDate: 726561180.513301)), which is '1 wk ago' - XCTAssertGreaterThanOrEqual(try XCTUnwrap(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 726561180.513301))), Date(timeIntervalSinceReferenceDate: 726364799.5266808)) + #expect(try #require(style.discreteInput(before: Date(timeIntervalSinceReferenceDate: 726561180.513301))) >= Date(timeIntervalSinceReferenceDate: 726364799.5266808)) now = Date(timeIntervalSinceReferenceDate: 725903036.660503) style = .init(anchor: now, presentation: .numeric, unitsStyle: .abbreviated) @@ -934,11 +932,11 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { /// style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 725318223.6599436)) returned Date(timeIntervalSinceReferenceDate: 725414400.1605031), but /// Date(timeIntervalSinceReferenceDate: 725398549.919868), which is a valid input, because style.input(before: Date(timeIntervalSinceReferenceDate: 725414400.1605031)) = Date(timeIntervalSinceReferenceDate: 725414400.1505032), /// already produces a different formatted output 'in 6 days' compared to style.format(Date(timeIntervalSinceReferenceDate: 725318223.6599436)), which is 'in 1 wk' - XCTAssertLessThanOrEqual(try XCTUnwrap(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 725318223.6599436))), Date(timeIntervalSinceReferenceDate: 725398549.919868)) + #expect(try #require(style.discreteInput(after: Date(timeIntervalSinceReferenceDate: 725318223.6599436))) <= Date(timeIntervalSinceReferenceDate: 725398549.919868)) } #if FIXME_RANDOMIZED_SAMPLES_123465054 - func testRandomSamples() throws { + @Test func randomSamples() throws { var style: Date.AnchoredRelativeFormatStyle let now = Date.now @@ -946,30 +944,37 @@ final class TestDateAnchoredRelativeDiscreteConformance : XCTestCase { style = .init(anchor: now, presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) style = .init(anchor: now, allowedFields: [.minute], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) style = .init(anchor: now, allowedFields: [.minute, .second], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) style = .init(anchor: now, allowedFields: [.month], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) style = .init(anchor: now, allowedFields: [.month, .week], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) style = .init(anchor: now, allowedFields: [.month, .week, .day, .hour], presentation: .numeric, unitsStyle: .abbreviated) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) style = .init(anchor: now, allowedFields: [.year, .month, .day, .hour, .minute], presentation: .named, unitsStyle: .wide) style.calendar = self.calendar + style.locale = enUSLocale try verifyDiscreteFormatStyleConformance(style, samples: 100, message) } #endif diff --git a/Tests/FoundationInternationalizationTests/Formatting/DiscreteFormatStyleTestUtilities.swift b/Tests/FoundationInternationalizationTests/Formatting/DiscreteFormatStyleTestUtilities.swift index c6b9a88e4..038f8044f 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DiscreteFormatStyleTestUtilities.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DiscreteFormatStyleTestUtilities.swift @@ -10,16 +10,25 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationEssentials) @testable import FoundationEssentials +@testable import FoundationInternationalization +#else +@testable import Foundation #endif -#if canImport(FoundationInternationalization) -@testable import FoundationInternationalization +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(WASI) +import WASILibc +#elseif os(Windows) +import CRT #endif extension DiscreteFormatStyle where FormatInput : Comparable { @@ -91,8 +100,7 @@ func verify( sequence: some Sequence, contains expectedExcerpts: some Sequence>, _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) { var iterator = sequence.makeIterator() @@ -110,7 +118,7 @@ func verify( next = iterator.next() guard let next = next else { - XCTFail("Expected '\(first)' but found \(potentialMatches.map { "\($0)" }.joined(separator: ", ")) instead \(message())", file: file, line: line) + Issue.record("Expected '\(first)' but found \(potentialMatches.map { "\($0)" }.joined(separator: ", ")) instead \(message())", sourceLocation: sourceLocation) break } @@ -119,7 +127,7 @@ func verify( while let expected = expectedIterator.next() { let next = iterator.next() - XCTAssertEqual(next, expected, message(), file: file, line: line) + #expect(next == expected, Comment(rawValue: message()), sourceLocation: sourceLocation) if next != expected { return } @@ -141,8 +149,7 @@ func verifyDiscreteFormatStyleConformance( strict: Bool = false, samples: Int = 10000, _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) throws where Style.FormatOutput : Equatable, Style.FormatInput == Duration { try verifyDiscreteFormatStyleConformance( style, @@ -159,8 +166,7 @@ func verifyDiscreteFormatStyleConformance( max: .seconds(Int64.max), codeFormatter: { "Duration(secondsComponent: \($0.components.seconds), attosecondsComponent: \($0.components.attoseconds))" }, message(), - file: file, - line: line + sourceLocation: sourceLocation ) } @@ -181,8 +187,7 @@ func verifyDiscreteFormatStyleConformance( min: Date = Date(timeIntervalSinceReferenceDate: -2000 * 365 * 24 * 3600), max: Date = Date(timeIntervalSinceReferenceDate: 2000 * 365 * 24 * 3600), _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) throws where Style.FormatOutput : Equatable, Style.FormatInput == Date { try verifyDiscreteFormatStyleConformance( style, @@ -199,8 +204,7 @@ func verifyDiscreteFormatStyleConformance( max: max, codeFormatter: { "Date(timeIntervalSinceReferenceDate: \($0.timeIntervalSinceReferenceDate))" }, message(), - file: file, - line: line + sourceLocation: sourceLocation ) } @@ -223,8 +227,7 @@ func verifyDiscreteFormatStyleConformance( min: Date = Date(timeIntervalSinceReferenceDate: -2000 * 365 * 24 * 3600), max: Date = Date(timeIntervalSinceReferenceDate: 2000 * 365 * 24 * 3600), _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) throws { var style = style @@ -245,8 +248,7 @@ func verifyDiscreteFormatStyleConformance( max: now..( max: Style.FormatInput, codeFormatter: (Style.FormatInput) -> String, _ message: @autoclosure () -> String = "", - file: StaticString = #filePath, - line: UInt = #line + sourceLocation: SourceLocation = #_sourceLocation ) throws where Style.FormatOutput : Equatable, Style.FormatInput : Equatable { func _message(assertion: Assertion, before: Bool, inputValue: Style.FormatInput, expectedValue: Style.FormatInput?, note: String) -> String { let message = message() @@ -325,18 +325,18 @@ func verifyDiscreteFormatStyleConformance( } return prefix + """ - XCTAssert\(assertion.rawValue)(try XCTUnwrap(style.discreteInput(\(before ? "before" : "after"): \(codeFormatter(inputValue)))), \(expectedValue == nil ? "nil" : codeFormatter(expectedValue!))) + #expect\(assertion.rawValue)(try #require(style.discreteInput(\(before ? "before" : "after"): \(codeFormatter(inputValue)))), \(expectedValue == nil ? "nil" : codeFormatter(expectedValue!))) \(reason) """ } func nextUp(_ input: Style.FormatInput) throws -> Style.FormatInput { - try XCTUnwrap(style.input(after: input), "\(message().isEmpty ? "" : message() + "\n")XCTAssertNotNil(style.input(after: \(codeFormatter(input))))", file: file, line: line) + try #require(style.input(after: input), "\(message().isEmpty ? "" : message() + "\n")#expect(style.input(after: \(codeFormatter(input))) != nil)", sourceLocation: sourceLocation) } func nextDown(_ input: Style.FormatInput) throws -> Style.FormatInput { - try XCTUnwrap(style.input(before: input), "\(message().isEmpty ? "" : message() + "\n")XCTAssertNotNil(style.input(before: \(codeFormatter(input))))", file: file, line: line) + try #require(style.input(before: input), "\(message().isEmpty ? "" : message() + "\n")#expect(style.input(before: \(codeFormatter(input))) != nil)", sourceLocation: sourceLocation) } for _ in 0..( guard let inputAfter = style.discreteInput(after: input) else { // if `inputAfter` is `nil`, we should get the same formatted output everywhere between `input` and `max` - XCTAssertEqual(style.format(max), output, _message(assertion: .unequal, before: false, inputValue: input, expectedValue: nil, note: "invalid upper bound"), file: file, line: line) + #expect(style.format(max) == output, Comment(rawValue: _message(assertion: .unequal, before: false, inputValue: input, expectedValue: nil, note: "invalid upper bound")), sourceLocation: sourceLocation) return } // check for invalid ordering guard isLower(input, inputAfter) else { - XCTFail(_message(assertion: .greater, before: false, inputValue: input, expectedValue: input, note: "invalid ordering"), file: file, line: line) + Issue.record(Comment(rawValue: _message(assertion: .greater, before: false, inputValue: input, expectedValue: input, note: "invalid ordering")), sourceLocation: sourceLocation) return } guard let inputBefore = style.discreteInput(before: input) else { // if `inputBefore` is `nil`, we should get the same formatted output everywhere between `input` and `min` - XCTAssertEqual(style.format(min), output, _message(assertion: .unequal, before: true, inputValue: input, expectedValue: nil, note: "invalid lower bound"), file: file, line: line) + #expect(style.format(min) == output, Comment(rawValue: _message(assertion: .unequal, before: true, inputValue: input, expectedValue: nil, note: "invalid lower bound")), sourceLocation: sourceLocation) return } // check for invalid ordering guard isLower(inputBefore, input) else { - XCTFail(_message(assertion: .lower, before: true, inputValue: input, expectedValue: input, note: "invalid ordering"), file: file, line: line) + Issue.record(Comment(rawValue: _message(assertion: .lower, before: true, inputValue: input, expectedValue: input, note: "invalid ordering")), sourceLocation: sourceLocation) return } @@ -374,12 +374,12 @@ func verifyDiscreteFormatStyleConformance( for check in [lowerSampleBound] + (0..<10).map({ _ in randomInput((lowerSampleBound, upperSampleBound)) }) + [upperSampleBound] { if isLower(check, input) { guard style.format(check) == output else { - XCTFail(_message(assertion: .greaterEqual, before: true, inputValue: input, expectedValue: check, note: "invalid lower bound"), file: file, line: line) + Issue.record(Comment(rawValue: _message(assertion: .greaterEqual, before: true, inputValue: input, expectedValue: check, note: "invalid lower bound")), sourceLocation: sourceLocation) return } } else { guard style.format(check) == output else { - XCTFail(_message(assertion: .lowerEqual, before: false, inputValue: input, expectedValue: check, note: "invalid upper bound"), file: file, line: line) + Issue.record(Comment(rawValue: _message(assertion: .lowerEqual, before: false, inputValue: input, expectedValue: check, note: "invalid upper bound")), sourceLocation: sourceLocation) return } } @@ -389,12 +389,12 @@ func verifyDiscreteFormatStyleConformance( // if strict checking is enabled, we also check that the formatted output for `inputAfter` and `inputBefore` is indeed different from `format(input)` if strict { guard style.format(inputAfter) != output else { - XCTFail(_message(assertion: .greater, before: false, inputValue: input, expectedValue: inputAfter, note: "short upper bound (strict)"), file: file, line: line) + Issue.record(Comment(rawValue: _message(assertion: .greater, before: false, inputValue: input, expectedValue: inputAfter, note: "short upper bound (strict)")), sourceLocation: sourceLocation) return } guard style.format(inputBefore) != output else { - XCTFail(_message(assertion: .lower, before: true, inputValue: input, expectedValue: inputBefore, note: "short lower bound (strict)"), file: file, line: line) + Issue.record(Comment(rawValue: _message(assertion: .lower, before: true, inputValue: input, expectedValue: inputBefore, note: "short lower bound (strict)")), sourceLocation: sourceLocation) return } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift index d39c39439..b161b68bd 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DurationTimeFormatStyleTests.swift @@ -10,16 +10,12 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif @@ -32,14 +28,15 @@ extension Duration { } } -final class DurationToMeasurementAdditionTests : XCTestCase { - typealias Unit = Duration._UnitsFormatStyle.Unit - func assertEqualDurationUnitValues(_ duration: Duration, units: [Unit], rounding: FloatingPointRoundingRule = .toNearestOrEven, trailingFractionalLength: Int = .max, roundingIncrement: Double? = nil, expectation values: [Double], file: StaticString = #filePath, line: UInt = #line) { +@Suite("Duration to Measurement Addition") +private struct DurationToMeasurementAdditionTests { + typealias Unit = Duration.UnitsFormatStyle.Unit + func assertEqualDurationUnitValues(_ duration: Duration, units: [Unit], rounding: FloatingPointRoundingRule = .toNearestOrEven, trailingFractionalLength: Int = .max, roundingIncrement: Double? = nil, expectation values: [Double], sourceLocation: SourceLocation = #_sourceLocation) { let result = duration.valuesForUnits(units, trailingFractionalLength: trailingFractionalLength, smallestUnitRounding: rounding, roundingIncrement: roundingIncrement) - XCTAssertEqual(result, values, file: file, line: line) + #expect(result == values, sourceLocation: sourceLocation) } - func testDurationToMeasurements() { + @Test func durationToMeasurements() { let hmsn : [Unit] = [ .hours, .minutes, .seconds, .nanoseconds] assertEqualDurationUnitValues(.seconds(0), units: hmsn, expectation: [0, 0, 0, 0]) assertEqualDurationUnitValues(.seconds(35), units: hmsn, expectation: [0, 0, 35, 0]) @@ -66,10 +63,12 @@ final class DurationToMeasurementAdditionTests : XCTestCase { assertEqualDurationUnitValues(.seconds(3600 + 60 + 30), units: hm, trailingFractionalLength: 1, expectation: [1, 1.5]) } - func testDurationRounding() { - func test(_ duration: Duration, units: [Unit], trailingFractionalLength: Int = 0, _ tests: (rounding: FloatingPointRoundingRule, expected: [Double])..., file: StaticString = #filePath, line: UInt = #line) { + @Test func durationRounding() { + func test(_ duration: Duration, units: [Unit], trailingFractionalLength: Int = 0, _ tests: (rounding: FloatingPointRoundingRule, expected: [Double])..., sourceLocation: SourceLocation = #_sourceLocation) { for (i, (rounding, expected)) in tests.enumerated() { - assertEqualDurationUnitValues(duration, units: units, rounding: rounding, trailingFractionalLength: trailingFractionalLength, expectation: expected, file: file, line: line + UInt(i) + 1) + var loc = sourceLocation + loc.line += i + 1 + assertEqualDurationUnitValues(duration, units: units, rounding: rounding, trailingFractionalLength: trailingFractionalLength, expectation: expected, sourceLocation: loc) let equivalentRoundingForNegativeValue: FloatingPointRoundingRule switch rounding { @@ -81,7 +80,7 @@ final class DurationToMeasurementAdditionTests : XCTestCase { equivalentRoundingForNegativeValue = rounding } - assertEqualDurationUnitValues(.zero - duration, units: units, rounding: equivalentRoundingForNegativeValue, trailingFractionalLength: trailingFractionalLength, expectation: expected.map { -$0 }, file: file, line: line + UInt(i) + 1) + assertEqualDurationUnitValues(.zero - duration, units: units, rounding: equivalentRoundingForNegativeValue, trailingFractionalLength: trailingFractionalLength, expectation: expected.map { -$0 }, sourceLocation: loc) } } // [.nanoseconds] @@ -295,18 +294,19 @@ final class DurationToMeasurementAdditionTests : XCTestCase { } } -final class TestDurationTimeFormatStyle : XCTestCase { +@Suite("Duration.TimeFormatStyle") +private struct TestDurationTimeFormatStyle { let enUS = Locale(identifier: "en_US") - func assertFormattedWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration._TimeFormatStyle.Pattern, grouping: NumberFormatStyleConfiguration.Grouping? = nil, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func assertFormattedWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration._TimeFormatStyle.Pattern, grouping: NumberFormatStyleConfiguration.Grouping? = nil, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { var style = Duration.TimeFormatStyle(pattern: pattern).locale(enUS) if let grouping { style.grouping = grouping } - XCTAssertEqual(Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)).formatted(style), expected, file: file, line: line) + #expect(Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)).formatted(style) == expected, sourceLocation: sourceLocation) } - func testDurationPatternStyle() { + @Test func durationPatternStyle() { assertFormattedWithPattern(seconds: 3695, pattern: .hourMinute, expected: "1:02") assertFormattedWithPattern(seconds: 3695, pattern: .hourMinute(padHourToLength: 1, roundSeconds: .down), expected: "1:01") assertFormattedWithPattern(seconds: 3695, pattern: .hourMinuteSecond, expected: "1:01:35") @@ -318,20 +318,20 @@ final class TestDurationTimeFormatStyle : XCTestCase { assertFormattedWithPattern(seconds: 3695, milliseconds: 350, pattern: .minuteSecond(padMinuteToLength: 2, fractionalSecondsLength: 2), expected: "61:35.35") } - func testDurationPatternPadding() { + @Test func durationPatternPadding() { assertFormattedWithPattern(seconds: 3695, pattern: .hourMinute(padHourToLength: 2), expected: "01:02") assertFormattedWithPattern(seconds: 3695, pattern: .hourMinuteSecond(padHourToLength: 2), expected: "01:01:35") assertFormattedWithPattern(seconds: 3695, pattern: .hourMinuteSecond(padHourToLength: 2, fractionalSecondsLength: 2), expected: "01:01:35.00") assertFormattedWithPattern(seconds: 3695, milliseconds: 500, pattern: .hourMinuteSecond(padHourToLength: 2, fractionalSecondsLength: 2), expected: "01:01:35.50") } - func testDurationPatternGrouping() { + @Test func durationPatternGrouping() { assertFormattedWithPattern(seconds: 36950000, pattern: .hourMinute(padHourToLength: 2), grouping: nil, expected: "10,263:53") assertFormattedWithPattern(seconds: 36950000, pattern: .hourMinute(padHourToLength: 2), grouping: .automatic, expected: "10,263:53") assertFormattedWithPattern(seconds: 36950000, pattern: .hourMinute(padHourToLength: 2), grouping: .never, expected: "10263:53") } - func testNoFractionParts() { + @Test func noFractionParts() { // minutes, seconds @@ -401,7 +401,7 @@ final class TestDurationTimeFormatStyle : XCTestCase { assertFormattedWithPattern(seconds: 5399, milliseconds: 500, pattern: .hourMinute, expected: "1:30") } - func testShowFractionalSeconds() { + @Test func showFractionalSeconds() { // minutes, seconds @@ -445,7 +445,7 @@ final class TestDurationTimeFormatStyle : XCTestCase { assertFormattedWithPattern(seconds: 7199, milliseconds: 995, pattern: .hourMinuteSecond(padHourToLength: 2, fractionalSecondsLength: 2), expected: "02:00:00.00") } - func testNegativeValues() { + @Test func negativeValues() { assertFormattedWithPattern(seconds: 0, milliseconds: -499, pattern: .hourMinuteSecond, expected: "0:00:00") assertFormattedWithPattern(seconds: 0, milliseconds: -500, pattern: .hourMinuteSecond, expected: "0:00:00") assertFormattedWithPattern(seconds: 0, milliseconds: -501, pattern: .hourMinuteSecond, expected: "-0:00:01") @@ -484,16 +484,17 @@ extension Sequence where Element == DurationTimeAttributedStyleTests.Segment { } } -final class DurationTimeAttributedStyleTests : XCTestCase { +@Suite("Duration.TimeFormatStyle.Attributed") +private struct DurationTimeAttributedStyleTests { typealias Segment = (String, AttributeScopes.FoundationAttributes.DurationFieldAttribute.Field?) let enUS = Locale(identifier: "en_US") - func assertWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration._TimeFormatStyle.Pattern, expected: [Segment], locale: Locale = Locale(identifier: "en_US"), file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)).formatted(.time(pattern: pattern).locale(locale).attributed), expected.attributedString, file: file, line: line) + func assertWithPattern(seconds: Int, milliseconds: Int = 0, pattern: Duration._TimeFormatStyle.Pattern, expected: [Segment], locale: Locale = Locale(identifier: "en_US"), sourceLocation: SourceLocation = #_sourceLocation) { + #expect(Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)).formatted(.time(pattern: pattern).locale(locale).attributed) == expected.attributedString, sourceLocation: sourceLocation) } - func testAttributedStyle_enUS() { + @Test func attributedStyle_enUS() { assertWithPattern(seconds: 3695, pattern: .hourMinute, expected: [ ("1", .hours), (":", nil), @@ -555,111 +556,110 @@ final class DurationTimeAttributedStyleTests : XCTestCase { // MARK: DiscreteFormatStyle conformance test -final class TestDurationTimeDiscreteConformance : XCTestCase { - func testBasics() throws { - var style: Duration._TimeFormatStyle +@Suite("Duration.TimeFormatStyle Discrete Conformance") +private struct TestDurationTimeDiscreteConformance { + @Test func basics() throws { + var style: Duration.TimeFormatStyle style = .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: .down)).locale(Locale(identifier: "en_US")) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(2)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(1).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .zero.nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .zero.nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .zero) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1).nextDown) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .zero) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(2)) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(1).nextDown) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(500)) == .zero.nextDown) + #expect(style.discreteInput(after: .milliseconds(0)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(0)) == .zero.nextDown) + #expect(style.discreteInput(after: .milliseconds(-500)) == .zero) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(-1)) == .zero) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-1).nextDown) style = .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: .up)) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(0)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .zero) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .zero.nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .zero.nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .seconds(-1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-2)) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(0)) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .milliseconds(500)) == .zero) + #expect(style.discreteInput(after: .milliseconds(0)) == .zero.nextUp) + #expect(style.discreteInput(before: .milliseconds(0)) == .seconds(-1)) + #expect(style.discreteInput(after: .milliseconds(-500)) == .zero.nextUp) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1)) + #expect(style.discreteInput(after: .seconds(-1)) == .seconds(-1).nextUp) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-2)) style = .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: .towardZero)) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(2)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(1).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .seconds(-1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-2)) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(2)) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(1).nextDown) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(500)) == .seconds(-1)) + #expect(style.discreteInput(after: .milliseconds(0)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(0)) == .seconds(-1)) + #expect(style.discreteInput(after: .milliseconds(-500)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1)) + #expect(style.discreteInput(after: .seconds(-1)) == .seconds(-1).nextUp) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-2)) style = .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: .awayFromZero)) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(0)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .zero) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .zero.nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .zero.nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .zero) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1).nextDown) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .zero) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(0)) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .milliseconds(500)) == .zero) + #expect(style.discreteInput(after: .milliseconds(0)) == .zero.nextUp) + #expect(style.discreteInput(before: .milliseconds(0)) == .zero.nextDown) + #expect(style.discreteInput(after: .milliseconds(-500)) == .zero) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(-1)) == .zero) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-1).nextDown) style = .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: .toNearestOrAwayFromZero)) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .milliseconds(1500)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .milliseconds(500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .milliseconds(1500)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .milliseconds(500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .milliseconds(500)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .milliseconds(-500)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .milliseconds(-500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .milliseconds(-1500)) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .milliseconds(-500).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .milliseconds(-1500)) + #expect(style.discreteInput(after: .seconds(1)) == .milliseconds(1500)) + #expect(style.discreteInput(before: .seconds(1)) == .milliseconds(500).nextDown) + #expect(style.discreteInput(after: .milliseconds(500)) == .milliseconds(1500)) + #expect(style.discreteInput(before: .milliseconds(500)) == .milliseconds(500).nextDown) + #expect(style.discreteInput(after: .milliseconds(0)) == .milliseconds(500)) + #expect(style.discreteInput(before: .milliseconds(0)) == .milliseconds(-500)) + #expect(style.discreteInput(after: .milliseconds(-500)) == .milliseconds(-500).nextUp) + #expect(style.discreteInput(before: .milliseconds(-500)) == .milliseconds(-1500)) + #expect(style.discreteInput(after: .seconds(-1)) == .milliseconds(-500).nextUp) + #expect(style.discreteInput(before: .seconds(-1)) == .milliseconds(-1500)) style = .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: .toNearestOrEven)) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .milliseconds(1500)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .milliseconds(500)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .milliseconds(500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .milliseconds(-500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .milliseconds(500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .milliseconds(-500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .milliseconds(500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .milliseconds(-500).nextDown) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .milliseconds(-500)) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .milliseconds(-1500)) + #expect(style.discreteInput(after: .seconds(1)) == .milliseconds(1500)) + #expect(style.discreteInput(before: .seconds(1)) == .milliseconds(500)) + #expect(style.discreteInput(after: .milliseconds(500)) == .milliseconds(500).nextUp) + #expect(style.discreteInput(before: .milliseconds(500)) == .milliseconds(-500).nextDown) + #expect(style.discreteInput(after: .milliseconds(0)) == .milliseconds(500).nextUp) + #expect(style.discreteInput(before: .milliseconds(0)) == .milliseconds(-500).nextDown) + #expect(style.discreteInput(after: .milliseconds(-500)) == .milliseconds(500).nextUp) + #expect(style.discreteInput(before: .milliseconds(-500)) == .milliseconds(-500).nextDown) + #expect(style.discreteInput(after: .seconds(-1)) == .milliseconds(-500)) + #expect(style.discreteInput(before: .seconds(-1)) == .milliseconds(-1500)) } - func testRegressions() throws { + @Test func regressions() throws { var style: Duration._TimeFormatStyle style = .init(pattern: .hourMinute(padHourToLength: 0, roundSeconds: .toNearestOrAwayFromZero)) - XCTAssertLessThanOrEqual(try XCTUnwrap(style.discreteInput(after: Duration(secondsComponent: -8, attosecondsComponent: -531546586433266880))), Duration(secondsComponent: 30, attosecondsComponent: 0)) + #expect(try #require(style.discreteInput(after: Duration(secondsComponent: -8, attosecondsComponent: -531546586433266880))) <= Duration(secondsComponent: 30, attosecondsComponent: 0)) } - func testRandomSamples() throws { - let styles: [Duration._TimeFormatStyle] = [FloatingPointRoundingRule.up, .down, .towardZero, .awayFromZero, .toNearestOrAwayFromZero, .toNearestOrEven].flatMap { roundingRule in + @Test(arguments: + [FloatingPointRoundingRule.up, .down, .towardZero, .awayFromZero, .toNearestOrAwayFromZero, .toNearestOrEven].flatMap { roundingRule in [ - .init(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: roundingRule)), - .init(pattern: .minuteSecond(padMinuteToLength: 0, fractionalSecondsLength: 2, roundFractionalSeconds: roundingRule)), - .init(pattern: .hourMinute(padHourToLength: 0, roundSeconds: roundingRule)) + Duration.TimeFormatStyle(pattern: .minuteSecond(padMinuteToLength: 0, roundFractionalSeconds: roundingRule)), + Duration.TimeFormatStyle(pattern: .minuteSecond(padMinuteToLength: 0, fractionalSecondsLength: 2, roundFractionalSeconds: roundingRule)), + Duration.TimeFormatStyle(pattern: .hourMinute(padHourToLength: 0, roundSeconds: roundingRule)) ] } - - - for style in styles { - try verifyDiscreteFormatStyleConformance(style, samples: 100, "\(style)") - } + ) + func randomSamples(style: Duration.TimeFormatStyle) throws { + try verifyDiscreteFormatStyleConformance(style.locale(Locale(identifier: "en_US")), samples: 100, "\(style)") } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift index 1454e080f..c3bd0b1b6 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/DurationUnitsFormatStyleTests.swift @@ -10,15 +10,12 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) +@testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif @@ -27,60 +24,61 @@ let day = 86400 let hour = 3600 let minute = 60 -final class DurationUnitsFormatStyleTests : XCTestCase { +@Suite("Duration.UnitsFormatStyle") +private struct DurationUnitsFormatStyleTests { let enUS = Locale(identifier: "en_US") - func testDurationUnitsFormatStyleAPI() { + @Test func durationUnitsFormatStyleAPI() { let d1 = Duration.seconds(2 * 3600 + 43 * 60 + 24) // 2hr 43min 24s let d2 = Duration.seconds(43 * 60 + 24) // 43min 24s let d3 = Duration(seconds: 24, milliseconds: 490) let d4 = Duration.seconds(43 * 60 + 5) // 43min 5s let d0 = Duration.seconds(0) - XCTAssertEqual(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)), "2 hours, 43 minutes, 24 seconds") - XCTAssertEqual(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)), "43 minutes, 24 seconds") - XCTAssertEqual(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)), "24 seconds") - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)), "0 seconds") - - XCTAssertEqual(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)), "2 hours, 43 minutes, 24 seconds") - XCTAssertEqual(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)), "0 hours, 43 minutes, 24 seconds") - XCTAssertEqual(d3.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)), "0 hours, 0 minutes, 24 seconds") - XCTAssertEqual(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)), "0 hours, 0 minutes, 0 seconds") - - XCTAssertEqual(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)), "3 hr") - XCTAssertEqual(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)), "43 min") - XCTAssertEqual(d3.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)), "24 sec") - XCTAssertEqual(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)), "0 sec") - - XCTAssertEqual(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)), "2.72 hr") - XCTAssertEqual(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)), "43.40 min") - XCTAssertEqual(d3.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)), "24.49 sec") - XCTAssertEqual(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)), "0.00 sec") - - XCTAssertEqual(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS)), "00 hours, 43 minutes, 24 seconds") - XCTAssertEqual(d4.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS)), "00 hours, 43 minutes, 05 seconds") - XCTAssertEqual(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS)), "00 hours, 00 minutes, 00 seconds") - - XCTAssertEqual(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)), "02 hours, 43 minutes, 24 seconds") - XCTAssertEqual(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)), "43 minutes, 24 seconds") - XCTAssertEqual(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)), "24 seconds") - XCTAssertEqual(d4.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)), "43 minutes, 05 seconds") - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)), "00 seconds") - - XCTAssertEqual(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)), "02 hours, 43 minutes, 24.00 seconds") - XCTAssertEqual(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)), "43 minutes, 24.00 seconds") - XCTAssertEqual(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)), "24.49 seconds") - XCTAssertEqual(d4.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)), "43 minutes, 05.00 seconds") - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)), "00.00 seconds") - XCTAssertEqual(Duration(minutes: 43, seconds: 24, milliseconds: 490).formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)), "43 minutes, 24.49 seconds") + #expect(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)) == "2 hours, 43 minutes, 24 seconds") + #expect(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)) == "43 minutes, 24 seconds") + #expect(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)) == "24 seconds") + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS)) == "0 seconds") + + #expect(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)) == "2 hours, 43 minutes, 24 seconds") + #expect(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)) == "0 hours, 43 minutes, 24 seconds") + #expect(d3.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)) == "0 hours, 0 minutes, 24 seconds") + #expect(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS)) == "0 hours, 0 minutes, 0 seconds") + + #expect(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)) == "3 hr") + #expect(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)) == "43 min") + #expect(d3.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)) == "24 sec") + #expect(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1).locale(enUS)) == "0 sec") + + #expect(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)) == "2.72 hr") + #expect(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)) == "43.40 min") + #expect(d3.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)) == "24.49 sec") + #expect(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS)) == "0.00 sec") + + #expect(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS)) == "00 hours, 43 minutes, 24 seconds") + #expect(d4.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS)) == "00 hours, 43 minutes, 05 seconds") + #expect(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS)) == "00 hours, 00 minutes, 00 seconds") + + #expect(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)) == "02 hours, 43 minutes, 24 seconds") + #expect(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)) == "43 minutes, 24 seconds") + #expect(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)) == "24 seconds") + #expect(d4.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)) == "43 minutes, 05 seconds") + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2).locale(enUS)) == "00 seconds") + + #expect(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "02 hours, 43 minutes, 24.00 seconds") + #expect(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "43 minutes, 24.00 seconds") + #expect(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "24.49 seconds") + #expect(d4.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "43 minutes, 05.00 seconds") + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "00.00 seconds") + #expect(Duration(minutes: 43, seconds: 24, milliseconds: 490).formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, valueLength: 2, fractionalPart: .show(length: 2)).locale(enUS)) == "43 minutes, 24.49 seconds") } - func verify(seconds: Int, milliseconds: Int, allowedUnits: Set, fractionalSecondsLength: Int = 0, rounding: FloatingPointRoundingRule = .toNearestOrEven, increment: Double? = nil, expected: String, file: StaticString = #filePath, line: UInt = #line) { + func verify(seconds: Int, milliseconds: Int, allowedUnits: Set, fractionalSecondsLength: Int = 0, rounding: FloatingPointRoundingRule = .toNearestOrEven, increment: Double? = nil, expected: String, sourceLocation: SourceLocation = #_sourceLocation) { let d = Duration(seconds: Int64(seconds), milliseconds: Int64(milliseconds)) - XCTAssertEqual(d.formatted(.units(allowed: allowedUnits, zeroValueUnits: .show(length: 1), fractionalPart: .show(length: fractionalSecondsLength, rounded: rounding, increment: increment)).locale(enUS)), expected, file: file, line: line) + #expect(d.formatted(.units(allowed: allowedUnits, zeroValueUnits: .show(length: 1), fractionalPart: .show(length: fractionalSecondsLength, rounded: rounding, increment: increment)).locale(enUS)) == expected, sourceLocation: sourceLocation) } - func testNoFractionParts() { + @Test func noFractionParts() { // [.minutes, .seconds] @@ -165,7 +163,7 @@ final class DurationUnitsFormatStyleTests : XCTestCase { verify(seconds: 5399, milliseconds: 500, allowedUnits: [.hours, .minutes], expected: "1 hr, 30 min") } - func testShowFractionParts() { + @Test func showFractionParts() { // [.minutes, .seconds] verify(seconds: 0, milliseconds: 499, allowedUnits: [.minutes, .seconds], fractionalSecondsLength: 2, expected: "0 min, 0.50 sec") @@ -286,18 +284,18 @@ final class DurationUnitsFormatStyleTests : XCTestCase { verify(seconds: w3_d6_h23_m59_s30, milliseconds: 0, allowedUnits: [ .weeks, .days, .hours ], fractionalSecondsLength: 2, rounding: .down, increment: 0.5, expected: "3 wks, 6 days, 23.50 hr") } - func testDurationUnitsFormatStyleAPI_largerThanDay() { + @Test func durationUnitsFormatStyleAPI_largerThanDay() { var duration: Duration! let allowedUnits: Set = [.weeks, .days, .hours] func assertZeroValueUnit(_ zeroFormat: Duration._UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, - file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(duration.formatted(.units(allowed: allowedUnits, width: .wide, zeroValueUnits: zeroFormat).locale(enUS)), expected, file: file, line: line) + sourceLocation: SourceLocation = #_sourceLocation) { + #expect(duration.formatted(.units(allowed: allowedUnits, width: .wide, zeroValueUnits: zeroFormat).locale(enUS)) == expected, sourceLocation: sourceLocation) } func assertMaxUnitCount(_ maxUnitCount: Int, fractionalPart: Duration._UnitsFormatStyle.FractionalPartDisplayStrategy, _ expected: String, - file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(duration.formatted(.units(allowed: allowedUnits, width: .wide, maximumUnitCount: maxUnitCount, fractionalPart: fractionalPart).locale(enUS)), expected, file: file, line: line) + sourceLocation: SourceLocation = #_sourceLocation) { + #expect(duration.formatted(.units(allowed: allowedUnits, width: .wide, maximumUnitCount: maxUnitCount, fractionalPart: fractionalPart).locale(enUS)) == expected, sourceLocation: sourceLocation) } @@ -331,12 +329,11 @@ final class DurationUnitsFormatStyleTests : XCTestCase { assertMaxUnitCount(1, fractionalPart: .show(length: 2), "13.33 hours") } - func testZeroValueUnits() { + @Test func zeroValueUnits() { var duration: Duration var allowedUnits: Set - func test(_ zeroFormat: Duration._UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, - file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(duration.formatted(.units(allowed: allowedUnits, width: .wide, zeroValueUnits: zeroFormat).locale(enUS)), expected, file: file, line: line) + func test(_ zeroFormat: Duration._UnitsFormatStyle.ZeroValueUnitsDisplayStrategy, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(duration.formatted(.units(allowed: allowedUnits, width: .wide, zeroValueUnits: zeroFormat).locale(enUS)) == expected, sourceLocation: sourceLocation) } do { @@ -435,21 +432,21 @@ final class DurationUnitsFormatStyleTests : XCTestCase { func assertEqual(_ duration: Duration, allowedUnits: Set, maximumUnitCount: Int? = nil, roundSmallerParts: FloatingPointRoundingRule = .toNearestOrEven, trailingFractionalPartLength: Int = Int.max, roundingIncrement: Double? = nil, dropZeroUnits: Bool = false, expected: (units: [Duration._UnitsFormatStyle.Unit], values: [Double]), - file: StaticString = #filePath, line: UInt = #line) { + sourceLocation: SourceLocation = #_sourceLocation) { let (units, values) = Duration._UnitsFormatStyle.unitsToUse(duration: duration, allowedUnits: allowedUnits, maximumUnitCount: maximumUnitCount, roundSmallerParts: roundSmallerParts, trailingFractionalPartLength: trailingFractionalPartLength, roundingIncrement: roundingIncrement, dropZeroUnits: dropZeroUnits) guard values.count == expected.values.count else { - XCTFail("\(values) is not equal to \(expected.values)", file: file, line: line) + Issue.record("\(values) is not equal to \(expected.values)", sourceLocation: sourceLocation) return } - XCTAssertEqual(units, expected.units, file: file, line: line) + #expect(units == expected.units, sourceLocation: sourceLocation) for (idx, value) in values.enumerated() { - XCTAssertEqual(value, expected.values[idx], accuracy: 0.001, file: file, line: line) + #expect(abs(value - expected.values[idx]) <= 0.001, sourceLocation: sourceLocation) } } - func testMaximumUnitCounts() { + @Test func maximumUnitCounts() { let duration = Duration.seconds(2 * 3600 + 43 * 60 + 24) // 2hr 43min 24s assertEqual(duration, allowedUnits: [.hours, .minutes, .seconds] , maximumUnitCount: nil, expected: ([.hours, .minutes, .seconds], [2, 43, 24])) assertEqual(duration, allowedUnits: [.hours, .minutes], maximumUnitCount: nil, expected: ([.hours, .minutes], [2, 43.4])) @@ -459,7 +456,7 @@ final class DurationUnitsFormatStyleTests : XCTestCase { assertEqual(duration, allowedUnits: [.hours, .minutes, .seconds], maximumUnitCount: 2, expected: ([.hours, .minutes], [2, 43.4])) } - func testRounding() { + @Test func rounding() { let duration = Duration.seconds(2 * 3600 + 43 * 60 + 24) // 2hr 43min 24s assertEqual(duration, allowedUnits: [.hours, .minutes, .seconds] , roundSmallerParts: .down, expected: ([.hours, .minutes, .seconds], [2, 43, 24])) @@ -474,7 +471,7 @@ final class DurationUnitsFormatStyleTests : XCTestCase { assertEqual(duration, allowedUnits: [.hours] , roundSmallerParts: .down, trailingFractionalPartLength: 0, expected: ([.hours], [2])) } - func testZeroUnitsDisplay() { + @Test func zeroUnitsDisplay() { let duration = Duration.seconds(2 * 3600 + 24) // 2hr 0min 24s assertEqual(duration, allowedUnits: [.hours, .minutes, .seconds] , dropZeroUnits: false, expected: ([.hours, .minutes, .seconds], [2, 0, 24])) assertEqual(duration, allowedUnits: [.hours, .minutes, .seconds] , dropZeroUnits: true, expected: ([.hours, .seconds], [2, 24])) @@ -490,15 +487,15 @@ final class DurationUnitsFormatStyleTests : XCTestCase { assertEqual(duration0, allowedUnits: [.hours, .minutes] , dropZeroUnits: true, expected: ([], [])) } - func testLengthRangeExpression() { + @Test func lengthRangeExpression() { var duration: Duration var allowedUnits: Set - func verify(intLimits: R, fracLimits: R2, _ expected: String, file: StaticString = #filePath, line: UInt = #line) where R.Bound == Int, R2.Bound == Int { + func verify(intLimits: R, fracLimits: R2, _ expected: String, sourceLocation: SourceLocation = #_sourceLocation) where R.Bound == Int, R2.Bound == Int { let style = Duration._UnitsFormatStyle(allowedUnits: allowedUnits, width: .abbreviated, valueLengthLimits: intLimits, fractionalPart: .init(lengthLimits: fracLimits)).locale(enUS) let formatted = style.format(duration) - XCTAssertEqual(formatted, expected, file: file, line: line) + #expect(formatted == expected, sourceLocation: sourceLocation) } let oneThousandWithMaxPadding = "000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,001,000" @@ -874,7 +871,7 @@ final class DurationUnitsFormatStyleTests : XCTestCase { } - func testNegativeValues() { + @Test func negativeValues() { verify(seconds: 0, milliseconds: -499, allowedUnits: [.hours, .minutes, .seconds], expected: "0 hr, 0 min, 0 sec") verify(seconds: 0, milliseconds: -500, allowedUnits: [.hours, .minutes, .seconds], expected: "0 hr, 0 min, 0 sec") verify(seconds: 0, milliseconds: -501, allowedUnits: [.hours, .minutes, .seconds], expected: "-0 hr, 0 min, 1 sec") @@ -921,12 +918,13 @@ extension Sequence where Element == DurationUnitAttributedFormatStyleTests.Segme } } -final class DurationUnitAttributedFormatStyleTests : XCTestCase { +@Suite("Duration.UnitsFormatStyle.Attributed") +private struct DurationUnitAttributedFormatStyleTests { typealias Segment = (String, AttributeScopes.FoundationAttributes.DurationFieldAttribute.Field?, AttributeScopes.FoundationAttributes.MeasurementAttribute.Component?) let enUS = Locale(identifier: "en_US") let frFR = Locale(identifier: "fr_FR") - func testAttributedStyle_enUS() { + @Test func attributedStyle_enUS() { let d1 = Duration.seconds(2 * 3600 + 43 * 60 + 24) // 2hr 43min 24s let d2 = Duration.seconds(43 * 60 + 24) // 43min 24s let d3 = Duration.seconds(24.490) // 24s 490ms @@ -937,7 +935,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { // Default configuration -- hide the field when its value is 0 - XCTAssertEqual(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed), + #expect(d1.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed) == [("2", .hours, .value), (" ", .hours, nil), ("hours", .hours, .unit), @@ -951,7 +949,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed), + #expect(d2.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed) == [("43", .minutes, .value), (" ", .minutes, nil), ("minutes", .minutes, .unit), @@ -961,19 +959,19 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed), + #expect(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed) == [("24", .seconds, .value), (" ", .seconds, nil), ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed), + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(enUS).attributed) == [("0", .seconds, .value), (" ", .seconds, nil), ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d4.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide).locale(enUS).attributed), + #expect(d4.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide).locale(enUS).attributed) == [("3", .weeks, .value), (" ", .weeks, nil), ("weeks", .weeks, .unit), @@ -983,7 +981,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { ("hours", .hours, .unit), ].attributedString) - XCTAssertEqual(d5.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide).locale(enUS).attributed), + #expect(d5.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide).locale(enUS).attributed) == [("3", .weeks, .value), (" ", .weeks, nil), ("weeks", .weeks, .unit), @@ -999,7 +997,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { // Always show zero value units - XCTAssertEqual(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed), + #expect(d2.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed) == [("0", .hours, .value), (" ", .hours, nil), ("hours", .hours, .unit), @@ -1013,7 +1011,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed), + #expect(d3.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed) == [("0", .hours, .value), (" ", .hours, nil), ("hours", .hours, .unit), @@ -1027,7 +1025,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed), + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed) == [("0", .hours, .value), (" ", .hours, nil), ("hours", .hours, .unit), @@ -1041,7 +1039,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { ("seconds", .seconds, .unit), ].attributedString) - XCTAssertEqual(d4.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed), + #expect(d4.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide, zeroValueUnits: .show(length: 1)).locale(enUS).attributed) == [("3", .weeks, .value), (" ", .weeks, nil), ("weeks", .weeks, .unit), @@ -1058,7 +1056,7 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { // Always show zero value units padded - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS).attributed), + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide, zeroValueUnits: .show(length: 2)).locale(enUS).attributed) == [("00", .hours, .value), (" ", .hours, nil), ("hours", .hours, .unit), @@ -1074,37 +1072,37 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { // Test fractional parts - XCTAssertEqual(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).attributed.locale(enUS)), + #expect(d1.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).attributed.locale(enUS)) == [("2.72", .hours, .value), (" ", .hours, nil), ("hr", .hours, .unit), ].attributedString) - XCTAssertEqual(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).attributed.locale(enUS)), + #expect(d0.formatted(.units(allowed:[.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 1, fractionalPart: .show(length: 2)).attributed.locale(enUS)) == [("0.00", .seconds, .value), (" ", .seconds, nil), ("sec", .seconds, .unit), ].attributedString) - XCTAssertEqual(d4.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS).attributed), + #expect(d4.formatted(.units(allowed: [.weeks, .days, .hours], width: .wide, maximumUnitCount: 1, fractionalPart: .show(length: 2)).locale(enUS).attributed) == [("3.08", .weeks, .value), (" ", .weeks, nil), ("weeks", .weeks, .unit), ].attributedString) } - func testAttributedStyle_frFR() { + @Test func testAttributedStyle_frFR() { let d1 = Duration.seconds(2 * 3600 + 43 * 60 + 24) // 2hr 43min 24s let d0 = Duration.seconds(0) let nbsp = " " - XCTAssertEqual(d1.formatted(.units(allowed: [.seconds], width: .wide).locale(frFR).attributed), + #expect(d1.formatted(.units(allowed: [.seconds], width: .wide).locale(frFR).attributed) == [("9 804", .seconds, .value), (nbsp, .seconds, nil), ("secondes", .seconds, .unit), ].attributedString) - XCTAssertEqual(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(frFR).attributed), + #expect(d0.formatted(.units(allowed: [.hours, .minutes, .seconds], width: .wide).locale(frFR).attributed) == [("0", .seconds, .value), (nbsp, .seconds, nil), ("seconde", .seconds, .unit), @@ -1115,96 +1113,96 @@ final class DurationUnitAttributedFormatStyleTests : XCTestCase { // MARK: DiscreteFormatStyle conformance test -final class TestDurationUnitsDiscreteConformance : XCTestCase { - func testBasics() throws { - var style: Duration._UnitsFormatStyle +@Suite("Duration.UnitsFormatStyle Discrete Conformance") +private struct TestDurationUnitsDiscreteConformance { + @Test func basics() throws { + var style: Duration.UnitsFormatStyle style = .units(fractionalPart: .hide(rounded: .down)).locale(Locale(identifier: "en_US")) - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(2)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(1).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .zero.nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .zero.nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .zero) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1).nextDown) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .zero) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(2)) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(1).nextDown) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(500)) == .zero.nextDown) + #expect(style.discreteInput(after: .milliseconds(0)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(0)) == .zero.nextDown) + #expect(style.discreteInput(after: .milliseconds(-500)) == .zero) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(-1)) == .zero) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-1).nextDown) style.fractionalPartDisplay.roundingRule = .up - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(0)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .zero) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .zero.nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .zero.nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .seconds(-1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-2)) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(0)) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .milliseconds(500)) == .zero) + #expect(style.discreteInput(after: .milliseconds(0)) == .zero.nextUp) + #expect(style.discreteInput(before: .milliseconds(0)) == .seconds(-1)) + #expect(style.discreteInput(after: .milliseconds(-500)) == .zero.nextUp) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1)) + #expect(style.discreteInput(after: .seconds(-1)) == .seconds(-1).nextUp) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-2)) style.fractionalPartDisplay.roundingRule = .towardZero - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(2)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(1).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .seconds(1)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1)) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .seconds(-1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-2)) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(2)) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(1).nextDown) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(500)) == .seconds(-1)) + #expect(style.discreteInput(after: .milliseconds(0)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(0)) == .seconds(-1)) + #expect(style.discreteInput(after: .milliseconds(-500)) == .seconds(1)) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1)) + #expect(style.discreteInput(after: .seconds(-1)) == .seconds(-1).nextUp) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-2)) style.fractionalPartDisplay.roundingRule = .awayFromZero - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .seconds(0)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .seconds(1).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .zero) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .zero.nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .zero.nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .zero) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .seconds(-1).nextDown) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .zero) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(1)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .seconds(1)) == .seconds(0)) + #expect(style.discreteInput(after: .milliseconds(500)) == .seconds(1).nextUp) + #expect(style.discreteInput(before: .milliseconds(500)) == .zero) + #expect(style.discreteInput(after: .milliseconds(0)) == .zero.nextUp) + #expect(style.discreteInput(before: .milliseconds(0)) == .zero.nextDown) + #expect(style.discreteInput(after: .milliseconds(-500)) == .zero) + #expect(style.discreteInput(before: .milliseconds(-500)) == .seconds(-1).nextDown) + #expect(style.discreteInput(after: .seconds(-1)) == .zero) + #expect(style.discreteInput(before: .seconds(-1)) == .seconds(-1).nextDown) style.fractionalPartDisplay.roundingRule = .toNearestOrAwayFromZero - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .milliseconds(1500)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .milliseconds(500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .milliseconds(1500)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .milliseconds(500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .milliseconds(500)) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .milliseconds(-500)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .milliseconds(-500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .milliseconds(-1500)) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .milliseconds(-500).nextUp) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .milliseconds(-1500)) + #expect(style.discreteInput(after: .seconds(1)) == .milliseconds(1500)) + #expect(style.discreteInput(before: .seconds(1)) == .milliseconds(500).nextDown) + #expect(style.discreteInput(after: .milliseconds(500)) == .milliseconds(1500)) + #expect(style.discreteInput(before: .milliseconds(500)) == .milliseconds(500).nextDown) + #expect(style.discreteInput(after: .milliseconds(0)) == .milliseconds(500)) + #expect(style.discreteInput(before: .milliseconds(0)) == .milliseconds(-500)) + #expect(style.discreteInput(after: .milliseconds(-500)) == .milliseconds(-500).nextUp) + #expect(style.discreteInput(before: .milliseconds(-500)) == .milliseconds(-1500)) + #expect(style.discreteInput(after: .seconds(-1)) == .milliseconds(-500).nextUp) + #expect(style.discreteInput(before: .seconds(-1)) == .milliseconds(-1500)) style.fractionalPartDisplay.roundingRule = .toNearestOrEven - XCTAssertEqual(style.discreteInput(after: .seconds(1)), .milliseconds(1500)) - XCTAssertEqual(style.discreteInput(before: .seconds(1)), .milliseconds(500)) - XCTAssertEqual(style.discreteInput(after: .milliseconds(500)), .milliseconds(500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(500)), .milliseconds(-500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(0)), .milliseconds(500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(0)), .milliseconds(-500).nextDown) - XCTAssertEqual(style.discreteInput(after: .milliseconds(-500)), .milliseconds(500).nextUp) - XCTAssertEqual(style.discreteInput(before: .milliseconds(-500)), .milliseconds(-500).nextDown) - XCTAssertEqual(style.discreteInput(after: .seconds(-1)), .milliseconds(-500)) - XCTAssertEqual(style.discreteInput(before: .seconds(-1)), .milliseconds(-1500)) + #expect(style.discreteInput(after: .seconds(1)) == .milliseconds(1500)) + #expect(style.discreteInput(before: .seconds(1)) == .milliseconds(500)) + #expect(style.discreteInput(after: .milliseconds(500)) == .milliseconds(500).nextUp) + #expect(style.discreteInput(before: .milliseconds(500)) == .milliseconds(-500).nextDown) + #expect(style.discreteInput(after: .milliseconds(0)) == .milliseconds(500).nextUp) + #expect(style.discreteInput(before: .milliseconds(0)) == .milliseconds(-500).nextDown) + #expect(style.discreteInput(after: .milliseconds(-500)) == .milliseconds(500).nextUp) + #expect(style.discreteInput(before: .milliseconds(-500)) == .milliseconds(-500).nextDown) + #expect(style.discreteInput(after: .seconds(-1)) == .milliseconds(-500)) + #expect(style.discreteInput(before: .seconds(-1)) == .milliseconds(-1500)) } - func testEvaluation() { + @Test func evaluation() { func assertEvaluation(of style: Duration._UnitsFormatStyle, rounding roundingRules: [FloatingPointRoundingRule] = [.up, .down, .towardZero, .awayFromZero, .toNearestOrAwayFromZero, .toNearestOrEven], in range: ClosedRange, includes expectedExcerpts: [String]..., - file: StaticString = #filePath, - line: UInt = #line) { + sourceLocation: SourceLocation = #_sourceLocation) { for rule in roundingRules { var style = style.locale(Locale(identifier: "en_US")) @@ -1213,21 +1211,19 @@ final class TestDurationUnitsDiscreteConformance : XCTestCase { sequence: style.evaluate(from: range.lowerBound, to: range.upperBound).map(\.output), contains: expectedExcerpts, "lowerbound to upperbound, rounding \(rule)", - file: file, - line: line) - + sourceLocation: sourceLocation) + verify( sequence: style.evaluate(from: range.upperBound, to: range.lowerBound).map(\.output), contains: expectedExcerpts .reversed() .map { $0.reversed() }, "upperbound to lowerbound, rounding \(rule)", - file: file, - line: line) + sourceLocation: sourceLocation) } } - - + + assertEvaluation( of: .init(allowedUnits: [.minutes, .seconds], width: .narrow, zeroValueUnits: .show(length: 1), fractionalPart: .hide), in: Duration.seconds(61).symmetricRange, @@ -1256,7 +1252,7 @@ final class TestDurationUnitsDiscreteConformance : XCTestCase { "1m 0s", "1m 1s", ]) - + assertEvaluation( of: .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .hide), in: Duration.seconds(120).symmetricRange, @@ -1285,7 +1281,7 @@ final class TestDurationUnitsDiscreteConformance : XCTestCase { "1m", "2m", ]) - + assertEvaluation( of: .init(allowedUnits: [.hours], width: .narrow, zeroValueUnits: .show(length: 1), fractionalPart: .hide), in: Duration.seconds(3 * 3600).symmetricRange, @@ -1298,7 +1294,7 @@ final class TestDurationUnitsDiscreteConformance : XCTestCase { "2h", "3h", ]) - + assertEvaluation( of: .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .show(length: 1)), in: Duration.seconds(120).symmetricRange, @@ -1376,20 +1372,20 @@ final class TestDurationUnitsDiscreteConformance : XCTestCase { "2.0m", ]) } - - func testRegressions() throws { + + @Test func regressions() throws { var style: Duration._UnitsFormatStyle - + style = .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .show(length: 1, rounded: .toNearestOrAwayFromZero)) - - XCTAssertLessThanOrEqual(try XCTUnwrap(style.discreteInput(after: Duration(secondsComponent: -75, attosecondsComponent: -535173016509531840))), Duration(secondsComponent: -73, attosecondsComponent: -122099659011723263)) - + + #expect(try #require(style.discreteInput(after: Duration(secondsComponent: -75, attosecondsComponent: -535173016509531840))) <= Duration(secondsComponent: -73, attosecondsComponent: -122099659011723263)) + style = .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .show(length: 1)) - - XCTAssertLessThanOrEqual(try XCTUnwrap(style.discreteInput(after: Duration(secondsComponent: -63, attosecondsComponent: -0))), Duration(secondsComponent: -59, attosecondsComponent: -900000000000000000)) + + #expect(try #require(style.discreteInput(after: Duration(secondsComponent: -63, attosecondsComponent: -0))) <= Duration(secondsComponent: -59, attosecondsComponent: -900000000000000000)) } - - func testRandomSamples() throws { + + @Test func randomSamples() throws { let styles: [Duration._UnitsFormatStyle] = [ .init(allowedUnits: [.minutes, .seconds], width: .narrow, zeroValueUnits: .show(length: 1), fractionalPart: .hide), .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .hide), @@ -1399,12 +1395,13 @@ final class TestDurationUnitsDiscreteConformance : XCTestCase { .init(allowedUnits: [.minutes, .seconds], width: .narrow, maximumUnitCount: 1, zeroValueUnits: .hide, fractionalPart: .show(length: 1, rounded: roundingRule)), ] } - - + + for style in styles { - try verifyDiscreteFormatStyleConformance(style, samples: 100, "\(style)") + try verifyDiscreteFormatStyleConformance(style.locale(Locale(identifier: "en_US")), samples: 100, "\(style)") } } + } extension Duration { diff --git a/Tests/FoundationInternationalizationTests/LocaleTests.swift b/Tests/FoundationInternationalizationTests/LocaleTests.swift index 118fd329d..2fa9fe866 100644 --- a/Tests/FoundationInternationalizationTests/LocaleTests.swift +++ b/Tests/FoundationInternationalizationTests/LocaleTests.swift @@ -9,16 +9,8 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %empty-directory(%t) -// -// RUN: %target-clang %S/Inputs/FoundationBridge/FoundationBridge.m -c -o %t/FoundationBridgeObjC.o -g -// RUN: %target-build-swift %s -I %S/Inputs/FoundationBridge/ -Xlinker %t/FoundationBridgeObjC.o -o %t/TestLocale -// RUN: %target-codesign %t/TestLocale -// RUN: %target-run %t/TestLocale > %t.txt -// REQUIRES: executable_test -// REQUIRES: objc_interop +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -27,43 +19,40 @@ @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -#if canImport(TestSupport) -import TestSupport -#endif +@Suite("Locale") +private struct LocaleTests { -final class LocaleTests : XCTestCase { - - func test_equality() { + @Test func equality() { let autoupdating = Locale.autoupdatingCurrent let autoupdating2 = Locale.autoupdatingCurrent - XCTAssertEqual(autoupdating, autoupdating2) + #expect(autoupdating == autoupdating2) let current = Locale.current - XCTAssertNotEqual(autoupdating, current) + #expect(autoupdating != current) } - func test_localizedStringFunctions() { + @Test func localizedStringFunctions() { let locale = Locale(identifier: "en") - XCTAssertEqual("English", locale.localizedString(forIdentifier: "en")) - XCTAssertEqual("France", locale.localizedString(forRegionCode: "fr")) - XCTAssertEqual("Spanish", locale.localizedString(forLanguageCode: "es")) - XCTAssertEqual("Simplified Han", locale.localizedString(forScriptCode: "Hans")) - XCTAssertEqual("Computer", locale.localizedString(forVariantCode: "POSIX")) - XCTAssertEqual("Buddhist Calendar", locale.localizedString(for: .buddhist)) - XCTAssertEqual("US Dollar", locale.localizedString(forCurrencyCode: "USD")) - XCTAssertEqual("Phonebook Sort Order", locale.localizedString(forCollationIdentifier: "phonebook")) + #expect("English" == locale.localizedString(forIdentifier: "en")) + #expect("France" == locale.localizedString(forRegionCode: "fr")) + #expect("Spanish" == locale.localizedString(forLanguageCode: "es")) + #expect("Simplified Han" == locale.localizedString(forScriptCode: "Hans")) + #expect("Computer" == locale.localizedString(forVariantCode: "POSIX")) + #expect("Buddhist Calendar" == locale.localizedString(for: .buddhist)) + #expect("US Dollar" == locale.localizedString(forCurrencyCode: "USD")) + #expect("Phonebook Sort Order" == locale.localizedString(forCollationIdentifier: "phonebook")) // Need to find a good test case for collator identifier - // XCTAssertEqual("something", locale.localizedString(forCollatorIdentifier: "en")) + // #expect("something" == locale.localizedString(forCollatorIdentifier: "en")) } @available(macOS, deprecated: 13) @available(iOS, deprecated: 16) @available(tvOS, deprecated: 16) @available(watchOS, deprecated: 9) - func test_properties_complexIdentifiers() { + @Test func properties_complexIdentifiers() { struct S { var identifier: String var countryCode: String? @@ -83,64 +72,66 @@ final class LocaleTests : XCTestCase { for t in tests { let l = Locale(identifier: t.identifier) - XCTAssertEqual(t.countryCode, l.regionCode, "Failure for id \(t.identifier)") - XCTAssertEqual(t.languageCode, l.languageCode, "Failure for id \(t.identifier)") - XCTAssertEqual(t.script, l.scriptCode, "Failure for id \(t.identifier)") - XCTAssertEqual(t.calendar, l.calendar.identifier, "Failure for id \(t.identifier)") - XCTAssertEqual(t.currency, l.currency, "Failure for id \(t.identifier)") - XCTAssertEqual(t.collation, l.collation, "Failure for id \(t.identifier)") + #expect(t.countryCode == l.regionCode, "Failure for id \(t.identifier)") + #expect(t.languageCode == l.languageCode, "Failure for id \(t.identifier)") + #expect(t.script == l.scriptCode, "Failure for id \(t.identifier)") + #expect(t.calendar == l.calendar.identifier, "Failure for id \(t.identifier)") + #expect(t.currency == l.currency, "Failure for id \(t.identifier)") + #expect(t.collation == l.collation, "Failure for id \(t.identifier)") } } - func test_AnyHashableContainingLocale() { + @Test func anyHashableContainingLocale() { let values: [Locale] = [ Locale(identifier: "en"), Locale(identifier: "uk"), Locale(identifier: "uk"), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(Locale.self, type(of: anyHashables[0].base)) - expectEqual(Locale.self, type(of: anyHashables[1].base)) - expectEqual(Locale.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(Locale.self == type(of: anyHashables[0].base)) + #expect(Locale.self == type(of: anyHashables[1].base)) + #expect(Locale.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func decodeHelper(_ l: Locale) -> Locale { + func decodeHelper(_ l: Locale) throws -> Locale { let je = JSONEncoder() - let data = try! je.encode(l) + let data = try je.encode(l) let jd = JSONDecoder() - return try! jd.decode(Locale.self, from: data) + return try jd.decode(Locale.self, from: data) } - func test_serializationOfCurrent() { - let current = Locale.current - let decodedCurrent = decodeHelper(current) - XCTAssertEqual(decodedCurrent, current) - - let autoupdatingCurrent = Locale.autoupdatingCurrent - let decodedAutoupdatingCurrent = decodeHelper(autoupdatingCurrent) - XCTAssertEqual(decodedAutoupdatingCurrent, autoupdatingCurrent) - - XCTAssertNotEqual(decodedCurrent, decodedAutoupdatingCurrent) - XCTAssertNotEqual(current, autoupdatingCurrent) - XCTAssertNotEqual(decodedCurrent, autoupdatingCurrent) - XCTAssertNotEqual(current, decodedAutoupdatingCurrent) + @Test func serializationOfCurrent() async throws { + try await usingCurrentInternationalizationPreferences { + let current = Locale.current + let decodedCurrent = try decodeHelper(current) + #expect(decodedCurrent == current) + + let autoupdatingCurrent = Locale.autoupdatingCurrent + let decodedAutoupdatingCurrent = try decodeHelper(autoupdatingCurrent) + #expect(decodedAutoupdatingCurrent == autoupdatingCurrent) + + #expect(decodedCurrent != decodedAutoupdatingCurrent) + #expect(current != autoupdatingCurrent) + #expect(decodedCurrent != autoupdatingCurrent) + #expect(current != decodedAutoupdatingCurrent) + } } - func test_identifierWithCalendar() throws { - XCTAssertEqual(Calendar.localeIdentifierWithCalendar(localeIdentifier: "en_US", calendarIdentifier: .islamicTabular), "en_US@calendar=islamic-tbla") - XCTAssertEqual(Calendar.localeIdentifierWithCalendar(localeIdentifier: "zh-Hant-TW@calendar=japanese;collation=pinyin;numbers=arab", calendarIdentifier: .islamicTabular), "zh-Hant_TW@calendar=islamic-tbla;collation=pinyin;numbers=arab") + @Test func identifierWithCalendar() throws { + #expect(Calendar.localeIdentifierWithCalendar(localeIdentifier: "en_US", calendarIdentifier: .islamicTabular) == "en_US@calendar=islamic-tbla") + #expect(Calendar.localeIdentifierWithCalendar(localeIdentifier: "zh-Hant-TW@calendar=japanese;collation=pinyin;numbers=arab", calendarIdentifier: .islamicTabular) == "zh-Hant_TW@calendar=islamic-tbla;collation=pinyin;numbers=arab") } - func test_identifierTypesFromComponents() throws { + @Test func identifierTypesFromComponents() throws { - func verify(cldr: String, bcp47: String, icu: String, file: StaticString = #filePath, line: UInt = #line, _ components: () -> Locale.Components) { + func verify(cldr: String, bcp47: String, icu: String, sourceLocation: SourceLocation = #_sourceLocation, _ components: () -> Locale.Components) { let loc = Locale(components: components()) let types: [Locale.IdentifierType] = [.cldr, .bcp47, .icu] let expected = [cldr, bcp47, icu] for (idx, type) in types.enumerated() { - XCTAssertEqual(loc.identifier(type), expected[idx], "type: \(type)", file: file, line: line) + #expect(loc.identifier(type) == expected[idx], "type: \(type)", sourceLocation: sourceLocation) } } @@ -246,12 +237,12 @@ final class LocaleTests : XCTestCase { } } - func verify(_ locID: String, cldr: String, bcp47: String, icu: String, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ locID: String, cldr: String, bcp47: String, icu: String, sourceLocation: SourceLocation = #_sourceLocation) { let loc = Locale(identifier: locID) let types: [Locale.IdentifierType] = [.cldr, .bcp47, .icu] let expected = [cldr, bcp47, icu] for (idx, type) in types.enumerated() { - XCTAssertEqual(loc.identifier(type), expected[idx], "type: \(type)", file: file, line: line) + #expect(loc.identifier(type) == expected[idx], "type: \(type)", sourceLocation: sourceLocation) } } @@ -264,255 +255,264 @@ final class LocaleTests : XCTestCase { return result } - func test_identifierFromComponents() { + @Test func identifierFromComponents() { var c: [String: String] = [:] c = comps(language: "zh", script: "Hans", country: "TW") - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW") // Set some keywords c["CuRrEnCy"] = "qqq" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@currency=qqq") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@currency=qqq") // Set some more keywords, check order c["d"] = "d" c["0"] = "0" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;currency=qqq;d=d") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;currency=qqq;d=d") // Add some non-ASCII keywords c["ê"] = "ê" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;currency=qqq;d=d") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;currency=qqq;d=d") // And some non-ASCII values c["n"] = "ñ" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;currency=qqq;d=d") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;currency=qqq;d=d") // And some values with other letters c["z"] = "Ab09_-+/" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;currency=qqq;d=d;z=Ab09_-+/") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;currency=qqq;d=d;z=Ab09_-+/") // And some really short keys c[""] = "hi" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;currency=qqq;d=d;z=Ab09_-+/") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;currency=qqq;d=d;z=Ab09_-+/") // And some really short values c["q"] = "" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;currency=qqq;d=d;z=Ab09_-+/") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;currency=qqq;d=d;z=Ab09_-+/") // All the valid stuff c["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+/" - XCTAssertEqual(Locale.identifier(fromComponents: c), "zh_Hans_TW@0=0;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+/;currency=qqq;d=d;z=Ab09_-+/") + #expect(Locale.identifier(fromComponents: c) == "zh_Hans_TW@0=0;abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz0123456789=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-+/;currency=qqq;d=d;z=Ab09_-+/") // POSIX let p = comps(language: "en", script: nil, country: "US", variant: "POSIX") - XCTAssertEqual(Locale.identifier(fromComponents: p), "en_US_POSIX") + #expect(Locale.identifier(fromComponents: p) == "en_US_POSIX") // Odd combos - XCTAssertEqual(Locale.identifier(fromComponents: comps(language: "en", variant: "POSIX")), "en__POSIX") + #expect(Locale.identifier(fromComponents: comps(language: "en", variant: "POSIX")) == "en__POSIX") - XCTAssertEqual(Locale.identifier(fromComponents: comps(variant: "POSIX")), "__POSIX") + #expect(Locale.identifier(fromComponents: comps(variant: "POSIX")) == "__POSIX") - XCTAssertEqual(Locale.identifier(fromComponents: comps(language: "en", script: "Hans", country: "US", variant: "POSIX")), "en_Hans_US_POSIX") + #expect(Locale.identifier(fromComponents: comps(language: "en", script: "Hans", country: "US", variant: "POSIX")) == "en_Hans_US_POSIX") - XCTAssertEqual(Locale.identifier(fromComponents: comps(language: "en")), "en") - XCTAssertEqual(Locale.identifier(fromComponents: comps(country: "US", variant: "POSIX")), "_US_POSIX") + #expect(Locale.identifier(fromComponents: comps(language: "en")) == "en") + #expect(Locale.identifier(fromComponents: comps(country: "US", variant: "POSIX")) == "_US_POSIX") } #if FOUNDATION_FRAMEWORK - func test_identifierFromAnyComponents() { + @Test func identifierFromAnyComponents() { // This internal Foundation-specific version allows for a Calendar entry let comps = comps(language: "zh", script: "Hans", country: "TW") - XCTAssertEqual(Locale.identifier(fromComponents: comps), "zh_Hans_TW") + #expect(Locale.identifier(fromComponents: comps) == "zh_Hans_TW") var anyComps : [String : Any] = [:] anyComps.merge(comps) { a, b in a } anyComps["kCFLocaleCalendarKey"] = Calendar(identifier: .gregorian) - XCTAssertEqual(Locale.identifier(fromAnyComponents: anyComps), "zh_Hans_TW@calendar=gregorian") + #expect(Locale.identifier(fromAnyComponents: anyComps) == "zh_Hans_TW@calendar=gregorian") // Verify what happens if we have the key in here under two different (but equivalent) names anyComps["calendar"] = "buddhist" - XCTAssertEqual(Locale.identifier(fromAnyComponents: anyComps), "zh_Hans_TW@calendar=gregorian") + #expect(Locale.identifier(fromAnyComponents: anyComps) == "zh_Hans_TW@calendar=gregorian") anyComps["currency"] = "xyz" - XCTAssertEqual(Locale.identifier(fromAnyComponents: anyComps), "zh_Hans_TW@calendar=gregorian;currency=xyz") + #expect(Locale.identifier(fromAnyComponents: anyComps) == "zh_Hans_TW@calendar=gregorian;currency=xyz") anyComps["AaA"] = "bBb" - XCTAssertEqual(Locale.identifier(fromAnyComponents: anyComps), "zh_Hans_TW@aaa=bBb;calendar=gregorian;currency=xyz") + #expect(Locale.identifier(fromAnyComponents: anyComps) == "zh_Hans_TW@aaa=bBb;calendar=gregorian;currency=xyz") } #endif - func test_identifierCapturingPreferences() { - func expectIdentifier(_ localeIdentifier: String, preferences: LocalePreferences, expectedFullIdentifier: String, file: StaticString = #filePath, line: UInt = #line) { - let locale = Locale.localeAsIfCurrent(name: localeIdentifier, overrides: preferences) - XCTAssertEqual(locale.identifier, localeIdentifier, file: file, line: line) - XCTAssertEqual(locale.identifierCapturingPreferences, expectedFullIdentifier, file: file, line: line) - } - - expectIdentifier("en_US", preferences: .init(metricUnits: true, measurementUnits: .centimeters), expectedFullIdentifier: "en_US@measure=metric") - expectIdentifier("en_US", preferences: .init(metricUnits: true, measurementUnits: .inches), expectedFullIdentifier: "en_US@measure=uksystem") - expectIdentifier("en_US", preferences: .init(metricUnits: false, measurementUnits: .inches), expectedFullIdentifier: "en_US@measure=ussystem") - // We treat it as US system as long as `metricUnits` is false - expectIdentifier("en_US", preferences: .init(metricUnits: false, measurementUnits: .centimeters), expectedFullIdentifier: "en_US@measure=ussystem") - - // 112778892: Country pref is intentionally ignored - expectIdentifier("en_US", preferences: .init(country: "GB"), expectedFullIdentifier: "en_US") - expectIdentifier("en_US", preferences: .init(country: "US"), expectedFullIdentifier: "en_US") - - expectIdentifier("en_US", preferences: .init(firstWeekday: [.gregorian : 3]), expectedFullIdentifier: "en_US@fw=tue") - // en_US locale doesn't use islamic calendar; preference is ignored - expectIdentifier("en_US", preferences: .init(firstWeekday: [.islamic : 3]), expectedFullIdentifier: "en_US") - - expectIdentifier("en_US", preferences: .init(force24Hour: true), expectedFullIdentifier: "en_US@hours=h23") - expectIdentifier("en_US", preferences: .init(force12Hour: true), expectedFullIdentifier: "en_US@hours=h12") - - // Preferences not representable by locale identifier are ignored - expectIdentifier("en_US", preferences: .init(minDaysInFirstWeek: [.gregorian: 7]), expectedFullIdentifier: "en_US") + @Test func identifierCapturingPreferences() async { + await usingCurrentInternationalizationPreferences { + // This test requires that no additional Locale preferences be set for the current locale + var prefs = LocalePreferences() + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + + func expectIdentifier(_ localeIdentifier: String, preferences: LocalePreferences, expectedFullIdentifier: String, sourceLocation: SourceLocation = #_sourceLocation) { + let locale = Locale.localeAsIfCurrent(name: localeIdentifier, overrides: preferences) + #expect(locale.identifier == localeIdentifier, sourceLocation: sourceLocation) + #expect(locale.identifierCapturingPreferences == expectedFullIdentifier, sourceLocation: sourceLocation) + } + + expectIdentifier("en_US", preferences: .init(metricUnits: true, measurementUnits: .centimeters), expectedFullIdentifier: "en_US@measure=metric") + expectIdentifier("en_US", preferences: .init(metricUnits: true, measurementUnits: .inches), expectedFullIdentifier: "en_US@measure=uksystem") + expectIdentifier("en_US", preferences: .init(metricUnits: false, measurementUnits: .inches), expectedFullIdentifier: "en_US@measure=ussystem") + // We treat it as US system as long as `metricUnits` is false + expectIdentifier("en_US", preferences: .init(metricUnits: false, measurementUnits: .centimeters), expectedFullIdentifier: "en_US@measure=ussystem") + + // 112778892: Country pref is intentionally ignored + expectIdentifier("en_US", preferences: .init(country: "GB"), expectedFullIdentifier: "en_US") + expectIdentifier("en_US", preferences: .init(country: "US"), expectedFullIdentifier: "en_US") + + expectIdentifier("en_US", preferences: .init(firstWeekday: [.gregorian : 3]), expectedFullIdentifier: "en_US@fw=tue") + // en_US locale doesn't use islamic calendar; preference is ignored + expectIdentifier("en_US", preferences: .init(firstWeekday: [.islamic : 3]), expectedFullIdentifier: "en_US") + + expectIdentifier("en_US", preferences: .init(force24Hour: true), expectedFullIdentifier: "en_US@hours=h23") + expectIdentifier("en_US", preferences: .init(force12Hour: true), expectedFullIdentifier: "en_US@hours=h12") + + // Preferences not representable by locale identifier are ignored + expectIdentifier("en_US", preferences: .init(minDaysInFirstWeek: [.gregorian: 7]), expectedFullIdentifier: "en_US") #if FOUNDATION_FRAMEWORK - expectIdentifier("en_US", preferences: .init(dateFormats: [.abbreviated: "custom style"]), expectedFullIdentifier: "en_US") + expectIdentifier("en_US", preferences: .init(dateFormats: [.abbreviated: "custom style"]), expectedFullIdentifier: "en_US") #endif + } } - func test_badWindowsLocaleID() { + @Test func badWindowsLocaleID() { // Negative values are invalid let result = Locale.identifier(fromWindowsLocaleCode: -1) - XCTAssertNil(result) + #expect(result == nil) } - func test_emptyComponents() throws { + @Test func emptyComponents() throws { let emptyLocale = Locale(identifier: "") - XCTAssertEqual(emptyLocale.language.languageCode, nil) - XCTAssertEqual(emptyLocale.language.script, nil) - XCTAssertEqual(emptyLocale.language.region, nil) - XCTAssertEqual(emptyLocale.language.maximalIdentifier, "") - XCTAssertEqual(emptyLocale.language.minimalIdentifier, "") - XCTAssertEqual(emptyLocale.identifier, "") + #expect(emptyLocale.language.languageCode == nil) + #expect(emptyLocale.language.script == nil) + #expect(emptyLocale.language.region == nil) + #expect(emptyLocale.language.maximalIdentifier == "") + #expect(emptyLocale.language.minimalIdentifier == "") + #expect(emptyLocale.identifier == "") let localeFromEmptyComp = Locale(components: Locale.Components(identifier: "")) - XCTAssertEqual(localeFromEmptyComp.language.languageCode, nil) - XCTAssertEqual(localeFromEmptyComp.language.script, nil) - XCTAssertEqual(localeFromEmptyComp.language.region, nil) - XCTAssertEqual(localeFromEmptyComp.language.maximalIdentifier, "") - XCTAssertEqual(localeFromEmptyComp.language.minimalIdentifier, "") - XCTAssertEqual(localeFromEmptyComp.identifier, "") + #expect(localeFromEmptyComp.language.languageCode == nil) + #expect(localeFromEmptyComp.language.script == nil) + #expect(localeFromEmptyComp.language.region == nil) + #expect(localeFromEmptyComp.language.maximalIdentifier == "") + #expect(localeFromEmptyComp.language.minimalIdentifier == "") + #expect(localeFromEmptyComp.identifier == "") let localeFromEmptyLanguageComponent = Locale(languageComponents: .init(identifier: "")) - XCTAssertEqual(localeFromEmptyLanguageComponent.language.languageCode, nil) - XCTAssertEqual(localeFromEmptyLanguageComponent.language.script, nil) - XCTAssertEqual(localeFromEmptyLanguageComponent.language.region, nil) - XCTAssertEqual(localeFromEmptyLanguageComponent.language.maximalIdentifier, "") - XCTAssertEqual(localeFromEmptyLanguageComponent.language.minimalIdentifier, "") - XCTAssertEqual(localeFromEmptyLanguageComponent.identifier, "") + #expect(localeFromEmptyLanguageComponent.language.languageCode == nil) + #expect(localeFromEmptyLanguageComponent.language.script == nil) + #expect(localeFromEmptyLanguageComponent.language.region == nil) + #expect(localeFromEmptyLanguageComponent.language.maximalIdentifier == "") + #expect(localeFromEmptyLanguageComponent.language.minimalIdentifier == "") + #expect(localeFromEmptyLanguageComponent.identifier == "") let localeFromEmptyLanguageComponentIndividual = Locale(languageComponents: .init(languageCode: "", script: "", region: "")) - XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.languageCode, nil) - XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.script, nil) - XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.region, nil) - XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.maximalIdentifier, "") - XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.language.minimalIdentifier, "") - XCTAssertEqual(localeFromEmptyLanguageComponentIndividual.identifier, "") + #expect(localeFromEmptyLanguageComponentIndividual.language.languageCode == nil) + #expect(localeFromEmptyLanguageComponentIndividual.language.script == nil) + #expect(localeFromEmptyLanguageComponentIndividual.language.region == nil) + #expect(localeFromEmptyLanguageComponentIndividual.language.maximalIdentifier == "") + #expect(localeFromEmptyLanguageComponentIndividual.language.minimalIdentifier == "") + #expect(localeFromEmptyLanguageComponentIndividual.identifier == "") let localeFromEmptyIndividualLanguageComponent = Locale(languageCode: "", script: "", languageRegion: "") - XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.languageCode, nil) - XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.script, nil) - XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.region, nil) - XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.maximalIdentifier, "") - XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.language.minimalIdentifier, "") - XCTAssertEqual(localeFromEmptyIndividualLanguageComponent.identifier, "") + #expect(localeFromEmptyIndividualLanguageComponent.language.languageCode == nil) + #expect(localeFromEmptyIndividualLanguageComponent.language.script == nil) + #expect(localeFromEmptyIndividualLanguageComponent.language.region == nil) + #expect(localeFromEmptyIndividualLanguageComponent.language.maximalIdentifier == "") + #expect(localeFromEmptyIndividualLanguageComponent.language.minimalIdentifier == "") + #expect(localeFromEmptyIndividualLanguageComponent.identifier == "") // Locale.Component let compFromEmptyLocale = Locale.Components(locale: emptyLocale) - XCTAssertEqual(compFromEmptyLocale.languageComponents.languageCode, nil) - XCTAssertEqual(compFromEmptyLocale.languageComponents.script, nil) - XCTAssertEqual(compFromEmptyLocale.languageComponents.region, nil) + #expect(compFromEmptyLocale.languageComponents.languageCode == nil) + #expect(compFromEmptyLocale.languageComponents.script == nil) + #expect(compFromEmptyLocale.languageComponents.region == nil) let emptyComp = Locale.Components(identifier: "") - XCTAssertEqual(emptyComp.languageComponents.languageCode, nil) - XCTAssertEqual(emptyComp.languageComponents.script, nil) - XCTAssertEqual(emptyComp.languageComponents.region, nil) + #expect(emptyComp.languageComponents.languageCode == nil) + #expect(emptyComp.languageComponents.script == nil) + #expect(emptyComp.languageComponents.region == nil) // Language let emptyLanguage = Locale.Language(identifier: "") - XCTAssertEqual(emptyLanguage.languageCode, nil) - XCTAssertEqual(emptyLanguage.script, nil) - XCTAssertEqual(emptyLanguage.region, nil) - XCTAssertEqual(emptyLanguage.maximalIdentifier, "") - XCTAssertEqual(emptyLanguage.minimalIdentifier, "") + #expect(emptyLanguage.languageCode == nil) + #expect(emptyLanguage.script == nil) + #expect(emptyLanguage.region == nil) + #expect(emptyLanguage.maximalIdentifier == "") + #expect(emptyLanguage.minimalIdentifier == "") let languageFromEmptyComponents = Locale.Language(components: .init(identifier: "")) - XCTAssertEqual(languageFromEmptyComponents.languageCode, nil) - XCTAssertEqual(languageFromEmptyComponents.script, nil) - XCTAssertEqual(languageFromEmptyComponents.region, nil) - XCTAssertEqual(languageFromEmptyComponents.maximalIdentifier, "") - XCTAssertEqual(languageFromEmptyComponents.minimalIdentifier, "") + #expect(languageFromEmptyComponents.languageCode == nil) + #expect(languageFromEmptyComponents.script == nil) + #expect(languageFromEmptyComponents.region == nil) + #expect(languageFromEmptyComponents.maximalIdentifier == "") + #expect(languageFromEmptyComponents.minimalIdentifier == "") let languageFromEmptyComponents2 = Locale.Language(components: .init(languageCode: "", script: "", region: "")) - XCTAssertEqual(languageFromEmptyComponents2.languageCode, "") - XCTAssertEqual(languageFromEmptyComponents2.script, "") - XCTAssertEqual(languageFromEmptyComponents2.region, "") - XCTAssertEqual(languageFromEmptyComponents2.maximalIdentifier, "") - XCTAssertEqual(languageFromEmptyComponents2.minimalIdentifier, "") + #expect(languageFromEmptyComponents2.languageCode == "") + #expect(languageFromEmptyComponents2.script == "") + #expect(languageFromEmptyComponents2.region == "") + #expect(languageFromEmptyComponents2.maximalIdentifier == "") + #expect(languageFromEmptyComponents2.minimalIdentifier == "") // Language.Component let languageCompFromEmptyLanguage = Locale.Language.Components(language: Locale.Language(identifier: "")) - XCTAssertEqual(languageCompFromEmptyLanguage.languageCode, nil) - XCTAssertEqual(languageCompFromEmptyLanguage.script, nil) - XCTAssertEqual(languageCompFromEmptyLanguage.region, nil) + #expect(languageCompFromEmptyLanguage.languageCode == nil) + #expect(languageCompFromEmptyLanguage.script == nil) + #expect(languageCompFromEmptyLanguage.region == nil) let emptyLanguageComponents = Locale.Language.Components(identifier: "") - XCTAssertEqual(emptyLanguageComponents.languageCode, nil) - XCTAssertEqual(emptyLanguageComponents.script, nil) - XCTAssertEqual(emptyLanguageComponents.region, nil) + #expect(emptyLanguageComponents.languageCode == nil) + #expect(emptyLanguageComponents.script == nil) + #expect(emptyLanguageComponents.region == nil) let emptyLanguageComponents2 = Locale.Language.Components(languageCode: "", script: "", region: "") - XCTAssertEqual(emptyLanguageComponents2.languageCode, "") - XCTAssertEqual(emptyLanguageComponents2.script, "") - XCTAssertEqual(emptyLanguageComponents2.region, "") + #expect(emptyLanguageComponents2.languageCode == "") + #expect(emptyLanguageComponents2.script == "") + #expect(emptyLanguageComponents2.region == "") } func test_nilComponents() { let nilLanguageComponents = Locale.Language.Components(languageCode: nil, script: nil, region: nil) - XCTAssertEqual(nilLanguageComponents.languageCode, nil) - XCTAssertEqual(nilLanguageComponents.script, nil) - XCTAssertEqual(nilLanguageComponents.region, nil) + #expect(nilLanguageComponents.languageCode == nil) + #expect(nilLanguageComponents.script == nil) + #expect(nilLanguageComponents.region == nil) let nilLanguage = Locale.Language(languageCode: nil, script: nil, region: nil) - XCTAssertEqual(nilLanguage.languageCode, nil) - XCTAssertEqual(nilLanguage.script, nil) - XCTAssertEqual(nilLanguage.region, nil) + #expect(nilLanguage.languageCode == nil) + #expect(nilLanguage.script == nil) + #expect(nilLanguage.region == nil) } } -final class LocalePropertiesTests : XCTestCase { - - func _verify(locale: Locale, expectedLanguage language: Locale.LanguageCode? = nil, script: Locale.Script? = nil, languageRegion: Locale.Region? = nil, region: Locale.Region? = nil, subdivision: Locale.Subdivision? = nil, measurementSystem: Locale.MeasurementSystem? = nil, calendar: Calendar.Identifier? = nil, hourCycle: Locale.HourCycle? = nil, currency: Locale.Currency? = nil, numberingSystem: Locale.NumberingSystem? = nil, numberingSystems: Set = [], firstDayOfWeek: Locale.Weekday? = nil, collation: Locale.Collation? = nil, variant: Locale.Variant? = nil, file: StaticString = #filePath, line: UInt = #line) { - XCTAssertEqual(locale.language.languageCode, language, "languageCode should be equal", file: file, line: line) - XCTAssertEqual(locale.language.script, script, "script should be equal", file: file, line: line) - XCTAssertEqual(locale.language.region, languageRegion, "language region should be equal", file: file, line: line) - XCTAssertEqual(locale.region, region, "region should be equal", file: file, line: line) - XCTAssertEqual(locale.subdivision, subdivision, "subdivision should be equal", file: file, line: line) - XCTAssertEqual(locale.measurementSystem, measurementSystem, "measurementSystem should be equal", file: file, line: line) - XCTAssertEqual(locale.calendar.identifier, calendar, "calendar.identifier should be equal", file: file, line: line) - XCTAssertEqual(locale.hourCycle, hourCycle, "hourCycle should be equal", file: file, line: line) - XCTAssertEqual(locale.currency, currency, "currency should be equal", file: file, line: line) - XCTAssertEqual(locale.numberingSystem, numberingSystem, "numberingSystem should be equal", file: file, line: line) - XCTAssertEqual(Set(locale.availableNumberingSystems), numberingSystems, "availableNumberingSystems should be equal", file: file, line: line) - XCTAssertEqual(locale.firstDayOfWeek, firstDayOfWeek, "firstDayOfWeek should be equal", file: file, line: line) - XCTAssertEqual(locale.collation, collation, "collation should be equal", file: file, line: line) - XCTAssertEqual(locale.variant, variant, "variant should be equal", file: file, line: line) +@Suite("Locale Properties") +private struct LocalePropertiesTests { + + func _verify(locale: Locale, expectedLanguage language: Locale.LanguageCode? = nil, script: Locale.Script? = nil, languageRegion: Locale.Region? = nil, region: Locale.Region? = nil, subdivision: Locale.Subdivision? = nil, measurementSystem: Locale.MeasurementSystem? = nil, calendar: Calendar.Identifier? = nil, hourCycle: Locale.HourCycle? = nil, currency: Locale.Currency? = nil, numberingSystem: Locale.NumberingSystem? = nil, numberingSystems: Set = [], firstDayOfWeek: Locale.Weekday? = nil, collation: Locale.Collation? = nil, variant: Locale.Variant? = nil, sourceLocation: SourceLocation = #_sourceLocation) { + #expect(locale.language.languageCode == language, "languageCode should be equal", sourceLocation: sourceLocation) + #expect(locale.language.script == script, "script should be equal", sourceLocation: sourceLocation) + #expect(locale.language.region == languageRegion, "language region should be equal", sourceLocation: sourceLocation) + #expect(locale.region == region, "region should be equal", sourceLocation: sourceLocation) + #expect(locale.subdivision == subdivision, "subdivision should be equal", sourceLocation: sourceLocation) + #expect(locale.measurementSystem == measurementSystem, "measurementSystem should be equal", sourceLocation: sourceLocation) + #expect(locale.calendar.identifier == calendar, "calendar.identifier should be equal", sourceLocation: sourceLocation) + #expect(locale.hourCycle == hourCycle, "hourCycle should be equal", sourceLocation: sourceLocation) + #expect(locale.currency == currency, "currency should be equal", sourceLocation: sourceLocation) + #expect(locale.numberingSystem == numberingSystem, "numberingSystem should be equal", sourceLocation: sourceLocation) + #expect(Set(locale.availableNumberingSystems) == numberingSystems, "availableNumberingSystems should be equal", sourceLocation: sourceLocation) + #expect(locale.firstDayOfWeek == firstDayOfWeek, "firstDayOfWeek should be equal", sourceLocation: sourceLocation) + #expect(locale.collation == collation, "collation should be equal", sourceLocation: sourceLocation) + #expect(locale.variant == variant, "variant should be equal", sourceLocation: sourceLocation) } - func verify(_ identifier: String, expectedLanguage language: Locale.LanguageCode? = nil, script: Locale.Script? = nil, languageRegion: Locale.Region? = nil, region: Locale.Region? = nil, subdivision: Locale.Subdivision? = nil, measurementSystem: Locale.MeasurementSystem? = nil, calendar: Calendar.Identifier? = nil, hourCycle: Locale.HourCycle? = nil, currency: Locale.Currency? = nil, numberingSystem: Locale.NumberingSystem? = nil, numberingSystems: Set = [], firstDayOfWeek: Locale.Weekday? = nil, collation: Locale.Collation? = nil, variant: Locale.Variant? = nil, file: StaticString = #filePath, line: UInt = #line) { + func verify(_ identifier: String, expectedLanguage language: Locale.LanguageCode? = nil, script: Locale.Script? = nil, languageRegion: Locale.Region? = nil, region: Locale.Region? = nil, subdivision: Locale.Subdivision? = nil, measurementSystem: Locale.MeasurementSystem? = nil, calendar: Calendar.Identifier? = nil, hourCycle: Locale.HourCycle? = nil, currency: Locale.Currency? = nil, numberingSystem: Locale.NumberingSystem? = nil, numberingSystems: Set = [], firstDayOfWeek: Locale.Weekday? = nil, collation: Locale.Collation? = nil, variant: Locale.Variant? = nil, sourceLocation: SourceLocation = #_sourceLocation) { let loc = Locale(identifier: identifier) - _verify(locale: loc, expectedLanguage: language, script: script, languageRegion: languageRegion, region: region, subdivision: subdivision, measurementSystem: measurementSystem, calendar: calendar, hourCycle: hourCycle, currency: currency, numberingSystem: numberingSystem, numberingSystems: numberingSystems, firstDayOfWeek: firstDayOfWeek, collation: collation, variant: variant, file: file, line: line) + _verify(locale: loc, expectedLanguage: language, script: script, languageRegion: languageRegion, region: region, subdivision: subdivision, measurementSystem: measurementSystem, calendar: calendar, hourCycle: hourCycle, currency: currency, numberingSystem: numberingSystem, numberingSystems: numberingSystems, firstDayOfWeek: firstDayOfWeek, collation: collation, variant: variant, sourceLocation: sourceLocation) } - func test_localeComponentsAndLocale() { - func verify(components: Locale.Components, identifier: String, file: StaticString = #filePath, line: UInt = #line) { + @Test func localeComponentsAndLocale() { + func verify(components: Locale.Components, identifier: String, sourceLocation: SourceLocation = #_sourceLocation) { let locFromComponents = Locale(components: components) let locFromIdentifier = Locale(identifier: identifier) - _verify(locale: locFromComponents, expectedLanguage: locFromIdentifier.language.languageCode, script: locFromIdentifier.language.script, languageRegion: locFromIdentifier.language.region, region: locFromIdentifier.region, measurementSystem: locFromIdentifier.measurementSystem, calendar: locFromIdentifier.calendar.identifier, hourCycle: locFromIdentifier.hourCycle, currency: locFromIdentifier.currency, numberingSystem: locFromIdentifier.numberingSystem, numberingSystems: Set(locFromIdentifier.availableNumberingSystems), firstDayOfWeek: locFromIdentifier.firstDayOfWeek, collation: locFromIdentifier.collation, variant: locFromIdentifier.variant, file: file, line: line) + _verify(locale: locFromComponents, expectedLanguage: locFromIdentifier.language.languageCode, script: locFromIdentifier.language.script, languageRegion: locFromIdentifier.language.region, region: locFromIdentifier.region, measurementSystem: locFromIdentifier.measurementSystem, calendar: locFromIdentifier.calendar.identifier, hourCycle: locFromIdentifier.hourCycle, currency: locFromIdentifier.currency, numberingSystem: locFromIdentifier.numberingSystem, numberingSystems: Set(locFromIdentifier.availableNumberingSystems), firstDayOfWeek: locFromIdentifier.firstDayOfWeek, collation: locFromIdentifier.collation, variant: locFromIdentifier.variant, sourceLocation: sourceLocation) } @@ -546,19 +546,19 @@ final class LocalePropertiesTests : XCTestCase { } // Test retrieving user's preference values as set in the system settings - func test_userPreferenceOverride_hourCycle() { - func verifyHourCycle(_ localeID: String, _ expectDefault: Locale.HourCycle, shouldRespectUserPref: Bool, file: StaticString = #filePath, line: UInt = #line) { + @Test func userPreferenceOverride_hourCycle() { + func verifyHourCycle(_ localeID: String, _ expectDefault: Locale.HourCycle, shouldRespectUserPref: Bool, sourceLocation: SourceLocation = #_sourceLocation) { let loc = Locale(identifier: localeID) - XCTAssertEqual(loc.hourCycle, expectDefault, "default did not match", file: file, line: line) + #expect(loc.hourCycle == expectDefault, "default did not match", sourceLocation: sourceLocation) let defaultLoc = Locale.localeAsIfCurrent(name: localeID, overrides: .init()) - XCTAssertEqual(defaultLoc.hourCycle, expectDefault, "explicit no override did not match", file: file, line: line) + #expect(defaultLoc.hourCycle == expectDefault, "explicit no override did not match", sourceLocation: sourceLocation) let force24 = Locale.localeAsIfCurrent(name: localeID, overrides: .init(force24Hour: true)) - XCTAssertEqual(force24.hourCycle, shouldRespectUserPref ? .zeroToTwentyThree : expectDefault, "force 24-hr did not match", file: file, line: line) + #expect(force24.hourCycle == (shouldRespectUserPref ? .zeroToTwentyThree : expectDefault), "force 24-hr did not match", sourceLocation: sourceLocation) let force12 = Locale.localeAsIfCurrent(name: localeID, overrides: .init(force12Hour: true)) - XCTAssertEqual(force12.hourCycle, shouldRespectUserPref ? .oneToTwelve : expectDefault, "force 12-hr did not match", file: file, line: line) + #expect(force12.hourCycle == (shouldRespectUserPref ? .oneToTwelve : expectDefault), "force 12-hr did not match", sourceLocation: sourceLocation) } @@ -579,19 +579,19 @@ final class LocalePropertiesTests : XCTestCase { verifyHourCycle("en_US@hours=h25", .oneToTwelve, shouldRespectUserPref: true) // Incorrect keyword value for "hour cycle" is ignored; correct is "hours=h23" } - func test_userPreferenceOverride_measurementSystem() { - func verify(_ localeID: String, _ expected: Locale.MeasurementSystem, shouldRespectUserPref: Bool, file: StaticString = #filePath, line: UInt = #line) { + @Test func userPreferenceOverride_measurementSystem() { + func verify(_ localeID: String, _ expected: Locale.MeasurementSystem, shouldRespectUserPref: Bool, sourceLocation: SourceLocation = #_sourceLocation) { let localeNoPref = Locale.localeAsIfCurrent(name: localeID, overrides: .init()) - XCTAssertEqual(localeNoPref.measurementSystem, expected, file: file, line: line) + #expect(localeNoPref.measurementSystem == expected, sourceLocation: sourceLocation) let fakeCurrentMetric = Locale.localeAsIfCurrent(name: localeID, overrides: .init(metricUnits: true, measurementUnits: .centimeters)) - XCTAssertEqual(fakeCurrentMetric.measurementSystem, shouldRespectUserPref ? .metric : expected, file: file, line: line) + #expect(fakeCurrentMetric.measurementSystem == (shouldRespectUserPref ? .metric : expected), sourceLocation: sourceLocation) let fakeCurrentUS = Locale.localeAsIfCurrent(name: localeID, overrides: .init(metricUnits: false, measurementUnits: .inches)) - XCTAssertEqual(fakeCurrentUS.measurementSystem, shouldRespectUserPref ? .us : expected, file: file, line: line) + #expect(fakeCurrentUS.measurementSystem == (shouldRespectUserPref ? .us : expected), sourceLocation: sourceLocation) let fakeCurrentUK = Locale.localeAsIfCurrent(name: localeID, overrides: .init(metricUnits: true, measurementUnits: .inches)) - XCTAssertEqual(fakeCurrentUK.measurementSystem, shouldRespectUserPref ? .uk : expected, file: file, line: line) + #expect(fakeCurrentUK.measurementSystem == (shouldRespectUserPref ? .uk : expected), sourceLocation: sourceLocation) } verify("en_US", .us, shouldRespectUserPref: true) @@ -613,62 +613,62 @@ final class LocalePropertiesTests : XCTestCase { @available(iOS, deprecated: 16) @available(tvOS, deprecated: 16) @available(watchOS, deprecated: 9) - func test_properties() { + @Test func properties() { let locale = Locale(identifier: "zh-Hant-HK") - XCTAssertEqual("zh-Hant-HK", locale.identifier) - XCTAssertEqual("zh", locale.languageCode) - XCTAssertEqual("HK", locale.regionCode) - XCTAssertEqual("Hant", locale.scriptCode) - XCTAssertEqual("POSIX", Locale(identifier: "en_POSIX").variantCode) + #expect("zh-Hant-HK" == locale.identifier) + #expect("zh" == locale.languageCode) + #expect("HK" == locale.regionCode) + #expect("Hant" == locale.scriptCode) + #expect("POSIX" == Locale(identifier: "en_POSIX").variantCode) #if FOUNDATION_FRAMEWORK - XCTAssertTrue(locale.exemplarCharacterSet != nil) + #expect(locale.exemplarCharacterSet != nil) #endif // The calendar we get back from Locale has the locale set, but not the one we create with Calendar(identifier:). So we configure our comparison calendar first. var c = Calendar(identifier: .gregorian) c.locale = Locale(identifier: "en_US") - XCTAssertEqual(c, Locale(identifier: "en_US").calendar) + #expect(c == Locale(identifier: "en_US").calendar) let localeCalendar = Locale(identifier: "en_US").calendar - XCTAssertEqual(c, localeCalendar) - XCTAssertEqual(c.identifier, localeCalendar.identifier) - XCTAssertEqual(c.locale, localeCalendar.locale) - XCTAssertEqual(c.timeZone, localeCalendar.timeZone) - XCTAssertEqual(c.firstWeekday, localeCalendar.firstWeekday) - XCTAssertEqual(c.minimumDaysInFirstWeek, localeCalendar.minimumDaysInFirstWeek) - - XCTAssertEqual("「", locale.quotationBeginDelimiter) - XCTAssertEqual("」", locale.quotationEndDelimiter) - XCTAssertEqual("『", locale.alternateQuotationBeginDelimiter) - XCTAssertEqual("』", locale.alternateQuotationEndDelimiter) - XCTAssertEqual("phonebook", Locale(identifier: "en_US@collation=phonebook").collationIdentifier) - XCTAssertEqual(".", locale.decimalSeparator) - - - XCTAssertEqual(".", locale.decimalSeparator) - XCTAssertEqual(",", locale.groupingSeparator) - XCTAssertEqual("HK$", locale.currencySymbol) - XCTAssertEqual("HKD", locale.currencyCode) - - XCTAssertTrue(Locale.availableIdentifiers.count > 0) - XCTAssertTrue(Locale.LanguageCode._isoLanguageCodeStrings.count > 0) - XCTAssertTrue(Locale.Region.isoCountries.count > 0) - XCTAssertTrue(Locale.Currency.isoCurrencies.map { $0.identifier }.count > 0) - XCTAssertTrue(Locale.commonISOCurrencyCodes.count > 0) - - XCTAssertTrue(Locale.preferredLanguages.count > 0) + #expect(c == localeCalendar) + #expect(c.identifier == localeCalendar.identifier) + #expect(c.locale == localeCalendar.locale) + #expect(c.timeZone == localeCalendar.timeZone) + #expect(c.firstWeekday == localeCalendar.firstWeekday) + #expect(c.minimumDaysInFirstWeek == localeCalendar.minimumDaysInFirstWeek) + + #expect("「" == locale.quotationBeginDelimiter) + #expect("」" == locale.quotationEndDelimiter) + #expect("『" == locale.alternateQuotationBeginDelimiter) + #expect("』" == locale.alternateQuotationEndDelimiter) + #expect("phonebook" == Locale(identifier: "en_US@collation=phonebook").collationIdentifier) + #expect("." == locale.decimalSeparator) + + + #expect("." == locale.decimalSeparator) + #expect("," == locale.groupingSeparator) + #expect("HK$" == locale.currencySymbol) + #expect("HKD" == locale.currencyCode) + + #expect(Locale.availableIdentifiers.count > 0) + #expect(Locale.LanguageCode._isoLanguageCodeStrings.count > 0) + #expect(Locale.Region.isoCountries.count > 0) + #expect(Locale.Currency.isoCurrencies.map { $0.identifier }.count > 0) + #expect(Locale.commonISOCurrencyCodes.count > 0) + + #expect(Locale.preferredLanguages.count > 0) // Need to find a good test case for collator identifier - // XCTAssertEqual("something", locale.collatorIdentifier) + // #expect("something" == locale.collatorIdentifier) } - func test_customizedProperties() { + @Test func customizedProperties() { let localePrefs = LocalePreferences(numberSymbols: [0 : "*", 1: "-"]) let customizedLocale = Locale.localeAsIfCurrent(name: "en_US", overrides: localePrefs) - XCTAssertEqual(customizedLocale.decimalSeparator, "*") - XCTAssertEqual(customizedLocale.groupingSeparator, "-") + #expect(customizedLocale.decimalSeparator == "*") + #expect(customizedLocale.groupingSeparator == "-") } - func test_defaultValue() { + @Test func defaultValue() { verify("en_US", expectedLanguage: "en", script: "Latn", languageRegion: "US", region: "US", measurementSystem: .us, calendar: .gregorian, hourCycle: .oneToTwelve, currency: "USD", numberingSystem: "latn", numberingSystems: [ "latn" ], firstDayOfWeek: .sunday, collation: .standard, variant: nil) verify("en_GB", expectedLanguage: "en", script: "Latn", languageRegion: "GB", region: "GB", measurementSystem: .uk, calendar: .gregorian, hourCycle: .zeroToTwentyThree, currency: "GBP", numberingSystem: "latn", numberingSystems: [ "latn" ], firstDayOfWeek: .monday, collation: .standard, variant: nil) @@ -678,7 +678,7 @@ final class LocalePropertiesTests : XCTestCase { verify("ar_EG", expectedLanguage: "ar", script: "arab", languageRegion: "EG", region: "EG", measurementSystem: .metric, calendar: .gregorian, hourCycle: .oneToTwelve, currency: "EGP", numberingSystem: "arab", numberingSystems: [ "latn", "arab" ], firstDayOfWeek: .saturday, collation: .standard, variant: nil) } - func test_keywordOverrides() { + @Test func keywordOverrides() { verify("ar_EG@calendar=ethioaa;collation=dict;currency=frf;fw=fri;hours=h11;measure=uksystem;numbers=traditio;rg=uszzzz", expectedLanguage: "ar", script: "arab", languageRegion: "EG", region: "us", subdivision: nil, measurementSystem: .uk, calendar: .ethiopicAmeteAlem, hourCycle: .zeroToEleven, currency: "FRF", numberingSystem: "traditio", numberingSystems: [ "traditio", "latn", "arab" ], firstDayOfWeek: .friday, collation: "dict") @@ -690,94 +690,95 @@ final class LocalePropertiesTests : XCTestCase { verify("ar_EG@calendar=ethioaa;collation=dict;currency=frf;fw=fri;hours=h11;measure=uksystem;numbers=traditio;rg=uszzzz;sd=usca", expectedLanguage: "ar", script: "arab", languageRegion: "EG", region: "us", subdivision: "usca", measurementSystem: .uk, calendar: .ethiopicAmeteAlem, hourCycle: .zeroToEleven, currency: "FRF", numberingSystem: "traditio", numberingSystems: [ "traditio", "latn", "arab" ], firstDayOfWeek: .friday, collation: "dict") } - func test_longLocaleKeywordValues() { + @Test func longLocaleKeywordValues() { let x = Locale.keywordValue(identifier: "ar_EG@vt=kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk", key: "vt") - XCTAssertEqual(x, "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk") + #expect(x == "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk") } } // MARK: - Bridging Tests #if FOUNDATION_FRAMEWORK -final class LocaleBridgingTests : XCTestCase { +@Suite("Locale Bridging") +private struct LocaleBridgingTests { @available(macOS, deprecated: 13) @available(iOS, deprecated: 16) @available(tvOS, deprecated: 16) @available(watchOS, deprecated: 9) - func test_getACustomLocale() { + @Test func customLocaleSubclass() { let loc = getACustomLocale("en_US") let objCLoc = loc as! CustomNSLocaleSubclass // Verify that accessing the properties of `l` calls back into ObjC - XCTAssertEqual(loc.identifier, "en_US") - XCTAssertEqual(objCLoc.last, "localeIdentifier") + #expect(loc.identifier == "en_US") + #expect(objCLoc.last == "localeIdentifier") - XCTAssertEqual(loc.currencyCode, "USD") - XCTAssertEqual(objCLoc.last, "objectForKey:") // Everything funnels through the primitives + #expect(loc.currencyCode == "USD") + #expect(objCLoc.last == "objectForKey:") // Everything funnels through the primitives - XCTAssertEqual(loc.regionCode, "US") - XCTAssertEqual(objCLoc.countryCode, "US") + #expect(loc.regionCode == "US") + #expect(objCLoc.countryCode == "US") } @available(macOS, deprecated: 13) @available(iOS, deprecated: 16) @available(tvOS, deprecated: 16) @available(watchOS, deprecated: 9) - func test_customLocaleCountryCode() { + @Test func customLocaleCountryCode() { let loc = getACustomLocale("en_US@rg=gbzzzz") let objCLoc = loc as! CustomNSLocaleSubclass - XCTAssertEqual(loc.identifier, "en_US@rg=gbzzzz") - XCTAssertEqual(objCLoc.last, "localeIdentifier") + #expect(loc.identifier == "en_US@rg=gbzzzz") + #expect(objCLoc.last == "localeIdentifier") - XCTAssertEqual(loc.currencyCode, "GBP") - XCTAssertEqual(objCLoc.last, "objectForKey:") // Everything funnels through the primitives + #expect(loc.currencyCode == "GBP") + #expect(objCLoc.last == "objectForKey:") // Everything funnels through the primitives - XCTAssertEqual(loc.regionCode, "GB") - XCTAssertEqual(objCLoc.countryCode, "GB") + #expect(loc.regionCode == "GB") + #expect(objCLoc.countryCode == "GB") } - func test_AnyHashableCreatedFromNSLocale() { + @Test func anyHashableCreatedFromNSLocale() { let values: [NSLocale] = [ NSLocale(localeIdentifier: "en"), NSLocale(localeIdentifier: "uk"), NSLocale(localeIdentifier: "uk"), ] let anyHashables = values.map(AnyHashable.init) - expectEqual(Locale.self, type(of: anyHashables[0].base)) - expectEqual(Locale.self, type(of: anyHashables[1].base)) - expectEqual(Locale.self, type(of: anyHashables[2].base)) - XCTAssertNotEqual(anyHashables[0], anyHashables[1]) - XCTAssertEqual(anyHashables[1], anyHashables[2]) + #expect(Locale.self == type(of: anyHashables[0].base)) + #expect(Locale.self == type(of: anyHashables[1].base)) + #expect(Locale.self == type(of: anyHashables[2].base)) + #expect(anyHashables[0] != anyHashables[1]) + #expect(anyHashables[1] == anyHashables[2]) } - func test_autoupdatingBridge() { + @Test func autoupdatingBridge() { let s1 = Locale.autoupdatingCurrent let s2 = Locale.autoupdatingCurrent let ns1 = s1 as NSLocale let ns2 = s2 as NSLocale // Verify that we don't create a new instance each time this is converted to NSLocale - XCTAssertTrue(ns1 === ns2) + #expect(ns1 === ns2) } - func test_bridgingTwice() { + @Test func bridgingTwice() { let s1 = NSLocale.system let l1 = s1 as Locale let s2 = NSLocale.system let l2 = s2 as Locale - XCTAssertTrue(l1 as NSLocale === l2 as NSLocale) + #expect((l1 as NSLocale) === (l2 as NSLocale)) } - func test_bridgingFixedTwice() { + @Test func bridgingFixedTwice() { let s1 = Locale(identifier: "en_US") let ns1 = s1 as NSLocale let s2 = Locale(identifier: "en_US") let ns2 = s2 as NSLocale - XCTAssertTrue(ns1 === ns2) + #expect(ns1 === ns2) } - func test_bridgingCurrentWithPrefs() { + @Test func bridgingCurrentWithPrefs() { // Verify that 'current with prefs' locales (which have identical identifiers but differing prefs) are correctly cached let s1 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(metricUnits: true), disableBundleMatching: false) let ns1 = s1 as NSLocale @@ -786,9 +787,9 @@ final class LocaleBridgingTests : XCTestCase { let s3 = Locale.localeAsIfCurrent(name: "en_US", overrides: .init(measurementUnits: .centimeters), disableBundleMatching: false) let ns3 = s3 as NSLocale - XCTAssertTrue(ns1 === ns2) - XCTAssertTrue(ns1 !== ns3) - XCTAssertTrue(ns2 !== ns3) + #expect(ns1 === ns2) + #expect(ns1 !== ns3) + #expect(ns2 !== ns3) } } @@ -797,16 +798,16 @@ final class LocaleBridgingTests : XCTestCase { // MARK: - FoundationPreview Disabled Tests #if FOUNDATION_FRAMEWORK extension LocaleTests { - func test_userPreferenceOverride_firstWeekday() { - func verify(_ localeID: String, _ expected: Locale.Weekday, shouldRespectUserPrefForGregorian: Bool, shouldRespectUserPrefForIslamic: Bool, file: StaticString = #filePath, line: UInt = #line) { + @Test func userPreferenceOverride_firstWeekday() { + func verify(_ localeID: String, _ expected: Locale.Weekday, shouldRespectUserPrefForGregorian: Bool, shouldRespectUserPrefForIslamic: Bool, sourceLocation: SourceLocation = #_sourceLocation) { let localeNoPref = Locale.localeAsIfCurrent(name: localeID, overrides: .init(firstWeekday: [:])) - XCTAssertEqual(localeNoPref.firstDayOfWeek, expected, file: file, line: line) + #expect(localeNoPref.firstDayOfWeek == expected, sourceLocation: sourceLocation) let wed = Locale.localeAsIfCurrent(name: localeID, overrides: .init(firstWeekday: [.gregorian : 4])) - XCTAssertEqual(wed.firstDayOfWeek, shouldRespectUserPrefForGregorian ? .wednesday : expected, file: file, line: line) + #expect(wed.firstDayOfWeek == (shouldRespectUserPrefForGregorian ? .wednesday : expected), sourceLocation: sourceLocation) let fri_islamic = Locale.localeAsIfCurrent(name: localeID, overrides: .init(firstWeekday: [.islamic : 6])) - XCTAssertEqual(fri_islamic.firstDayOfWeek, shouldRespectUserPrefForIslamic ? .friday : expected, file: file, line: line) + #expect(fri_islamic.firstDayOfWeek == (shouldRespectUserPrefForIslamic ? .friday : expected), sourceLocation: sourceLocation) } verify("en_US", .sunday, shouldRespectUserPrefForGregorian: true, shouldRespectUserPrefForIslamic: false) @@ -828,7 +829,7 @@ extension LocaleTests { } // TODO: Reenable once (Locale.canonicalIdentifier) is implemented - func test_identifierTypesFromICUIdentifier() throws { + @Test func identifierTypesFromICUIdentifier() throws { verify("und_ZZ", cldr: "und_ZZ", bcp47: "und-ZZ", icu: "und_ZZ") verify("@calendar=gregorian", cldr: "und_u_ca_gregory", bcp47: "und-u-ca-gregory", icu: "@calendar=gregorian") @@ -850,7 +851,7 @@ extension LocaleTests { } // TODO: Reenable once (Locale.canonicalIdentifier) is implemented - func test_identifierTypesFromBCP47Identifier() throws { + @Test func identifierTypesFromBCP47Identifier() throws { verify("fr-FR-1606nict-u-ca-gregory-x-test", cldr: "fr_FR_1606nict_u_ca_gregory_x_test", bcp47: "fr-FR-1606nict-u-ca-gregory-x-test", icu: "fr_FR_1606NICT@calendar=gregorian;x=test") @@ -864,7 +865,7 @@ extension LocaleTests { } // TODO: Reenable once (Locale.canonicalIdentifier) is implemented - func test_identifierTypesFromSpecialIdentifier() throws { + @Test func identifierTypesFromSpecialIdentifier() throws { verify("", cldr: "root", bcp47: "und", icu: "") verify("root", cldr: "root", bcp47: "root", icu: "root") verify("und", cldr: "root", bcp47: "und", icu: "und") @@ -895,13 +896,15 @@ extension LocaleTests { verify("Hant", cldr: "hant", bcp47: "hant", icu: "hant") } - func test_asIfCurrentWithBundleLocalizations() { - let currentLanguage = Locale.current.language.languageCode! - var localizations = Set([ "zh", "fr", "en" ]) - localizations.insert(currentLanguage.identifier) // We're not sure what the current locale is when test runs. Ensure that it's always in the list of available localizations - // Foundation framework-only test - let fakeCurrent = Locale.localeAsIfCurrentWithBundleLocalizations(Array(localizations), allowsMixedLocalizations: false) - XCTAssertEqual(fakeCurrent?.language.languageCode, currentLanguage) + @Test func asIfCurrentWithBundleLocalizations() async { + await usingCurrentInternationalizationPreferences { + let currentLanguage = Locale.current.language.languageCode! + var localizations = Set([ "zh", "fr", "en" ]) + localizations.insert(currentLanguage.identifier) // We're not sure what the current locale is when test runs. Ensure that it's always in the list of available localizations + // Foundation framework-only test + let fakeCurrent = Locale.localeAsIfCurrentWithBundleLocalizations(Array(localizations), allowsMixedLocalizations: false) + #expect(fakeCurrent?.language.languageCode == currentLanguage) + } } } diff --git a/Tests/FoundationInternationalizationTests/PredicateInternationalizationTests.swift b/Tests/FoundationInternationalizationTests/PredicateInternationalizationTests.swift index 83052b101..4d92a374f 100644 --- a/Tests/FoundationInternationalizationTests/PredicateInternationalizationTests.swift +++ b/Tests/FoundationInternationalizationTests/PredicateInternationalizationTests.swift @@ -10,11 +10,10 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing -final class PredicateInternationalizationTests: XCTestCase { +@Suite("Predicate (Internationalization)") +private struct PredicateInternationalizationTests { struct Object { var string: String = "" @@ -22,43 +21,26 @@ final class PredicateInternationalizationTests: XCTestCase { #if FOUNDATION_FRAMEWORK - func testLocalizedCompare() throws { - let predicate = Predicate { - // $0.localizedCompare($1) == $2 - PredicateExpressions.build_Equal( - lhs: PredicateExpressions.build_localizedCompare( - PredicateExpressions.build_Arg($0), - PredicateExpressions.build_Arg($1) - ), - rhs: PredicateExpressions.build_Arg($2) - ) + @Test(arguments: [ + ("ABC", "ABC", ComparisonResult.orderedSame), + ("ABC", "abc", .orderedDescending), + ("abc", "ABC", .orderedAscending), + ("ABC", "ÁḄÇ", .orderedAscending) + ]) + func testLocalizedCompare(input: (String, String, ComparisonResult)) throws { + let predicate = #Predicate { + $0.localizedCompare($1) == $2 } - let tests: [(String, String, ComparisonResult)] = [ - ("ABC", "ABC", .orderedSame), - ("ABC", "abc", .orderedDescending), - ("abc", "ABC", .orderedAscending), - ("ABC", "ÁḄÇ", .orderedAscending) - ] - for test in tests { - XCTAssertTrue(try predicate.evaluate(test.0, test.1, test.2), "Comparison failed for inputs '\(test.0)', '\(test.1)' - expected \(test.2.rawValue)") - } + #expect(try predicate.evaluate(input.0, input.1, input.2), "Comparison failed for inputs '\(input.0)', '\(input.1)' - expected \(input.2.rawValue)") } - func testLocalizedStandardContains() throws { - let predicate = Predicate { - // $0.string.localizedStandardContains("ABC") - PredicateExpressions.build_localizedStandardContains( - PredicateExpressions.build_KeyPath( - root: PredicateExpressions.build_Arg($0), - keyPath: \.string - ), - PredicateExpressions.build_Arg("ABC") - ) + @Test(arguments: ["ABCDEF", "abcdef", "ÁḄÇDEF"]) + func testLocalizedStandardContains(value: String) throws { + let predicate = #Predicate { + $0.string.localizedStandardContains("ABC") } - XCTAssertTrue(try predicate.evaluate(Object(string: "ABCDEF"))) - XCTAssertTrue(try predicate.evaluate(Object(string: "abcdef"))) - XCTAssertTrue(try predicate.evaluate(Object(string: "ÁḄÇDEF"))) + #expect(try predicate.evaluate(Object(string: value))) } #endif diff --git a/Tests/FoundationInternationalizationTests/SortDescriptorConversionTests.swift b/Tests/FoundationInternationalizationTests/SortDescriptorConversionTests.swift index fd3c1724d..5c7cd4bb8 100644 --- a/Tests/FoundationInternationalizationTests/SortDescriptorConversionTests.swift +++ b/Tests/FoundationInternationalizationTests/SortDescriptorConversionTests.swift @@ -10,9 +10,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -22,72 +20,73 @@ import TestSupport #if FOUNDATION_FRAMEWORK -/// Tests interop with Objective-C `NSSortDescriptor`. -class SortDescriptorConversionTests: XCTestCase { - @objcMembers class Root: NSObject { - let word: String - let number: Int - let double: Double - let float: Float - let int16: Int16 - let int32: Int32 - let int64: Int64 - let uInt8: UInt8 - let uInt16: UInt16 - let uInt32: UInt32 - let uInt64: UInt64 - let uInt: UInt - let data: Data - - init( - word: String = "wow", - number: Int = 1, - double: Double = 1, - float: Float = 1, - int16: Int16 = 1, - int32: Int32 = 1, - int64: Int64 = 1, - uInt8: UInt8 = 1, - uInt16: UInt16 = 1, - uInt32: UInt32 = 1, - uInt64: UInt64 = 1, - uInt: UInt = 1, - data: Data = Data() - ) { - self.word = word - self.number = number - self.double = double - self.float = float - self.int16 = int16 - self.int32 = int32 - self.int64 = int64 - self.uInt8 = uInt8 - self.uInt16 = uInt16 - self.uInt32 = uInt32 - self.uInt64 = uInt64 - self.uInt = uInt - self.data = data - } - - override func isEqual(_ object: Any?) -> Bool { - guard let other = object as? Root else { return false } - return self == other - } - - static func ==(_ lhs: Root, _ rhs: Root) -> Bool { - return lhs.word == rhs.word && - lhs.number == rhs.number && - lhs.double == rhs.double && - lhs.float == rhs.float && - lhs.int16 == rhs.int16 && - lhs.int32 == rhs.int32 && - lhs.int64 == rhs.int64 && - lhs.uInt == rhs.uInt && - lhs.data == rhs.data - } +@objcMembers private class Root: NSObject { + let word: String + let number: Int + let double: Double + let float: Float + let int16: Int16 + let int32: Int32 + let int64: Int64 + let uInt8: UInt8 + let uInt16: UInt16 + let uInt32: UInt32 + let uInt64: UInt64 + let uInt: UInt + let data: Data + + init( + word: String = "wow", + number: Int = 1, + double: Double = 1, + float: Float = 1, + int16: Int16 = 1, + int32: Int32 = 1, + int64: Int64 = 1, + uInt8: UInt8 = 1, + uInt16: UInt16 = 1, + uInt32: UInt32 = 1, + uInt64: UInt64 = 1, + uInt: UInt = 1, + data: Data = Data() + ) { + self.word = word + self.number = number + self.double = double + self.float = float + self.int16 = int16 + self.int32 = int32 + self.int64 = int64 + self.uInt8 = uInt8 + self.uInt16 = uInt16 + self.uInt32 = uInt32 + self.uInt64 = uInt64 + self.uInt = uInt + self.data = data + } + + override func isEqual(_ object: Any?) -> Bool { + guard let other = object as? Root else { return false } + return self == other } + + static func ==(_ lhs: Root, _ rhs: Root) -> Bool { + return lhs.word == rhs.word && + lhs.number == rhs.number && + lhs.double == rhs.double && + lhs.float == rhs.float && + lhs.int16 == rhs.int16 && + lhs.int32 == rhs.int32 && + lhs.int64 == rhs.int64 && + lhs.uInt == rhs.uInt && + lhs.data == rhs.data + } +} - func test_sortdescriptor_to_nssortdescriptor_selector_conversion() { +/// Tests interop with Objective-C `NSSortDescriptor`. +@Suite("SortDescriptor Conversion") +struct SortDescriptorConversionTests { + @Test func sortdescriptor_to_nssortdescriptor_selector_conversion() throws { let localizedStandard = SortDescriptor(\Root.word, comparator: .localizedStandard) let localized = SortDescriptor(\Root.word, comparator: .localized) let lexical = SortDescriptor(\Root.word, comparator: .lexical) @@ -95,12 +94,18 @@ class SortDescriptorConversionTests: XCTestCase { let nsLocalized = NSSortDescriptor(localized) let nsLexical = NSSortDescriptor(lexical) - XCTAssert(nsLocalizedStandard.selector != nil) - XCTAssertEqual(NSStringFromSelector(nsLocalizedStandard.selector!), "localizedStandardCompare:") - XCTAssert(nsLocalized.selector != nil) - XCTAssertEqual(NSStringFromSelector(nsLocalized.selector!), "localizedCompare:") - XCTAssert(nsLexical.selector != nil) - XCTAssertEqual(NSStringFromSelector(nsLexical.selector!), "compare:") + do { + let selector = try #require(nsLocalizedStandard.selector) + #expect(NSStringFromSelector(selector) == "localizedStandardCompare:") + } + do { + let selector = try #require(nsLocalized.selector) + #expect(NSStringFromSelector(selector) == "localizedCompare:") + } + do { + let selector = try #require(nsLexical.selector) + #expect(NSStringFromSelector(selector) == "compare:") + } let compareBased: [SortDescriptor] = [ .init(\.word, comparator: .lexical), @@ -118,127 +123,120 @@ class SortDescriptorConversionTests: XCTestCase { for descriptor in compareBased { let nsDescriptor = NSSortDescriptor(descriptor) - XCTAssert(nsDescriptor.selector != nil) - XCTAssertEqual(NSStringFromSelector(nsDescriptor.selector!), "compare:") + let selector = try #require(nsDescriptor.selector) + #expect(NSStringFromSelector(selector) == "compare:") } } - func test_sortdescriptor_to_nssortdescriptor_order_conversion() { + @Test func sortdescriptor_to_nssortdescriptor_order_conversion() { let forward = SortDescriptor(\Root.number, order: .forward) let reverse = SortDescriptor(\Root.number, order: .reverse) let nsAscending = NSSortDescriptor(forward) let nsDescending = NSSortDescriptor(reverse) - XCTAssert(nsAscending.ascending) - XCTAssertFalse(nsDescending.ascending) + #expect(nsAscending.ascending) + #expect(!nsDescending.ascending) } - func test_nssortdescriptor_to_sortdescriptor_conversion() { + @Test func nssortdescriptor_to_sortdescriptor_conversion() { let intDescriptor = NSSortDescriptor(keyPath: \Root.number, ascending: true) - XCTAssertEqual(SortDescriptor(intDescriptor, comparing: Root.self), SortDescriptor(\Root.number)) + #expect(SortDescriptor(intDescriptor, comparing: Root.self) == SortDescriptor(\Root.number)) let stringDescriptor = NSSortDescriptor(keyPath: \Root.word, ascending: true) - XCTAssertEqual(SortDescriptor(stringDescriptor, comparing: Root.self), SortDescriptor(\Root.word, comparator: .lexical)) + #expect(SortDescriptor(stringDescriptor, comparing: Root.self) == SortDescriptor(\Root.word, comparator: .lexical)) // test custom string selector conversion let localizedStandard = NSSortDescriptor(key: "word", ascending: true, selector: #selector(NSString.localizedStandardCompare)) - XCTAssertEqual(SortDescriptor(localizedStandard, comparing: Root.self), SortDescriptor(\Root.word)) + #expect(SortDescriptor(localizedStandard, comparing: Root.self) == SortDescriptor(\Root.word)) let localized = NSSortDescriptor(key: "word", ascending: true, selector: #selector(NSString.localizedCompare)) - XCTAssertEqual(SortDescriptor(localized, comparing: Root.self), SortDescriptor(\Root.word, comparator: .localized)) + #expect(SortDescriptor(localized, comparing: Root.self) == SortDescriptor(\Root.word, comparator: .localized)) } - func test_nssortdescriptor_to_sortdescriptor_conversion_failure() { + @Test func nssortdescriptor_to_sortdescriptor_conversion_failure() throws { let ascending = NSSortDescriptor(keyPath: \Root.word, ascending: true) let descending = NSSortDescriptor(keyPath: \Root.word, ascending: false) - guard let forward = SortDescriptor(ascending, comparing: Root.self) else { - XCTFail() - return - } + let forward = try #require(SortDescriptor(ascending, comparing: Root.self)) + let reverse = try #require(SortDescriptor(descending, comparing: Root.self)) - guard let reverse = SortDescriptor(descending, comparing: Root.self) else { - XCTFail() - return - } - - XCTAssertEqual(forward.order, .forward) - XCTAssertEqual(reverse.order, .reverse) + #expect(forward.order == .forward) + #expect(reverse.order == .reverse) } - func test_conversion_from_uninitializable_descriptor() throws { + @Test func conversion_from_uninitializable_descriptor() throws { let nsDesc = NSSortDescriptor(key: "data", ascending: true) - let desc = try XCTUnwrap(SortDescriptor(nsDesc, comparing: Root.self)) + let desc = try #require(SortDescriptor(nsDesc, comparing: Root.self)) //` NSSortDescriptor`s pointing to `Data` support equality, but not // full comparison so we should be able to get a same result. Anything // else will crash. let compareResult = desc.compare(Root(), Root()) - XCTAssertEqual(compareResult, .orderedSame) + #expect(compareResult == .orderedSame) } - func test_conversion_from_invalid_descriptor() throws { + @Test func conversion_from_invalid_descriptor() throws { let localizedCaseInsensitive = NSSortDescriptor(key: "word", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare)) let caseInsensitive = NSSortDescriptor(key: "word", ascending: true, selector: #selector(NSString.caseInsensitiveCompare)) let caseInsensitiveNumeric = NSSortDescriptor(key: "word", ascending: true, selector: Selector(("_caseInsensitiveNumericCompare:"))) - XCTAssertNil(SortDescriptor(localizedCaseInsensitive, comparing: Root.self)) - XCTAssertNil(SortDescriptor(caseInsensitive, comparing: Root.self)) - XCTAssertNil(SortDescriptor(caseInsensitiveNumeric, comparing: Root.self)) + #expect(SortDescriptor(localizedCaseInsensitive, comparing: Root.self) == nil) + #expect(SortDescriptor(caseInsensitive, comparing: Root.self) == nil) + #expect(SortDescriptor(caseInsensitiveNumeric, comparing: Root.self) == nil) } - func test_key_path_optionality() { - XCTAssertNotNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.word).keyPath) - XCTAssertNotNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeWord).keyPath) - XCTAssertNotNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.gadget).keyPath) - XCTAssertNotNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeGadget).keyPath) + @Test func key_path_optionality() throws { + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.word).keyPath != nil) + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeWord).keyPath != nil) + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.gadget).keyPath != nil) + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeGadget).keyPath != nil) - XCTAssertNil(SortDescriptor(\Root.word).keyPath) - XCTAssertNil(SortDescriptor(\Root.number).keyPath) + #expect(SortDescriptor(\Root.word).keyPath == nil) + #expect(SortDescriptor(\Root.number).keyPath == nil) let ns = NSSortDescriptor(key: "number", ascending: true) - XCTAssertNil(SortDescriptor(ns, comparing: Root.self)!.keyPath) + #expect(try #require(SortDescriptor(ns, comparing: Root.self)).keyPath == nil) } - func test_string_comparator_optionality() { - XCTAssertNotNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.word).stringComparator) - XCTAssertNotNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeWord).stringComparator) - XCTAssertNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.gadget).stringComparator) - XCTAssertNil(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeGadget).stringComparator) + @Test func string_comparator_optionality() throws { + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.word).stringComparator != nil) + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeWord).stringComparator != nil) + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.gadget).stringComparator == nil) + #expect(SortDescriptor(\SortDescriptorTests.NonNSObjectRoot.maybeGadget).stringComparator == nil) - XCTAssertNotNil(SortDescriptor(\Root.word).stringComparator) - XCTAssertNil(SortDescriptor(\Root.number).stringComparator) + #expect(SortDescriptor(\Root.word).stringComparator != nil) + #expect(SortDescriptor(\Root.number).stringComparator == nil) let ns = NSSortDescriptor(key: "word", ascending: true) - XCTAssertNil(SortDescriptor(ns, comparing: Root.self)!.stringComparator) + #expect(try #require(SortDescriptor(ns, comparing: Root.self)).stringComparator == nil) } - func test_ordering() { + @Test func ordering() { let forwardInt = SortDescriptor(\Root.number) - XCTAssertEqual(forwardInt.compare(Root(number: 3), Root(number: 4)), ComparisonResult.orderedAscending) - XCTAssertEqual(forwardInt.compare(Root(number: 4), Root(number: 3)), .orderedDescending) + #expect(forwardInt.compare(Root(number: 3), Root(number: 4)) == ComparisonResult.orderedAscending) + #expect(forwardInt.compare(Root(number: 4), Root(number: 3)) == .orderedDescending) let reverseInt = SortDescriptor(\Root.number, order: .reverse) - XCTAssertEqual(reverseInt.compare(Root(number: 3), Root(number: 4)), .orderedDescending) - XCTAssertEqual(reverseInt.compare(Root(number: 4), Root(number: 3)), .orderedAscending) + #expect(reverseInt.compare(Root(number: 3), Root(number: 4)) == .orderedDescending) + #expect(reverseInt.compare(Root(number: 4), Root(number: 3)) == .orderedAscending) } - func test_mutable_order() { + @Test func mutable_order() { var intComparator = SortDescriptor(\Root.number) - XCTAssertEqual(intComparator.compare(Root(number: 3), Root(number: 4)), .orderedAscending) + #expect(intComparator.compare(Root(number: 3), Root(number: 4)) == .orderedAscending) intComparator.order = .reverse - XCTAssertEqual(intComparator.compare(Root(number: 3), Root(number: 4)), .orderedDescending) + #expect(intComparator.compare(Root(number: 3), Root(number: 4)) == .orderedDescending) } - func test_default_comparator() { + @Test func default_comparator() { let stringComparator = SortDescriptor(\Root.word) - XCTAssertEqual(stringComparator.comparison, .compareString(.localizedStandard)) + #expect(stringComparator.comparison == .compareString(.localizedStandard)) let intDescriptor = SortDescriptor(\Root.number) let intCompare = intDescriptor.comparison - XCTAssertEqual(intCompare, .compare) + #expect(intCompare == .compare) } - func test_sorting_by_keypath_comparator() { - let a = SortDescriptor(\Root.word) + @Test func sorting_by_keypath_comparator() { + let a = SortDescriptor(\Root.word, comparator: .lexical) let b = SortDescriptor(\Root.number) let c = SortDescriptor(\Root.float, order: .reverse) - + let items: [Root] = [ Root(word: "d", number: 10), Root(word: "b", number: -10), @@ -248,7 +246,7 @@ class SortDescriptorConversionTests: XCTestCase { Root(word: "d", number: 5), Root(word: "c", number: 500), ] - + let expectedA: [Root] = [ Root(word: "a", number: 0), Root(word: "b", number: -10), @@ -258,7 +256,7 @@ class SortDescriptorConversionTests: XCTestCase { Root(word: "d", number: 20), Root(word: "d", number: 5), ] - + let expectedAB: [Root] = [ Root(word: "a", number: 0), Root(word: "b", number: -10), @@ -268,7 +266,7 @@ class SortDescriptorConversionTests: XCTestCase { Root(word: "d", number: 10, float: 10), Root(word: "d", number: 20), ] - + let expectedABC: [Root] = [ Root(word: "a", number: 0), Root(word: "b", number: -10), @@ -278,111 +276,110 @@ class SortDescriptorConversionTests: XCTestCase { Root(word: "d", number: 10), Root(word: "d", number: 20), ] - - XCTAssertEqual(items.sorted(using: a), expectedA) - XCTAssertEqual(items.sorted(using: [a, b]), expectedAB) - XCTAssertEqual(items.sorted(using: [a, b, c]), expectedABC) + + #expect(items.sorted(using: a) == expectedA) + #expect(items.sorted(using: [a, b]) == expectedAB) + #expect(items.sorted(using: [a, b, c]) == expectedABC) } - func test_codability() throws { - let descriptor = SortDescriptor(\Root.word, comparator: .localizedStandard) - let encoder = JSONEncoder() - let encoded = try encoder.encode(descriptor) - let decoder = JSONDecoder() - let reconstructed = try decoder.decode(SortDescriptor.self, from: encoded) - XCTAssertEqual(descriptor, reconstructed) - - // ensure the comparison still works after reconstruction - XCTAssertEqual(reconstructed.compare(Root(word: "a"), Root(word: "b")), .orderedAscending) + @Test func codability() async throws { + try await usingCurrentInternationalizationPreferences { + let descriptor = SortDescriptor(\Root.word, comparator: .localizedStandard) + let encoder = JSONEncoder() + let encoded = try encoder.encode(descriptor) + let decoder = JSONDecoder() + let reconstructed = try decoder.decode(SortDescriptor.self, from: encoded) + #expect(descriptor == reconstructed) + + // ensure the comparison still works after reconstruction + #expect(reconstructed.compare(Root(word: "a"), Root(word: "b")) == .orderedAscending) + } } - - func test_decoding_dissallow_invaled() throws { - var otherLocale: Locale { - let attempt = Locale(identifier: "ta") - if Locale.current == attempt { - return Locale(identifier: "en_US") + + @Test func decoding_dissallow_invaled() async throws { + try await usingCurrentInternationalizationPreferences { + var otherLocale: Locale { + let attempt = Locale(identifier: "ta") + if Locale.current == attempt { + return Locale(identifier: "en_US") + } + return attempt } - return attempt - } - - let encoder = JSONEncoder() - let localeStr = String(data: try encoder.encode(Locale.current), encoding: .utf8)! - let otherLocaleStr = String(data: try encoder.encode(otherLocale), encoding: .utf8)! - - let invalidRawValue = """ - { - "order": true, - "keyString": "word", - "comparison": { - "rawValue": 2131, - "stringComparator": { - "options": 1, - "locale": \(localeStr), - "order": true + + let encoder = JSONEncoder() + let localeStr = String(data: try encoder.encode(Locale.current), encoding: .utf8)! + let otherLocaleStr = String(data: try encoder.encode(otherLocale), encoding: .utf8)! + + let invalidRawValue = """ + { + "order": true, + "keyString": "word", + "comparison": { + "rawValue": 2131, + "stringComparator": { + "options": 1, + "locale": \(localeStr), + "order": true + } } } - } - """.data(using: .utf8)! - - let nonStandardComparator = """ - { - "order": true, - "keyString": "word", - "comparison": { - "rawValue": 13, - "stringComparator": { - "options": 8, - "locale": \(localeStr), - "order": true + """.data(using: .utf8)! + + let nonStandardComparator = """ + { + "order": true, + "keyString": "word", + "comparison": { + "rawValue": 13, + "stringComparator": { + "options": 8, + "locale": \(localeStr), + "order": true + } } } - } - """.data(using: .utf8)! - - let nonStandardLocale = """ - { - "order": true, - "keyString": "word", - "comparison": { - "rawValue": 13, - "stringComparator": { - "options": 8, - "locale": \(otherLocaleStr), - "order": true + """.data(using: .utf8)! + + let nonStandardLocale = """ + { + "order": true, + "keyString": "word", + "comparison": { + "rawValue": 13, + "stringComparator": { + "options": 8, + "locale": \(otherLocaleStr), + "order": true + } } } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + + #expect(throws: (any Error).self) { + try decoder.decode(SortDescriptor.self, from: invalidRawValue) + } + + #expect(throws: (any Error).self) { + try decoder.decode(SortDescriptor.self, from: nonStandardComparator) + } + + #expect(throws: (any Error).self) { + let _ = try decoder.decode(SortDescriptor.self, from: nonStandardLocale) + } } - """.data(using: .utf8)! - - let decoder = JSONDecoder() - - do { - let _ = try decoder.decode(SortDescriptor.self, from: invalidRawValue) - XCTFail() - } catch {} - - do { - let _ = try decoder.decode(SortDescriptor.self, from: nonStandardComparator) - XCTFail() - } catch {} - - do { - let _ = try decoder.decode(SortDescriptor.self, from: nonStandardLocale) - XCTFail() - } catch {} } - func test_string_comparator_property_polarity() { + @Test func string_comparator_property_polarity() { // `.stringComparator?.order` should always be `.forward` regardless // of the value of `SortDescriptor().order` - XCTAssertEqual( - SortDescriptor(\Root.word).stringComparator?.order, - .forward + #expect( + SortDescriptor(\Root.word).stringComparator?.order == .forward ) - XCTAssertEqual( - SortDescriptor(\Root.word, order: .reverse).stringComparator?.order, - .forward + #expect( + SortDescriptor(\Root.word, order: .reverse).stringComparator?.order == .forward ) } diff --git a/Tests/FoundationInternationalizationTests/SortDescriptorTests.swift b/Tests/FoundationInternationalizationTests/SortDescriptorTests.swift index 0635a5786..8055abf3f 100644 --- a/Tests/FoundationInternationalizationTests/SortDescriptorTests.swift +++ b/Tests/FoundationInternationalizationTests/SortDescriptorTests.swift @@ -10,9 +10,7 @@ // //===----------------------------------------------------------------------===// -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if FOUNDATION_FRAMEWORK @testable import Foundation @@ -21,14 +19,8 @@ import TestSupport @testable import FoundationInternationalization #endif // FOUNDATION_FRAMEWORK -class Hello { - var str: NSMutableString = "hi" -} - -@available(*, unavailable) -extension Hello : Sendable {} - -final class SortDescriptorTests: XCTestCase { +@Suite("SortDescriptor") +struct SortDescriptorTests { struct NonNSObjectRoot { enum Gadget: Int, Comparable { case foo = 0 @@ -40,7 +32,6 @@ final class SortDescriptorTests: XCTestCase { } } - var o = Hello() let number: Int let word: String let maybeWord: String? @@ -56,176 +47,149 @@ final class SortDescriptorTests: XCTestCase { } } - func test_none_nsobject_comparable() { + @Test func none_nsobject_comparable() { let forwardComparator = SortDescriptor(\NonNSObjectRoot.gadget) let reverseComparator = SortDescriptor(\NonNSObjectRoot.gadget, order: .reverse) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(gadget: .foo), NonNSObjectRoot(gadget: .bar)), - .orderedAscending + #expect( + forwardComparator.compare(NonNSObjectRoot(gadget: .foo), NonNSObjectRoot(gadget: .bar)) == .orderedAscending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(gadget: .foo), NonNSObjectRoot(gadget: .bar)), - .orderedDescending + #expect( + reverseComparator.compare(NonNSObjectRoot(gadget: .foo), NonNSObjectRoot(gadget: .bar)) == .orderedDescending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(gadget: .bar), NonNSObjectRoot(gadget: .baz)), - .orderedDescending + #expect( + forwardComparator.compare(NonNSObjectRoot(gadget: .bar), NonNSObjectRoot(gadget: .baz)) == .orderedDescending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(gadget: .bar), NonNSObjectRoot(gadget: .baz)), - .orderedAscending + #expect( + reverseComparator.compare(NonNSObjectRoot(gadget: .bar), NonNSObjectRoot(gadget: .baz)) == .orderedAscending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(gadget: .baz), NonNSObjectRoot(gadget: .baz)), - .orderedSame + #expect( + forwardComparator.compare(NonNSObjectRoot(gadget: .baz), NonNSObjectRoot(gadget: .baz)) == .orderedSame ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(gadget: .baz), NonNSObjectRoot(gadget: .baz)), - .orderedSame + #expect( + reverseComparator.compare(NonNSObjectRoot(gadget: .baz), NonNSObjectRoot(gadget: .baz)) == .orderedSame ) } - func test_none_nsobject_optional_comparable() { + @Test func none_nsobject_optional_comparable() { let forwardComparator = SortDescriptor(\NonNSObjectRoot.maybeGadget) let reverseComparator = SortDescriptor( \NonNSObjectRoot.maybeGadget, order: .reverse) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeGadget: .foo), NonNSObjectRoot(maybeGadget: .bar)), - .orderedAscending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeGadget: .foo), NonNSObjectRoot(maybeGadget: .bar)) == .orderedAscending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeGadget: .foo), NonNSObjectRoot(maybeGadget: .bar)), - .orderedDescending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeGadget: .foo), NonNSObjectRoot(maybeGadget: .bar)) == .orderedDescending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: .bar)), - .orderedAscending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: .bar)) == .orderedAscending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: .bar)), - .orderedDescending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: .bar)) == .orderedDescending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: .baz)), - .orderedDescending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: .baz)) == .orderedDescending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: .baz)), - .orderedAscending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: .baz)) == .orderedAscending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: nil)), - .orderedDescending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: nil)) == .orderedDescending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: nil)), - .orderedAscending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeGadget: .bar), NonNSObjectRoot(maybeGadget: nil)) == .orderedAscending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeGadget: .baz), NonNSObjectRoot(maybeGadget: .baz)), - .orderedSame + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeGadget: .baz), NonNSObjectRoot(maybeGadget: .baz)) == .orderedSame ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeGadget: .baz), NonNSObjectRoot(maybeGadget: .baz)), - .orderedSame + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeGadget: .baz), NonNSObjectRoot(maybeGadget: .baz)) == .orderedSame ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: nil)), - .orderedSame + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: nil)) == .orderedSame ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: nil)), - .orderedSame + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeGadget: nil), NonNSObjectRoot(maybeGadget: nil)) == .orderedSame ) } - func test_none_nsobject_optional_string_comparable() { - let forwardComparator = SortDescriptor(\NonNSObjectRoot.maybeWord) - let reverseComparator = SortDescriptor(\NonNSObjectRoot.maybeWord, order: .reverse) + @Test func none_nsobject_optional_string_comparable() async { + let forwardComparator = SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .lexical) + let reverseComparator = SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .lexical, order: .reverse) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: "b")), - .orderedAscending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: "b")) == .orderedAscending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: "b")), - .orderedDescending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: "b")) == .orderedDescending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: "b")), - .orderedAscending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: "b")) == .orderedAscending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: "b")), - .orderedDescending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: "b")) == .orderedDescending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: nil)), - .orderedDescending + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: nil)) == .orderedDescending ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: nil)), - .orderedAscending + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeWord: "a"), NonNSObjectRoot(maybeWord: nil)) == .orderedAscending ) - XCTAssertEqual( - forwardComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: nil)), - .orderedSame + #expect( + forwardComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: nil)) == .orderedSame ) - XCTAssertEqual( - reverseComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: nil)), - .orderedSame + #expect( + reverseComparator.compare(NonNSObjectRoot(maybeWord: nil), NonNSObjectRoot(maybeWord: nil)) == .orderedSame ) } - func test_none_nsobject_string_comparison() { - let forwardComparator = SortDescriptor(\NonNSObjectRoot.word) - let reverseComparator = SortDescriptor(\NonNSObjectRoot.word, order: .reverse) + @Test func none_nsobject_string_comparison() { + let forwardComparator = SortDescriptor(\NonNSObjectRoot.word, comparator: .lexical) + let reverseComparator = SortDescriptor(\NonNSObjectRoot.word, comparator: .lexical, order: .reverse) - XCTAssert( + #expect( forwardComparator.compare(NonNSObjectRoot(word: "a"), NonNSObjectRoot(word: "b")) == .orderedAscending ) - XCTAssert( + #expect( reverseComparator.compare(NonNSObjectRoot(word: "a"), NonNSObjectRoot(word: "b")) == .orderedDescending ) } - func test_encoding_comparable_throws() { - let descriptors : [SortDescriptor] = [ - SortDescriptor(\NonNSObjectRoot.word), - SortDescriptor(\NonNSObjectRoot.maybeWord), - SortDescriptor(\NonNSObjectRoot.gadget), - SortDescriptor(\NonNSObjectRoot.maybeGadget), - ] - - for descriptor in descriptors { - let encoder = JSONEncoder() - XCTAssertThrowsError(try encoder.encode(descriptor)) + @Test(arguments: [ + SortDescriptor(\NonNSObjectRoot.word), + SortDescriptor(\NonNSObjectRoot.maybeWord), + SortDescriptor(\NonNSObjectRoot.gadget), + SortDescriptor(\NonNSObjectRoot.maybeGadget), + ]) + func encoding_comparable_throws(descriptor: SortDescriptor) { + let encoder = JSONEncoder() + #expect(throws: (any Error).self) { + try encoder.encode(descriptor) } } @@ -233,82 +197,69 @@ final class SortDescriptorTests: XCTestCase { // TODO: When String.compare(_:options:locale:) is available in FoundationInternationalization, enable these tests // https://github.com/apple/swift-foundation/issues/284 - func test_string_comparator_order() { + @Test func string_comparator_order() { let reverseComparator = { var comparator = String.StandardComparator.localized comparator.order = .reverse return comparator }() - XCTAssertEqual(SortDescriptor(\NonNSObjectRoot.word).order, .forward) + #expect(SortDescriptor(\NonNSObjectRoot.word).order == .forward) - XCTAssertEqual(SortDescriptor(\NonNSObjectRoot.maybeWord).order, .forward) + #expect(SortDescriptor(\NonNSObjectRoot.maybeWord).order == .forward) - XCTAssertEqual(SortDescriptor(\NonNSObjectRoot.word, comparator: .localized).order, .forward) + #expect(SortDescriptor(\NonNSObjectRoot.word, comparator: .localized).order == .forward) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .localized).order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .localized).order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.word, comparator: reverseComparator).order, - .reverse + #expect( + SortDescriptor(\NonNSObjectRoot.word, comparator: reverseComparator).order == .reverse ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: reverseComparator).order, - .reverse + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: reverseComparator).order == .reverse ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.word, comparator: .localized, order: .forward).order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.word, comparator: .localized, order: .forward).order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .localized, order: .forward).order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .localized, order: .forward).order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.word, comparator: reverseComparator, order: .forward).order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.word, comparator: reverseComparator, order: .forward).order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: reverseComparator, order: .forward).order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: reverseComparator, order: .forward).order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.word, comparator: .localized, order: .reverse).order, - .reverse + #expect( + SortDescriptor(\NonNSObjectRoot.word, comparator: .localized, order: .reverse).order == .reverse ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .localized, order: .reverse).order, - .reverse + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord, comparator: .localized, order: .reverse).order == .reverse ) } - func test_string_comparator_property_polarity() { - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.word).stringComparator?.order, - .forward + @Test func string_comparator_property_polarity() { + #expect( + SortDescriptor(\NonNSObjectRoot.word).stringComparator?.order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord).stringComparator?.order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord).stringComparator?.order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.word, order: .reverse).stringComparator?.order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.word, order: .reverse).stringComparator?.order == .forward ) - XCTAssertEqual( - SortDescriptor(\NonNSObjectRoot.maybeWord, order: .reverse).stringComparator?.order, - .forward + #expect( + SortDescriptor(\NonNSObjectRoot.maybeWord, order: .reverse).stringComparator?.order == .forward ) } #endif diff --git a/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift b/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift index a4bf70d11..8cc167c87 100644 --- a/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift +++ b/Tests/FoundationInternationalizationTests/StringSortComparatorTests.swift @@ -45,10 +45,15 @@ private struct StringSortComparatorTests { #expect(swedishComparator.compare("ă", "ã") == .orderedDescending) } - @Test(.enabled(if: Locale.current.language.languageCode == .english, "Test only verified to work with English as current language")) - func standardLocalized() throws { - let localizedStandard = String.StandardComparator.localizedStandard - #expect(localizedStandard.compare("ă", "ã") == .orderedAscending) + @Test func standardLocalized() async { + await usingCurrentInternationalizationPreferences { + var prefs = LocalePreferences() + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + let localizedStandard = String.StandardComparator.localizedStandard + #expect(localizedStandard.compare("ă", "ã") == .orderedAscending) + } let unlocalizedStandard = String.StandardComparator.lexical #expect(unlocalizedStandard.compare("ă", "ã") == .orderedDescending) From 5d54b930b9846b37998afec785d52d8f80d3c8b2 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Wed, 2 Jul 2025 08:22:50 -0700 Subject: [PATCH 09/12] build: adjust the autolink library for Windows (#1312) When building statically, ensure that we prefix the Swift libraries with the correct prefix to permit static linking of the libraries. --- Sources/FoundationEssentials/CMakeLists.txt | 2 +- Sources/FoundationInternationalization/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/FoundationEssentials/CMakeLists.txt b/Sources/FoundationEssentials/CMakeLists.txt index b88ee86d7..c25d493c6 100644 --- a/Sources/FoundationEssentials/CMakeLists.txt +++ b/Sources/FoundationEssentials/CMakeLists.txt @@ -102,7 +102,7 @@ if(NOT BUILD_SHARED_LIBS) target_compile_options(FoundationEssentials PRIVATE "SHELL:$<$:-Xfrontend -public-autolink-library -Xfrontend $<$:${CMAKE_STATIC_LIBRARY_PREFIX_Swift}>_FoundationCollections>") target_compile_options(FoundationEssentials PRIVATE - "SHELL:$<$:-Xfrontend -public-autolink-library -Xfrontend swiftSynchronization>") + "SHELL:$<$:-Xfrontend -public-autolink-library -Xfrontend $<$:${CMAKE_STATIC_LIBRARY_PREFIX_Swift}>swiftSynchronization>") endif() set_target_properties(FoundationEssentials PROPERTIES diff --git a/Sources/FoundationInternationalization/CMakeLists.txt b/Sources/FoundationInternationalization/CMakeLists.txt index 6cf0629ef..7edec0fb4 100644 --- a/Sources/FoundationInternationalization/CMakeLists.txt +++ b/Sources/FoundationInternationalization/CMakeLists.txt @@ -49,7 +49,7 @@ if(NOT BUILD_SHARED_LIBS) target_compile_options(FoundationInternationalization PRIVATE "SHELL:$<$:-Xfrontend -public-autolink-library -Xfrontend _FoundationICU>") target_compile_options(FoundationEssentials PRIVATE - "SHELL:$<$:-Xfrontend -public-autolink-library -Xfrontend swiftSynchronization>") + "SHELL:$<$:-Xfrontend -public-autolink-library -Xfrontend $<$:${CMAKE_STATIC_LIBRARY_PREFIX_Swift}>swiftSynchronization>") endif() set_target_properties(FoundationInternationalization PROPERTIES From 6d877883519efa0f20b34b039a9bb148e7095bdd Mon Sep 17 00:00:00 2001 From: Tina L <49205802+itingliu@users.noreply.github.com> Date: Wed, 2 Jul 2025 08:44:15 -0700 Subject: [PATCH 10/12] Fix running benchmarks (Issue 1386) (#1393) * Attempted fix * Fix URL temporary directory * Fix invalid redeclaration of 'benchmarks' --- Benchmarks/Benchmarks/Formatting/BenchmarkFormatting.swift | 1 + .../Benchmarks/Internationalization/BenchmarkCalendar.swift | 4 +++- .../Benchmarks/Internationalization/BenchmarkLocale.swift | 2 +- .../InternationalizationBenchmark.swift | 6 ++++++ Benchmarks/Benchmarks/String/BenchmarkString.swift | 6 +++--- Benchmarks/Package.swift | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 Benchmarks/Benchmarks/Internationalization/InternationalizationBenchmark.swift diff --git a/Benchmarks/Benchmarks/Formatting/BenchmarkFormatting.swift b/Benchmarks/Benchmarks/Formatting/BenchmarkFormatting.swift index cdd51b86e..f38d204d7 100644 --- a/Benchmarks/Benchmarks/Formatting/BenchmarkFormatting.swift +++ b/Benchmarks/Benchmarks/Formatting/BenchmarkFormatting.swift @@ -16,6 +16,7 @@ import Dispatch #if os(macOS) && USE_PACKAGE import FoundationEssentials +import FoundationInternationalization #else import Foundation #endif diff --git a/Benchmarks/Benchmarks/Internationalization/BenchmarkCalendar.swift b/Benchmarks/Benchmarks/Internationalization/BenchmarkCalendar.swift index d611487e4..55f072b1e 100644 --- a/Benchmarks/Benchmarks/Internationalization/BenchmarkCalendar.swift +++ b/Benchmarks/Benchmarks/Internationalization/BenchmarkCalendar.swift @@ -20,7 +20,8 @@ import FoundationInternationalization import Foundation #endif -let benchmarks = { +func calendarBenchmarks() { + Benchmark.defaultConfiguration.maxIterations = 1_000 Benchmark.defaultConfiguration.maxDuration = .seconds(3) Benchmark.defaultConfiguration.scalingFactor = .kilo @@ -229,3 +230,4 @@ let benchmarks = { } } } + diff --git a/Benchmarks/Benchmarks/Internationalization/BenchmarkLocale.swift b/Benchmarks/Benchmarks/Internationalization/BenchmarkLocale.swift index 16a6b82d4..273fdd5b5 100644 --- a/Benchmarks/Benchmarks/Internationalization/BenchmarkLocale.swift +++ b/Benchmarks/Benchmarks/Internationalization/BenchmarkLocale.swift @@ -20,7 +20,7 @@ import FoundationInternationalization import Foundation #endif -let benchmarks = { +func localeBenchmarks() { Benchmark.defaultConfiguration.maxIterations = 1_000 Benchmark.defaultConfiguration.maxDuration = .seconds(3) Benchmark.defaultConfiguration.scalingFactor = .kilo diff --git a/Benchmarks/Benchmarks/Internationalization/InternationalizationBenchmark.swift b/Benchmarks/Benchmarks/Internationalization/InternationalizationBenchmark.swift new file mode 100644 index 000000000..759e9eba7 --- /dev/null +++ b/Benchmarks/Benchmarks/Internationalization/InternationalizationBenchmark.swift @@ -0,0 +1,6 @@ +import Benchmark + +let benchmarks = { + calendarBenchmarks() + localeBenchmarks() +} diff --git a/Benchmarks/Benchmarks/String/BenchmarkString.swift b/Benchmarks/Benchmarks/String/BenchmarkString.swift index b92363a92..b46dc3969 100644 --- a/Benchmarks/Benchmarks/String/BenchmarkString.swift +++ b/Benchmarks/Benchmarks/String/BenchmarkString.swift @@ -19,7 +19,7 @@ import FoundationEssentials import Foundation #endif -#if !os(macOS) +#if !FOUNDATION_FRAMEWORK private func autoreleasepool(_ block: () -> T) -> T { block() } #endif @@ -157,7 +157,7 @@ let benchmarks = { let str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." Benchmark("read-utf8") { benchmark in - let rootURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString, isDirectory: true) + let rootURL = URL.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) #if compiler(>=6) let fileURL = rootURL.appending(path: "benchmark.txt", directoryHint: .notDirectory) #else @@ -178,7 +178,7 @@ let benchmarks = { } Benchmark("read-utf16") { benchmark in - let rootURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString, isDirectory: true) + let rootURL = URL.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true) #if compiler(>=6) let fileURL = rootURL.appending(path: "benchmark.txt", directoryHint: .notDirectory) #else diff --git a/Benchmarks/Package.swift b/Benchmarks/Package.swift index 1879f73c7..fa0854186 100644 --- a/Benchmarks/Package.swift +++ b/Benchmarks/Package.swift @@ -54,7 +54,7 @@ print("swift-foundation benchmarks: \(usePackage.description)") var packageDependency : [Package.Dependency] = [.package(url: "https://github.com/ordo-one/package-benchmark.git", from: "1.11.1")] var targetDependency : [Target.Dependency] = [.product(name: "Benchmark", package: "package-benchmark")] var i18nTargetDependencies : [Target.Dependency] = [] -var swiftSettings : [SwiftSetting] = [] +var swiftSettings : [SwiftSetting] = [.unsafeFlags(["-Rmodule-loading"]), .enableUpcomingFeature("MemberImportVisibility")] switch usePackage { case .useLocalPackage(let root): From 3ead71fa848271c2636e3c5dbcb39b78c9b93e22 Mon Sep 17 00:00:00 2001 From: Tina L <49205802+itingliu@users.noreply.github.com> Date: Wed, 2 Jul 2025 08:48:14 -0700 Subject: [PATCH 11/12] Work towards leap month bug fixes (#1399) There is a bug in ICU that returns the wrong value for min/max range. Workaround that here. Also fix the same issue in Gregorian calendar, where the returned value isn't sensible. 153548677 --- .../Calendar/Calendar_Gregorian.swift | 9 +- .../Calendar/Calendar_ICU.swift | 10 +- .../Calendar/Calendar_ObjC.swift | 1 + .../GregorianCalendarTests.swift | 93 +++++++++++++++++++ 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift b/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift index 1060a9cfa..72737abfa 100644 --- a/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift +++ b/Sources/FoundationEssentials/Calendar/Calendar_Gregorian.swift @@ -350,7 +350,8 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable case .weekOfYear: 1..<53 case .yearForWeekOfYear: 140742..<140743 case .nanosecond: 0..<1000000000 - case .isLeapMonth: 0..<2 + // There is no leap month in Gregorian calendar + case .isLeapMonth: 0..<1 case .dayOfYear: 1..<366 case .calendar, .timeZone: nil @@ -380,7 +381,7 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable case .weekOfYear: return 1..<54 case .yearForWeekOfYear: return 140742..<144684 case .nanosecond: return 0..<1000000000 - case .isLeapMonth: return 0..<2 + case .isLeapMonth: return 0..<1 case .dayOfYear: return 1..<367 case .calendar, .timeZone: return nil @@ -1654,6 +1655,10 @@ internal final class _CalendarGregorian: _CalendarProtocol, @unchecked Sendable if let value = components.hour { guard validHour.contains(value) else { return false } } if let value = components.minute { guard validMinute.contains(value) else { return false } } if let value = components.second { guard validSecond.contains(value) else { return false } } + if let value = components.isLeapMonth { + // The only valid `isLeapMonth` setting is false + return value == false + } return true } diff --git a/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift b/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift index 23de92073..136e85586 100644 --- a/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift +++ b/Sources/FoundationInternationalization/Calendar/Calendar_ICU.swift @@ -287,7 +287,15 @@ internal final class _CalendarICU: _CalendarProtocol, @unchecked Sendable { return 1..<5 case .calendar, .timeZone: return nil - case .era, .year, .month, .day, .weekdayOrdinal, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .isLeapMonth, .dayOfYear: + case .isLeapMonth: + // Fast path but also workaround an ICU bug where they return 1 as the max value even for calendars without leap month + let hasLeapMonths = identifier == .chinese || identifier == .dangi || identifier == .gujarati || identifier == .kannada || identifier == .marathi || identifier == .telugu || identifier == .vietnamese || identifier == .vikram + if !hasLeapMonths { + return 0..<1 + } else { + return nil + } + case .era, .year, .month, .day, .weekdayOrdinal, .weekOfMonth, .weekOfYear, .yearForWeekOfYear, .dayOfYear: return nil } } diff --git a/Sources/FoundationInternationalization/Calendar/Calendar_ObjC.swift b/Sources/FoundationInternationalization/Calendar/Calendar_ObjC.swift index e5867deab..5dfad26d3 100644 --- a/Sources/FoundationInternationalization/Calendar/Calendar_ObjC.swift +++ b/Sources/FoundationInternationalization/Calendar/Calendar_ObjC.swift @@ -634,6 +634,7 @@ private func _fromNSCalendarUnit(_ unit: NSCalendar.Unit) -> Calendar.Component? case .calendar: return .calendar case .timeZone: return .timeZone case .deprecatedWeekUnit: return .weekOfYear + case .isLeapMonth: return .isLeapMonth default: return nil } diff --git a/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift b/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift index 1ba4f0a15..3e30b89f1 100644 --- a/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift +++ b/Tests/FoundationEssentialsTests/GregorianCalendarTests.swift @@ -43,6 +43,99 @@ private struct GregorianCalendarTests { _ = d.julianDay } + // MARK: Leap month + @Test func calendarUnitLeapMonth_gregorianCalendar() { + // Test leap month with a calendar that does not observe leap month + + // Gregorian: 2023-03-22. + let date1 = Date(timeIntervalSinceReferenceDate: 701161200) + // Gregorian: 2023-03-02. + let date2 = Date(timeIntervalSinceReferenceDate: 699433200) + + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = .gmt + + let minRange = calendar.minimumRange(of: .isLeapMonth) + #expect(minRange?.lowerBound == 0) + #expect(minRange?.count == 1) + + let maxRange = calendar.maximumRange(of: .isLeapMonth) + #expect(maxRange?.lowerBound == 0) + #expect(maxRange?.count == 1) + + let leapMonthRange = calendar.range(of: .isLeapMonth, in: .year, for: date1) + #expect(leapMonthRange == nil) + + let dateIntervial = calendar.dateInterval(of: .isLeapMonth, for: date1) + #expect(dateIntervial == nil) + + // Invalid ordinality flag + let ordinal = calendar.ordinality(of: .isLeapMonth, in: .year, for: date1) + #expect(ordinal == nil) + + // Invalid ordinality flag + let ordinal2 = calendar.ordinality(of: .day, in: .isLeapMonth, for: date1) + #expect(ordinal2 == nil) + + let extractedComponents = calendar.dateComponents([.year, .month], from: date1) + #expect(extractedComponents.isLeapMonth == false) + #expect(extractedComponents.month == 3) + + let isLeap = calendar.component(.isLeapMonth, from: date1) + #expect(isLeap == 0) + + let extractedLeapMonthComponents_onlyLeapMonth = calendar.dateComponents([.isLeapMonth], from: date1) + #expect(extractedLeapMonthComponents_onlyLeapMonth.isLeapMonth == false) + + let extractedLeapMonthComponents = calendar.dateComponents([.isLeapMonth, .month], from: date1) + #expect(extractedLeapMonthComponents.isLeapMonth == false) + #expect(extractedLeapMonthComponents.month == 3) + + let isEqualMonth = calendar.isDate(date1, equalTo: date2, toGranularity: .month) + #expect(isEqualMonth) // Both are in month 3 + + let isEqualLeapMonth = calendar.isDate(date1, equalTo: date2, toGranularity: .isLeapMonth) + #expect(isEqualLeapMonth) // Both are not in leap month + + // Invalid granularity flag. Return what we return for other invalid `Calendar.Component` inputs + let result = calendar.compare(date1, to: date2, toGranularity: .month) + #expect(result == .orderedSame) + + // Invalid granularity flag. Return what we return for other invalid `Calendar.Component` inputs + let onlyLeapMonthComparisonResult = calendar.compare(date1, to: date2, toGranularity: .isLeapMonth) + #expect(onlyLeapMonthComparisonResult == .orderedSame) + + let nextLeapMonthDate = calendar.nextDate(after: date1, matching: DateComponents(isLeapMonth: true), matchingPolicy: .strict) + #expect(nextLeapMonthDate == nil) // There is not a date in Gregorian that is a leap month + +#if FIXED_SINGLE_LEAPMONTH + let nextNonLeapMonthDate = calendar.nextDate(after: date1, matching: DateComponents(isLeapMonth: false), matchingPolicy: .strict) + #expect(nextNonLeapMonthDate == date1) // date1 matches the condition already +#endif + + var settingLeapMonthComponents = calendar.dateComponents([.year, .month, .day], from: date1) + settingLeapMonthComponents.isLeapMonth = true + let settingLeapMonthDate = calendar.date(from: settingLeapMonthComponents) + #expect(settingLeapMonthDate == nil) // There is not a date in Gregorian that is a leap month + + var settingNonLeapMonthComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date1) + settingNonLeapMonthComponents.isLeapMonth = false + let settingNonLeapMonthDate = calendar.date(from: settingNonLeapMonthComponents) + #expect(settingNonLeapMonthDate == date1) // date1 matches the condition already + + let diffComponents = calendar.dateComponents([.month, .day, .isLeapMonth], from: date1, to: date2) + #expect(diffComponents.month == 0) + #expect(diffComponents.isLeapMonth == nil) + #expect(diffComponents.day == -20) + + let addedDate = calendar.date(byAdding: .isLeapMonth, value: 1, to: date1) + #expect(addedDate == nil) + + // Invalid argument; cannot add a boolean component with an integer value + let addedDate_notLeap = calendar.date(byAdding: .isLeapMonth, value: 0, to: date1) + #expect(addedDate_notLeap == nil) + } + // MARK: Date from components @Test func testDateFromComponents() { From 1d5d70997410fc8b7700c8648b10d6fc28194202 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 3 Jul 2025 13:27:00 -0700 Subject: [PATCH 12/12] Finish FoundationInternationalization swift-testing migration (#1402) --- .../ProcessInfoTests.swift | 8 +- .../Formatting/ListFormatStyleTests.swift | 85 +- .../Formatting/NumberFormatStyleTests.swift | 1194 ++++++++--------- .../Formatting/NumberParseStrategyTests.swift | 398 +++--- 4 files changed, 861 insertions(+), 824 deletions(-) diff --git a/Tests/FoundationEssentialsTests/ProcessInfoTests.swift b/Tests/FoundationEssentialsTests/ProcessInfoTests.swift index cad8f18dd..3661bb5d2 100644 --- a/Tests/FoundationEssentialsTests/ProcessInfoTests.swift +++ b/Tests/FoundationEssentialsTests/ProcessInfoTests.swift @@ -159,15 +159,15 @@ private struct ProcessInfoTests { @Test func processName() { #if FOUNDATION_FRAMEWORK - let targetName = "TestHost" + let targetNames = ["TestHost"] #elseif os(Linux) || os(Windows) || os(Android) || os(FreeBSD) - let targetName = "swift-foundationPackageTests.xctest" + let targetNames = ["swift-foundationPackageTests.xctest"] #else - let targetName = "swiftpm-testing-helper" + let targetNames = ["swiftpm-testing-helper", "xctest"] #endif let processInfo = ProcessInfo.processInfo let originalProcessName = processInfo.processName - #expect(originalProcessName == targetName) + #expect(targetNames.contains(originalProcessName)) // Try assigning a new process name. let newProcessName = "TestProcessName" diff --git a/Tests/FoundationInternationalizationTests/Formatting/ListFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/ListFormatStyleTests.swift index 8fc7fad38..89b87a241 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/ListFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/ListFormatStyleTests.swift @@ -5,14 +5,8 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @@ -23,68 +17,69 @@ import TestSupport @testable import Foundation #endif -class ListFormatStyleTests : XCTestCase { - func test_orList() { +@Suite("ListFormatStyle") +private struct ListFormatStyleTests { + @Test func orList() { var style: ListFormatStyle = .list(type: .or, width: .standard) style.locale = Locale(identifier: "en_US") - XCTAssertEqual(["one", "two"].formatted(style), "one or two") - XCTAssertEqual(["one", "two", "three"].formatted(style), "one, two, or three") + #expect(["one", "two"].formatted(style) == "one or two") + #expect(["one", "two", "three"].formatted(style) == "one, two, or three") } - func test_andList() { + @Test func andList() { var style: ListFormatStyle = .list(type: .and, width: .standard) style.locale = Locale(identifier: "en_US") - XCTAssertEqual(["one", "two"].formatted(style), "one and two") - XCTAssertEqual(["one", "two", "three"].formatted(style), "one, two, and three") + #expect(["one", "two"].formatted(style) == "one and two") + #expect(["one", "two", "three"].formatted(style) == "one, two, and three") } - func test_narrowList() { + @Test func narrowList() { var style: ListFormatStyle = .list(type: .and, width: .narrow) style.locale = Locale(identifier: "en_US") - XCTAssertEqual(["one", "two"].formatted(style), "one, two") - XCTAssertEqual(["one", "two", "three"].formatted(style), "one, two, three") + #expect(["one", "two"].formatted(style) == "one, two") + #expect(["one", "two", "three"].formatted(style) == "one, two, three") } - func test_shortList() { + @Test func shortList() { var style: ListFormatStyle = .list(type: .and, width: .short) style.locale = Locale(identifier: "en_US") - XCTAssertEqual(["one", "two"].formatted(style), "one & two") - XCTAssertEqual(["one", "two", "three"].formatted(style), "one, two, & three") + #expect(["one", "two"].formatted(style) == "one & two") + #expect(["one", "two", "three"].formatted(style) == "one, two, & three") } -#if FOUNDATION_FRAMEWORK // FIXME: rdar://104091257 - func test_leadingDotSyntax() { + @Test func leadingDotSyntax() { let _ = ["one", "two"].formatted(.list(type: .and)) let _ = ["one", "two"].formatted() let _ = [1, 2].formatted(.list(memberStyle: .number, type: .or, width: .standard)) } -#endif - func testAutoupdatingCurrentChangesFormatResults() { - let locale = Locale.autoupdatingCurrent - let list = ["one", "two", "three", "four"] - - // Get a formatted result from es-ES - var prefs = LocalePreferences() - prefs.languages = ["es-ES"] - prefs.locale = "es_ES" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedSpanish = list.formatted(.list(type: .and).locale(locale)) - - // Get a formatted result from en-US - prefs.languages = ["en-US"] - prefs.locale = "en_US" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedEnglish = list.formatted(.list(type: .and).locale(locale)) - - // Reset to current preferences before any possibility of failing this test - LocaleCache.cache.reset() - - // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. - XCTAssertNotEqual(formattedSpanish, formattedEnglish) + @Test func autoupdatingCurrentChangesFormatResults() async { + await usingCurrentInternationalizationPreferences { + let locale = Locale.autoupdatingCurrent + let list = ["one", "two", "three", "four"] + + // Get a formatted result from es-ES + var prefs = LocalePreferences() + prefs.languages = ["es-ES"] + prefs.locale = "es_ES" + LocaleCache.cache.resetCurrent(to: prefs) + let formattedSpanish = list.formatted(.list(type: .and).locale(locale)) + + // Get a formatted result from en-US + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + let formattedEnglish = list.formatted(.list(type: .and).locale(locale)) + + // Reset to current preferences before any possibility of failing this test + LocaleCache.cache.reset() + + // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. + #expect(formattedSpanish != formattedEnglish) + } } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift b/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift index 2623c40cf..5b4a4faef 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift @@ -5,127 +5,107 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class NumberFormatStyleTests: XCTestCase { - +@Suite("Number FormatStyle") +private struct NumberFormatStyleTests { let enUSLocale = Locale(identifier: "en_US") let frFRLocale = Locale(identifier: "fr_FR") - override func setUp() { - resetAllNumberFormatterCaches() - } - let testNegativePositiveIntegerData: [Int] = [ -98, -9, 0, 9, 98 ] let testNegativePositiveDoubleData: [Double] = [ 87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0, -0.008765, -876.5, -87650 ] let testNegativePositiveDecimalData: [Decimal] = [ Decimal(string:"87650")!, Decimal(string:"8765")!, Decimal(string:"876.5")!, Decimal(string:"87.65")!, Decimal(string:"8.765")!, Decimal(string:"0.8765")!, Decimal(string:"0.08765")!, Decimal(string:"0.008765")!, Decimal(string:"0")!, Decimal(string:"-0.008765")!, Decimal(string:"-876.5")!, Decimal(string:"-87650")! ] - func _testNegativePositiveInt(_ style: F, _ expected: [String], _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) where F.FormatInput == Int, F.FormatOutput == String { + func _testNegativePositiveInt(_ style: F, _ expected: [String], _ testName: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation) where F.FormatInput == Int, F.FormatOutput == String { for i in 0..(_ style: F, _ expected: [String], _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) where F.FormatInput == Double, F.FormatOutput == String { + func _testNegativePositiveDouble(_ style: F, _ expected: [String], _ testName: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation) where F.FormatInput == Double, F.FormatOutput == String { for i in 0..(_ style: F, _ expected: [String], _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) where F.FormatInput == Decimal, F.FormatOutput == String { + func _testNegativePositiveDecimal(_ style: F, _ expected: [String], _ testName: Comment? = nil, sourceLocation: SourceLocation = #_sourceLocation) where F.FormatInput == Decimal, F.FormatOutput == String { for i in 0.. = IntegerFormatStyle().locale(locale) - let formatter = ICUNumberFormatter.create(for: style) - XCTAssertNotNil(formatter) + let style = IntegerFormatStyle().locale(locale) + let formatter = try #require(ICUNumberFormatter.create(for: style)) - XCTAssertEqual(formatter!.format(42 as Int64), "42") - XCTAssertEqual(formatter!.format(42 as Double), "42") + #expect(formatter.format(42 as Int64) == "42") + #expect(formatter.format(42 as Double) == "42") // Test strings longer than the stack buffer - let longStyle: IntegerFormatStyle = IntegerFormatStyle().locale(locale).precision(.integerAndFractionLength(integerLimits: 40..., fractionLimits: 0..<1)) - let formatter_long = ICUNumberFormatter.create(for: longStyle) - XCTAssertNotNil(formatter_long) - XCTAssertEqual(formatter_long!.format(42 as Int64), "0,000,000,000,000,000,000,000,000,000,000,000,000,042") - XCTAssertEqual(formatter_long!.format(42 as Double), "0,000,000,000,000,000,000,000,000,000,000,000,000,042") + let longStyle = IntegerFormatStyle().locale(locale).precision(.integerAndFractionLength(integerLimits: 40..., fractionLimits: 0..<1)) + let formatter_long = try #require(ICUNumberFormatter.create(for: longStyle)) + #expect(formatter_long.format(42 as Int64) == "0,000,000,000,000,000,000,000,000,000,000,000,000,042") + #expect(formatter_long.format(42 as Double) == "0,000,000,000,000,000,000,000,000,000,000,000,000,042") } #if !os(watchOS) // 99504292 - func testNSICUNumberFormatterCache() throws { + @Test func nsICUNumberFormatterCache() throws { - let intStyle: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) - let intFormatter = ICUNumberFormatter.create(for: intStyle) - XCTAssertNotNil(intFormatter) + let intStyle = IntegerFormatStyle(locale: Locale(identifier: "en_US")) + let intFormatter = try #require(ICUNumberFormatter.create(for: intStyle)) - let int64Style: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) - let int64Formatter = ICUNumberFormatter.create(for: int64Style) - XCTAssertNotNil(int64Formatter) + let int64Style = IntegerFormatStyle(locale: Locale(identifier: "en_US")) + let int64Formatter = try #require(ICUNumberFormatter.create(for: int64Style)) - XCTAssertEqual(intFormatter!.uformatter, int64Formatter!.uformatter) + #expect(intFormatter.uformatter == int64Formatter.uformatter) // -- - let int64StyleFr: IntegerFormatStyle = .init(locale: Locale(identifier: "fr_FR")) - let int64FrFormatter = ICUNumberFormatter.create(for: int64StyleFr) - XCTAssertNotNil(int64FrFormatter) + let int64StyleFr = IntegerFormatStyle(locale: Locale(identifier: "fr_FR")) + let int64FrFormatter = try #require(ICUNumberFormatter.create(for: int64StyleFr)) // Different formatter for different locale - XCTAssertNotEqual(intFormatter!.uformatter, int64FrFormatter!.uformatter) + #expect(intFormatter.uformatter != int64FrFormatter.uformatter) - let int64StylePrecision: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")).precision(.integerLength(10...)) - let int64PrecisionFormatter = ICUNumberFormatter.create(for: int64StylePrecision) + let int64StylePrecision = IntegerFormatStyle(locale: Locale(identifier: "en_US")).precision(.integerLength(10...)) + let int64PrecisionFormatter = try #require(ICUNumberFormatter.create(for: int64StylePrecision)) // Different formatter for different precision - XCTAssertNotEqual(intFormatter!.uformatter, int64PrecisionFormatter!.uformatter) + #expect(intFormatter.uformatter != int64PrecisionFormatter.uformatter) // -- - let floatStyle: FloatingPointFormatStyle = .init(locale: Locale(identifier: "en_US")) - let floatFormatter = ICUNumberFormatter.create(for: floatStyle) - XCTAssertNotNil(floatFormatter) - XCTAssertEqual(floatFormatter!.uformatter, int64Formatter!.uformatter) + let floatStyle = FloatingPointFormatStyle(locale: Locale(identifier: "en_US")) + let floatFormatter = try #require(ICUNumberFormatter.create(for: floatStyle)) + #expect(floatFormatter.uformatter == int64Formatter.uformatter) - let doubleStyle: FloatingPointFormatStyle = .init(locale: Locale(identifier: "en_US")) - let doubleFormatter = ICUNumberFormatter.create(for: doubleStyle) - XCTAssertNotNil(doubleFormatter) - XCTAssertEqual(doubleFormatter!.uformatter, floatFormatter!.uformatter) + let doubleStyle = FloatingPointFormatStyle(locale: Locale(identifier: "en_US")) + let doubleFormatter = try #require(ICUNumberFormatter.create(for: doubleStyle)) + #expect(doubleFormatter.uformatter == floatFormatter.uformatter) - let doubleCurrencyStyle: FloatingPointFormatStyle.Currency = .init(code: "USD", locale: Locale(identifier: "en_US")) - let doubleCurrencyFormatter = ICUCurrencyNumberFormatter.create(for: doubleCurrencyStyle) - XCTAssertNotNil(doubleCurrencyFormatter) - XCTAssertNotEqual(doubleCurrencyFormatter!.uformatter, doubleFormatter!.uformatter, "Should use a different uformatter for an unseen style") + let doubleCurrencyStyle = FloatingPointFormatStyle.Currency(code: "USD", locale: Locale(identifier: "en_US")) + let doubleCurrencyFormatter = try #require(ICUCurrencyNumberFormatter.create(for: doubleCurrencyStyle)) + #expect(doubleCurrencyFormatter.uformatter != doubleFormatter.uformatter, "Should use a different uformatter for an unseen style") } #endif - func testIntegerFormatStyle() throws { + @Test func integerFormatStyle() throws { let testData: [Int] = [ 87650000, 8765000, 876500, 87650, 8765, 876, 87, 8, 0 ] - func testIntValues(_ style: IntegerFormatStyle, expected: [String]) { + func testIntValues(_ style: IntegerFormatStyle, expected: [String], sourceLocation: SourceLocation = #_sourceLocation) { for i in 0..(locale: locale).sign(strategy: .always()), expected: [ "+87,650,000", "+8,765,000", "+876,500", "+87,650", "+8,765", "+876", "+87", "+8", "+0" ]) } - func testIntegerFormatStyleFixedWidthLimits() throws { - func test(type: I.Type = I.self, min: String, max: String) { + @Test func integerFormatStyleFixedWidthLimits() throws { + func test(type: I.Type = I.self, min: String, max: String, sourceLocation: SourceLocation = #_sourceLocation) { do { - let style: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US_POSIX")) - XCTAssertEqual(style.format(I.min), I.min.description) - XCTAssertEqual(style.format(I.max), I.max.description) + let style = IntegerFormatStyle(locale: Locale(identifier: "en_US_POSIX")) + #expect(style.format(I.min) == I.min.description, sourceLocation: sourceLocation) + #expect(style.format(I.max) == I.max.description, sourceLocation: sourceLocation) } do { - let style: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) - XCTAssertEqual(style.format(I.min), min) - XCTAssertEqual(style.format(I.max), max) + let style = IntegerFormatStyle(locale: Locale(identifier: "en_US")) + #expect(style.format(I.min) == min, sourceLocation: sourceLocation) + #expect(style.format(I.max) == max, sourceLocation: sourceLocation) } do { - let style: IntegerFormatStyle.Percent = .init(locale: Locale(identifier: "en_US")) - XCTAssertEqual(style.format(I.min), min + "%") - XCTAssertEqual(style.format(I.max), max + "%") + let style = IntegerFormatStyle.Percent(locale: Locale(identifier: "en_US")) + #expect(style.format(I.min) == min + "%", sourceLocation: sourceLocation) + #expect(style.format(I.max) == max + "%", sourceLocation: sourceLocation) } do { - let style: IntegerFormatStyle.Currency = .init(code: "USD", locale: Locale(identifier: "en_US")).presentation(.narrow) + let style = IntegerFormatStyle.Currency(code: "USD", locale: Locale(identifier: "en_US")).presentation(.narrow) let negativeSign = (min.first == "-" ? "-" : "") - XCTAssertEqual(style.format(I.min), "\(negativeSign)$\(min.drop(while: { $0 == "-" })).00") - XCTAssertEqual(style.format(I.max), "$\(max).00") + #expect(style.format(I.min) == "\(negativeSign)$\(min.drop(while: { $0 == "-" })).00", sourceLocation: sourceLocation) + #expect(style.format(I.max) == "$\(max).00", sourceLocation: sourceLocation) } } @@ -177,33 +157,33 @@ final class NumberFormatStyleTests: XCTestCase { test(type: UInt64.self, min: "0", max: "18,446,744,073,709,551,615") } - func testInteger_Precision() throws { - let style: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) + @Test func integer_Precision() throws { + let style = IntegerFormatStyle(locale: Locale(identifier: "en_US")) _testNegativePositiveInt(style.precision(.significantDigits(3...3)), [ "-98.0", "-9.00", "0.00", "9.00", "98.0" ], "exact significant digits") _testNegativePositiveInt(style.precision(.significantDigits(2...)), [ "-98", "-9.0", "0.0", "9.0", "98" ], "min significant digits") _testNegativePositiveInt(style.precision(.integerAndFractionLength(integerLimits: 4..., fractionLimits: 0...0)), [ "-0,098", "-0,009", "0,000", "0,009", "0,098" ]) } - func testIntegerFormatStyle_Percent() throws { - let style: IntegerFormatStyle.Percent = .init(locale: Locale(identifier: "en_US")) + @Test func integerFormatStyle_Percent() throws { + let style = IntegerFormatStyle.Percent(locale: Locale(identifier: "en_US")) _testNegativePositiveInt(style, [ "-98%", "-9%", "0%", "9%", "98%" ], "percent default") _testNegativePositiveInt(style.precision(.significantDigits(3...3)), [ "-98.0%", "-9.00%", "0.00%", "9.00%", "98.0%" ], "percent + significant digit") } - func testIntegerFormatStyle_Currency() throws { - let style: IntegerFormatStyle.Currency = .init(code: "GBP", locale: Locale(identifier: "en_US")) + @Test func integerFormatStyle_Currency() throws { + let style = IntegerFormatStyle.Currency(code: "GBP", locale: Locale(identifier: "en_US")) _testNegativePositiveInt(style.presentation(.narrow), [ "-£98.00", "-£9.00", "£0.00", "£9.00", "£98.00" ], "currency narrow") _testNegativePositiveInt(style.presentation(.isoCode), [ "-GBP 98.00", "-GBP 9.00", "GBP 0.00", "GBP 9.00", "GBP 98.00" ], "currency isoCode") _testNegativePositiveInt(style.presentation(.standard), [ "-£98.00", "-£9.00", "£0.00", "£9.00", "£98.00" ], "currency standard") _testNegativePositiveInt(style.presentation(.fullName), [ "-98.00 British pounds", "-9.00 British pounds", "0.00 British pounds", "9.00 British pounds", "98.00 British pounds" ], "currency fullname") - let styleUSD: IntegerFormatStyle.Currency = .init(code: "USD", locale: Locale(identifier: "en_CA")) + let styleUSD = IntegerFormatStyle.Currency(code: "USD", locale: Locale(identifier: "en_CA")) _testNegativePositiveInt(styleUSD.presentation(.standard), [ "-US$98.00", "-US$9.00", "US$0.00", "US$9.00", "US$98.00" ], "currency standard") } - func testFloatingPointFormatStyle() throws { - let style: FloatingPointFormatStyle = .init(locale: Locale(identifier: "en_US")) + @Test func floatingPointFormatStyle() throws { + let style = FloatingPointFormatStyle(locale: Locale(identifier: "en_US")) _testNegativePositiveDouble(style.precision(.significantDigits(...2)), [ "88,000", "8,800", "880", "88", "8.8", "0.88", "0.088", "0.0088", "0", "-0.0088", "-880", "-88,000" ], "max 2 significant digits") _testNegativePositiveDouble(style.precision(.fractionLength(1...3)), [ "87,650.0", "8,765.0", "876.5", "87.65", "8.765", "0.876", "0.088", "0.009", "0.0", "-0.009", "-876.5", "-87,650.0" ], "fraction limit") _testNegativePositiveDouble(style.precision(.integerLength(3...)), [ "87,650", "8,765", "876.5", "087.65", "008.765", "000.8765", "000.08765", "000.008765", "000", "-000.008765", "-876.5", "-87,650" ], "min 3 integer digits") @@ -219,13 +199,13 @@ final class NumberFormatStyleTests: XCTestCase { _testNegativePositiveDouble(style.precision(.integerAndFractionLength(integerLimits: 0...0, fractionLimits: 2...2)), [ "87,650.00", "8,765.00", "876.50", "87.65", "8.76", ".88", ".09", ".01", ".00", "-.01", "-876.50", "-87,650.00"], "exact 2 integer digits") } - func testFloatingPointFormatStyle_Percent() throws { - let style: FloatingPointFormatStyle.Percent = .init(locale: Locale(identifier: "en_US")) + @Test func floatingPointFormatStyle_Percent() throws { + let style = FloatingPointFormatStyle.Percent(locale: Locale(identifier: "en_US")) _testNegativePositiveDouble(style, [ "8,765,000%", "876,500%", "87,650%", "8,765%", "876.5%", "87.65%", "8.765%", "0.8765%", "0%", "-0.8765%", "-87,650%", "-8,765,000%" ] , "percent default") _testNegativePositiveDouble(style.precision(.significantDigits(2)), [ "8,800,000%", "880,000%", "88,000%", "8,800%", "880%", "88%", "8.8%", "0.88%", "0.0%", "-0.88%", "-88,000%", "-8,800,000%" ], "percent 2 significant digits") } - func testFloatingPointFormatStyle_BigNumber() throws { + @Test func floatingPointFormatStyle_BigNumber() throws { let bigData: [(Double, String)] = [ (9007199254740992, "9,007,199,254,740,992.00"), // Maximum integer that can be precisely represented by a double (-9007199254740992, "-9,007,199,254,740,992.00"), // Minimum integer that can be precisely represented by a double @@ -234,51 +214,51 @@ final class NumberFormatStyleTests: XCTestCase { (9007199254740991.5, "9,007,199,254,740,992.00"), // Would round to the closest ] - let style: FloatingPointFormatStyle = .init(locale: Locale(identifier: "en_US")).precision(.fractionLength(2...)) + let style = FloatingPointFormatStyle(locale: Locale(identifier: "en_US")).precision(.fractionLength(2...)) for (v, expected) in bigData { - XCTAssertEqual(style.format(v), expected) + #expect(style.format(v) == expected) } - XCTAssertEqual(Float64.greatestFiniteMagnitude.formatted(.number.locale(enUSLocale)), "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000") - XCTAssertEqual(Float64.infinity.formatted(.number.locale(enUSLocale)), "∞") - XCTAssertEqual(Float64.leastNonzeroMagnitude.formatted(.number.locale(enUSLocale)), "0") - XCTAssertEqual(Float64.nan.formatted(.number.locale(enUSLocale)), "NaN") - XCTAssertEqual(Float64.nan.formatted(.number.locale(enUSLocale).precision(.fractionLength(2))), "NaN") - XCTAssertEqual(Float64.nan.formatted(.number.locale(Locale(identifier: "uz_Cyrl"))), "ҳақиқий сон эмас") - - XCTAssertEqual(Float64.greatestFiniteMagnitude.formatted(.percent.locale(enUSLocale)), "17,976,931,348,623,157,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000%") - XCTAssertEqual(Float64.infinity.formatted(.percent.locale(enUSLocale)), "∞%") - XCTAssertEqual(Float64.leastNonzeroMagnitude.formatted(.percent.locale(enUSLocale)), "0%") - XCTAssertEqual(Float64.nan.formatted(.percent.locale(enUSLocale)), "NaN%") - XCTAssertEqual(Float64.nan.formatted(.percent.locale(enUSLocale).precision(.fractionLength(2))), "NaN%") - XCTAssertEqual(Float64.nan.formatted(.percent.locale(Locale(identifier: "uz_Cyrl"))), "ҳақиқий сон эмас%") - - XCTAssertEqual(Float64.greatestFiniteMagnitude.formatted(.currency(code: "USD").locale(enUSLocale)), "$179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000.00") - XCTAssertEqual(Float64.infinity.formatted(.currency(code: "USD").locale(enUSLocale)), "$∞") - XCTAssertEqual(Float64.leastNonzeroMagnitude.formatted(.currency(code: "USD").locale(enUSLocale)), "$0.00") - XCTAssertEqual(Float64.nan.formatted(.currency(code: "USD").locale(enUSLocale)), "$NaN") - XCTAssertEqual(Float64.nan.formatted(.currency(code: "USD").locale(enUSLocale).precision(.fractionLength(2))), "$NaN") - XCTAssertEqual(Float64.nan.formatted(.currency(code: "USD").locale(Locale(identifier: "uz_Cyrl"))), "ҳақиқий сон эмас US$") + #expect(Float64.greatestFiniteMagnitude.formatted(.number.locale(enUSLocale)) == "179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000") + #expect(Float64.infinity.formatted(.number.locale(enUSLocale)) == "∞") + #expect(Float64.leastNonzeroMagnitude.formatted(.number.locale(enUSLocale)) == "0") + #expect(Float64.nan.formatted(.number.locale(enUSLocale)) == "NaN") + #expect(Float64.nan.formatted(.number.locale(enUSLocale).precision(.fractionLength(2))) == "NaN") + #expect(Float64.nan.formatted(.number.locale(Locale(identifier: "uz_Cyrl"))) == "ҳақиқий сон эмас") + + #expect(Float64.greatestFiniteMagnitude.formatted(.percent.locale(enUSLocale)) == "17,976,931,348,623,157,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000%") + #expect(Float64.infinity.formatted(.percent.locale(enUSLocale)) == "∞%") + #expect(Float64.leastNonzeroMagnitude.formatted(.percent.locale(enUSLocale)) == "0%") + #expect(Float64.nan.formatted(.percent.locale(enUSLocale)) == "NaN%") + #expect(Float64.nan.formatted(.percent.locale(enUSLocale).precision(.fractionLength(2))) == "NaN%") + #expect(Float64.nan.formatted(.percent.locale(Locale(identifier: "uz_Cyrl"))) == "ҳақиқий сон эмас%") + + #expect(Float64.greatestFiniteMagnitude.formatted(.currency(code: "USD").locale(enUSLocale)) == "$179,769,313,486,231,570,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000.00") + #expect(Float64.infinity.formatted(.currency(code: "USD").locale(enUSLocale)) == "$∞") + #expect(Float64.leastNonzeroMagnitude.formatted(.currency(code: "USD").locale(enUSLocale)) == "$0.00") + #expect(Float64.nan.formatted(.currency(code: "USD").locale(enUSLocale)) == "$NaN") + #expect(Float64.nan.formatted(.currency(code: "USD").locale(enUSLocale).precision(.fractionLength(2))) == "$NaN") + #expect(Float64.nan.formatted(.currency(code: "USD").locale(Locale(identifier: "uz_Cyrl"))) == "ҳақиқий сон эмас US$") } - func testFormattedAttributedLeadingDotSyntax() throws { + @Test func formattedAttributedLeadingDotSyntax() throws { let int = 42 - XCTAssertEqual(int.formatted(.number.attributed), IntegerFormatStyle().attributed.format(int)) - XCTAssertEqual(int.formatted(.percent.attributed), IntegerFormatStyle.Percent().attributed.format(int)) - XCTAssertEqual(int.formatted(.currency(code: "GBP").attributed), IntegerFormatStyle.Currency(code: "GBP").attributed.format(int)) + #expect(int.formatted(.number.attributed) == IntegerFormatStyle().attributed.format(int)) + #expect(int.formatted(.percent.attributed) == IntegerFormatStyle.Percent().attributed.format(int)) + #expect(int.formatted(.currency(code: "GBP").attributed) == IntegerFormatStyle.Currency(code: "GBP").attributed.format(int)) let float = 3.14159 - XCTAssertEqual(float.formatted(.number.attributed), FloatingPointFormatStyle().attributed.format(float)) - XCTAssertEqual(float.formatted(.percent.attributed), FloatingPointFormatStyle.Percent().attributed.format(float)) - XCTAssertEqual(float.formatted(.currency(code: "GBP").attributed), FloatingPointFormatStyle.Currency(code: "GBP").attributed.format(float)) + #expect(float.formatted(.number.attributed) == FloatingPointFormatStyle().attributed.format(float)) + #expect(float.formatted(.percent.attributed) == FloatingPointFormatStyle.Percent().attributed.format(float)) + #expect(float.formatted(.currency(code: "GBP").attributed) == FloatingPointFormatStyle.Currency(code: "GBP").attributed.format(float)) let decimal = Decimal(2.999) - XCTAssertEqual(decimal.formatted(.number.attributed), Decimal.FormatStyle().attributed.format(decimal)) - XCTAssertEqual(decimal.formatted(.percent.attributed), Decimal.FormatStyle.Percent().attributed.format(decimal)) - XCTAssertEqual(decimal.formatted(.currency(code: "GBP").attributed), Decimal.FormatStyle.Currency(code: "GBP").attributed.format(decimal)) + #expect(decimal.formatted(.number.attributed) == Decimal.FormatStyle().attributed.format(decimal)) + #expect(decimal.formatted(.percent.attributed) == Decimal.FormatStyle.Percent().attributed.format(decimal)) + #expect(decimal.formatted(.currency(code: "GBP").attributed) == Decimal.FormatStyle.Currency(code: "GBP").attributed.format(decimal)) } - func testDecimalFormatStyle() throws { + @Test func decimalFormatStyle() throws { let style = Decimal.FormatStyle(locale: enUSLocale) _testNegativePositiveDecimal(style.precision(.significantDigits(...2)), [ "88,000", "8,800", "880", "88", "8.8", "0.88", "0.088", "0.0088", "0", "-0.0088", "-880", "-88,000" ], "max 2 significant digits") _testNegativePositiveDecimal(style.precision(.fractionLength(1...3)), [ "87,650.0", "8,765.0", "876.5", "87.65", "8.765", "0.876", "0.088", "0.009", "0.0", "-0.009", "-876.5", "-87,650.0" ], "fraction limit") @@ -294,7 +274,7 @@ final class NumberFormatStyleTests: XCTestCase { _testNegativePositiveDecimal(style.precision(.integerAndFractionLength(integerLimits: 2...2, fractionLimits: 0...0)), [ "50", "65", "76", "88", "09", "01", "00", "00", "00", "-00", "-76", "-50"], "exact 2 integer digits; 0 fractional digits") } - func testDecimalFormatStyle_Percent() throws { + @Test func decimalFormatStyle_Percent() throws { let style = Decimal.FormatStyle.Percent(locale: enUSLocale) _testNegativePositiveDecimal(style.precision(.significantDigits(...2)), [ "8,800,000%", "880,000%", "88,000%", "8,800%", "880%", "88%", "8.8%", "0.88%", "0%", "-0.88%", "-88,000%", "-8,800,000%" ], "max 2 significant digits") _testNegativePositiveDecimal(style.precision(.fractionLength(1...3)), [ "8,765,000.0%", @@ -347,259 +327,278 @@ final class NumberFormatStyleTests: XCTestCase { "-00%" ], "exact 2 integer digits") } - func testDecimalFormatStyle_Currency() throws { + @Test func decimalFormatStyle_Currency() throws { let style = Decimal.FormatStyle.Currency(code: "USD", locale: enUSLocale) _testNegativePositiveDecimal(style, [ "$87,650.00", "$8,765.00", "$876.50", "$87.65", "$8.76", "$0.88", "$0.09", "$0.01", "$0.00", "-$0.01", "-$876.50", "-$87,650.00" ], "currency style") } - func testDecimal_withCustomShorthand() throws { - guard Locale.autoupdatingCurrent.language.isEquivalent(to: Locale.Language(identifier: "en_US")) else { - throw XCTSkip("This test can only be run with the system set to the en_US language") + @Test func decimal_withCustomShorthand() async { + await usingCurrentInternationalizationPreferences { + // This test can only be run with the system set to the en_US language + var prefs = LocalePreferences() + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + + #expect((12345 as Decimal).formatted(.number.grouping(.never)) == "12345") + #expect((12345.678 as Decimal).formatted(.percent.sign(strategy: .always())) == "+1,234,567.8%") + #expect((-3000.14159 as Decimal).formatted(.currency(code:"USD").sign(strategy: .accounting)) == "($3,000.14)") } - XCTAssertEqual((12345 as Decimal).formatted(.number.grouping(.never)), "12345") - XCTAssertEqual((12345.678 as Decimal).formatted(.percent.sign(strategy: .always())), "+1,234,567.8%") - XCTAssertEqual((-3000.14159 as Decimal).formatted(.currency(code:"USD").sign(strategy: .accounting)), "($3,000.14)") } - func testDecimal_withShorthand_enUS() throws { - guard Locale.autoupdatingCurrent.language.isEquivalent(to: Locale.Language(identifier: "en_US")) else { - throw XCTSkip("This test can only be run with the system set to the en_US language") + @Test func decimal_withShorthand_enUS() async { + await usingCurrentInternationalizationPreferences { + // This test can only be run with the system set to the en_US language + var prefs = LocalePreferences() + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + + + #expect((12345 as Decimal).formatted(.number) == "12,345") + #expect((12345.678 as Decimal).formatted(.number) == "12,345.678") + #expect((0 as Decimal).formatted(.number) == "0") + #expect((3.14159 as Decimal).formatted(.number) == "3.14159") + #expect((-3.14159 as Decimal).formatted(.number) == "-3.14159") + #expect((-3000.14159 as Decimal).formatted(.number) == "-3,000.14159") + + #expect((0.12345 as Decimal).formatted(.percent) == "12.345%") + #expect((0.0012345 as Decimal).formatted(.percent) == "0.12345%") + #expect((12345 as Decimal).formatted(.percent) == "1,234,500%") + #expect((12345.678 as Decimal).formatted(.percent) == "1,234,567.8%") + #expect((0 as Decimal).formatted(.percent) == "0%") + #expect((3.14159 as Decimal).formatted(.percent) == "314.159%") + #expect((-3.14159 as Decimal).formatted(.percent) == "-314.159%") + #expect((-3000.14159 as Decimal).formatted(.percent) == "-300,014.159%") + + #expect((12345 as Decimal).formatted(.currency(code:"USD")) == "$12,345.00") + #expect((12345.678 as Decimal).formatted(.currency(code:"USD")) == "$12,345.68") + #expect((0 as Decimal).formatted(.currency(code:"USD")) == "$0.00") + #expect((3.14159 as Decimal).formatted(.currency(code:"USD")) == "$3.14") + #expect((-3.14159 as Decimal).formatted(.currency(code:"USD")) == "-$3.14") + #expect((-3000.14159 as Decimal).formatted(.currency(code:"USD")) == "-$3,000.14") } - - XCTAssertEqual((12345 as Decimal).formatted(.number), "12,345") - XCTAssertEqual((12345.678 as Decimal).formatted(.number), "12,345.678") - XCTAssertEqual((0 as Decimal).formatted(.number), "0") - XCTAssertEqual((3.14159 as Decimal).formatted(.number), "3.14159") - XCTAssertEqual((-3.14159 as Decimal).formatted(.number), "-3.14159") - XCTAssertEqual((-3000.14159 as Decimal).formatted(.number), "-3,000.14159") - - XCTAssertEqual((0.12345 as Decimal).formatted(.percent), "12.345%") - XCTAssertEqual((0.0012345 as Decimal).formatted(.percent), "0.12345%") - XCTAssertEqual((12345 as Decimal).formatted(.percent), "1,234,500%") - XCTAssertEqual((12345.678 as Decimal).formatted(.percent), "1,234,567.8%") - XCTAssertEqual((0 as Decimal).formatted(.percent), "0%") - XCTAssertEqual((3.14159 as Decimal).formatted(.percent), "314.159%") - XCTAssertEqual((-3.14159 as Decimal).formatted(.percent), "-314.159%") - XCTAssertEqual((-3000.14159 as Decimal).formatted(.percent), "-300,014.159%") - - XCTAssertEqual((12345 as Decimal).formatted(.currency(code:"USD")), "$12,345.00") - XCTAssertEqual((12345.678 as Decimal).formatted(.currency(code:"USD")), "$12,345.68") - XCTAssertEqual((0 as Decimal).formatted(.currency(code:"USD")), "$0.00") - XCTAssertEqual((3.14159 as Decimal).formatted(.currency(code:"USD")), "$3.14") - XCTAssertEqual((-3.14159 as Decimal).formatted(.currency(code:"USD")), "-$3.14") - XCTAssertEqual((-3000.14159 as Decimal).formatted(.currency(code:"USD")), "-$3,000.14") } - func testDecimal_default() throws { - let style = Decimal.FormatStyle() - XCTAssertEqual((12345 as Decimal).formatted(), style.format(12345)) - XCTAssertEqual((12345.678 as Decimal).formatted(), style.format(12345.678)) - XCTAssertEqual((0 as Decimal).formatted(), style.format(0)) - XCTAssertEqual((3.14159 as Decimal).formatted(), style.format(3.14159)) - XCTAssertEqual((-3.14159 as Decimal).formatted(), style.format(-3.14159)) - XCTAssertEqual((-3000.14159 as Decimal).formatted(), style.format(-3000.14159)) + @Test func decimal_default() async { + await usingCurrentInternationalizationPreferences { + let style = Decimal.FormatStyle() + #expect((12345 as Decimal).formatted() == style.format(12345)) + #expect((12345.678 as Decimal).formatted() == style.format(12345.678)) + #expect((0 as Decimal).formatted() == style.format(0)) + #expect((3.14159 as Decimal).formatted() == style.format(3.14159)) + #expect((-3.14159 as Decimal).formatted() == style.format(-3.14159)) + #expect((-3000.14159 as Decimal).formatted() == style.format(-3000.14159)) + } } - func testDecimal_default_enUS() throws { - guard Locale.autoupdatingCurrent.language.isEquivalent(to: Locale.Language(identifier: "en_US")) else { - throw XCTSkip("This test can only be run with the system set to the en_US language") + @Test func decimal_default_enUS() async { + await usingCurrentInternationalizationPreferences { + // This test can only be run with the system set to the en_US language + var prefs = LocalePreferences() + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + + #expect((12345 as Decimal).formatted() == "12,345") + #expect((12345.678 as Decimal).formatted() == "12,345.678") + #expect((0 as Decimal).formatted() == "0") + #expect((3.14159 as Decimal).formatted() == "3.14159") + #expect((-3.14159 as Decimal).formatted() == "-3.14159") + #expect((-3000.14159 as Decimal).formatted() == "-3,000.14159") } - XCTAssertEqual((12345 as Decimal).formatted(), "12,345") - XCTAssertEqual((12345.678 as Decimal).formatted(), "12,345.678") - XCTAssertEqual((0 as Decimal).formatted(), "0") - XCTAssertEqual((3.14159 as Decimal).formatted(), "3.14159") - XCTAssertEqual((-3.14159 as Decimal).formatted(), "-3.14159") - XCTAssertEqual((-3000.14159 as Decimal).formatted(), "-3,000.14159") } - func testDecimal_withShorthand() throws { - let style = Decimal.FormatStyle() - XCTAssertEqual((12345 as Decimal).formatted(.number), style.format(12345)) - XCTAssertEqual((12345.678 as Decimal).formatted(.number), style.format(12345.678)) - XCTAssertEqual((0 as Decimal).formatted(.number), style.format(0)) - XCTAssertEqual((3.14159 as Decimal).formatted(.number), style.format(3.14159)) - XCTAssertEqual((-3.14159 as Decimal).formatted(.number), style.format(-3.14159)) - XCTAssertEqual((-3000.14159 as Decimal).formatted(.number), style.format(-3000.14159)) - - let percentStyle = Decimal.FormatStyle.Percent() - XCTAssertEqual((12345 as Decimal).formatted(.percent), percentStyle.format(12345)) - XCTAssertEqual((12345.678 as Decimal).formatted(.percent), percentStyle.format(12345.678)) - XCTAssertEqual((0 as Decimal).formatted(.percent), percentStyle.format(0)) - XCTAssertEqual((3.14159 as Decimal).formatted(.percent), percentStyle.format(3.14159)) - XCTAssertEqual((-3.14159 as Decimal).formatted(.percent), percentStyle.format(-3.14159)) - XCTAssertEqual((-3000.14159 as Decimal).formatted(.percent), percentStyle.format(-3000.14159)) - - let currencyStyle = Decimal.FormatStyle.Currency(code: "USD") - XCTAssertEqual((12345 as Decimal).formatted(.currency(code:"USD")), currencyStyle.format(12345)) - XCTAssertEqual((12345.678 as Decimal).formatted(.currency(code:"USD")), currencyStyle.format(12345.678)) - XCTAssertEqual((0 as Decimal).formatted(.currency(code:"USD")), currencyStyle.format(0)) - XCTAssertEqual((3.14159 as Decimal).formatted(.currency(code:"USD")), currencyStyle.format(3.14159)) - XCTAssertEqual((-3.14159 as Decimal).formatted(.currency(code:"USD")), currencyStyle.format(-3.14159)) - XCTAssertEqual((-3000.14159 as Decimal).formatted(.currency(code:"USD")), currencyStyle.format(-3000.14159)) + @Test func decimal_withShorthand() throws { + let style = Decimal.FormatStyle(locale: enUSLocale) + #expect((12345 as Decimal).formatted(.number.locale(enUSLocale)) == style.format(12345)) + #expect((12345.678 as Decimal).formatted(.number.locale(enUSLocale)) == style.format(12345.678)) + #expect((0 as Decimal).formatted(.number.locale(enUSLocale)) == style.format(0)) + #expect((3.14159 as Decimal).formatted(.number.locale(enUSLocale)) == style.format(3.14159)) + #expect((-3.14159 as Decimal).formatted(.number.locale(enUSLocale)) == style.format(-3.14159)) + #expect((-3000.14159 as Decimal).formatted(.number.locale(enUSLocale)) == style.format(-3000.14159)) + + let percentStyle = Decimal.FormatStyle.Percent(locale: enUSLocale) + #expect((12345 as Decimal).formatted(.percent.locale(enUSLocale)) == percentStyle.format(12345)) + #expect((12345.678 as Decimal).formatted(.percent.locale(enUSLocale)) == percentStyle.format(12345.678)) + #expect((0 as Decimal).formatted(.percent.locale(enUSLocale)) == percentStyle.format(0)) + #expect((3.14159 as Decimal).formatted(.percent.locale(enUSLocale)) == percentStyle.format(3.14159)) + #expect((-3.14159 as Decimal).formatted(.percent.locale(enUSLocale)) == percentStyle.format(-3.14159)) + #expect((-3000.14159 as Decimal).formatted(.percent.locale(enUSLocale)) == percentStyle.format(-3000.14159)) + + let currencyStyle = Decimal.FormatStyle.Currency(code: "USD", locale: enUSLocale) + #expect((12345 as Decimal).formatted(.currency(code:"USD").locale(enUSLocale)) == currencyStyle.format(12345)) + #expect((12345.678 as Decimal).formatted(.currency(code:"USD").locale(enUSLocale)) == currencyStyle.format(12345.678)) + #expect((0 as Decimal).formatted(.currency(code:"USD").locale(enUSLocale)) == currencyStyle.format(0)) + #expect((3.14159 as Decimal).formatted(.currency(code:"USD").locale(enUSLocale)) == currencyStyle.format(3.14159)) + #expect((-3.14159 as Decimal).formatted(.currency(code:"USD").locale(enUSLocale)) == currencyStyle.format(-3.14159)) + #expect((-3000.14159 as Decimal).formatted(.currency(code:"USD").locale(enUSLocale)) == currencyStyle.format(-3000.14159)) } #if FOUNDATION_FRAMEWORK - func test_autoupdatingCurrentChangesFormatResults() { - let locale = Locale.autoupdatingCurrent - let number = 50_000 + @Test func autoupdatingCurrentChangesFormatResults() async { + await usingCurrentInternationalizationPreferences { + let locale = Locale.autoupdatingCurrent + let number = 50_000 #if FOUNDATION_FRAMEWORK - // Measurement is not yet available in the package - let measurement = Measurement(value: 0.8, unit: UnitLength.meters) + // Measurement is not yet available in the package + let measurement = Measurement(value: 0.8, unit: UnitLength.meters) #endif - let currency = Decimal(123.45) - let percent = 54.32 - let bytes = 1_234_567_890 - - // Get a formatted result from es-ES - var prefs = LocalePreferences() - prefs.languages = ["es-ES"] - prefs.locale = "es_ES" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedSpanishNumber = number.formatted(.number.locale(locale)) + let currency = Decimal(123.45) + let percent = 54.32 + let bytes = 1_234_567_890 + + // Get a formatted result from es-ES + var prefs = LocalePreferences() + prefs.languages = ["es-ES"] + prefs.locale = "es_ES" + LocaleCache.cache.resetCurrent(to: prefs) + let formattedSpanishNumber = number.formatted(.number.locale(locale)) #if FOUNDATION_FRAMEWORK - let formattedSpanishMeasurement = measurement.formatted(.measurement(width: .narrow).locale(locale)) + let formattedSpanishMeasurement = measurement.formatted(.measurement(width: .narrow).locale(locale)) #endif - let formattedSpanishCurrency = currency.formatted(.currency(code: "USD").locale(locale)) - let formattedSpanishPercent = percent.formatted(.percent.locale(locale)) - let formattedSpanishBytes = bytes.formatted(.byteCount(style: .decimal).locale(locale)) - - // Get a formatted result from en-US - prefs.languages = ["en-US"] - prefs.locale = "en_US" - LocaleCache.cache.resetCurrent(to: prefs) - let formattedEnglishNumber = number.formatted(.number.locale(locale)) + let formattedSpanishCurrency = currency.formatted(.currency(code: "USD").locale(locale)) + let formattedSpanishPercent = percent.formatted(.percent.locale(locale)) + let formattedSpanishBytes = bytes.formatted(.byteCount(style: .decimal).locale(locale)) + + // Get a formatted result from en-US + prefs.languages = ["en-US"] + prefs.locale = "en_US" + LocaleCache.cache.resetCurrent(to: prefs) + let formattedEnglishNumber = number.formatted(.number.locale(locale)) #if FOUNDATION_FRAMEWORK - let formattedEnglishMeasurement = measurement.formatted(.measurement(width: .narrow).locale(locale)) + let formattedEnglishMeasurement = measurement.formatted(.measurement(width: .narrow).locale(locale)) #endif - let formattedEnglishCurrency = currency.formatted(.currency(code: "USD").locale(locale)) - let formattedEnglishPercent = percent.formatted(.percent.locale(locale)) - let formattedEnglishBytes = bytes.formatted(.byteCount(style: .decimal).locale(locale)) - - // Reset to current preferences before any possibility of failing this test - LocaleCache.cache.reset() - - // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. - XCTAssertNotEqual(formattedSpanishNumber, formattedEnglishNumber) + let formattedEnglishCurrency = currency.formatted(.currency(code: "USD").locale(locale)) + let formattedEnglishPercent = percent.formatted(.percent.locale(locale)) + let formattedEnglishBytes = bytes.formatted(.byteCount(style: .decimal).locale(locale)) + + // Reset to current preferences before any possibility of failing this test + LocaleCache.cache.reset() + + // No matter what 'current' was before this test was run, formattedSpanish and formattedEnglish should be different. + #expect(formattedSpanishNumber != formattedEnglishNumber) #if FOUNDATION_FRAMEWORK - XCTAssertNotEqual(formattedSpanishMeasurement, formattedEnglishMeasurement) + #expect(formattedSpanishMeasurement != formattedEnglishMeasurement) #endif - XCTAssertNotEqual(formattedSpanishCurrency, formattedEnglishCurrency) - XCTAssertNotEqual(formattedSpanishPercent, formattedEnglishPercent) - XCTAssertNotEqual(formattedSpanishBytes, formattedEnglishBytes) + #expect(formattedSpanishCurrency != formattedEnglishCurrency) + #expect(formattedSpanishPercent != formattedEnglishPercent) + #expect(formattedSpanishBytes != formattedEnglishBytes) + } } #endif // FOUNDATION_PREVIEW #if !os(watchOS) // These tests require Int to be Int64, which is not always true on watch OSs yet - func testCurrency_compactName() throws { + @Test func currency_compactName() throws { let baseStyle = Decimal.FormatStyle.Currency(code: "USD", locale: Locale(identifier: "en_US")).notation(.compactName) // significant digits // `compactName` naturally rounds the number to the closest "name". - XCTAssertEqual( (922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$920T") - XCTAssertEqual( (92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$92T") - XCTAssertEqual( (9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2T") - XCTAssertEqual( (922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$920B") - XCTAssertEqual( (92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$92B") - XCTAssertEqual( (9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2B") - XCTAssertEqual( (922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$920M") - XCTAssertEqual( (92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$92M") - XCTAssertEqual( (9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2M") - XCTAssertEqual( (922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$920K") - XCTAssertEqual( (92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$92K") - XCTAssertEqual( (9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2K") - XCTAssertEqual( (922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$920") - XCTAssertEqual( (92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$92") - XCTAssertEqual( (9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.0") - XCTAssertEqual( (0 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$0.0") - XCTAssertEqual( (-9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.0") - XCTAssertEqual( (-92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$92") - XCTAssertEqual( (-922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$920") - XCTAssertEqual( (-9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2K") - XCTAssertEqual( (-92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$92K") - XCTAssertEqual( (-922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$920K") - XCTAssertEqual( (-9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2M") - XCTAssertEqual( (-92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$92M") - XCTAssertEqual( (-922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$920M") - XCTAssertEqual( (-9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2B") - XCTAssertEqual( (-92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$92B") - XCTAssertEqual( (-922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$920B") - XCTAssertEqual( (-9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2T") - XCTAssertEqual( (-92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$92T") - XCTAssertEqual((-922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$920T") + #expect( (922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$920T") + #expect( (92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$92T") + #expect( (9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2T") + #expect( (922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$920B") + #expect( (92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$92B") + #expect( (9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2B") + #expect( (922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$920M") + #expect( (92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$92M") + #expect( (9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2M") + #expect( (922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$920K") + #expect( (92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$92K") + #expect( (9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2K") + #expect( (922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$920") + #expect( (92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$92") + #expect( (9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.0") + #expect( (0 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$0.0") + #expect( (-9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.0") + #expect( (-92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$92") + #expect( (-922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$920") + #expect( (-9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2K") + #expect( (-92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$92K") + #expect( (-922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$920K") + #expect( (-9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2M") + #expect( (-92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$92M") + #expect( (-922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$920M") + #expect( (-9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2B") + #expect( (-92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$92B") + #expect( (-922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$920B") + #expect( (-9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2T") + #expect( (-92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$92T") + #expect((-922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$920T") // fraction length - XCTAssertEqual( (922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$922.34T") - XCTAssertEqual( (92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$92.23T") - XCTAssertEqual( (9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22T") - XCTAssertEqual( (922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$922.34B") - XCTAssertEqual( (92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$92.23B") - XCTAssertEqual( (9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22B") - XCTAssertEqual( (922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$922.34M") - XCTAssertEqual( (92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$92.23M") - XCTAssertEqual( (9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22M") - XCTAssertEqual( (922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$922.34K") - XCTAssertEqual( (92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$92.23K") - XCTAssertEqual( (9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22K") - XCTAssertEqual( (922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$922.00") - XCTAssertEqual( (92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$92.00") - XCTAssertEqual( (9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.00") - XCTAssertEqual( (0 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$0.00") - XCTAssertEqual( (-9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.00") - XCTAssertEqual( (-92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$92.00") - XCTAssertEqual( (-922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$922.00") - XCTAssertEqual( (-9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22K") - XCTAssertEqual( (-92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$92.23K") - XCTAssertEqual( (-922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$922.34K") - XCTAssertEqual( (-9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22M") - XCTAssertEqual( (-92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$92.23M") - XCTAssertEqual( (-922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$922.34M") - XCTAssertEqual( (-9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22B") - XCTAssertEqual( (-92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$92.23B") - XCTAssertEqual( (-922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$922.34B") - XCTAssertEqual( (-9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22T") - XCTAssertEqual( (-92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$92.23T") - XCTAssertEqual((-922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$922.34T") + #expect( (922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$922.34T") + #expect( (92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$92.23T") + #expect( (9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22T") + #expect( (922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$922.34B") + #expect( (92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$92.23B") + #expect( (9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22B") + #expect( (922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$922.34M") + #expect( (92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$92.23M") + #expect( (9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22M") + #expect( (922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$922.34K") + #expect( (92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$92.23K") + #expect( (9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22K") + #expect( (922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$922.00") + #expect( (92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$92.00") + #expect( (9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.00") + #expect( (0 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$0.00") + #expect( (-9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.00") + #expect( (-92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$92.00") + #expect( (-922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$922.00") + #expect( (-9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22K") + #expect( (-92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$92.23K") + #expect( (-922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$922.34K") + #expect( (-9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22M") + #expect( (-92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$92.23M") + #expect( (-922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$922.34M") + #expect( (-9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22B") + #expect( (-92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$92.23B") + #expect( (-922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$922.34B") + #expect( (-9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22T") + #expect( (-92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$92.23T") + #expect((-922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$922.34T") // rounded - XCTAssertEqual( (922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$1000T") - XCTAssertEqual( (92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100T") - XCTAssertEqual( (9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100T") - XCTAssertEqual( (922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100T") - XCTAssertEqual( (92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100B") - XCTAssertEqual( (9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100B") - XCTAssertEqual( (922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100B") - XCTAssertEqual( (92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100M") - XCTAssertEqual( (9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100M") - XCTAssertEqual( (922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100M") - XCTAssertEqual( (92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100K") - XCTAssertEqual( (9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100K") - XCTAssertEqual( (922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100K") - XCTAssertEqual( (92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100") - XCTAssertEqual( (9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100") - XCTAssertEqual( (0 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$0") - XCTAssertEqual( (-9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100") - XCTAssertEqual( (-92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100") - XCTAssertEqual( (-922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100K") - XCTAssertEqual( (-9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100K") - XCTAssertEqual( (-92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100K") - XCTAssertEqual( (-922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100M") - XCTAssertEqual( (-9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100M") - XCTAssertEqual( (-92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100M") - XCTAssertEqual( (-922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100B") - XCTAssertEqual( (-9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100B") - XCTAssertEqual( (-92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100B") - XCTAssertEqual( (-922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100T") - XCTAssertEqual( (-9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100T") - XCTAssertEqual( (-92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100T") - XCTAssertEqual((-922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$1000T") + #expect( (922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$1000T") + #expect( (92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100T") + #expect( (9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100T") + #expect( (922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100T") + #expect( (92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100B") + #expect( (9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100B") + #expect( (922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100B") + #expect( (92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100M") + #expect( (9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100M") + #expect( (922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100M") + #expect( (92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100K") + #expect( (9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100K") + #expect( (922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100K") + #expect( (92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100") + #expect( (9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100") + #expect( (0 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$0") + #expect( (-9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100") + #expect( (-92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100") + #expect( (-922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100K") + #expect( (-9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100K") + #expect( (-92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100K") + #expect( (-922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100M") + #expect( (-9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100M") + #expect( (-92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100M") + #expect( (-922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100B") + #expect( (-9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100B") + #expect( (-92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100B") + #expect( (-922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100T") + #expect( (-9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100T") + #expect( (-92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100T") + #expect((-922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$1000T") } - func testCurrency_Codable() throws { + @Test func currency_Codable() throws { let gbpInUS = Decimal.FormatStyle.Currency(code: "GBP", locale: enUSLocale) let _ = try JSONEncoder().encode(gbpInUS) // Valid JSON presentation of the format style - let previouslyEncoded = """ + let previouslyEncoded = try #require(""" { "collection": { @@ -615,119 +614,114 @@ final class NumberFormatStyleTests: XCTestCase { "identifier": "en_US" } } - """.data(using: String._Encoding.utf8) - - guard let previouslyEncoded else { - XCTFail() - return - } + """.data(using: String.Encoding.utf8)) let decoded = try JSONDecoder().decode(Decimal.FormatStyle.Currency.self, from: previouslyEncoded) - XCTAssertEqual(decoded, gbpInUS) + #expect(decoded == gbpInUS) } - func testCurrency_scientific() throws { + @Test func currency_scientific() throws { let baseStyle = Decimal.FormatStyle.Currency(code: "USD", locale: Locale(identifier: "en_US")).notation(.scientific) // significant digits // `compactName` naturally rounds the number to the closest "name". - XCTAssertEqual( (922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E14") - XCTAssertEqual( (92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E13") - XCTAssertEqual( (9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E12") - XCTAssertEqual( (922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E11") - XCTAssertEqual( (92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E10") - XCTAssertEqual( (9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E9") - XCTAssertEqual( (922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E8") - XCTAssertEqual( (92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E7") - XCTAssertEqual( (9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E6") - XCTAssertEqual( (922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E5") - XCTAssertEqual( (92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E4") - XCTAssertEqual( (9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E3") - XCTAssertEqual( (922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E2") - XCTAssertEqual( (92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.2E1") - XCTAssertEqual( (9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$9.0E0") - XCTAssertEqual( (0 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "$0.0E0") - XCTAssertEqual( (-9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.0E0") - XCTAssertEqual( (-92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E1") - XCTAssertEqual( (-922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E2") - XCTAssertEqual( (-9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E3") - XCTAssertEqual( (-92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E4") - XCTAssertEqual( (-922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E5") - XCTAssertEqual( (-9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E6") - XCTAssertEqual( (-92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E7") - XCTAssertEqual( (-922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E8") - XCTAssertEqual( (-9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E9") - XCTAssertEqual( (-92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E10") - XCTAssertEqual( (-922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E11") - XCTAssertEqual( (-9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E12") - XCTAssertEqual( (-92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E13") - XCTAssertEqual((-922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))), "-$9.2E14") + #expect( (922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E14") + #expect( (92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E13") + #expect( (9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E12") + #expect( (922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E11") + #expect( (92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E10") + #expect( (9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E9") + #expect( (922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E8") + #expect( (92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E7") + #expect( (9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E6") + #expect( (922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E5") + #expect( (92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E4") + #expect( (9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E3") + #expect( (922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E2") + #expect( (92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.2E1") + #expect( (9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$9.0E0") + #expect( (0 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "$0.0E0") + #expect( (-9 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.0E0") + #expect( (-92 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E1") + #expect( (-922 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E2") + #expect( (-9223 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E3") + #expect( (-92233 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E4") + #expect( (-922337 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E5") + #expect( (-9223372 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E6") + #expect( (-92233720 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E7") + #expect( (-922337203 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E8") + #expect( (-9223372036 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E9") + #expect( (-92233720368 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E10") + #expect( (-922337203685 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E11") + #expect( (-9223372036854 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E12") + #expect( (-92233720368547 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E13") + #expect((-922337203685477 as Decimal).formatted(baseStyle.precision(.significantDigits(2...2))) == "-$9.2E14") // fraction length - XCTAssertEqual( (922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E14") - XCTAssertEqual( (92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E13") - XCTAssertEqual( (9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E12") - XCTAssertEqual( (922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E11") - XCTAssertEqual( (92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E10") - XCTAssertEqual( (9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E9") - XCTAssertEqual( (922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E8") - XCTAssertEqual( (92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E7") - XCTAssertEqual( (9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E6") - XCTAssertEqual( (922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E5") - XCTAssertEqual( (92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E4") - XCTAssertEqual( (9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E3") - XCTAssertEqual( (922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.22E2") - XCTAssertEqual( (92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.20E1") - XCTAssertEqual( (9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$9.00E0") - XCTAssertEqual( (0 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "$0.00E0") - XCTAssertEqual( (-9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.00E0") - XCTAssertEqual( (-92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.20E1") - XCTAssertEqual( (-922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E2") - XCTAssertEqual( (-9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E3") - XCTAssertEqual( (-92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E4") - XCTAssertEqual( (-922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E5") - XCTAssertEqual( (-9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E6") - XCTAssertEqual( (-92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E7") - XCTAssertEqual( (-922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E8") - XCTAssertEqual( (-9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E9") - XCTAssertEqual( (-92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E10") - XCTAssertEqual( (-922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E11") - XCTAssertEqual( (-9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E12") - XCTAssertEqual( (-92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E13") - XCTAssertEqual((-922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))), "-$9.22E14") + #expect( (922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E14") + #expect( (92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E13") + #expect( (9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E12") + #expect( (922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E11") + #expect( (92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E10") + #expect( (9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E9") + #expect( (922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E8") + #expect( (92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E7") + #expect( (9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E6") + #expect( (922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E5") + #expect( (92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E4") + #expect( (9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E3") + #expect( (922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.22E2") + #expect( (92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.20E1") + #expect( (9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$9.00E0") + #expect( (0 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "$0.00E0") + #expect( (-9 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.00E0") + #expect( (-92 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.20E1") + #expect( (-922 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E2") + #expect( (-9223 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E3") + #expect( (-92233 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E4") + #expect( (-922337 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E5") + #expect( (-9223372 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E6") + #expect( (-92233720 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E7") + #expect( (-922337203 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E8") + #expect( (-9223372036 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E9") + #expect( (-92233720368 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E10") + #expect( (-922337203685 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E11") + #expect( (-9223372036854 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E12") + #expect( (-92233720368547 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E13") + #expect((-922337203685477 as Decimal).formatted(baseStyle.precision(.fractionLength(2...2))) == "-$9.22E14") // rounded - XCTAssertEqual( (922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E15") - XCTAssertEqual( (92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E14") - XCTAssertEqual( (9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E13") - XCTAssertEqual( (922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E12") - XCTAssertEqual( (92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E11") - XCTAssertEqual( (9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E10") - XCTAssertEqual( (922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E9") - XCTAssertEqual( (92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E8") - XCTAssertEqual( (9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E7") - XCTAssertEqual( (922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E6") - XCTAssertEqual( (92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E5") - XCTAssertEqual( (9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E4") - XCTAssertEqual( (922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E3") - XCTAssertEqual( (92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E2") - XCTAssertEqual( (9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$100E1") - XCTAssertEqual( (0 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "$0E0") - XCTAssertEqual( (-9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E1") - XCTAssertEqual( (-92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E2") - XCTAssertEqual( (-922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E3") - XCTAssertEqual( (-9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E4") - XCTAssertEqual( (-92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E5") - XCTAssertEqual( (-922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E6") - XCTAssertEqual( (-9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E7") - XCTAssertEqual( (-92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E8") - XCTAssertEqual( (-922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E9") - XCTAssertEqual( (-9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E10") - XCTAssertEqual( (-92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E11") - XCTAssertEqual( (-922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E12") - XCTAssertEqual( (-9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E13") - XCTAssertEqual( (-92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E14") - XCTAssertEqual((-922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)), "-$100E15") + #expect( (922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E15") + #expect( (92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E14") + #expect( (9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E13") + #expect( (922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E12") + #expect( (92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E11") + #expect( (9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E10") + #expect( (922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E9") + #expect( (92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E8") + #expect( (9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E7") + #expect( (922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E6") + #expect( (92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E5") + #expect( (9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E4") + #expect( (922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E3") + #expect( (92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E2") + #expect( (9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$100E1") + #expect( (0 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "$0E0") + #expect( (-9 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E1") + #expect( (-92 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E2") + #expect( (-922 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E3") + #expect( (-9223 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E4") + #expect( (-92233 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E5") + #expect( (-922337 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E6") + #expect( (-9223372 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E7") + #expect( (-92233720 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E8") + #expect( (-922337203 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E9") + #expect( (-9223372036 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E10") + #expect( (-92233720368 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E11") + #expect( (-922337203685 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E12") + #expect( (-9223372036854 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E13") + #expect( (-92233720368547 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E14") + #expect((-922337203685477 as Decimal).formatted(baseStyle.rounded(rule: .awayFromZero, increment: 100)) == "-$100E15") } #endif // !os(watchOS) } @@ -760,11 +754,12 @@ extension IntegerFormatStyle { } #if !os(watchOS) // These tests require Int to be 64 bits, which is not always true on watch OSs yet -final class IntegerFormatStyleExhaustiveTests: XCTestCase { +@Suite("IntegerFormatStyle (Exhaustive)") +private struct IntegerFormatStyleExhaustiveTests { let exhaustiveIntNumbers : [Int64] = [9223372036854775807, 922337203685477580, 92233720368547758, 9223372036854775, 922337203685477, 92233720368547, 9223372036854, 922337203685, 92233720368, 9223372036, 922337203, 92233720, 9223372, 922337, 92233, 9223, 922, 92, 9, 0, -9, -92, -922, -9223, -92233, -922337, -9223372, -92233720, -922337203, -9223372036, -92233720368, -922337203685, -9223372036854, -92233720368547, -922337203685477, -9223372036854775, -92233720368547758, -922337203685477580, -9223372036854775808 ] - let baseStyle: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) + let baseStyle = IntegerFormatStyle(locale: Locale(identifier: "en_US")) - func testIntegerLongStyles() throws { + @Test(.timeLimit(.minutes(1))) func integerLongStyles() throws { let testSuperLongStyles: [IntegerFormatStyle] = [ baseStyle.precision(.significantDigits(Int.max...)), baseStyle.precision(.significantDigits(Int.min...Int.max)), @@ -775,24 +770,24 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { baseStyle.precision(.fractionLength(Int.max...)), baseStyle.precision(.fractionLength(Int.max...Int.max)), baseStyle.precision(.fractionLength(...Int.max)), - + // Styles that do not make sense baseStyle.precision(.significantDigits(...Int.min)), baseStyle.precision(.integerAndFractionLength(integerLimits: ...Int.min, fractionLimits: ...Int.min)), - + baseStyle.scale(Double(Int.max)), baseStyle.scale(Double(Int.min)), ] - + // The results are too long so let's just verify that they're not empty and they won't spin for style in testSuperLongStyles { for value in exhaustiveIntNumbers { - XCTAssertTrue(style.format(Int(value)).count > 0) + #expect(style.format(Int(value)).count > 0) } } } - func testEquivalentStyles() throws { + @Test func equivalentStyles() throws { let equivalentStyles: [[IntegerFormatStyle]] = [ [ baseStyle.precision(.significantDigits(2..<2)), @@ -832,7 +827,7 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { results[value] = style.format(Int(value)) } if let previousResults = previousResults, let previousStyle = previousStyle { - XCTAssertEqual(results, previousResults, "style: \(style.debugDescription) and style: \(previousStyle.debugDescription) should produce the same strings") + #expect(results == previousResults, "style: \(style.debugDescription) and style: \(previousStyle.debugDescription) should produce the same strings") } previousResults = results previousStyle = style @@ -840,7 +835,7 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { } } - func test_plainStyle_scale() throws { + @Test func plainStyle_scale() throws { let expectations: [IntegerFormatStyle : [String]] = [ baseStyle: [ "9,223,372,036,854,775,807", "922,337,203,685,477,580", @@ -1087,12 +1082,12 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { for (style, expectedStrings) in expectations { for i in 0.. : [String]] = [ baseStyle.sign(strategy: .never): [ "9,223,372,036,854,775,807", "922,337,203,685,477,580", @@ -1217,11 +1212,11 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { ] for (style, expectedStrings) in expectations { for i in 0.. : [String]] = [ baseStyle.rounded(rule: .toNearestOrEven, increment: 5): [ "9,223,372,036,854,775,805", "922,337,203,685,477,580", @@ -1307,13 +1302,13 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { for (idx, (style, expectedStrings)) in expectations.enumerated() { for i in 0.. : [String]] = [ baseStyle.notation(.scientific): [ "9.223372E18", "9.223372E17", @@ -1490,12 +1485,12 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { ] for (style, expectedStrings) in expectations { for i in 0.. : [String]] = [ // `compactName` naturally rounds the number to the closest "name". baseStyle.precision(.significantDigits(2...2)).notation(.compactName): [ @@ -1624,7 +1619,7 @@ final class IntegerFormatStyleExhaustiveTests: XCTestCase { ] for (style, expectedStrings) in expectations { for i in 0.. = .init(locale: enUS) + @Test func integerStyle() throws { + let style = IntegerFormatStyle(locale: enUS) let value = -12345 let expectations: [IntegerFormatStyle : [Segment]] = [ style: [("-", nil, .sign), ("12", .integer, nil), (",", .integer, .groupingSeparator), ("345", .integer, nil)], @@ -1674,12 +1670,12 @@ class TestNumberAttributeFormatStyle: XCTestCase { for (style, expectation) in expectations { let formatted = style.attributed.format(value) - XCTAssertEqual(formatted, expectation.attributedString) + #expect(formatted == expectation.attributedString) } } - func testIntegerStyle_Currency() throws { - let style: IntegerFormatStyle.Currency = .init(code: "EUR", locale: enUS) + @Test func integerStyle_Currency() throws { + let style = IntegerFormatStyle.Currency(code: "EUR", locale: enUS) let value = -12345 let expectations: [IntegerFormatStyle.Currency : [Segment]] = [ style: [("-", nil, .sign), ("€", nil, .currency), ("12", .integer, nil), (",", .integer, .groupingSeparator), ("345", .integer, nil), (".", nil, .decimalSeparator), ("00", .fraction, nil)], @@ -1689,12 +1685,12 @@ class TestNumberAttributeFormatStyle: XCTestCase { for (style, expectation) in expectations { let formatted = style.attributed.format(value) - XCTAssertEqual(formatted, expectation.attributedString) + #expect(formatted == expectation.attributedString) } } - func testIntegerStyle_Percent() throws { - let style: IntegerFormatStyle.Percent = .init(locale: enUS) + @Test func integerStyle_Percent() throws { + let style = IntegerFormatStyle.Percent(locale: enUS) let value = -12345 let expectations: [IntegerFormatStyle.Percent : [Segment]] = [ style: [("-", nil, .sign), ("12", .integer, nil), (",", .integer, .groupingSeparator), ("345", .integer, nil), ("%", nil, .percent)], @@ -1703,126 +1699,127 @@ class TestNumberAttributeFormatStyle: XCTestCase { for (style, expectation) in expectations { let formatted = style.attributed.format(value) - XCTAssertEqual(formatted, expectation.attributedString) + #expect(formatted == expectation.attributedString) } } - func testFloatingPoint() throws { + @Test func floatingPoint() throws { let style: FloatingPointFormatStyle = .init(locale: enUS) let value = -3000.14 - XCTAssertEqual(style.attributed.format(value), + #expect(style.attributed.format(value) == [("-", nil, .sign), ("3", .integer, nil), (",", .integer, .groupingSeparator), ("000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil)].attributedString) - XCTAssertEqual(style.precision(.fractionLength(3...3)).attributed.format(value), + #expect(style.precision(.fractionLength(3...3)).attributed.format(value) == [("-", nil, .sign), ("3", .integer, nil), (",", .integer, .groupingSeparator), ("000", .integer, nil), (".", nil, .decimalSeparator), ("140", .fraction, nil)].attributedString) - XCTAssertEqual(style.grouping(.never).attributed.format(value), + #expect(style.grouping(.never).attributed.format(value) == [("-", nil, .sign), ("3000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil)].attributedString) let percent: FloatingPointFormatStyle.Percent = .init(locale: enUS) - XCTAssertEqual(percent.attributed.format(value), + #expect(percent.attributed.format(value) == [("-", nil, .sign), ("300", .integer, nil), (",", .integer, .groupingSeparator), ("014", .integer, nil), ("%", nil, .percent)].attributedString) let currency: FloatingPointFormatStyle.Currency = .init(code: "EUR", locale: Locale(identifier: "zh_TW")) - XCTAssertEqual(currency.grouping(.never).attributed.format(value), + #expect(currency.grouping(.never).attributed.format(value) == [("-", nil, .sign), ("€", nil, .currency), ("3000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil)].attributedString) - XCTAssertEqual(currency.presentation(.fullName).attributed.format(value), + #expect(currency.presentation(.fullName).attributed.format(value) == [("-", nil, .sign), ("3", .integer, nil), (",", .integer, .groupingSeparator), ("000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil), ("歐元", nil, .currency)].attributedString) } - func testDecimalStyle() throws { + @Test func decimalStyle() throws { let style = Decimal.FormatStyle(locale: enUS) let value = Decimal(-3000.14) - XCTAssertEqual(style.attributed.format(value), + #expect(style.attributed.format(value) == [("-", nil, .sign), ("3", .integer, nil), (",", .integer, .groupingSeparator), ("000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil)].attributedString) - XCTAssertEqual(style.precision(.fractionLength(3...3)).attributed.format(value), + #expect(style.precision(.fractionLength(3...3)).attributed.format(value) == [("-", nil, .sign), ("3", .integer, nil), (",", .integer, .groupingSeparator), ("000", .integer, nil), (".", nil, .decimalSeparator), ("140", .fraction, nil)].attributedString) - XCTAssertEqual(style.grouping(.never).attributed.format(value), + #expect(style.grouping(.never).attributed.format(value) == [("-", nil, .sign), ("3000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil)].attributedString) let percent = Decimal.FormatStyle.Percent(locale: enUS) - XCTAssertEqual(percent.attributed.format(value), + #expect(percent.attributed.format(value) == [("-", nil, .sign), ("300", .integer, nil), (",", .integer, .groupingSeparator), ("014", .integer, nil), ("%", nil, .percent)].attributedString) let currency = Decimal.FormatStyle.Currency(code: "EUR", locale: Locale(identifier: "zh_TW")) - XCTAssertEqual(currency.grouping(.never).attributed.format(value), + #expect(currency.grouping(.never).attributed.format(value) == [("-", nil, .sign), ("€", nil, .currency), ("3000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil)].attributedString) - XCTAssertEqual(currency.presentation(.fullName).attributed.format(value), + #expect(currency.presentation(.fullName).attributed.format(value) == [("-", nil, .sign), ("3", .integer, nil), (",", .integer, .groupingSeparator), ("000", .integer, nil), (".", nil, .decimalSeparator), ("14", .fraction, nil), ("歐元", nil, .currency)].attributedString) } - func testSettingLocale() throws { + @Test func settingLocale() throws { let int = 42000 let double = 42000.123 let decimal = Decimal(42000.123) - XCTAssertEqual(int.formatted(.number.attributed.locale(enUS)).string, "42,000") - XCTAssertEqual(int.formatted(.number.attributed.locale(frFR)).string, "42 000") + #expect(int.formatted(.number.attributed.locale(enUS)).string == "42,000") + #expect(int.formatted(.number.attributed.locale(frFR)).string == "42 000") - XCTAssertEqual(int.formatted(.number.locale(enUS).attributed).string, "42,000") - XCTAssertEqual(int.formatted(.number.locale(frFR).attributed).string, "42 000") + #expect(int.formatted(.number.locale(enUS).attributed).string == "42,000") + #expect(int.formatted(.number.locale(frFR).attributed).string == "42 000") - XCTAssertEqual(int.formatted(.percent.attributed.locale(enUS)).string, "42,000%") - XCTAssertEqual(int.formatted(.percent.attributed.locale(frFR)).string, "42 000 %") + #expect(int.formatted(.percent.attributed.locale(enUS)).string == "42,000%") + #expect(int.formatted(.percent.attributed.locale(frFR)).string == "42 000 %") - XCTAssertEqual(int.formatted(.percent.locale(enUS).attributed).string, "42,000%") - XCTAssertEqual(int.formatted(.percent.locale(frFR).attributed).string, "42 000 %") + #expect(int.formatted(.percent.locale(enUS).attributed).string == "42,000%") + #expect(int.formatted(.percent.locale(frFR).attributed).string == "42 000 %") - XCTAssertEqual(int.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(enUS)).string, "42,000.00 US dollars") - XCTAssertEqual(int.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(frFR)).string, "42 000,00 dollars des États-Unis") + #expect(int.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(enUS)).string == "42,000.00 US dollars") + #expect(int.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(frFR)).string == "42 000,00 dollars des États-Unis") - XCTAssertEqual(int.formatted(.currency(code: "USD").presentation(.fullName).locale(enUS).attributed).string, "42,000.00 US dollars") - XCTAssertEqual(int.formatted(.currency(code: "USD").presentation(.fullName).locale(frFR).attributed).string, "42 000,00 dollars des États-Unis") + #expect(int.formatted(.currency(code: "USD").presentation(.fullName).locale(enUS).attributed).string == "42,000.00 US dollars") + #expect(int.formatted(.currency(code: "USD").presentation(.fullName).locale(frFR).attributed).string == "42 000,00 dollars des États-Unis") // Double - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.number.attributed.locale(enUS)).string, "42,000.123") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.number.attributed.locale(frFR)).string, "42 000,123") + #expect(double.formatted(FloatingPointFormatStyle.number.attributed.locale(enUS)).string == "42,000.123") + #expect(double.formatted(FloatingPointFormatStyle.number.attributed.locale(frFR)).string == "42 000,123") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.number.locale(enUS).attributed).string, "42,000.123") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.number.locale(frFR).attributed).string, "42 000,123") + #expect(double.formatted(FloatingPointFormatStyle.number.locale(enUS).attributed).string == "42,000.123") + #expect(double.formatted(FloatingPointFormatStyle.number.locale(frFR).attributed).string == "42 000,123") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.Percent.percent.attributed.locale(enUS)).string, "4,200,012.3%") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.Percent.percent.attributed.locale(frFR)).string, "4 200 012,3 %") + #expect(double.formatted(FloatingPointFormatStyle.Percent.percent.attributed.locale(enUS)).string == "4,200,012.3%") + #expect(double.formatted(FloatingPointFormatStyle.Percent.percent.attributed.locale(frFR)).string == "4 200 012,3 %") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.Percent.percent.locale(enUS).attributed).string, "4,200,012.3%") - XCTAssertEqual(double.formatted(FloatingPointFormatStyle.Percent.percent.locale(frFR).attributed).string, "4 200 012,3 %") + #expect(double.formatted(FloatingPointFormatStyle.Percent.percent.locale(enUS).attributed).string == "4,200,012.3%") + #expect(double.formatted(FloatingPointFormatStyle.Percent.percent.locale(frFR).attributed).string == "4 200 012,3 %") - XCTAssertEqual(double.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(enUS)).string, "42,000.12 US dollars") - XCTAssertEqual(double.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(frFR)).string, "42 000,12 dollars des États-Unis") + #expect(double.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(enUS)).string == "42,000.12 US dollars") + #expect(double.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(frFR)).string == "42 000,12 dollars des États-Unis") - XCTAssertEqual(double.formatted(.currency(code: "USD").presentation(.fullName).locale(enUS).attributed).string, "42,000.12 US dollars") - XCTAssertEqual(double.formatted(.currency(code: "USD").presentation(.fullName).locale(frFR).attributed).string, "42 000,12 dollars des États-Unis") + #expect(double.formatted(.currency(code: "USD").presentation(.fullName).locale(enUS).attributed).string == "42,000.12 US dollars") + #expect(double.formatted(.currency(code: "USD").presentation(.fullName).locale(frFR).attributed).string == "42 000,12 dollars des États-Unis") // Decimal - XCTAssertEqual(decimal.formatted(.number.attributed.locale(enUS)).string, "42,000.123") - XCTAssertEqual(decimal.formatted(.number.attributed.locale(frFR)).string, "42 000,123") + #expect(decimal.formatted(.number.attributed.locale(enUS)).string == "42,000.123") + #expect(decimal.formatted(.number.attributed.locale(frFR)).string == "42 000,123") - XCTAssertEqual(decimal.formatted(.number.locale(enUS).attributed).string, "42,000.123") - XCTAssertEqual(decimal.formatted(.number.locale(frFR).attributed).string, "42 000,123") + #expect(decimal.formatted(.number.locale(enUS).attributed).string == "42,000.123") + #expect(decimal.formatted(.number.locale(frFR).attributed).string == "42 000,123") - XCTAssertEqual(decimal.formatted(.percent.attributed.locale(enUS)).string, "4,200,012.3%") - XCTAssertEqual(decimal.formatted(.percent.attributed.locale(frFR)).string, "4 200 012,3 %") + #expect(decimal.formatted(.percent.attributed.locale(enUS)).string == "4,200,012.3%") + #expect(decimal.formatted(.percent.attributed.locale(frFR)).string == "4 200 012,3 %") - XCTAssertEqual(decimal.formatted(.percent.locale(enUS).attributed).string, "4,200,012.3%") - XCTAssertEqual(decimal.formatted(.percent.locale(frFR).attributed).string, "4 200 012,3 %") + #expect(decimal.formatted(.percent.locale(enUS).attributed).string == "4,200,012.3%") + #expect(decimal.formatted(.percent.locale(frFR).attributed).string == "4 200 012,3 %") - XCTAssertEqual(decimal.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(enUS)).string, "42,000.12 US dollars") - XCTAssertEqual(decimal.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(frFR)).string, "42 000,12 dollars des États-Unis") + #expect(decimal.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(enUS)).string == "42,000.12 US dollars") + #expect(decimal.formatted(.currency(code: "USD").presentation(.fullName).attributed.locale(frFR)).string == "42 000,12 dollars des États-Unis") - XCTAssertEqual(decimal.formatted(.currency(code: "USD").presentation(.fullName).locale(enUS).attributed).string, "42,000.12 US dollars") - XCTAssertEqual(decimal.formatted(.currency(code: "USD").presentation(.fullName).locale(frFR).attributed).string, "42 000,12 dollars des États-Unis") + #expect(decimal.formatted(.currency(code: "USD").presentation(.fullName).locale(enUS).attributed).string == "42,000.12 US dollars") + #expect(decimal.formatted(.currency(code: "USD").presentation(.fullName).locale(frFR).attributed).string == "42 000,12 dollars des États-Unis") } } // MARK: Pattern Matching -final class FormatStylePatternMatchingTests : XCTestCase { +@Suite("FormatStyle Pattern Matching") +private struct FormatStylePatternMatchingTests { let frFR = Locale(identifier: "fr_FR") let enUS = Locale(identifier: "en_US") typealias TestCase = (string: String, style: IntegerFormatStyle, value: Int?) - func testIntegerFormatStyle_Consumer() { + @Test func integerFormatStyle_Consumer() { let style: IntegerFormatStyle = .init() let string = "42,000,000" @@ -1862,8 +1859,8 @@ final class FormatStylePatternMatchingTests : XCTestCase { } } - func testPercentFormatStyle_Consumer() { - let style: IntegerFormatStyle.Percent = .init() + @Test func percentFormatStyle_Consumer() { + let style = IntegerFormatStyle.Percent() let string = "42%" _verifyMatching(string, formatStyle: style, expectedUpperBound: string.endIndex, expectedValue: 42) @@ -1903,9 +1900,9 @@ final class FormatStylePatternMatchingTests : XCTestCase { } } - func testCurrencyFormatStyle_Consumer() { - let style: IntegerFormatStyle.Currency = .init(code: "USD", locale: enUS) - let floatStyle: FloatingPointFormatStyle.Currency = .init(code: "USD", locale: enUS) + @Test func currencyFormatStyle_Consumer() { + let style = IntegerFormatStyle.Currency(code: "USD", locale: enUS) + let floatStyle = FloatingPointFormatStyle.Currency(code: "USD", locale: enUS) let decimalStyle = Decimal.FormatStyle.Currency(code: "USD", locale: enUS) let string = "$52,249" @@ -1937,7 +1934,7 @@ final class FormatStylePatternMatchingTests : XCTestCase { let frenchStyle: IntegerFormatStyle.Currency = .init(code: "EUR", locale: frFR) let frenchPrice = frenchStyle.format(57379) - XCTAssertEqual(frenchPrice, "57 379,00 €") + #expect(frenchPrice == "57 379,00 €") _verifyMatching("57 379,00 €", formatStyle: frenchStyle, expectedUpperBound: "57 379,00 €".endIndex, expectedValue: 57379) _verifyMatching("57 379 €", formatStyle: frenchStyle, expectedUpperBound: "57 379 €".endIndex, expectedValue: 57379) _verifyMatching("57 379,00 € semble beaucoup", formatStyle: frenchStyle, expectedUpperBound: "57 379,00 €".endIndex, expectedValue: 57379) @@ -1999,10 +1996,10 @@ final class FormatStylePatternMatchingTests : XCTestCase { } } - func testMatchPartialRange_Number() { + @Test func matchPartialRange_Number() { let decimalStyle = Decimal.FormatStyle(locale: enUS) - let intStyle: IntegerFormatStyle = .init(locale: enUS) - let floatStyle: FloatingPointFormatStyle = .init(locale: enUS) + let intStyle = IntegerFormatStyle(locale: enUS) + let floatStyle = FloatingPointFormatStyle(locale: enUS) let string = "12,345,678,900" _match(string, decimalStyle, range: 0..<6, @@ -2113,7 +2110,7 @@ final class FormatStylePatternMatchingTests : XCTestCase { } /* FIXME: These return nil currently. Should these return greedily-matched numbers? - func testGreedyMatchPartialRange() { + @Test func greedyMatchPartialRange() { let style = Decimal.FormatStyle(locale: enUS) _match(string, style, range: 0..<8, expectedUpperBound: 6, expectedValue: 12345) // "12,345,6" @@ -2132,11 +2129,11 @@ extension FormatStylePatternMatchingTests { range: Range, expectedUpperBound: Int?, expectedValue: Value?, - file: StaticString = #filePath, line: UInt = #line) where Consumer.RegexOutput == Value { + sourceLocation: SourceLocation = #_sourceLocation) where Consumer.RegexOutput == Value { let upperInString = expectedUpperBound != nil ? str.index(str.startIndex, offsetBy: expectedUpperBound!) : nil let rangeInString = str.index(str.startIndex, offsetBy: range.lowerBound)..? = nil, expectedUpperBound: String.Index?, expectedValue: Value?, - file: StaticString = #filePath, line: UInt = #line) where Consumer.RegexOutput == Value { + sourceLocation: SourceLocation = #_sourceLocation) where Consumer.RegexOutput == Value { let resolvedRange = range ?? str.startIndex ..< str.endIndex let m = try? formatStyle.consuming(str, startingAt: startingAt ?? resolvedRange.lowerBound, in: resolvedRange) let upperBound = m?.upperBound @@ -2155,46 +2152,47 @@ extension FormatStylePatternMatchingTests { let upperBoundDescription = upperBound?.utf16Offset(in: str) let expectedUpperBoundDescription = expectedUpperBound?.utf16Offset(in: str) - XCTAssertEqual( - upperBound, expectedUpperBound, + #expect(upperBound == expectedUpperBound, "found upperBound: \(String(describing: upperBoundDescription)); expected: \(String(describing: expectedUpperBoundDescription))", - file: file, line: line) - XCTAssertEqual(match, expectedValue, file: file, line: line) + sourceLocation: sourceLocation) + #expect(match == expectedValue, sourceLocation: sourceLocation) } } // MARK: - FoundationPreview Disabled Tests #if FOUNDATION_FRAMEWORK extension NumberFormatStyleTests { - func testFormattedLeadingDotSyntax() { - let integer = 12345 - XCTAssertEqual(integer.formatted(.number), integer.formatted(IntegerFormatStyle.number)) - XCTAssertEqual(integer.formatted(.percent), integer.formatted(IntegerFormatStyle.Percent.percent)) - XCTAssertEqual(integer.formatted(.currency(code: "usd")), integer.formatted(IntegerFormatStyle.Currency.currency(code: "usd"))) - - let double = 1.2345 - XCTAssertEqual(double.formatted(.number), double.formatted(FloatingPointFormatStyle.number)) - XCTAssertEqual(double.formatted(.percent), double.formatted(FloatingPointFormatStyle.Percent.percent)) - XCTAssertEqual(double.formatted(.currency(code: "usd")), double.formatted(FloatingPointFormatStyle.Currency.currency(code: "usd"))) - - - func parseableFunc(_ value: Style.FormatInput, style: Style) -> Style { style } - - XCTAssertEqual(parseableFunc(UInt8(), style: .number), parseableFunc(UInt8(), style: IntegerFormatStyle.number)) - XCTAssertEqual(parseableFunc(Int16(), style: .percent), parseableFunc(Int16(), style: IntegerFormatStyle.Percent.percent)) - XCTAssertEqual(parseableFunc(Int(), style: .currency(code: "usd")), parseableFunc(Int(), style: IntegerFormatStyle.Currency.currency(code: "usd"))) - - XCTAssertEqual(parseableFunc(Float(), style: .number), parseableFunc(Float(), style: FloatingPointFormatStyle.number)) - XCTAssertEqual(parseableFunc(Double(), style: .percent), parseableFunc(Double(), style: FloatingPointFormatStyle.Percent.percent)) - XCTAssertEqual(parseableFunc(CGFloat(), style: .currency(code: "usd")), parseableFunc(CGFloat(), style: FloatingPointFormatStyle.Currency.currency(code: "usd"))) - - XCTAssertEqual(parseableFunc(Decimal(), style: .number), parseableFunc(Decimal(), style: Decimal.FormatStyle.number)) - XCTAssertEqual(parseableFunc(Decimal(), style: .percent), parseableFunc(Decimal(), style: Decimal.FormatStyle.Percent.percent)) - XCTAssertEqual(parseableFunc(Decimal(), style: .currency(code: "usd")), parseableFunc(Decimal(), style: Decimal.FormatStyle.Currency.currency(code: "usd"))) - - struct GenericWrapper {} - func parseableWrapperFunc(_ value: GenericWrapper, style: Style) -> Style { style } - XCTAssertEqual(parseableWrapperFunc(GenericWrapper(), style: .number), parseableWrapperFunc(GenericWrapper(), style: FloatingPointFormatStyle.number)) + @Test func formattedLeadingDotSyntax() async { + await usingCurrentInternationalizationPreferences { + let integer = 12345 + #expect(integer.formatted(.number) == integer.formatted(IntegerFormatStyle.number)) + #expect(integer.formatted(.percent) == integer.formatted(IntegerFormatStyle.Percent.percent)) + #expect(integer.formatted(.currency(code: "usd")) == integer.formatted(IntegerFormatStyle.Currency.currency(code: "usd"))) + + let double = 1.2345 + #expect(double.formatted(.number) == double.formatted(FloatingPointFormatStyle.number)) + #expect(double.formatted(.percent) == double.formatted(FloatingPointFormatStyle.Percent.percent)) + #expect(double.formatted(.currency(code: "usd")) == double.formatted(FloatingPointFormatStyle.Currency.currency(code: "usd"))) + + + func parseableFunc(_ value: Style.FormatInput, style: Style) -> Style { style } + + #expect(parseableFunc(UInt8(), style: .number) == parseableFunc(UInt8(), style: IntegerFormatStyle.number)) + #expect(parseableFunc(Int16(), style: .percent) == parseableFunc(Int16(), style: IntegerFormatStyle.Percent.percent)) + #expect(parseableFunc(Int(), style: .currency(code: "usd")) == parseableFunc(Int(), style: IntegerFormatStyle.Currency.currency(code: "usd"))) + + #expect(parseableFunc(Float(), style: .number) == parseableFunc(Float(), style: FloatingPointFormatStyle.number)) + #expect(parseableFunc(Double(), style: .percent) == parseableFunc(Double(), style: FloatingPointFormatStyle.Percent.percent)) + #expect(parseableFunc(CGFloat(), style: .currency(code: "usd")) == parseableFunc(CGFloat(), style: FloatingPointFormatStyle.Currency.currency(code: "usd"))) + + #expect(parseableFunc(Decimal(), style: .number) == parseableFunc(Decimal(), style: Decimal.FormatStyle.number)) + #expect(parseableFunc(Decimal(), style: .percent) == parseableFunc(Decimal(), style: Decimal.FormatStyle.Percent.percent)) + #expect(parseableFunc(Decimal(), style: .currency(code: "usd")) == parseableFunc(Decimal(), style: Decimal.FormatStyle.Currency.currency(code: "usd"))) + + struct GenericWrapper {} + func parseableWrapperFunc(_ value: GenericWrapper, style: Style) -> Style { style } + #expect(parseableWrapperFunc(GenericWrapper(), style: .number) == parseableWrapperFunc(GenericWrapper(), style: FloatingPointFormatStyle.number)) + } } } #endif @@ -2203,47 +2201,47 @@ extension NumberFormatStyleTests { extension NumberFormatStyleTests { - func testIntegerFormatStyleBigNumberNoCrash() throws { - let uint64Style: IntegerFormatStyle = .init(locale: enUSLocale) - XCTAssertEqual(uint64Style.format(UInt64.max), "18,446,744,073,709,551,615") - XCTAssertEqual(UInt64.max.formatted(.number.locale(enUSLocale)), "18,446,744,073,709,551,615") + @Test func integerFormatStyleBigNumberNoCrash() throws { + let uint64Style = IntegerFormatStyle(locale: enUSLocale) + #expect(uint64Style.format(UInt64.max) == "18,446,744,073,709,551,615") + #expect(UInt64.max.formatted(.number.locale(enUSLocale)) == "18,446,744,073,709,551,615") - let uint64Percent: IntegerFormatStyle.Percent = .init(locale: enUSLocale) - XCTAssertEqual(uint64Percent.format(UInt64.max), "18,446,744,073,709,551,615%") - XCTAssertEqual(UInt64.max.formatted(.percent.locale(enUSLocale)), "18,446,744,073,709,551,615%") + let uint64Percent = IntegerFormatStyle.Percent(locale: enUSLocale) + #expect(uint64Percent.format(UInt64.max) == "18,446,744,073,709,551,615%") + #expect(UInt64.max.formatted(.percent.locale(enUSLocale)) == "18,446,744,073,709,551,615%") - let uint64Currency: IntegerFormatStyle.Currency = .init(code: "USD", locale: enUSLocale) - XCTAssertEqual(uint64Currency.format(UInt64.max), "$18,446,744,073,709,551,615.00") - XCTAssertEqual(UInt64.max.formatted(.currency(code: "USD").locale(enUSLocale)), "$18,446,744,073,709,551,615.00") + let uint64Currency = IntegerFormatStyle.Currency(code: "USD", locale: enUSLocale) + #expect(uint64Currency.format(UInt64.max) == "$18,446,744,073,709,551,615.00") + #expect(UInt64.max.formatted(.currency(code: "USD").locale(enUSLocale)) == "$18,446,744,073,709,551,615.00") let uint64StyleAttributed: IntegerFormatStyle.Attributed = IntegerFormatStyle(locale: enUSLocale).attributed - XCTAssertEqual(String(uint64StyleAttributed.format(UInt64.max).characters), "18,446,744,073,709,551,615") - XCTAssertEqual(String(UInt64.max.formatted(.number.locale(enUSLocale).attributed).characters), "18,446,744,073,709,551,615") + #expect(String(uint64StyleAttributed.format(UInt64.max).characters) == "18,446,744,073,709,551,615") + #expect(String(UInt64.max.formatted(.number.locale(enUSLocale).attributed).characters) == "18,446,744,073,709,551,615") let uint64PercentAttributed: IntegerFormatStyle.Attributed = IntegerFormatStyle.Percent(locale: enUSLocale).attributed - XCTAssertEqual(String(uint64PercentAttributed.format(UInt64.max).characters), "18,446,744,073,709,551,615%") - XCTAssertEqual(String(UInt64.max.formatted(.percent.locale(enUSLocale).attributed).characters), "18,446,744,073,709,551,615%") + #expect(String(uint64PercentAttributed.format(UInt64.max).characters) == "18,446,744,073,709,551,615%") + #expect(String(UInt64.max.formatted(.percent.locale(enUSLocale).attributed).characters) == "18,446,744,073,709,551,615%") let uint64CurrencyAttributed: IntegerFormatStyle.Attributed = IntegerFormatStyle.Currency(code: "USD", locale: enUSLocale).attributed - XCTAssertEqual(String(uint64CurrencyAttributed.format(UInt64.max).characters), "$18,446,744,073,709,551,615.00") - XCTAssertEqual(String(UInt64.max.formatted(.currency(code: "USD").locale(enUSLocale).attributed).characters), "$18,446,744,073,709,551,615.00") - - let int64Style: IntegerFormatStyle = .init(locale: enUSLocale) - XCTAssertEqual(int64Style.format(Int64.max), "9,223,372,036,854,775,807") - XCTAssertEqual(int64Style.format(Int64.min), "-9,223,372,036,854,775,808") - XCTAssertEqual(Int64.max.formatted(.number.locale(enUSLocale)), "9,223,372,036,854,775,807") - XCTAssertEqual(Int64.min.formatted(.number.locale(enUSLocale)), "-9,223,372,036,854,775,808") - - let int64Percent: IntegerFormatStyle.Percent = .init(locale: enUSLocale) - XCTAssertEqual(int64Percent.format(Int64.max), "9,223,372,036,854,775,807%") - XCTAssertEqual(int64Percent.format(Int64.min), "-9,223,372,036,854,775,808%") - XCTAssertEqual(Int64.max.formatted(.percent.locale(enUSLocale)), "9,223,372,036,854,775,807%") - XCTAssertEqual(Int64.min.formatted(.percent.locale(enUSLocale)), "-9,223,372,036,854,775,808%") - - let int64Currency: IntegerFormatStyle.Currency = .init(code: "USD", locale: enUSLocale) - XCTAssertEqual(int64Currency.format(Int64.max), "$9,223,372,036,854,775,807.00") - XCTAssertEqual(int64Currency.format(Int64.min), "-$9,223,372,036,854,775,808.00") - XCTAssertEqual(Int64.max.formatted(.currency(code: "USD").locale(enUSLocale)), "$9,223,372,036,854,775,807.00") - XCTAssertEqual(Int64.min.formatted(.currency(code: "USD").locale(enUSLocale)), "-$9,223,372,036,854,775,808.00") + #expect(String(uint64CurrencyAttributed.format(UInt64.max).characters) == "$18,446,744,073,709,551,615.00") + #expect(String(UInt64.max.formatted(.currency(code: "USD").locale(enUSLocale).attributed).characters) == "$18,446,744,073,709,551,615.00") + + let int64Style = IntegerFormatStyle(locale: enUSLocale) + #expect(int64Style.format(Int64.max) == "9,223,372,036,854,775,807") + #expect(int64Style.format(Int64.min) == "-9,223,372,036,854,775,808") + #expect(Int64.max.formatted(.number.locale(enUSLocale)) == "9,223,372,036,854,775,807") + #expect(Int64.min.formatted(.number.locale(enUSLocale)) == "-9,223,372,036,854,775,808") + + let int64Percent = IntegerFormatStyle.Percent(locale: enUSLocale) + #expect(int64Percent.format(Int64.max) == "9,223,372,036,854,775,807%") + #expect(int64Percent.format(Int64.min) == "-9,223,372,036,854,775,808%") + #expect(Int64.max.formatted(.percent.locale(enUSLocale)) == "9,223,372,036,854,775,807%") + #expect(Int64.min.formatted(.percent.locale(enUSLocale)) == "-9,223,372,036,854,775,808%") + + let int64Currency = IntegerFormatStyle.Currency(code: "USD", locale: enUSLocale) + #expect(int64Currency.format(Int64.max) == "$9,223,372,036,854,775,807.00") + #expect(int64Currency.format(Int64.min) == "-$9,223,372,036,854,775,808.00") + #expect(Int64.max.formatted(.currency(code: "USD").locale(enUSLocale)) == "$9,223,372,036,854,775,807.00") + #expect(Int64.min.formatted(.currency(code: "USD").locale(enUSLocale)) == "-$9,223,372,036,854,775,808.00") } } diff --git a/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift b/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift index 1aedc1055..ad2896f6e 100644 --- a/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift +++ b/Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift @@ -5,80 +5,73 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// -// RUN: %target-run-simple-swift -// REQUIRES: executable_test -// REQUIRES: objc_interop -#if canImport(TestSupport) -import TestSupport -#endif +import Testing #if canImport(FoundationInternationalization) @testable import FoundationEssentials @testable import FoundationInternationalization -#endif - -#if FOUNDATION_FRAMEWORK +#elseif FOUNDATION_FRAMEWORK @testable import Foundation #endif -final class NumberParseStrategyTests : XCTestCase { - func testIntStrategy() { - let format: IntegerFormatStyle = .init() +@Suite("Number ParseStrategy") +private struct NumberParseStrategyTests { + @Test func intStrategy() throws { + let format: IntegerFormatStyle = .init(locale: Locale(identifier: "en_US")) let strategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssert(try! strategy.parse("123,123") == 123123) - XCTAssert(try! strategy.parse(" -123,123 ") == -123123) - XCTAssert(try! strategy.parse("+8,765,000") == 8765000) - XCTAssert(try! strategy.parse("+87,650,000") == 87650000) + #expect(try strategy.parse("123,123") == 123123) + #expect(try strategy.parse(" -123,123 ") == -123123) + #expect(try strategy.parse("+8,765,000") == 8765000) + #expect(try strategy.parse("+87,650,000") == 87650000) } - - func testParsingCurrency() throws { + + @Test func parsingCurrency() throws { let currencyStyle: IntegerFormatStyle.Currency = .init(code: "USD", locale: Locale(identifier: "en_US")) let strategy = IntegerParseStrategy(format: currencyStyle, lenient: true) - XCTAssertEqual(try! strategy.parse("$1.00"), 1) - XCTAssertEqual(try! strategy.parse("1.00 US dollars"), 1) - XCTAssertEqual(try! strategy.parse("USD\u{00A0}1.00"), 1) - - XCTAssertEqual(try! strategy.parse("$1,234.56"), 1234) - XCTAssertEqual(try! strategy.parse("1,234.56 US dollars"), 1234) - XCTAssertEqual(try! strategy.parse("USD\u{00A0}1,234.56"), 1234) - - XCTAssertEqual(try! strategy.parse("-$1,234.56"), -1234) - XCTAssertEqual(try! strategy.parse("-1,234.56 US dollars"), -1234) - XCTAssertEqual(try! strategy.parse("-USD\u{00A0}1,234.56"), -1234) - + #expect(try strategy.parse("$1.00") == 1) + #expect(try strategy.parse("1.00 US dollars") == 1) + #expect(try strategy.parse("USD\u{00A0}1.00") == 1) + + #expect(try strategy.parse("$1,234.56") == 1234) + #expect(try strategy.parse("1,234.56 US dollars") == 1234) + #expect(try strategy.parse("USD\u{00A0}1,234.56") == 1234) + + #expect(try strategy.parse("-$1,234.56") == -1234) + #expect(try strategy.parse("-1,234.56 US dollars") == -1234) + #expect(try strategy.parse("-USD\u{00A0}1,234.56") == -1234) + let accounting = IntegerParseStrategy(format: currencyStyle.sign(strategy: .accounting), lenient: true) - XCTAssertEqual(try! accounting.parse("($1,234.56)"), -1234) + #expect(try accounting.parse("($1,234.56)") == -1234) } - - func testParsingIntStyle() throws { - func _verifyResult(_ testData: [String], _ expected: [Int], _ style: IntegerFormatStyle, _ testName: String = "") { + + @Test func parsingIntStyle() throws { + func _verifyResult(_ testData: [String], _ expected: [Int], _ style: IntegerFormatStyle, _ testName: Comment? = nil) throws { for i in 0.. = .init(locale: locale) let data = [ 87650000, 8765000, 876500, 87650, 8765, 876, 87, 8, 0 ] - - _verifyResult([ "8.765E7", "8.765E6", "8.765E5", "8.765E4", "8.765E3", "8.76E2", "8.7E1", "8E0", "0E0" ], data, style.notation(.scientific), "int style, notation: scientific") - _verifyResult([ "87,650,000.", "8,765,000.", "876,500.", "87,650.", "8,765.", "876.", "87.", "8.", "0." ], data, style.decimalSeparator(strategy: .always), "int style, decimal separator: always") + + try _verifyResult([ "8.765E7", "8.765E6", "8.765E5", "8.765E4", "8.765E3", "8.76E2", "8.7E1", "8E0", "0E0" ], data, style.notation(.scientific), "int style, notation: scientific") + try _verifyResult([ "87,650,000.", "8,765,000.", "876,500.", "87,650.", "8,765.", "876.", "87.", "8.", "0." ], data, style.decimalSeparator(strategy: .always), "int style, decimal separator: always") } - - func testRoundtripParsing_percent() { - func _verifyRoundtripPercent(_ testData: [Int], _ style: IntegerFormatStyle.Percent, _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) { + + @Test func roundtripParsing_percent() throws { + func _verifyRoundtripPercent(_ testData: [Int], _ style: IntegerFormatStyle.Percent, _ testName: String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { for value in testData { let str = style.format(value) - let parsed = try! Int(str, strategy: style.parseStrategy) - XCTAssertEqual(value, parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", file: file, line: line) - - let nonLenientParsed = try! Int(str, format: style, lenient: false) - XCTAssertEqual(value, nonLenientParsed, file: file, line: line) + let parsed = try Int(str, strategy: style.parseStrategy) + #expect(value == parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", sourceLocation: sourceLocation) + + let nonLenientParsed = try Int(str, format: style, lenient: false) + #expect(value == nonLenientParsed, sourceLocation: sourceLocation) } } let locale = Locale(identifier: "en_US") @@ -89,38 +82,38 @@ final class NumberParseStrategyTests : XCTestCase { let negativeData: [Int] = [ -87650000, -8765000, -876500, -87650, -8765, -876, -87, -8 ] - _verifyRoundtripPercent(testData, percentStyle, "percent style") - _verifyRoundtripPercent(testData, percentStyle.sign(strategy: .always()), "percent style, sign: always") - _verifyRoundtripPercent(testData, percentStyle.grouping(.never), "percent style, grouping: never") - _verifyRoundtripPercent(testData, percentStyle.notation(.scientific), "percent style, scientific notation") - _verifyRoundtripPercent(testData, percentStyle.decimalSeparator(strategy: .always), "percent style, decimal display: always") - - _verifyRoundtripPercent(negativeData, percentStyle, "percent style") - _verifyRoundtripPercent(negativeData, percentStyle.grouping(.never), "percent style, grouping: never") - _verifyRoundtripPercent(negativeData, percentStyle.notation(.scientific), "percent style, scientific notation") - _verifyRoundtripPercent(negativeData, percentStyle.decimalSeparator(strategy: .always), "percent style, decimal display: always") - - func _verifyRoundtripPercent(_ testData: [Double], _ style: FloatingPointFormatStyle.Percent, _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) { + try _verifyRoundtripPercent(testData, percentStyle, "percent style") + try _verifyRoundtripPercent(testData, percentStyle.sign(strategy: .always()), "percent style, sign: always") + try _verifyRoundtripPercent(testData, percentStyle.grouping(.never), "percent style, grouping: never") + try _verifyRoundtripPercent(testData, percentStyle.notation(.scientific), "percent style, scientific notation") + try _verifyRoundtripPercent(testData, percentStyle.decimalSeparator(strategy: .always), "percent style, decimal display: always") + + try _verifyRoundtripPercent(negativeData, percentStyle, "percent style") + try _verifyRoundtripPercent(negativeData, percentStyle.grouping(.never), "percent style, grouping: never") + try _verifyRoundtripPercent(negativeData, percentStyle.notation(.scientific), "percent style, scientific notation") + try _verifyRoundtripPercent(negativeData, percentStyle.decimalSeparator(strategy: .always), "percent style, decimal display: always") + + func _verifyRoundtripPercent(_ testData: [Double], _ style: FloatingPointFormatStyle.Percent, _ testName: String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { for value in testData { let str = style.format(value) - let parsed = try! Double(str, format: style, lenient: true) - XCTAssertEqual(value, parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", file: file, line: line) + let parsed = try Double(str, format: style, lenient: true) + #expect(value == parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", sourceLocation: sourceLocation) - let nonLenientParsed = try! Double(str, format: style, lenient: false) - XCTAssertEqual(value, nonLenientParsed, file: file, line: line) + let nonLenientParsed = try Double(str, format: style, lenient: false) + #expect(value == nonLenientParsed, sourceLocation: sourceLocation) } } let floatData = testData.map { Double($0) } let floatStyle: FloatingPointFormatStyle.Percent = .init(locale: locale) - _verifyRoundtripPercent(floatData, floatStyle, "percent style") - _verifyRoundtripPercent(floatData, floatStyle.sign(strategy: .always()), "percent style, sign: always") - _verifyRoundtripPercent(floatData, floatStyle.grouping(.never), "percent style, grouping: never") - _verifyRoundtripPercent(floatData, floatStyle.notation(.scientific), "percent style, scientific notation") - _verifyRoundtripPercent(floatData, floatStyle.decimalSeparator(strategy: .always), "percent style, decimal display: always") + try _verifyRoundtripPercent(floatData, floatStyle, "percent style") + try _verifyRoundtripPercent(floatData, floatStyle.sign(strategy: .always()), "percent style, sign: always") + try _verifyRoundtripPercent(floatData, floatStyle.grouping(.never), "percent style, grouping: never") + try _verifyRoundtripPercent(floatData, floatStyle.notation(.scientific), "percent style, scientific notation") + try _verifyRoundtripPercent(floatData, floatStyle.decimalSeparator(strategy: .always), "percent style, decimal display: always") } - func test_roundtripCurrency() { + @Test func roundtripCurrency() { let testData: [Int] = [ 87650000, 8765000, 876500, 87650, 8765, 876, 87, 8, 0 ] @@ -128,14 +121,14 @@ final class NumberParseStrategyTests : XCTestCase { -87650000, -8765000, -876500, -87650, -8765, -876, -87, -8 ] - func _verifyRoundtripCurrency(_ testData: [Int], _ style: IntegerFormatStyle.Currency, _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) { + func _verifyRoundtripCurrency(_ testData: [Int], _ style: IntegerFormatStyle.Currency, _ testName: String = "", sourceLocation: SourceLocation = #_sourceLocation) { for value in testData { let str = style.format(value) let parsed = try! Int(str, strategy: style.parseStrategy) - XCTAssertEqual(value, parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", file: file, line: line) + #expect(value == parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", sourceLocation: sourceLocation) let nonLenientParsed = try! Int(str, format: style, lenient: false) - XCTAssertEqual(value, nonLenientParsed, file: file, line: line) + #expect(value == nonLenientParsed, sourceLocation: sourceLocation) } } @@ -157,31 +150,31 @@ final class NumberParseStrategyTests : XCTestCase { _verifyRoundtripCurrency(negativeData, currencyStyle.decimalSeparator(strategy: .always), "currency style, decimal display: always") } - func testParseCurrencyWithDifferentCodes() throws { + @Test func parseCurrencyWithDifferentCodes() throws { let enUS = Locale(identifier: "en_US") // Decimal let style = Decimal.FormatStyle.Currency(code: "GBP", locale: enUS).presentation(.isoCode) - XCTAssertEqual(style.format(3.14), "GBP 3.14") + #expect(style.format(3.14) == "GBP 3.14") let parsed = try style.parseStrategy.parse("GBP 3.14") - XCTAssertEqual(parsed, 3.14) + #expect(parsed == 3.14) // Floating point let floatingPointStyle: FloatingPointFormatStyle.Currency = .init(code: "GBP", locale: enUS).presentation(.isoCode) - XCTAssertEqual(floatingPointStyle.format(3.14), "GBP 3.14") + #expect(floatingPointStyle.format(3.14) == "GBP 3.14") let parsedFloatingPoint = try floatingPointStyle.parseStrategy.parse("GBP 3.14") - XCTAssertEqual(parsedFloatingPoint, 3.14) + #expect(parsedFloatingPoint == 3.14) // Integer let integerStyle: IntegerFormatStyle.Currency = .init(code: "GBP", locale: enUS).presentation(.isoCode) - XCTAssertEqual(integerStyle.format(32), "GBP 32.00") + #expect(integerStyle.format(32) == "GBP 32.00") let parsedInt = try integerStyle.parseStrategy.parse("GBP 32.00") - XCTAssertEqual(parsedInt, 32) + #expect(parsedInt == 32) } - func test_roundtripForeignCurrency() throws { + @Test func roundtripForeignCurrency() throws { let testData: [Int] = [ 87650000, 8765000, 876500, 87650, 8765, 876, 87, 8, 0 ] @@ -189,14 +182,14 @@ final class NumberParseStrategyTests : XCTestCase { -87650000, -8765000, -876500, -87650, -8765, -876, -87, -8 ] - func _verifyRoundtripCurrency(_ testData: [Int], _ style: IntegerFormatStyle.Currency, _ testName: String = "", file: StaticString = #filePath, line: UInt = #line) throws { + func _verifyRoundtripCurrency(_ testData: [Int], _ style: IntegerFormatStyle.Currency, _ testName: String = "", sourceLocation: SourceLocation = #_sourceLocation) throws { for value in testData { let str = style.format(value) let parsed = try Int(str, strategy: style.parseStrategy) - XCTAssertEqual(value, parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", file: file, line: line) + #expect(value == parsed, "\(testName): formatted string: \(str) parsed: \(parsed)", sourceLocation: sourceLocation) let nonLenientParsed = try Int(str, format: style, lenient: false) - XCTAssertEqual(value, nonLenientParsed, file: file, line: line) + #expect(value == nonLenientParsed, sourceLocation: sourceLocation) } } @@ -218,7 +211,7 @@ final class NumberParseStrategyTests : XCTestCase { try _verifyRoundtripCurrency(negativeData, currencyStyle.decimalSeparator(strategy: .always), "currency style, decimal display: always") } - func test_parseStategyCodable_sameCurrency() throws { + @Test func parseStategyCodable_sameCurrency() throws { // same currency code let fs: IntegerFormatStyle.Currency = .init(code: "USD", locale: Locale(identifier:"en_US")) let p = IntegerParseStrategy(format: fs) @@ -227,18 +220,15 @@ final class NumberParseStrategyTests : XCTestCase { {"formatStyle":{"locale":{"current":0,"identifier":"en_US"},"collection":{"presentation":{"option":1}},"currencyCode":"USD"},"numberFormatType":{"currency":{"_0":{"presentation":{"option":1}}}},"lenient":true,"locale":{"identifier":"en_US","current":0}} """ - guard let existingData = existingSerializedParseStrategy.data(using: String._Encoding.utf8) else { - XCTFail("Unable to get data from JSON string") - return - } + let existingData = try #require(existingSerializedParseStrategy.data(using: String.Encoding.utf8)) let decoded: IntegerParseStrategy.Currency> = try JSONDecoder().decode(IntegerParseStrategy.Currency>.self, from: existingData) - XCTAssertEqual(decoded, p) - XCTAssertEqual(decoded.formatStyle, fs) - XCTAssertEqual(decoded.formatStyle.currencyCode, "USD") + #expect(decoded == p) + #expect(decoded.formatStyle == fs) + #expect(decoded.formatStyle.currencyCode == "USD") } - func test_parseStategyCodable_differentCurrency() throws { + @Test func parseStategyCodable_differentCurrency() throws { let fs: IntegerFormatStyle.Currency = .init(code: "GBP", locale: Locale(identifier:"en_US")) let p = IntegerParseStrategy(format: fs) // Valid JSON representation for `p` @@ -246,164 +236,218 @@ final class NumberParseStrategyTests : XCTestCase { {"formatStyle":{"collection":{"presentation":{"option":1}},"locale":{"current":0,"identifier":"en_US"},"currencyCode":"GBP"},"lenient":true,"locale":{"current":0,"identifier":"en_US"},"numberFormatType":{"currency":{"_0":{"presentation":{"option":1}}}}} """ - guard let existingData = existingSerializedParseStrategy.data(using: String._Encoding.utf8) else { - XCTFail("Unable to get data from JSON string") - return - } + let existingData = try #require(existingSerializedParseStrategy.data(using: String.Encoding.utf8)) let decoded: IntegerParseStrategy.Currency> = try JSONDecoder().decode(IntegerParseStrategy.Currency>.self, from: existingData) - XCTAssertEqual(decoded, p) - XCTAssertEqual(decoded.formatStyle, fs) - XCTAssertEqual(decoded.formatStyle.currencyCode, "GBP") + #expect(decoded == p) + #expect(decoded.formatStyle == fs) + #expect(decoded.formatStyle.currencyCode == "GBP") } let testNegativePositiveDecimalData: [Decimal] = [ Decimal(string:"87650")!, Decimal(string:"8765")!, Decimal(string:"876.5")!, Decimal(string:"87.65")!, Decimal(string:"8.765")!, Decimal(string:"0.8765")!, Decimal(string:"0.08765")!, Decimal(string:"0.008765")!, Decimal(string:"0")!, Decimal(string:"-0.008765")!, Decimal(string:"-876.5")!, Decimal(string:"-87650")! ] - func testDecimalParseStrategy() throws { - func _verifyRoundtrip(_ testData: [Decimal], _ style: Decimal.FormatStyle, _ testName: String = "") { + @Test func decimalParseStrategy() throws { + func _verifyRoundtrip(_ testData: [Decimal], _ style: Decimal.FormatStyle, _ testName: Comment = "") throws { for value in testData { let str = style.format(value) - let parsed = try! Decimal(str, strategy: Decimal.ParseStrategy(formatStyle: style, lenient: true)) - XCTAssertEqual(value, parsed, "\(testName): formatted string: \(str) parsed: \(parsed)") + let parsed = try Decimal(str, strategy: Decimal.ParseStrategy(formatStyle: style, lenient: true)) + #expect(value == parsed, "\(testName): formatted string: \(str) parsed: \(parsed)") } } let style = Decimal.FormatStyle(locale: Locale(identifier: "en_US")) - _verifyRoundtrip(testNegativePositiveDecimalData, style) + try _verifyRoundtrip(testNegativePositiveDecimalData, style) } - func testDecimalParseStrategy_Currency() throws { + @Test func decimalParseStrategy_Currency() throws { let currencyStyle = Decimal.FormatStyle.Currency(code: "USD", locale: Locale(identifier: "en_US")) let strategy = Decimal.ParseStrategy(formatStyle: currencyStyle, lenient: true) - XCTAssertEqual(try! strategy.parse("$1.00"), 1) - XCTAssertEqual(try! strategy.parse("1.00 US dollars"), 1) - XCTAssertEqual(try! strategy.parse("USD\u{00A0}1.00"), 1) + #expect(try strategy.parse("$1.00") == 1) + #expect(try strategy.parse("1.00 US dollars") == 1) + #expect(try strategy.parse("USD\u{00A0}1.00") == 1) - XCTAssertEqual(try! strategy.parse("$1,234.56"), Decimal(string: "1234.56")!) - XCTAssertEqual(try! strategy.parse("1,234.56 US dollars"), Decimal(string: "1234.56")!) - XCTAssertEqual(try! strategy.parse("USD\u{00A0}1,234.56"), Decimal(string: "1234.56")!) + #expect(try strategy.parse("$1,234.56") == Decimal(string: "1234.56")!) + #expect(try strategy.parse("1,234.56 US dollars") == Decimal(string: "1234.56")!) + #expect(try strategy.parse("USD\u{00A0}1,234.56") == Decimal(string: "1234.56")!) - XCTAssertEqual(try! strategy.parse("-$1,234.56"), Decimal(string: "-1234.56")!) - XCTAssertEqual(try! strategy.parse("-1,234.56 US dollars"), Decimal(string: "-1234.56")!) - XCTAssertEqual(try! strategy.parse("-USD\u{00A0}1,234.56"), Decimal(string: "-1234.56")!) + #expect(try strategy.parse("-$1,234.56") == Decimal(string: "-1234.56")!) + #expect(try strategy.parse("-1,234.56 US dollars") == Decimal(string: "-1234.56")!) + #expect(try strategy.parse("-USD\u{00A0}1,234.56") == Decimal(string: "-1234.56")!) } - func testNumericBoundsParsing() throws { + @Test func numericBoundsParsing() throws { let locale = Locale(identifier: "en_US") do { let format: IntegerFormatStyle = .init(locale: locale) let parseStrategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssertEqual(try parseStrategy.parse(Int8.min.formatted(format)), Int8.min) - XCTAssertEqual(try parseStrategy.parse(Int8.max.formatted(format)), Int8.max) - XCTAssertThrowsError(try parseStrategy.parse("-129")) - XCTAssertThrowsError(try parseStrategy.parse("128")) + #expect(try parseStrategy.parse(Int8.min.formatted(format)) == Int8.min) + #expect(try parseStrategy.parse(Int8.max.formatted(format)) == Int8.max) + #expect(throws: (any Error).self) { + try parseStrategy.parse("-129") + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("128") + } } do { let format: IntegerFormatStyle = .init(locale: locale) let parseStrategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssertEqual(try parseStrategy.parse(Int64.min.formatted(format)), Int64.min) - XCTAssertEqual(try parseStrategy.parse(Int64.max.formatted(format)), Int64.max) - XCTAssertThrowsError(try parseStrategy.parse("-9223372036854775809")) - XCTAssertThrowsError(try parseStrategy.parse("9223372036854775808")) + #expect(try parseStrategy.parse(Int64.min.formatted(format)) == Int64.min) + #expect(try parseStrategy.parse(Int64.max.formatted(format)) == Int64.max) + #expect(throws: (any Error).self) { + try parseStrategy.parse("-9223372036854775809") + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("9223372036854775808") + } } do { let format: IntegerFormatStyle = .init(locale: locale) let parseStrategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssertEqual(try parseStrategy.parse(UInt8.min.formatted(format)), UInt8.min) - XCTAssertEqual(try parseStrategy.parse(UInt8.max.formatted(format)), UInt8.max) - XCTAssertThrowsError(try parseStrategy.parse("-1")) - XCTAssertThrowsError(try parseStrategy.parse("256")) + #expect(try parseStrategy.parse(UInt8.min.formatted(format)) == UInt8.min) + #expect(try parseStrategy.parse(UInt8.max.formatted(format)) == UInt8.max) + #expect(throws: (any Error).self) { + try parseStrategy.parse("-1") + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("256") + } } do { // TODO: Parse integers greater than Int64 let format: IntegerFormatStyle = .init(locale: locale) let parseStrategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssertEqual( try parseStrategy.parse(UInt64.min.formatted(format)), UInt64.min) - XCTAssertThrowsError(try parseStrategy.parse(UInt64.max.formatted(format))) - XCTAssertThrowsError(try parseStrategy.parse("-1")) - XCTAssertThrowsError(try parseStrategy.parse("18446744073709551616")) + #expect(try parseStrategy.parse(UInt64.min.formatted(format)) == UInt64.min) + #expect(throws: (any Error).self) { + try parseStrategy.parse(UInt64.max.formatted(format)) + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("-1") + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("18446744073709551616") + } // TODO: Parse integers greater than Int64 let maxInt64 = UInt64(Int64.max) - XCTAssertEqual( try parseStrategy.parse((maxInt64 + 0).formatted(format)), maxInt64) // not a Double - XCTAssertThrowsError(try parseStrategy.parse((maxInt64 + 1).formatted(format))) // exact Double - XCTAssertThrowsError(try parseStrategy.parse((maxInt64 + 2).formatted(format))) // not a Double - XCTAssertThrowsError(try parseStrategy.parse((maxInt64 + 3).formatted(format))) // not a Double + #expect(try parseStrategy.parse((maxInt64 + 0).formatted(format)) == maxInt64) // not a Double + #expect(throws: (any Error).self) { + try parseStrategy.parse((maxInt64 + 1).formatted(format)) // exact Doubl + } + #expect(throws: (any Error).self) { + try parseStrategy.parse((maxInt64 + 2).formatted(format)) // not a Doubl + } + #expect(throws: (any Error).self) { + try parseStrategy.parse((maxInt64 + 3).formatted(format)) // not a Doubl + } } } - func testIntegerParseStrategyDoesNotRoundLargeIntegersToNearestDouble() { - XCTAssertEqual(Double("9007199254740992"), Double(exactly: UInt64(1) << 53)!) // +2^53 + 0 -> +2^53 - XCTAssertEqual(Double("9007199254740993"), Double(exactly: UInt64(1) << 53)!) // +2^53 + 1 -> +2^53 - XCTAssertEqual(Double.significandBitCount, 52, "Double can represent each integer in -2^53 ... 2^53") + @Test func integerParseStrategyDoesNotRoundLargeIntegersToNearestDouble() throws { + #expect(Double("9007199254740992") == Double(exactly: UInt64(1) << 53)!) // +2^53 + 0 -> +2^53 + #expect(Double("9007199254740993") == Double(exactly: UInt64(1) << 53)!) // +2^53 + 1 -> +2^53 + #expect(Double.significandBitCount == 52, "Double can represent each integer in -2^53 ... 2^53") let locale = Locale(identifier: "en_US") do { let format: IntegerFormatStyle = .init(locale: locale) let parseStrategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssertNotEqual(try? parseStrategy.parse("-9223372036854776832"), -9223372036854775808) // -2^63 - 1024 (Double: -2^63) - XCTAssertNil( try? parseStrategy.parse("-9223372036854776833")) // -2^63 - 1025 (Double: -2^63 - 2048) + #expect(throws: (any Error).self) { + try parseStrategy.parse("-9223372036854776832") // -2^63 - 1024 (Double: -2^63) + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("-9223372036854776833") // -2^63 - 1025 (Double: -2^63 - 2048) + } } do { let format: IntegerFormatStyle = .init(locale: locale) let parseStrategy = IntegerParseStrategy(format: format, lenient: true) - XCTAssertNotEqual(try? parseStrategy.parse( "9223372036854776832"), 9223372036854775808) // +2^63 + 1024 (Double: +2^63) - XCTAssertNotEqual(try? parseStrategy.parse( "9223372036854776833"), 9223372036854777856) // +2^63 + 1025 (Double: +2^63 + 2048) + #expect(throws: (any Error).self) { + try parseStrategy.parse("9223372036854776832") // +2^63 + 1024 (Double: +2^63) + } + #expect(throws: (any Error).self) { + try parseStrategy.parse("9223372036854776833") // +2^63 + 1025 (Double: +2^63 + 2048) + } } } } -final class NumberExtensionParseStrategyTests: XCTestCase { +@Suite("Number Extension ParseStrategy") +private struct NumberExtensionParseStrategyTests { let enUS = Locale(identifier: "en_US") - func testDecimal_stringLength() throws { + @Test func decimal_stringLength() throws { let numberStyle = Decimal.FormatStyle(locale: enUS) - XCTAssertNotNil(try Decimal("-3,000.14159", format: numberStyle)) - XCTAssertNotNil(try Decimal("-3.14159", format: numberStyle)) - XCTAssertNotNil(try Decimal("12,345.678", format: numberStyle)) - XCTAssertNotNil(try Decimal("0.00", format: numberStyle)) + #expect(throws: Never.self) { + try Decimal("-3,000.14159", format: numberStyle) + } + #expect(throws: Never.self) { + try Decimal("-3.14159", format: numberStyle) + } + #expect(throws: Never.self) { + try Decimal("12,345.678", format: numberStyle) + } + #expect(throws: Never.self) { + try Decimal("0.00", format: numberStyle) + } let percentStyle = Decimal.FormatStyle.Percent(locale: enUS) - XCTAssertNotNil(try Decimal("-3,000.14159%", format: percentStyle)) - XCTAssertNotNil(try Decimal("-3.14159%", format: percentStyle)) - XCTAssertNotNil(try Decimal("12,345.678%", format: percentStyle)) - XCTAssertNotNil(try Decimal("0.00%", format: percentStyle)) + #expect(throws: Never.self) { + try Decimal("-3,000.14159%", format: percentStyle) + } + #expect(throws: Never.self) { + try Decimal("-3.14159%", format: percentStyle) + } + #expect(throws: Never.self) { + try Decimal("12,345.678%", format: percentStyle) + } + #expect(throws: Never.self) { + try Decimal("0.00%", format: percentStyle) + } let currencyStyle = Decimal.FormatStyle.Currency(code: "USD", locale: enUS) - XCTAssertNotNil(try Decimal("$12,345.00", format: currencyStyle)) - XCTAssertNotNil(try Decimal("$12345.68", format: currencyStyle)) - XCTAssertNotNil(try Decimal("$0.00", format: currencyStyle)) - XCTAssertNotNil(try Decimal("-$3000.0000014", format: currencyStyle)) + #expect(throws: Never.self) { + try Decimal("$12,345.00", format: currencyStyle) + } + #expect(throws: Never.self) { + try Decimal("$12345.68", format: currencyStyle) + } + #expect(throws: Never.self) { + try Decimal("$0.00", format: currencyStyle) + } + #expect(throws: Never.self) { + try Decimal("-$3000.0000014", format: currencyStyle) + } } - func testDecimal_withFormat() throws { - XCTAssertEqual(try Decimal("+3000", format: .number.locale(enUS).grouping(.never).sign(strategy: .always())), Decimal(3000)) - XCTAssertEqual(try Decimal("$3000", format: .currency(code: "USD").locale(enUS).grouping(.never)), Decimal(3000)) + @Test func decimal_withFormat() throws { + #expect(try Decimal("+3000", format: .number.locale(enUS).grouping(.never).sign(strategy: .always())) == Decimal(3000)) + #expect(try Decimal("$3000", format: .currency(code: "USD").locale(enUS).grouping(.never)) == Decimal(3000)) } - func testDecimal_withFormat_localeDependent() throws { + @Test func decimal_withFormat_localeDependent() throws { guard Locale.autoupdatingCurrent.identifier == "en_US" else { print("Your current locale is \(Locale.autoupdatingCurrent). Set it to en_US to run this test") return } - XCTAssertEqual(try Decimal("-3,000.14159", format: .number), Decimal(-3000.14159)) - XCTAssertEqual(try Decimal("-3.14159", format: .number), Decimal(-3.14159)) - XCTAssertEqual(try Decimal("12,345.678", format: .number), Decimal(12345.678)) - XCTAssertEqual(try Decimal("0.00", format: .number), 0) - - XCTAssertEqual(try Decimal("-3,000.14159%", format: .percent), Decimal(-30.0014159)) - XCTAssertEqual(try Decimal("-314.159%", format: .percent), Decimal(-3.14159)) - XCTAssertEqual(try Decimal("12,345.678%", format: .percent), Decimal(123.45678)) - XCTAssertEqual(try Decimal("0.00%", format: .percent), 0) - - XCTAssertEqual(try Decimal("$12,345.00", format: .currency(code: "USD")), Decimal(12345)) - XCTAssertEqual(try Decimal("$12345.68", format: .currency(code: "USD")), Decimal(12345.68)) - XCTAssertEqual(try Decimal("$0.00", format: .currency(code: "USD")), Decimal(0)) - XCTAssertEqual(try Decimal("-$3000.0000014", format: .currency(code: "USD")), Decimal(string: "-3000.0000014")!) + #expect(try Decimal("-3,000.14159", format: .number) == Decimal(-3000.14159)) + #expect(try Decimal("-3.14159", format: .number) == Decimal(-3.14159)) + #expect(try Decimal("12,345.678", format: .number) == Decimal(12345.678)) + #expect(try Decimal("0.00", format: .number) == 0) + + #expect(try Decimal("-3,000.14159%", format: .percent) == Decimal(-30.0014159)) + #expect(try Decimal("-314.159%", format: .percent) == Decimal(-3.14159)) + #expect(try Decimal("12,345.678%", format: .percent) == Decimal(123.45678)) + #expect(try Decimal("0.00%", format: .percent) == 0) + + #expect(try Decimal("$12,345.00", format: .currency(code: "USD")) == Decimal(12345)) + #expect(try Decimal("$12345.68", format: .currency(code: "USD")) == Decimal(12345.68)) + #expect(try Decimal("$0.00", format: .currency(code: "USD")) == Decimal(0)) + #expect(try Decimal("-$3000.0000014", format: .currency(code: "USD")) == Decimal(string: "-3000.0000014")!) } }