Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationInternationalization)
@testable import FoundationEssentials
@testable import FoundationInternationalization
#else
@testable import Foundation
#endif

// This actor is private and not exposed to tests to prevent accidentally writing tests annotated with @CurrentInternationalizationPreferencesActor which may have suspension points
// Using the global helper function below ensures that only synchronous work with no suspension points is queued
@globalActor
private actor CurrentInternationalizationPreferencesActor: GlobalActor {
static let shared = CurrentInternationalizationPreferencesActor()

private init() {}

@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 {
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()
}
}

internal func usingCurrentInternationalizationPreferences(_ body: sending () throws -> Void) async rethrows {
try await CurrentInternationalizationPreferencesActor.usingCurrentInternationalizationPreferences(body: body)
}
95 changes: 56 additions & 39 deletions Tests/FoundationInternationalizationTests/DecimalTests+Locale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,75 @@
//
//===----------------------------------------------------------------------===//

#if canImport(TestSupport)
import TestSupport
import Testing

#if canImport(FoundationInternationalization)
@testable import FoundationEssentials
@testable import FoundationInternationalization
#elseif FOUNDATION_FRAMEWORK
@testable import Foundation
#endif

final class DecimalLocaleTests : XCTestCase {
func test_stringWithLocale() {
@Suite("Decimal (Locale)")
private struct DecimalLocaleTests {

@Test func stringWithLocale() {

let en_US = Locale(identifier: "en_US")
let fr_FR = Locale(identifier: "fr_FR")

XCTAssertEqual(Decimal(string: "1,234.56")! * 1000, Decimal(1000))
XCTAssertEqual(Decimal(string: "1,234.56", locale: en_US)! * 1000, Decimal(1000))
XCTAssertEqual(Decimal(string: "1,234.56", locale: fr_FR)! * 1000, Decimal(1234))
XCTAssertEqual(Decimal(string: "1.234,56", locale: en_US)! * 1000, Decimal(1234))
XCTAssertEqual(Decimal(string: "1.234,56", locale: fr_FR)! * 1000, Decimal(1000))
#expect(Decimal(string: "1,234.56")! * 1000 == Decimal(1000))
#expect(Decimal(string: "1,234.56", locale: en_US)! * 1000 == Decimal(1000))
#expect(Decimal(string: "1,234.56", locale: fr_FR)! * 1000 == Decimal(1234))
#expect(Decimal(string: "1.234,56", locale: en_US)! * 1000 == Decimal(1234))
#expect(Decimal(string: "1.234,56", locale: fr_FR)! * 1000 == Decimal(1000))

XCTAssertEqual(Decimal(string: "-1,234.56")! * 1000, Decimal(-1000))
XCTAssertEqual(Decimal(string: "+1,234.56")! * 1000, Decimal(1000))
XCTAssertEqual(Decimal(string: "+1234.56e3"), Decimal(1234560))
XCTAssertEqual(Decimal(string: "+1234.56E3"), Decimal(1234560))
XCTAssertEqual(Decimal(string: "+123456000E-3"), Decimal(123456))
#expect(Decimal(string: "-1,234.56")! * 1000 == Decimal(-1000))
#expect(Decimal(string: "+1,234.56")! * 1000 == Decimal(1000))
#expect(Decimal(string: "+1234.56e3") == Decimal(1234560))
#expect(Decimal(string: "+1234.56E3") == Decimal(1234560))
#expect(Decimal(string: "+123456000E-3") == Decimal(123456))

XCTAssertNil(Decimal(string: ""))
XCTAssertNil(Decimal(string: "x"))
XCTAssertEqual(Decimal(string: "-x"), Decimal.zero)
XCTAssertEqual(Decimal(string: "+x"), Decimal.zero)
XCTAssertEqual(Decimal(string: "-"), Decimal.zero)
XCTAssertEqual(Decimal(string: "+"), Decimal.zero)
XCTAssertEqual(Decimal(string: "-."), Decimal.zero)
XCTAssertEqual(Decimal(string: "+."), Decimal.zero)
#expect(Decimal(string: "") == nil)
#expect(Decimal(string: "x") == nil)
#expect(Decimal(string: "-x") == Decimal.zero)
#expect(Decimal(string: "+x") == Decimal.zero)
#expect(Decimal(string: "-") == Decimal.zero)
#expect(Decimal(string: "+") == Decimal.zero)
#expect(Decimal(string: "-.") == Decimal.zero)
#expect(Decimal(string: "+.") == Decimal.zero)

XCTAssertEqual(Decimal(string: "-0"), Decimal.zero)
XCTAssertEqual(Decimal(string: "+0"), Decimal.zero)
XCTAssertEqual(Decimal(string: "-0."), Decimal.zero)
XCTAssertEqual(Decimal(string: "+0."), Decimal.zero)
XCTAssertEqual(Decimal(string: "e1"), Decimal.zero)
XCTAssertEqual(Decimal(string: "e-5"), Decimal.zero)
XCTAssertEqual(Decimal(string: ".3e1"), Decimal(3))
#expect(Decimal(string: "-0") == Decimal.zero)
#expect(Decimal(string: "+0") == Decimal.zero)
#expect(Decimal(string: "-0.") == Decimal.zero)
#expect(Decimal(string: "+0.") == Decimal.zero)
#expect(Decimal(string: "e1") == Decimal.zero)
#expect(Decimal(string: "e-5") == Decimal.zero)
#expect(Decimal(string: ".3e1") == Decimal(3))

XCTAssertEqual(Decimal(string: "."), Decimal.zero)
XCTAssertEqual(Decimal(string: ".", locale: en_US), Decimal.zero)
XCTAssertNil(Decimal(string: ".", locale: fr_FR))
#expect(Decimal(string: ".") == Decimal.zero)
#expect(Decimal(string: ".", locale: en_US) == Decimal.zero)
#expect(Decimal(string: ".", locale: fr_FR) == nil)

XCTAssertNil(Decimal(string: ","))
XCTAssertEqual(Decimal(string: ",", locale: fr_FR), Decimal.zero)
XCTAssertNil(Decimal(string: ",", locale: en_US))
#expect(Decimal(string: ",") == nil)
#expect(Decimal(string: ",", locale: fr_FR) == Decimal.zero)
#expect(Decimal(string: ",", locale: en_US) == nil)

let s1 = "1234.5678"
XCTAssertEqual(Decimal(string: s1, locale: en_US)?.description, s1)
XCTAssertEqual(Decimal(string: s1, locale: fr_FR)?.description, "1234")
#expect(Decimal(string: s1, locale: en_US)?.description == s1)
#expect(Decimal(string: s1, locale: fr_FR)?.description == "1234")

let s2 = "1234,5678"
XCTAssertEqual(Decimal(string: s2, locale: en_US)?.description, "1234")
XCTAssertEqual(Decimal(string: s2, locale: fr_FR)?.description, s1)
#expect(Decimal(string: s2, locale: en_US)?.description == "1234")
#expect(Decimal(string: s2, locale: fr_FR)?.description == s1)
}

@Test func descriptionWithLocale() throws {
let decimal = Decimal(string: "-123456.789")!
#expect(decimal._toString(withDecimalSeparator: ".") == "-123456.789")
let en = decimal._toString(withDecimalSeparator: try #require(Locale(identifier: "en_GB").decimalSeparator))
#expect(en == "-123456.789")
let fr = decimal._toString(withDecimalSeparator: try #require(Locale(identifier: "fr_FR").decimalSeparator))
#expect(fr == "-123456,789")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,28 @@
//
//===----------------------------------------------------------------------===//

#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 DurationExtensionTests : XCTestCase {
@Suite("Duration Extension")
private struct DurationExtensionTests {

func testRoundingMode() {
@Test func roundingMode() {

func verify(_ tests: [Int64], increment: Int64, expected: [FloatingPointRoundingRule: [Int64]], file: StaticString = #filePath, line: UInt = #line) {
func verify(_ tests: [Int64], increment: Int64, expected: [FloatingPointRoundingRule: [Int64]], sourceLocation: SourceLocation = #_sourceLocation) {
let modes: [FloatingPointRoundingRule] = [.down, .up, .towardZero, .awayFromZero, .toNearestOrEven, .toNearestOrAwayFromZero]
for mode in modes {
var actual: [Duration] = []
for test in tests {
actual.append(Duration.seconds(test).rounded(increment: Duration.seconds(increment), rule: mode))
}
XCTAssertEqual(actual, expected[mode]?.map { Duration.seconds($0) }, "\(mode) does not match", file: file, line: line)
#expect(actual == expected[mode]?.map { Duration.seconds($0) }, "\(mode) does not match", sourceLocation: sourceLocation)
}
}

Expand Down
Loading
Loading