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)