diff --git a/Sources/Currency/AnyCurrency+Algorithms.swift b/Sources/Currency/AnyCurrency+Algorithms.swift deleted file mode 100644 index e48ef3e..0000000 --- a/Sources/Currency/AnyCurrency+Algorithms.swift +++ /dev/null @@ -1,99 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftCurrency open source project -// -// Copyright (c) 2020 SwiftCurrency project authors -// Licensed under MIT License -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -import Foundation - -// MARK: Value Distribution - -extension AnyCurrency { - /// Distributes the current amount into a set number of parts as evenly as possible. - /// - Note: Passing a negative or `0` value will result in an empty result. - /// - Complexity: O(*n*), where *n* is the `numParts`. - /// - Parameter numParts: The count of new values the single value should be distributed between as evenly as possible. - /// - Returns: A collection of currency values with their share of the amount distribution. - public func distributedEvenly(intoParts numParts: Int) -> [Self] { - guard numParts > 0 else { return [] } - - let count = Int64(numParts) - - // courtesy of https://codereview.stackexchange.com/a/221221 - let units = self.minorUnits - let fraction = units / count - let remainder = Int(abs(units) % count) - - var results: [Self] = .init(repeating: .zero, count: numParts) - for index in 0..( - between originalValues: T - ) -> [Self] - where T: Collection, T.Element == Self - { - guard originalValues.count > 0 else { return [] } - - var results: [Self] = .init(repeating: .zero, count: originalValues.count) - - let desiredTotalUnits = self.minorUnits - guard desiredTotalUnits != 0 else { return results } - - let originalTotalUnits = originalValues.sum().minorUnits - - var currentTotalUnits: Int64 = 0 - var index = 0 - for value in originalValues.dropLast() { - defer { index += 1 } - - let proportion = Decimal(value.minorUnits) / .init(originalTotalUnits) - let newValue = Self(scalingAndRounding: self.amount * proportion) - - defer { currentTotalUnits += newValue.minorUnits } - - results[index] = newValue - } - - results[originalValues.count - 1] = Self(minorUnits: desiredTotalUnits - currentTotalUnits) - - return results - } -} diff --git a/Sources/Currency/AnyCurrency+Sequence.swift b/Sources/Currency/AnyCurrency+Sequence.swift deleted file mode 100644 index e826adf..0000000 --- a/Sources/Currency/AnyCurrency+Sequence.swift +++ /dev/null @@ -1,75 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftCurrency open source project -// -// Copyright (c) 2020 SwiftCurrency project authors -// Licensed under MIT License -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -import Foundation - -// MARK: Sum - -extension Sequence where Element: AnyCurrency { - /// Returns the sum total of all amounts in the sequence. - /// - /// For example: - /// - /// let amounts: [USD] = [304.98, 19.02] - /// let sumTotal = amounts.sum() - /// print(sumTotal) - /// // prints "324" - /// - /// If the sequence has no elements, you will receive a currency with a value of "0". - /// - Complexity: O(*n*) , where *n* is the length of the sequence. - /// - Returns: A currency value representing the sum total of all the amounts in the sequence. - public func sum() -> Element { - return self.reduce(into: .zero, +=) - } - - /// Returns the sum total of all amounts in the sequence that satify the given predicate. - /// For example: - /// - /// let amounts: [USD] = [304.98, 19.02, 30.21] - /// let sumTotal = amounts.sum(where: { $0.amount > 20 }) - /// print(sumTotal) - /// // prints "335.19" - /// - /// - Complexity: O(*n*), where *n* is the length of the sequence. - /// - Parameter isIncluded: A closure that takes a currency element as its argument - /// and returns a Boolean value that indicates whether the passed element should be included in the sum. - /// - Returns: A currency value representing the sum total of all the amounts `isIncluded` allowed. - @inlinable - public func sum(where isIncluded: (Element) throws -> Bool) rethrows -> Element { - return try self.reduce(into: .zero) { result, next in - guard try isIncluded(next) else { return } - result += next - } - } - - /// Returns the sum total of amounts in the sequence after applying the provided transform. - /// - /// Rather than doing a `.map(_:)` and then `.sum()`, the `sum` result will be calculated inline while applying the transformations. - /// - /// For example, you may want to calculate what the total taxes would be from a sequence of individual prices: - /// - /// let prices: [USD] = [3.00, 2.99, 5.98] - /// // apply a 9% tax rate to each price and calculate the sum - /// let totalTaxes = prices.sum { $0 * Decimal(0.09) } - /// // totalTaxes == USD(1.08) - /// - /// - Complexity: O(*n*), where *n* is the length of the sequence. - /// - Parameter transform: A mapping closure. `transform` accepts an element of this sequence as its parameter - /// and returns a transformed value of the same type. - /// - Returns: A currency value representing the sum total of all the transformed amounts in the sequence. - @inlinable - public func sum(_ transform: (Element) throws -> (Element)) rethrows -> Element { - return try self.reduce(into: .zero) { $0 += try transform($1) } - } -} diff --git a/Sources/Currency/AnyCurrency.swift b/Sources/Currency/AnyCurrency.swift deleted file mode 100644 index b70cdc3..0000000 --- a/Sources/Currency/AnyCurrency.swift +++ /dev/null @@ -1,296 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftCurrency open source project -// -// Copyright (c) 2020 SwiftCurrency project authors -// Licensed under MIT License -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -import Foundation - -/// A representation of a value in a specific currency that can be used as an existential. -/// -/// In most cases, it's preferable to work with currencies in their `CurrencyProtocol` generic form. -/// -/// **Value Representation** -/// -/// All currencies have a "minorUnits" scale, as defined by their `CurrencyMetadata` that determine their precision when represented as a `Foundation.Decimal`. -/// -/// For example, as the USD uses 1/100 for its minor unit, with the value `USD(1.0)`, `minorUnits` will be the value `100`. -/// -/// _Equality comparisons and most arithmetic operations use this value._ -/// -/// **Floating Point Initialization** -/// -/// All floating point type values provided to initializers will be "bankers" rounded to their `CurrencyMetadata.minorUnits` scale before being stored in memory. -public protocol AnyCurrency: - CustomStringConvertible, CustomDebugStringConvertible, CustomPlaygroundDisplayConvertible, - CustomLeafReflectable -{ - /// The ISO 4217 information about this currency. - static var metadata: CurrencyMetadata.Type { get } - - /// The amount represented as a whole number of the currency's "minorUnits". - /// See `CurrencyMetadata.minorUnits` and `Foundation.RoundingMode.bankers`. - var minorUnits: Int64 { get } - - /// The `bankers` rounded amount of money being represented, as defined by the currency. - /// - /// For example: - /// - /// let usd = USD(10.007) - /// let yen = JPY(100.9) - /// let dinar = KWD(100.0019) - /// print(usd, yen, dinar) - /// // "USD(10.01), JPY(101), KWD(100.002) - /// - /// See `CurrencyMetadata.minorUnits` and `Foundation.RoundingMode.bankers`. - var amount: Decimal { get } - - /// Creates a representation of the provided amount as the currency's exact smallest unit. - /// - /// For example, the USD has cents, which are 1/100 of 1 USD. Calling `USD(minorUnits: 100)` will create a value of `1.0`. - /// - /// See `CurrencyMetadata.minorUnits`. - /// - Parameter minorUnits: The exact amount of the smallest units in the currency to represent. - init(minorUnits: T) - - /// Creates a representation of the provided amount in the desired currency, if it can be represented. - /// - /// The value will be "bankers" rounded, to the precision as defined by the currency's "minorUnits" metadata. - /// - /// See `CurrencyMetadata.minorUnits` and `Foundation.Decimal.RoundingMode.bankers`. - /// - Parameter amount: The amount this instance should represent, after "bankers" rounding. If this value is `NaN`, then initialization will fail. - init?(amount: Decimal) -} - -// MARK: Helpers - -extension AnyCurrency { - internal init(scalingAndRounding value: Decimal) { - let result = value.roundedAndScaled(to: Self.metadata.minorUnits) - self.init(minorUnits: result.int64Value) - } -} - -// MARK: Default Implementations - -extension AnyCurrency { - public var amount: Decimal { - return Decimal(minorUnits).scaled(to: .init(Self.metadata.minorUnits), inverse: true) - } - - public var customMirror: Mirror { - return .init(self, children: [ - "minorUnits": self.minorUnits, - "metadata": Self.metadata - ]) - } - - public init?(amount: Decimal) { - switch amount { - case .nan, .quietNaN: return nil - default: self.init(scalingAndRounding: amount) - } - } - - public static func ==(lhs: Self, rhs: Self) -> Bool { - return lhs.minorUnits == rhs.minorUnits - } - - public static func <(lhs: Self, rhs: Self) -> Bool { - return lhs.minorUnits < rhs.minorUnits - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(self.minorUnits) - } -} - -extension AnyCurrency where Self: CurrencyMetadata { - public static var metadata: CurrencyMetadata.Type { return Self.self } - - public var customMirror: Mirror { - return .init(self, children: [ - "minorUnits": self.minorUnits, - "metadata": ( - name: Self.name, - alphabeticCode: Self.alphabeticCode, - numericCode: Self.numericCode, - minorUnits: Self.minorUnits - ) - ]) - } -} - -// MARK: Arithmetic - -extension AnyCurrency { - public static var zero: Self { return Self(minorUnits: 0) } - - public static func +(lhs: Self, rhs: Self) -> Self { - return .init(minorUnits: lhs.minorUnits + rhs.minorUnits) - } - - public static func -(lhs: Self, rhs: Self) -> Self { - return .init(minorUnits: lhs.minorUnits - rhs.minorUnits) - } - - public static func *(lhs: Self, rhs: Self) -> Self { - let result = Double(lhs.minorUnits * rhs.minorUnits) / pow(10, Double(Self.metadata.minorUnits)) - return .init(minorUnits: Int64(result.rounded(.toNearestOrEven))) - } - - public static func /(lhs: Self, rhs: Self) -> Self { - let quotent = Double(lhs.minorUnits) / .init(rhs.minorUnits) - let result = quotent * pow(10, Double(Self.metadata.minorUnits)) - return .init(minorUnits: Int64(result.rounded(.toNearestOrEven))) - } - - public static func +=(lhs: inout Self, rhs: Self) { lhs = lhs + rhs } - public static func -=(lhs: inout Self, rhs: Self) { lhs = lhs - rhs } - public static func *=(lhs: inout Self, rhs: Self) { lhs = lhs * rhs } - public static func /=(lhs: inout Self, rhs: Self) { lhs = lhs / rhs } - - /// Returns the current value as its additive inverse. - /// - /// The following example uses the `negated()` method to negate the currency value: - /// - /// let negativeAmount = USD(3.40).negated() - /// // negativeAmount == USD(-3.40) - /// - /// - Note: The minimum value of fixed-width integer types cannot be represented when negated, as it causes an integer overflow. - /// e.g. `Int8.min` is `-128` while `Int8.max` is `127`. - /// - /// If the resulting value is not representable within an `Int64`, then the value will be clamped to `Int64.max` to avoid integer overflow. - /// - /// For example: - /// - /// let negationOverflow = USD(minorUnits: Int64.min).negated() - /// // negationOverflow == USD(minorUnits: Int64.max) - /// - Complexity: O(1) - public func negated() -> Self { - // overflow of integer by flipping the sign can only happen when going from negative to positive - // this is because any valid positive value can be represented as its negative inverse - but the inverse is not true - // Int8.min == -128, while Int8.max == 127 - let result = self.minorUnits.multipliedReportingOverflow(by: -1) - return .init(minorUnits: result.overflow ? Int64.max : result.partialValue) - } -} - -// MARK: String Representation - -// price.description 3000.98 USD -// price.debugDescription USD(3000.98) -// price.playgroundDescription USD(3000.98) -// "\(localize: price)" $3,000.98 -// "\(localize: price, with: ...)" $3,000.98 -// "\(localize: price, for: ...)" $3,000.98 - -extension AnyCurrency { - public var description: String { return "\(self.amount.description) \(Self.metadata.alphabeticCode)" } - public var debugDescription: String { return "\(Self.metadata.alphabeticCode)(\(self.amount.description))"} - public var playgroundDescription: Any { return self.debugDescription } - - /// Creates a string representation of the currency value, localized to a particular locale. - /// - /// let usd = USD(30.03) - /// print(usd.localizedString(for: .init(identifier: "fr_FR"))) - /// // 30,03 $US - /// print(usd.localizedString()) - /// // $30.03, assuming `Locale.current` is "en_US" - /// - /// - Note: This can also be done with String interpolation: - /// - /// let pounds = GBP(30.03) - /// let localizedString = "\(localize: pounds, for: Locale(identifier: "de_DE"))" - /// print(localizedString) - /// // 30,03 £ - /// print("\(localize: pounds)") - /// // £30.03, assuming `Locale.current` is "en_US" - /// - /// - Parameters: - /// - locale: The Locale to localize the value for. The default is `.current`, ig. the runtime environment's Locale. - /// - nilValue: The String representation to use if the value is `nil` or localization fails. The default is `"nil"`. - /// - Returns: A localized String representation of the currency value. - public func localizedString(for locale: Locale = .current, nilDescription nilValue: String = "nil") -> String { - return "\(localize: self, for: locale, nilDescription: nilValue)" - } - - /// Creates a string representation of the currency value, using the provided formatter. - /// - /// let formatter = NumberFormatter() - /// formatter.numberStyle = .currency - /// formatter.currencyGroupingSeparator = " " - /// formatter.currencyDecimalSeparator = "'" - /// formatter.currencyCode = "GBP" - /// - /// let pounds = GBP(14928.02) - /// print(pounds.localizedString(using: formatter)) - /// // £14 928'02 - /// - ///- Note: This can also be done with String interpolation: - /// - /// let formatter = ... - /// let currency = ... - /// let localizedString = "\(localize: currency, with: formatter)" - /// - /// - Important: Since `Foundation.NumberFormatter` is a class, this method does not set the `currencyCode` property automatically. - /// - /// If it did, this method would no longer be thread safe, and would mutate the provided instance. - /// - /// If the same formatter is to be used for different currencies, the property will need to be updated before calling this method. - /// - /// - Parameters: - /// - formatter: The pre-configured formatter to use. - /// - nilValue: The String representation to use if the value is `nil` or localization fails. The default is `"nil"`. - /// - Returns: A localized String representation of the currency value. - public func localizedString(using formatter: NumberFormatter, nilDescription nilValue: String = "nil") -> String { - return "\(localize: self, with: formatter, nilDescription: nilValue)" - } -} - -extension String.StringInterpolation { - public mutating func appendInterpolation( - localize value: Currency?, - for locale: Locale = .current, - nilDescription nilValue: String = "nil" - ) { - guard case let .some(value) = value else { return nilValue.write(to: &self) } - - let formatter = NumberFormatter() - formatter.numberStyle = .currency - formatter.locale = locale - formatter.currencyCode = Currency.metadata.alphabeticCode - - self.appendInterpolation(localize: value, with: formatter, nilDescription: nilValue) - } - - public mutating func appendInterpolation( - localize value: Currency?, - with formatter: NumberFormatter, - nilDescription nilValue: String = "nil" - ) { - guard case let .some(value) = value else { return nilValue.write(to: &self) } - - #if swift(<5.1) - guard let localizedString = formatter.string(from: NSDecimalNumber(decimal: value.amount)) else { - nilValue.write(to: &self) - return - } - #else - guard let localizedString = formatter.string(from: value.amount as NSDecimalNumber) else { - nilValue.write(to: &self) - return - } - #endif - - localizedString.write(to: &self) - } -} diff --git a/Sources/Currency/CurrencyProtocol.swift b/Sources/Currency/CurrencyProtocol.swift deleted file mode 100644 index 0a3ed72..0000000 --- a/Sources/Currency/CurrencyProtocol.swift +++ /dev/null @@ -1,51 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftCurrency open source project -// -// Copyright (c) 2020 SwiftCurrency project authors -// Licensed under MIT License -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -import struct Foundation.Decimal - -/// A representation of a value in a specific currency. -/// -/// When a value instance needs to be used outside of a generic context, the `AnyCurrency` protocol can be used as an existential. -/// -/// `CurrencyProtocol` provides the same functionality of `AnyCurrency`, -/// in addition to `Comparable`, `Hashable`, `ExpressibleByIntegerLiteral` and `ExpressibleByFloatLiteral`. -public protocol CurrencyProtocol: AnyCurrency, - Comparable, Hashable, - AdditiveArithmetic, - ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral -{ } - -// MARK: Default Implementations - -extension CurrencyProtocol { - public init(floatLiteral value: Double) { - self.init(scalingAndRounding: Decimal(value)) - } - - public init(integerLiteral value: Int64) { - self.init(scalingAndRounding: Decimal(value)) - } -} - -// MARK: AnyCurrency Implementation Overrides - -extension CurrencyProtocol { - public static var zero: Self { return Self(minorUnits: 0) } - - // https://bugs.swift.org/browse/SR-12128 - #if swift(>=5.2) - public static func +=(lhs: inout Self, rhs: Self) { lhs = lhs + rhs } - public static func -=(lhs: inout Self, rhs: Self) { lhs = lhs - rhs } - #endif -} diff --git a/Sources/Currency/Extensions/Decimal.swift b/Sources/Currency/Extensions/Decimal.swift index f51be1f..3502997 100644 --- a/Sources/Currency/Extensions/Decimal.swift +++ b/Sources/Currency/Extensions/Decimal.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftCurrency open source project // -// Copyright (c) 2020 SwiftCurrency project authors +// Copyright (c) 2024 SwiftCurrency project authors // Licensed under MIT License // // See LICENSE.txt for license information @@ -15,24 +15,5 @@ import Foundation extension Decimal { - #if swift(<5.1) - internal var int64Value: Int64 { return NSDecimalNumber(decimal: self).int64Value } - #else internal var int64Value: Int64 { return (self as NSNumber).int64Value } - #endif - - internal func roundedAndScaled(to unitScale: UInt8) -> Decimal { - let scale = Int(unitScale) - var result = Decimal.zero - withUnsafePointer(to: self) { NSDecimalRound(&result, $0, scale, .bankers) } - return result.scaled(to: scale) - } - - internal func scaled(to scale: Int, inverse: Bool = false) -> Decimal { - return self * .init( - sign: .plus, - exponent: inverse ? scale * -1 : scale, - significand: 1 - ) - } } diff --git a/Sources/ISOStandardCodegen/ISOCurrencyGeneration.swift b/Sources/ISOStandardCodegen/ISOCurrencyGeneration.swift index 67c3f6d..8ae8a60 100644 --- a/Sources/ISOStandardCodegen/ISOCurrencyGeneration.swift +++ b/Sources/ISOStandardCodegen/ISOCurrencyGeneration.swift @@ -53,21 +53,7 @@ private func makeTypeDefinitions(from metadata: [CurrencyMetadata]) -> [String] return """ \(summary) - public struct \(definition.identifiers.alphabetic): CurrencyProtocol, CurrencyMetadata { - public static var name: String { "\(definition.name)" } - public static var alphabeticCode: String { "\(definition.identifiers.alphabetic)" } - public static var numericCode: UInt16 { \(definition.identifiers.numeric) } - public static var minorUnits: UInt8 { \(definition.minorUnits) } - - public var minorUnits: Int64 { self._minorUnits } - - private let _minorUnits: Int64 - - public init(minorUnits: T) { self._minorUnits = .init(minorUnits) } - } - - \(summary) - public struct _New_\(definition.identifiers.alphabetic): Currency, CurrencyDescriptor { + public struct \(definition.identifiers.alphabetic): Currency, CurrencyDescriptor { public static var name: String { "\(definition.name)" } public static var alphabeticCode: String { "\(definition.identifiers.alphabetic)" } public static var numericCode: UInt16 { \(definition.identifiers.numeric) } diff --git a/Sources/ISOStandardCodegen/MintISOSupportGeneration.swift b/Sources/ISOStandardCodegen/MintISOSupportGeneration.swift index d3df8be..58dd221 100644 --- a/Sources/ISOStandardCodegen/MintISOSupportGeneration.swift +++ b/Sources/ISOStandardCodegen/MintISOSupportGeneration.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftCurrency open source project // -// Copyright (c) 2022 SwiftCurrency project authors +// Copyright (c) 2024 SwiftCurrency project authors // Licensed under MIT License // // See LICENSE.txt for license information @@ -43,7 +43,7 @@ fileprivate func makeIdentifierLookupSnippet( .map { let patternMatch = type == .numeric ? $0.identifiers.numeric.description : "\"\($0.identifiers.alphabetic)\"" - return "case \(patternMatch): return _New_\($0.identifiers.alphabetic).self" + return "case \(patternMatch): return \($0.identifiers.alphabetic).self" } .joined(separator: "\n\t\t") diff --git a/Tests/CurrencyTests/AnyCurrencyAlgorithmsTests.swift b/Tests/CurrencyTests/AnyCurrencyAlgorithmsTests.swift deleted file mode 100644 index 099692e..0000000 --- a/Tests/CurrencyTests/AnyCurrencyAlgorithmsTests.swift +++ /dev/null @@ -1,187 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftCurrency open source project -// -// Copyright (c) 2020 SwiftCurrency project authors -// Licensed under MIT License -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -import Currency -import XCTest - -public final class AnyCurrencyAlgorithmsTests: XCTestCase { } - -// MARK: Sequence - -extension AnyCurrencyAlgorithmsTests { - func testSequenceSum() { - let amounts = [USD(30.47), -107.8239, 1_203.9832, -504.3982] - XCTAssertEqual(amounts.sum().amount, 622.23) - } - - func testSequenceSum_withPredicate() { - let amounts: [USD] = [304.98, 19.02, 30.21] - let sumTotal = amounts.sum(where: { $0.amount > 20 }) - XCTAssertEqual(sumTotal.amount, 335.19) - } -} - - -// MARK: Distributed Evenly - -extension AnyCurrencyAlgorithmsTests { - func test_distributedEvenly() { - let amount = USD(15.01) - XCTAssertEqual(amount.distributedEvenly(intoParts: 3), [5.01, 5, 5]) - XCTAssertEqual(amount.distributedEvenly(intoParts: 0), []) - XCTAssertEqual(amount.distributedEvenly(intoParts: -1), []) - XCTAssertEqual(amount.negated().distributedEvenly(intoParts: 4), [-3.76, -3.75, -3.75, -3.75]) - } - - // minorUnits == 2 - func test_distributedEvenly_usd() { - self.run_distributedEvenlyTest( - sourceAmount: USD(10.05), - numParts: 6, - expectedResults: .init(repeating: USD(1.68), count: 3) + .init(repeating: USD(1.67), count: 3) - ) - } - - // minorUnits == 3 - func test_distributedEvenly_bhd() { - self.run_distributedEvenlyTest( - sourceAmount: BHD(180), - numParts: 7, - expectedResults: .init(repeating: BHD(25.715), count: 2) + .init(repeating: BHD(25.714), count: 5) - ) - self.run_distributedEvenlyTest( - sourceAmount: BHD(10.1983), - numParts: 3, - expectedResults: [BHD(3.4)] + .init(repeating: BHD(3.399), count: 2) - ) - } - - /// minorUnits == 0 - func test_distributedEvenly_bif() { - self.run_distributedEvenlyTest( - sourceAmount: BIF(298), - numParts: 3, - expectedResults: [100] + .init(repeating: BIF(99), count: 2) - ) - self.run_distributedEvenlyTest( - sourceAmount: BIF(157.982), - numParts: 9, - expectedResults: .init(repeating: BIF(18), count: 5) + .init(repeating: BIF(17), count: 4) - ) - } - - private func run_distributedEvenlyTest( - sourceAmount: Currency, - numParts count: Int, - expectedResults: [Currency], - file: StaticString = #file, - line: UInt = #line - ) { - guard count == expectedResults.count else { - return XCTFail( - "Inconsistent desire: Asked for \(count) parts, but expect \(expectedResults.count) results", - file: file, line: line - ) - } - let actualResults = sourceAmount.distributedEvenly(intoParts: count) - XCTAssertEqual(actualResults, expectedResults, file: file, line: line) - XCTAssertEqual(sourceAmount, expectedResults.sum(), file: file, line: line) - XCTAssertEqual( - sourceAmount.negated().distributedEvenly(intoParts: count), - expectedResults.map({ $0.negated() }), - file: file, line: line - ) - } -} - -// MARK: Distributed Proportionally - -extension AnyCurrencyAlgorithmsTests { - func test_distributedProportionally() { - let amount = USD(10) - XCTAssertEqual(amount.distributedProportionally(between: [2.5, 2.5]), [5, 5]) - XCTAssertEqual(amount.distributedProportionally(between: []), []) - XCTAssertEqual(amount.negated().distributedProportionally(between: [5, 8.25]), [-3.77, -6.23]) - } - - // minorUnits == 2 - func test_distributedProportionally_usd() { - self.run_distributedProportionallyTest( - sourceAmount: USD(10.05), - originalValues: .init(repeating: USD(1), count: 6), - expectedValues: .init(repeating: USD(1.67), count: 5) + [USD(1.7)] - ) - - let sourceValues: [USD] = [4, 103, 0.99, 68, 100] // 275.99 USD - self.run_distributedProportionallyTest( - sourceAmount: .init(201.385), - originalValues: sourceValues, - expectedValues: [2.92, 75.16, 0.72, 49.62, 72.96] - ) - self.run_distributedProportionallyTest( - sourceAmount: .init(583), - originalValues: sourceValues, - expectedValues: [8.45, 217.58, 2.09, 143.64, 211.24] - ) - } - - // minorUnits == 3 - func test_distributedProportionally_bhd() { - let sourceValues: [BHD] = [4.1982, 39.2983, 12.1345, 17.293, 100] // 172.924 BHD - self.run_distributedProportionallyTest( - sourceAmount: .init(180), - originalValues: sourceValues, - expectedValues: [4.37, 40.906, 12.631, 18.001, 104.092] - ) - self.run_distributedProportionallyTest( - sourceAmount: .init(10.1983), - originalValues: sourceValues, - expectedValues: [0.248, 2.318, 0.716, 1.02, 5.896] - ) - } - - // minorUnits == 0 - func test_distributedProportionally_bif() { - let sourceValues: [BIF] = [4, 39, 12, 1, 100.2983] // 156 BIF - self.run_distributedProportionallyTest( - sourceAmount: .init(298), - originalValues: sourceValues, - expectedValues: [8, 74, 23, 2, 191] - ) - self.run_distributedProportionallyTest( - sourceAmount: .init(47.582), - originalValues: sourceValues, - expectedValues: [1, 12, 4, 0, 31] - ) - } - - private func run_distributedProportionallyTest( - sourceAmount: Currency?, - originalValues: [Currency], - expectedValues: [Currency], - file: StaticString = #file, - line: UInt = #line - ) { - guard let sourceAmount = sourceAmount else { return XCTFail("sourceAmount is nil!", file: file, line: line) } - guard originalValues.count == expectedValues.count else { - return XCTFail( - "Inconsistent desire: Provided \(originalValues.count) values, but expect \(expectedValues.count) results", - file: file, line: line - ) - } - let actualResults = sourceAmount.distributedProportionally(between: originalValues) - XCTAssertEqual(actualResults, expectedValues, file: file, line: line) - XCTAssertEqual(sourceAmount, actualResults.sum(), file: file, line: line) - } -} diff --git a/Tests/CurrencyTests/AnyCurrencyTests.swift b/Tests/CurrencyTests/AnyCurrencyTests.swift deleted file mode 100644 index f55be63..0000000 --- a/Tests/CurrencyTests/AnyCurrencyTests.swift +++ /dev/null @@ -1,315 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftCurrency open source project -// -// Copyright (c) 2020 SwiftCurrency project authors -// Licensed under MIT License -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftCurrency project authors -// -// SPDX-License-Identifier: MIT -// -//===----------------------------------------------------------------------===// - -@testable import Currency -import XCTest - -final class AnyCurrencyTests: XCTestCase { - override func setUp() { - UserDefaults.standard.set(["en_US"], forKey: "AppleLanguages") - } -} - -// MARK: Initialization - -extension AnyCurrencyTests { - func testDecimalInit() { - _assertCurrencyValue(USD(30.23), equalsAmount: 30.23, equalsMinorUnits: 3023) - _assertCurrencyValue(USD(-208.001), equalsAmount: -208.00, equalsMinorUnits: -20800) - - _assertCurrencyValue(JPY(100.23), equalsAmount: 100, equalsMinorUnits: 100) - _assertCurrencyValue(JPY(-39820), equalsAmount: -39820, equalsMinorUnits: -39820) - - _assertCurrencyValue(KWD(02838.29708), equalsAmount: 2838.297, equalsMinorUnits: 2838297) - _assertCurrencyValue(KWD(-300.87), equalsAmount: -300.87, equalsMinorUnits: -300870) - } - - func testNaNInit() { - XCTAssertNil(USD(amount: .nan)) - XCTAssertNil(JPY(amount: .quietNaN)) - } - - func testMinorUnitInit() { - _assertCurrencyValue(USD(minorUnits: 300), equalsAmount: 3, equalsMinorUnits: 300) - _assertCurrencyValue(USD(minorUnits: -39820), equalsAmount: -398.2, equalsMinorUnits: -39820) - - _assertCurrencyValue(JPY(minorUnits: 2838), equalsAmount: 2838, equalsMinorUnits: 2838) - _assertCurrencyValue(JPY(minorUnits: -300), equalsAmount: -300, equalsMinorUnits: -300) - - _assertCurrencyValue(KWD(minorUnits: 2838297), equalsAmount: 2838.297, equalsMinorUnits: 2838297) - _assertCurrencyValue(KWD(minorUnits: -300877), equalsAmount: -300.877, equalsMinorUnits: -300877) - } - - private func _assertCurrencyValue( - _ value: Currency?, - equalsAmount expectedAmount: Decimal, - equalsMinorUnits expectedMinorUnits: Int64, - file: StaticString = #file, - line: UInt = #line) { - guard let value = value else { return XCTFail("Nil value found", file: file, line: line) } - XCTAssertEqual(value.minorUnits, expectedMinorUnits, file: file, line: line) - XCTAssertEqual(value.amount, expectedAmount, file: file, line: line) - } -} - -// MARK: - -// MARK: Protocol Conformances - -extension AnyCurrencyTests { - func testEquatable() { - let first = USD(30.23) - XCTAssertEqual(first, USD(30.23)) - XCTAssertEqual(first, USD(30.226)) - XCTAssertNotEqual(first, USD(30.235)) - XCTAssertNotEqual(first, USD(30.225)) - } - - func testComparable() { - let first: USD = 37.0237 - let second: USD = 198.157 - XCTAssertTrue(second > first) - } - - func testHashable() { - let usd = USD(30.23) - XCTAssertEqual(usd.hashValue, usd.minorUnits.hashValue) - - var hasher = Hasher() - hasher.combine(usd) - XCTAssertEqual(hasher.finalize(), usd.minorUnits.hashValue) - } -} - -// MARK: - -// MARK: String Representations - -extension AnyCurrencyTests { - func testReflectionRepresentation() { - let str = String(reflecting: USD(30.02)) - XCTAssertEqual(str, "USD(30.02)") - } - - func testDescriptionRepresentations() { - let yen = JPY(400.98) - XCTAssertEqual(yen.description, "401 JPY") - XCTAssertEqual("\(yen)", yen.description) - - let usd = USD(300.8) - XCTAssertEqual(usd.description, "300.8 USD") - XCTAssertEqual(usd.debugDescription, "USD(300.8)") - XCTAssertEqual(usd.playgroundDescription as? String, usd.debugDescription) - } - - func testStringInterpolation_defaultFormatter() { - XCTAssertEqual("\(localize: USD(4321.389))", "$4,321.39") - - #if swift(<5.2) && os(Linux) - let expectedDinar = "KWD301.982" - #else - let expectedDinar = "KWD 301.982" - #endif - XCTAssertEqual("\(localize: KWD(301.9823))", expectedDinar) - - #if swift(<5.1) && os(Linux) - let expectedYen = "¥401.00" - #else - let expectedYen = "¥401" - #endif - XCTAssertEqual("\(localize: JPY(400.9))", expectedYen) - } - - func testStringInterpolation_customLocale() { - let pounds = GBP(14928.789) - XCTAssertEqual("\(localize: pounds, for: .init(identifier: "en_UK"))", "£14,928.79") - XCTAssertEqual("\(localize: pounds, for: .init(identifier: "de_DE"))", "14.928,79 £") - - let yen = JPY(400.9) - #if swift(<5.1) && os(Linux) - let expectedFrenchYen = "401,00 JPY" - let expectedGreekYen = "401,00 JP¥" - #else - let expectedFrenchYen = "401 JPY" - let expectedGreekYen = "401 JP¥" - #endif - let frenchLocale = Locale(identifier: "fr") - XCTAssertEqual("\(localize: yen, for: frenchLocale)", expectedFrenchYen) - XCTAssertEqual(yen.localizedString(for: frenchLocale), expectedFrenchYen) - XCTAssertEqual("\(localize: yen, for: .init(identifier: "el"))", expectedGreekYen) - - let dinar = KWD(100.9289) - #if swift(<5.2) && os(Linux) - let expectedIrishDinar = "KWD100.929" - #else - let expectedIrishDinar = "KWD 100.929" - #endif - XCTAssertEqual("\(localize: dinar, for: .init(identifier: "ga"))", expectedIrishDinar) - XCTAssertEqual("\(localize: dinar, for: .init(identifier: "hr"))", "100,929 KWD") - } - - func testStringInterpolation_customFormatter() { - let formatter = NumberFormatter() - formatter.numberStyle = .currency - formatter.currencyGroupingSeparator = " " - formatter.currencyDecimalSeparator = "'" - - let pounds = GBP(14928.018) - formatter.currencyCode = GBP.alphabeticCode - XCTAssertEqual("\(localize: pounds, with: formatter)", "£14 928'02") - - let expectedYenResult = "¥4 001" - let yen = JPY(4000.9) - formatter.currencyCode = JPY.alphabeticCode - XCTAssertEqual("\(localize: yen, with: formatter)", expectedYenResult) - XCTAssertEqual(yen.localizedString(using: formatter), expectedYenResult) - - let dinar = KWD(92.0299) - formatter.currencyCode = KWD.alphabeticCode - #if swift(<5.2) && os(Linux) - let expectedDinar = "KWD92'030" - #else - let expectedDinar = "KWD 92'030" - #endif - XCTAssertEqual("\(localize: dinar, with: formatter)", expectedDinar) - } - - func testStringInterpolation_optional() { - var pounds = Optional.none - XCTAssertEqual("\(localize: pounds)", "nil") - XCTAssertEqual("\(localize: pounds, nilDescription: "N/A")", "N/A") - - pounds = .init(398.983) - XCTAssertEqual("\(localize: pounds)", "£398.98") - } -} - -// MARK: - - -// MARK: Basic Arithmetic - -extension AnyCurrencyTests { - func testAddition() { - let first = USD(300.12) - XCTAssertEqual(first + 30, 330.12) - - var second = USD(32.12) - second += 45 - XCTAssertEqual(second, 77.12) - } - - func testSubtraction() { - let first = USD(300.12) - XCTAssertEqual(first - 30, 270.12) - - var second = USD(32.12) - second -= 45 - XCTAssertEqual(second, -12.88) - } - - func testDivision() { - let first = USD(300.12) - XCTAssertEqual(first / USD(30.198), 9.94) - - var second = USD(32.12) - second /= USD(45.2) - XCTAssertEqual(second.amount, 0.71) - } - - func testMultiplication() { - let first = USD(300.12) - XCTAssertEqual(first * USD(0.309), 93.04) - - var second = USD(32.12) - second *= USD(45.2) - XCTAssertEqual(second.amount, 1451.82) - } - - func testNegation() { - let min = USD(minorUnits: Int8.min) - XCTAssertEqual(min.minorUnits, -128) - XCTAssertEqual(min.negated().minorUnits, 128) - - let max = KWD(minorUnits: Int8.max) - XCTAssertEqual(max.minorUnits, 127) - XCTAssertEqual(max.negated().minorUnits, -127) - - let yen = JPY(minorUnits: 30) - XCTAssertEqual(yen.minorUnits, 30) - XCTAssertEqual(yen.negated().minorUnits, -30) - } -} - -// MARK: Advanced Calculations - -extension Sequence where Element: AnyCurrency { - func applyingRate(_ rate: Decimal) -> [Element] { - return self.reduce(into: [Element]()) { result, next in - let newElement = next + Element(scalingAndRounding: next.amount * rate) - result.append(newElement) - } - } -} - -extension AnyCurrencyTests { - func testSampleUSDCalculations() { - /* - original price taxes (9%) result - 3.00 0.27 3.27 - 2.99 0.2691 => 0.27 3.2591 => 3.26 - 5.98 0.5382 => 0.54 6.5182 => 6.52 - === === === - 11.97 1.0773 => 1.08 13.0473 => 13.05 - */ - let prices: [USD] = [ - 3.00, - 2.99, - 5.98 - ] - - let subtotal = prices.sum() - XCTAssertEqual(subtotal, 11.97) - - let taxes = prices.sum { $0 * 0.09 } - XCTAssertEqual(taxes, 1.08) - XCTAssertEqual(subtotal * 0.09, 1.08) - - XCTAssertEqual(subtotal + taxes, 13.05) - XCTAssertEqual(prices.applyingRate(0.09).sum(), 13.05) - - /* .228735 - original price discount (15%) discount price taxes (9%) result - 3.00 0.45 2.55 0.2295 => 0.23 2.7795 => 2.78 - 2.99 0.4485 => 0.45 2.5415 => 2.54 0.2286 => 0.23 2.7701 => 2.77 - 5.98 0.897 => 0.90 5.083 => 5.08 0.4575 => 0.46 5.5405 => 5.54 - === === === === === - 11.97 1.7955 => 1.80 10.1745 => 10.17 0.9156 => 0.92 11.0901 => 11.09 - */ - - let discount = prices.sum { $0 * 0.15 } - XCTAssertEqual(discount, 1.80) - let discountPrices = prices.applyingRate(-0.15) - XCTAssertEqual(discountPrices.sum(), 10.17) - - let discountTaxes = discountPrices.sum { $0 * 0.09 } - XCTAssertEqual(discountTaxes, 0.92) - - let discountTotal = discountPrices.applyingRate(0.09).sum() - XCTAssertEqual(discountTotal, 11.09) - - let chained = prices - .applyingRate(-0.15) // discount - .applyingRate(0.09) // taxes - .sum() - XCTAssertEqual(chained, discountTotal) - } -} diff --git a/Tests/CurrencyTests/Currency+AlgorithmsTests.swift b/Tests/CurrencyTests/Currency+AlgorithmsTests.swift index 4f5a62c..5e00616 100644 --- a/Tests/CurrencyTests/Currency+AlgorithmsTests.swift +++ b/Tests/CurrencyTests/Currency+AlgorithmsTests.swift @@ -21,45 +21,45 @@ final class CurrencyAlgorithmsTests: XCTestCase { } extension CurrencyAlgorithmsTests { func test_distributedEvenly_with0_orNegative_NumParts_returnsEmptyResult() { - let value = _New_USD(5) + let value = USD(5) XCTAssertTrue(value.distributedEvenly(intoParts: 0).isEmpty) XCTAssertTrue(value.distributedEvenly(intoParts: -1).isEmpty) } func test_distributedEvenly_with0MinorUnits() { self.run_distributedEvenlyTest( - for: _New_JPY(10.05), + for: JPY(10.05), numParts: 6, - expectedResults: .init(repeating: _New_JPY(2), count: 4) + .init(repeating: _New_JPY(1), count: 2) + expectedResults: .init(repeating: JPY(2), count: 4) + .init(repeating: JPY(1), count: 2) ) } func test_distributedEvenly_with1MinorUnit() { self.run_distributedEvenlyTest( - for: _New_XTS(10.05), + for: XTS(10.05), numParts: 6, - expectedResults: .init(repeating: _New_XTS(1.7), count: 4) + .init(repeating: _New_XTS(1.6), count: 2) + expectedResults: .init(repeating: XTS(1.7), count: 4) + .init(repeating: XTS(1.6), count: 2) ) } func test_distributedEvenly_with2MinorUnit() { self.run_distributedEvenlyTest( - for: _New_USD(15.01), + for: USD(15.01), numParts: 3, - expectedResults: [_New_USD(exactAmount: Decimal(string: "5.01")!), 5, 5] + expectedResults: [USD(exactAmount: Decimal(string: "5.01")!), 5, 5] ) self.run_distributedEvenlyTest( - for: _New_USD(10.05), + for: USD(10.05), numParts: 6, - expectedResults: .init(repeating: _New_USD(1.68), count: 3) + .init(repeating: _New_USD(1.67), count: 3) + expectedResults: .init(repeating: USD(1.68), count: 3) + .init(repeating: USD(1.67), count: 3) ) } func test_distributedEvenly_with3MinorUnit() { self.run_distributedEvenlyTest( - for: _New_KWD(10.05), + for: KWD(10.05), numParts: 6, - expectedResults: .init(repeating: _New_KWD(1.675), count: 6) + expectedResults: .init(repeating: KWD(1.675), count: 6) ) } diff --git a/Tests/CurrencyTests/Currency+ArithmeticTests.swift b/Tests/CurrencyTests/Currency+ArithmeticTests.swift index 7c9072d..ffd4505 100644 --- a/Tests/CurrencyTests/Currency+ArithmeticTests.swift +++ b/Tests/CurrencyTests/Currency+ArithmeticTests.swift @@ -21,9 +21,9 @@ final class CurrencyArithmeticTests: XCTestCase { } extension CurrencyArithmeticTests { func test_zero_equalsDecimalZero() { - XCTAssertTrue(_New_USD.zero.exactAmount == .zero) - XCTAssertTrue(_New_JPY.zero.exactAmount == .zero) - XCTAssertTrue(_New_KWD.zero.exactAmount == .zero) + XCTAssertTrue(USD.zero.exactAmount == .zero) + XCTAssertTrue(JPY.zero.exactAmount == .zero) + XCTAssertTrue(KWD.zero.exactAmount == .zero) } func test_negated_correctlyInvertsValues() { @@ -45,9 +45,9 @@ extension CurrencyArithmeticTests { ) } - assertNegationIsCorrect(for: _New_USD.self) - assertNegationIsCorrect(for: _New_JPY.self) - assertNegationIsCorrect(for: _New_KWD.self) + assertNegationIsCorrect(for: USD.self) + assertNegationIsCorrect(for: JPY.self) + assertNegationIsCorrect(for: KWD.self) } } @@ -82,9 +82,9 @@ extension CurrencyArithmeticTests { ) } - assertAdditionIsCorrect(for: _New_USD.self) - assertAdditionIsCorrect(for: _New_JPY.self) - assertAdditionIsCorrect(for: _New_KWD.self) + assertAdditionIsCorrect(for: USD.self) + assertAdditionIsCorrect(for: JPY.self) + assertAdditionIsCorrect(for: KWD.self) } func test_addition_withBinaryInteger_isCorrect() { @@ -115,9 +115,9 @@ extension CurrencyArithmeticTests { ) } - assertAdditionIsCorrect(for: _New_USD.self) - assertAdditionIsCorrect(for: _New_JPY.self) - assertAdditionIsCorrect(for: _New_KWD.self) + assertAdditionIsCorrect(for: USD.self) + assertAdditionIsCorrect(for: JPY.self) + assertAdditionIsCorrect(for: KWD.self) } func test_addition_withDecimal_isCorrect() { @@ -148,9 +148,9 @@ extension CurrencyArithmeticTests { ) } - assertAdditionIsCorrect(for: _New_USD.self) - assertAdditionIsCorrect(for: _New_JPY.self) - assertAdditionIsCorrect(for: _New_KWD.self) + assertAdditionIsCorrect(for: USD.self) + assertAdditionIsCorrect(for: JPY.self) + assertAdditionIsCorrect(for: KWD.self) } } @@ -185,9 +185,9 @@ extension CurrencyArithmeticTests { ) } - assertSubtractionIsCorrect(for: _New_USD.self) - assertSubtractionIsCorrect(for: _New_JPY.self) - assertSubtractionIsCorrect(for: _New_KWD.self) + assertSubtractionIsCorrect(for: USD.self) + assertSubtractionIsCorrect(for: JPY.self) + assertSubtractionIsCorrect(for: KWD.self) } func test_subtraction_withBinaryInteger_isCorrect() { @@ -218,9 +218,9 @@ extension CurrencyArithmeticTests { ) } - assertSubtractionIsCorrect(for: _New_USD.self) - assertSubtractionIsCorrect(for: _New_JPY.self) - assertSubtractionIsCorrect(for: _New_KWD.self) + assertSubtractionIsCorrect(for: USD.self) + assertSubtractionIsCorrect(for: JPY.self) + assertSubtractionIsCorrect(for: KWD.self) } func test_subtraction_withDecimal_isCorrect() { @@ -251,9 +251,9 @@ extension CurrencyArithmeticTests { ) } - assertSubtractionIsCorrect(for: _New_USD.self) - assertSubtractionIsCorrect(for: _New_JPY.self) - assertSubtractionIsCorrect(for: _New_KWD.self) + assertSubtractionIsCorrect(for: USD.self) + assertSubtractionIsCorrect(for: JPY.self) + assertSubtractionIsCorrect(for: KWD.self) } } @@ -288,9 +288,9 @@ extension CurrencyArithmeticTests { ) } - assertMultiplicationIsCorrect(for: _New_USD.self) - assertMultiplicationIsCorrect(for: _New_JPY.self) - assertMultiplicationIsCorrect(for: _New_KWD.self) + assertMultiplicationIsCorrect(for: USD.self) + assertMultiplicationIsCorrect(for: JPY.self) + assertMultiplicationIsCorrect(for: KWD.self) } func test_multiplication_withDecimal_isCorrect() { @@ -321,9 +321,9 @@ extension CurrencyArithmeticTests { ) } - assertMultiplicationIsCorrect(for: _New_USD.self) - assertMultiplicationIsCorrect(for: _New_JPY.self) - assertMultiplicationIsCorrect(for: _New_KWD.self) + assertMultiplicationIsCorrect(for: USD.self) + assertMultiplicationIsCorrect(for: JPY.self) + assertMultiplicationIsCorrect(for: KWD.self) } } @@ -358,9 +358,9 @@ extension CurrencyArithmeticTests { ) } - assertDivisionIsCorrect(for: _New_USD.self) - assertDivisionIsCorrect(for: _New_JPY.self) - assertDivisionIsCorrect(for: _New_KWD.self) + assertDivisionIsCorrect(for: USD.self) + assertDivisionIsCorrect(for: JPY.self) + assertDivisionIsCorrect(for: KWD.self) } func test_division_withDecimal_isCorrect() { @@ -391,9 +391,9 @@ extension CurrencyArithmeticTests { ) } - assertDivisionIsCorrect(for: _New_USD.self) - assertDivisionIsCorrect(for: _New_JPY.self) - assertDivisionIsCorrect(for: _New_KWD.self) + assertDivisionIsCorrect(for: USD.self) + assertDivisionIsCorrect(for: JPY.self) + assertDivisionIsCorrect(for: KWD.self) } } @@ -409,7 +409,7 @@ extension CurrencyArithmeticTests { === === === 11.97 1.0773 => 1.08 13.0473 => 13.05 */ - let prices: [_New_USD] = [ + let prices: [USD] = [ 3.00, 2.99, 5.98 @@ -437,7 +437,7 @@ extension CurrencyArithmeticTests { === === === === === 11.97 1.7955 => 1.80 10.1745 => 10.17 0.915705 => 0.92 11.090205 => 11.09 */ - let prices: [_New_USD] = [ + let prices: [USD] = [ 3.00, 2.99, 5.98 @@ -477,10 +477,10 @@ extension CurrencyArithmeticTests { 10% Commission 147.4056556 | 147.406 => 147.41 Grand Total 1,621.4622116 | 1,621.466 => 1,621.47 */ - let roomDailyRate = _New_USD(199.98) + let roomDailyRate = USD(199.98) let discountRate = Decimal(0.06) let taxRate = Decimal(0.09) - let flatFranchiseFee = _New_USD(5.68) + let flatFranchiseFee = USD(5.68) var runningDailyTotal = roomDailyRate @@ -523,6 +523,6 @@ extension CurrencyArithmeticTests { }() XCTAssertEqual(expectedResult, 1_621.4622116) - XCTAssertEqual(totalPrice, _New_USD(exactAmount: expectedResult)) + XCTAssertEqual(totalPrice, USD(exactAmount: expectedResult)) } } diff --git a/Tests/CurrencyTests/Currency+StringRepresentationTests.swift b/Tests/CurrencyTests/Currency+StringRepresentationTests.swift index 6e18167..2a4ccd0 100644 --- a/Tests/CurrencyTests/Currency+StringRepresentationTests.swift +++ b/Tests/CurrencyTests/Currency+StringRepresentationTests.swift @@ -16,16 +16,16 @@ import Currency import XCTest final class CurrencyStringRepresentationTests: XCTestCase { - let sampleDollar = _New_USD(300.8) - let sampleYen = _New_JPY(400.98) + let sampleDollar = USD(300.8) + let sampleYen = JPY(400.98) } // MARK: Reflection Description extension CurrencyStringRepresentationTests { func test_reflectionRepresentation_includesIdentifierName() { - XCTAssertTrue(String(reflecting: self.sampleDollar).contains(_New_USD.alphabeticCode)) - XCTAssertTrue(String(reflecting: self.sampleYen).contains(_New_JPY.alphabeticCode)) + XCTAssertTrue(String(reflecting: self.sampleDollar).contains(USD.alphabeticCode)) + XCTAssertTrue(String(reflecting: self.sampleYen).contains(JPY.alphabeticCode)) } func test_reflectionRepresentation_includesExactAmount() { @@ -57,7 +57,7 @@ extension CurrencyStringRepresentationTests { .children .first(where: { $0.label == "descriptor" }) ) - XCTAssertNotNil(child.value as? _New_USD.Type) + XCTAssertNotNil(child.value as? USD.Type) } } @@ -65,8 +65,8 @@ extension CurrencyStringRepresentationTests { extension CurrencyStringRepresentationTests { func test_description_includesIdentifierName() { - XCTAssertTrue(self.sampleDollar.description.contains(_New_USD.alphabeticCode)) - XCTAssertTrue(self.sampleYen.description.contains(_New_JPY.alphabeticCode)) + XCTAssertTrue(self.sampleDollar.description.contains(USD.alphabeticCode)) + XCTAssertTrue(self.sampleYen.description.contains(JPY.alphabeticCode)) } func test_description_includesExactAmount() { @@ -79,8 +79,8 @@ extension CurrencyStringRepresentationTests { extension CurrencyStringRepresentationTests { func test_debugDescription_includesIdentifierName() { - XCTAssertTrue(self.sampleDollar.debugDescription.contains(_New_USD.alphabeticCode)) - XCTAssertTrue(self.sampleYen.debugDescription.contains(_New_JPY.alphabeticCode)) + XCTAssertTrue(self.sampleDollar.debugDescription.contains(USD.alphabeticCode)) + XCTAssertTrue(self.sampleYen.debugDescription.contains(JPY.alphabeticCode)) } func test_debugDescription_includesExactAmount() { @@ -107,27 +107,27 @@ extension CurrencyStringRepresentationTests { extension CurrencyStringRepresentationTests { func test_stringInterpolation_forLocale_whenNilValue_usesNilDescription_whenProvided() { - let value: _New_USD? = nil + let value: USD? = nil XCTAssertEqual("\(localize: value, nilDescription: #function)", #function) } func test_stringInterpolation_withFormatter_whenNilValue_usesNilDescription_whenProvided() { - let value: _New_USD? = nil + let value: USD? = nil XCTAssertEqual("\(localize: value, with: NumberFormatter(), nilDescription: #function)", #function) } func test_stringInterpolation_forLocale_whenNilValue_hasDefaultDescription() { - let value: _New_USD? = nil + let value: USD? = nil XCTAssertEqual("\(localize: value)", "nil") } func test_stringInterpolation_withFormatter_whenNilValue_hasDefaultDescription() { - let value: _New_USD? = nil + let value: USD? = nil XCTAssertEqual("\(localize: value, with: NumberFormatter())", "nil") } func test_stringInterpolation_forLocale_usesLocale_whenProvided() { - let pounds = _New_GBP(14928.789) + let pounds = GBP(14928.789) XCTAssertEqual("\(localize: pounds, for: .init(identifier: "en_UK"))", "£14,928.79") XCTAssertEqual("\(localize: pounds, for: .init(identifier: "de_DE"))", "14.928,79 £") } @@ -138,18 +138,18 @@ extension CurrencyStringRepresentationTests { formatter.currencyGroupingSeparator = " " formatter.currencyDecimalSeparator = "'" - let pounds = _New_GBP(14928.018) - formatter.currencyCode = _New_GBP.alphabeticCode + let pounds = GBP(14928.018) + formatter.currencyCode = GBP.alphabeticCode XCTAssertEqual("\(localize: pounds, with: formatter)", "£14 928'02") let expectedYenResult = "¥4 001" - let yen = _New_JPY(4000.9) - formatter.currencyCode = _New_JPY.alphabeticCode + let yen = JPY(4000.9) + formatter.currencyCode = JPY.alphabeticCode XCTAssertEqual("\(localize: yen, with: formatter)", expectedYenResult) } func test_stringInterpolation_forLocale_usesDefaultLocale() { - XCTAssertEqual("\(localize: _New_USD(4321.389))", "$4,321.39") + XCTAssertEqual("\(localize: USD(4321.389))", "$4,321.39") } } @@ -157,11 +157,11 @@ extension CurrencyStringRepresentationTests { extension CurrencyStringRepresentationTests { func test_localizedString_forLocale_usesDefaultLocale() { - XCTAssertEqual(_New_USD(4321.389).localizedString(), "$4,321.39") + XCTAssertEqual(USD(4321.389).localizedString(), "$4,321.39") } func test_localizedString_forLocale_usesProvidedLocale() { - let pounds = _New_GBP(14928.789) + let pounds = GBP(14928.789) XCTAssertEqual(pounds.localizedString(for: .init(identifier: "en_UK")), "£14,928.79") XCTAssertEqual(pounds.localizedString(for: .init(identifier: "de_DE")), "14.928,79 £") } @@ -172,13 +172,13 @@ extension CurrencyStringRepresentationTests { formatter.currencyGroupingSeparator = " " formatter.currencyDecimalSeparator = "'" - let pounds = _New_GBP(14928.018) - formatter.currencyCode = _New_GBP.alphabeticCode + let pounds = GBP(14928.018) + formatter.currencyCode = GBP.alphabeticCode XCTAssertEqual(pounds.localizedString(using: formatter), "£14 928'02") let expectedYenResult = "¥4 001" - let yen = _New_JPY(4000.9) - formatter.currencyCode = _New_JPY.alphabeticCode + let yen = JPY(4000.9) + formatter.currencyCode = JPY.alphabeticCode XCTAssertEqual(yen.localizedString(using: formatter), expectedYenResult) } } diff --git a/Tests/CurrencyTests/CurrencyDescriptorTests.swift b/Tests/CurrencyTests/CurrencyDescriptorTests.swift index 21cd22c..01a23c2 100644 --- a/Tests/CurrencyTests/CurrencyDescriptorTests.swift +++ b/Tests/CurrencyTests/CurrencyDescriptorTests.swift @@ -21,16 +21,16 @@ final class CurrencyDescriptorTests: XCTestCase { } extension CurrencyDescriptorTests { func test_minorUnitsCoefficient_forExactAmount_shiftsLeftByMinorUnits() { - XCTAssertEqual(_New_JPY.minorUnitsCoefficient(for: .exactAmount), 1) - XCTAssertEqual(_New_XTS.minorUnitsCoefficient(for: .exactAmount), 10) - XCTAssertEqual(_New_USD.minorUnitsCoefficient(for: .exactAmount), 100) - XCTAssertEqual(_New_KWD.minorUnitsCoefficient(for: .exactAmount), 1000) + XCTAssertEqual(JPY.minorUnitsCoefficient(for: .exactAmount), 1) + XCTAssertEqual(XTS.minorUnitsCoefficient(for: .exactAmount), 10) + XCTAssertEqual(USD.minorUnitsCoefficient(for: .exactAmount), 100) + XCTAssertEqual(KWD.minorUnitsCoefficient(for: .exactAmount), 1000) } func test_minorUnitsCoefficient_forMinorUnits_shiftsRightByMinorUnits() { - XCTAssertEqual(_New_JPY.minorUnitsCoefficient(for: .minorUnits), 1) - XCTAssertEqual(_New_XTS.minorUnitsCoefficient(for: .minorUnits), 0.1) - XCTAssertEqual(_New_USD.minorUnitsCoefficient(for: .minorUnits), 0.01) - XCTAssertEqual(_New_KWD.minorUnitsCoefficient(for: .minorUnits), 0.001) + XCTAssertEqual(JPY.minorUnitsCoefficient(for: .minorUnits), 1) + XCTAssertEqual(XTS.minorUnitsCoefficient(for: .minorUnits), 0.1) + XCTAssertEqual(USD.minorUnitsCoefficient(for: .minorUnits), 0.01) + XCTAssertEqual(KWD.minorUnitsCoefficient(for: .minorUnits), 0.001) } } diff --git a/Tests/CurrencyTests/CurrencyMintTests.swift b/Tests/CurrencyTests/CurrencyMintTests.swift index f83b1a0..2809f9c 100644 --- a/Tests/CurrencyTests/CurrencyMintTests.swift +++ b/Tests/CurrencyTests/CurrencyMintTests.swift @@ -61,13 +61,13 @@ extension CurrencyMintTests { } func test_withDefaultCurrencyLookup_whenLookupFails_returnsDefaultCurrency() { - let mint = CurrencyMint(defaultCurrency: _New_USD.self) + let mint = CurrencyMint(defaultCurrency: USD.self) var money = mint.make(identifier: .fakeCurrencyAlphaCode) - XCTAssertTrue(money is _New_USD) + XCTAssertTrue(money is USD) money = mint.make(identifier: .fakeCurrencyNumericCode) - XCTAssertTrue(money is _New_USD) + XCTAssertTrue(money is USD) } } @@ -78,26 +78,26 @@ extension CurrencyMintTests { let mint = CurrencyMint.standard let pounds = mint.make(identifier: .poundsAlphaCode, minorUnits: .zero) - XCTAssertTrue(pounds is _New_GBP) + XCTAssertTrue(pounds is GBP) let yen = mint.make(identifier: .yenNumericCode, minorUnits: 300) - XCTAssertTrue(yen is _New_JPY) + XCTAssertTrue(yen is JPY) let euros = CurrencyMint.standard.make(identifier: 978, minorUnits: .zero) - XCTAssertTrue(euros is _New_EUR) + XCTAssertTrue(euros is EUR) } func test_make_withAmount_returnsISOCurrency() { let mint = CurrencyMint.standard let pounds = mint.make(identifier: .poundsAlphaCode, exactAmount: .zero) - XCTAssertTrue(pounds is _New_GBP) + XCTAssertTrue(pounds is GBP) let yen = mint.make(identifier: .yenNumericCode, exactAmount: 192.8) - XCTAssertTrue(yen is _New_JPY) + XCTAssertTrue(yen is JPY) let euros = CurrencyMint.standard.make(identifier: 978, exactAmount: .zero) - XCTAssertTrue(euros is _New_EUR) + XCTAssertTrue(euros is EUR) } func test_make_withMinorUnits_matchesMinorUnits() { @@ -127,6 +127,6 @@ extension CurrencyMint.CurrencyIdentifier { fileprivate static var fakeCurrencyAlphaCode: Self { "KED" } // "darseks" fileprivate static var fakeCurrencyNumericCode: Self { 666 } - fileprivate static var poundsAlphaCode: Self { .alphaCode(_New_GBP.alphabeticCode) } - fileprivate static var yenNumericCode: Self { .numericCode(_New_JPY.numericCode) } + fileprivate static var poundsAlphaCode: Self { .alphaCode(GBP.alphabeticCode) } + fileprivate static var yenNumericCode: Self { .numericCode(JPY.numericCode) } } diff --git a/Tests/CurrencyTests/CurrencyTests.swift b/Tests/CurrencyTests/CurrencyTests.swift index 32db4b7..34d0adc 100644 --- a/Tests/CurrencyTests/CurrencyTests.swift +++ b/Tests/CurrencyTests/CurrencyTests.swift @@ -29,23 +29,23 @@ extension CurrencyTests { ) } - assertInit(amount: 30.23, for: _New_USD.self) - assertInit(amount: -208.001, for: _New_USD.self) - assertInit(amount: .nan, for: _New_USD.self) + assertInit(amount: 30.23, for: USD.self) + assertInit(amount: -208.001, for: USD.self) + assertInit(amount: .nan, for: USD.self) - assertInit(amount: 100.23, for: _New_JPY.self) - assertInit(amount: -39820, for: _New_JPY.self) - assertInit(amount: .nan, for: _New_JPY.self) + assertInit(amount: 100.23, for: JPY.self) + assertInit(amount: -39820, for: JPY.self) + assertInit(amount: .nan, for: JPY.self) - assertInit(amount: 02838.29708, for: _New_KWD.self) - assertInit(amount: -300.87, for: _New_KWD.self) - assertInit(amount: .nan, for: _New_KWD.self) + assertInit(amount: 02838.29708, for: KWD.self) + assertInit(amount: -300.87, for: KWD.self) + assertInit(amount: .nan, for: KWD.self) } func test_init_minorUnits_correctlyConvertsToDecimal() { - XCTAssertEqual(_New_USD(minorUnits: 100), _New_USD(exactAmount: 1.0)) - XCTAssertEqual(_New_USD(minorUnits: 101), _New_USD(exactAmount: 1.01)) - XCTAssertEqual(_New_USD(minorUnits: 50011), _New_USD(exactAmount: 500.11)) + XCTAssertEqual(USD(minorUnits: 100), USD(exactAmount: 1.0)) + XCTAssertEqual(USD(minorUnits: 101), USD(exactAmount: 1.01)) + XCTAssertEqual(USD(minorUnits: 50011), USD(exactAmount: 500.11)) } } @@ -53,27 +53,27 @@ extension CurrencyTests { extension CurrencyTests { func test_0MinorUnits_representationIsCorrect() { - XCTAssertEqual(_New_JPY.zero.minorUnits, .zero) - XCTAssertEqual(_New_JPY(exactAmount: 10.01).minorUnits, 10) - XCTAssertEqual(_New_JPY(exactAmount: 100).minorUnits, 100) + XCTAssertEqual(JPY.zero.minorUnits, .zero) + XCTAssertEqual(JPY(exactAmount: 10.01).minorUnits, 10) + XCTAssertEqual(JPY(exactAmount: 100).minorUnits, 100) } func test_1MinorUnits_representationIsCorrect() { - XCTAssertEqual(_New_XTS.zero.minorUnits, .zero) - XCTAssertEqual(_New_XTS(exactAmount: 10.01).minorUnits, 100) - XCTAssertEqual(_New_XTS(exactAmount: 100).minorUnits, 1000) + XCTAssertEqual(XTS.zero.minorUnits, .zero) + XCTAssertEqual(XTS(exactAmount: 10.01).minorUnits, 100) + XCTAssertEqual(XTS(exactAmount: 100).minorUnits, 1000) } func test_2MinorUnits_representationIsCorrect() { - XCTAssertEqual(_New_USD.zero.minorUnits, .zero) - XCTAssertEqual(_New_USD(exactAmount: 10.01).minorUnits, 1001) - XCTAssertEqual(_New_USD(exactAmount: 100).minorUnits, 10000) + XCTAssertEqual(USD.zero.minorUnits, .zero) + XCTAssertEqual(USD(exactAmount: 10.01).minorUnits, 1001) + XCTAssertEqual(USD(exactAmount: 100).minorUnits, 10000) } func test_3MinorUnits_representationIsCorrect() { - XCTAssertEqual(_New_KWD.zero.minorUnits, .zero) - XCTAssertEqual(_New_KWD(exactAmount: 10.01).minorUnits, 10010) - XCTAssertEqual(_New_KWD(exactAmount: 100).minorUnits, 100000) + XCTAssertEqual(KWD.zero.minorUnits, .zero) + XCTAssertEqual(KWD(exactAmount: 10.01).minorUnits, 10010) + XCTAssertEqual(KWD(exactAmount: 100).minorUnits, 100000) } } @@ -90,7 +90,7 @@ extension CurrencyTests { } func test_equatable_whenDifferentDescriptors_isFalse() { - XCTAssertFalse(_New_USD(exactAmount: .nan) == TestCurrency(exactAmount: .nan)) + XCTAssertFalse(USD(exactAmount: .nan) == TestCurrency(exactAmount: .nan)) } func test_equatable_whenSameDescriptors_andSameExactAmount_isTrue() { @@ -105,8 +105,8 @@ extension CurrencyTests { } func test_comparable_whenDifferentDescriptors_comparesDescriptorPrimaryCode() { - XCTAssertTrue(TestCurrency(exactAmount: 30) < _New_USD(exactAmount: 30)) - XCTAssertTrue(_New_KWD(exactAmount: 30) < TestCurrency(exactAmount: 30)) + XCTAssertTrue(TestCurrency(exactAmount: 30) < USD(exactAmount: 30)) + XCTAssertTrue(KWD(exactAmount: 30) < TestCurrency(exactAmount: 30)) } func test_comparable_whenSameDescriptors_comparesExactAmount() { @@ -115,7 +115,7 @@ extension CurrencyTests { func test_hashable_whenDifferentDescriptors_hasDifferentHashValues() { let first = self._hash_currency(TestCurrency(exactAmount: 30)) - let second = self._hash_currency(_New_USD(exactAmount: 30)) + let second = self._hash_currency(USD(exactAmount: 30)) XCTAssertNotEqual(first, second) } @@ -142,3 +142,25 @@ extension CurrencyTests { } // MARK: Example Usage + +extension CurrencyTests { + func test_existential_mathCompiles() { + let value: any Currency = USD(minorUnits: 300) + + XCTAssertTrue(value is USD) + XCTAssertEqual(value.adding(3.5).exactAmount, 6.5) + } + + func test_existential_isImplicitlyOpened() { + func someGenericContext(_ value: some Currency) -> Bool { + return value is USD + } + func someOtherGenericContext(_ value: C) -> C { + return value + 3.5 + } + + let value: any Currency = USD.zero + XCTAssertTrue(someGenericContext(value)) + XCTAssertEqual(someOtherGenericContext(value).exactAmount, 3.5) + } +} diff --git a/Tests/CurrencyTests/Sequence+CurrencyTests.swift b/Tests/CurrencyTests/Sequence+CurrencyTests.swift index fc708f4..2abeb96 100644 --- a/Tests/CurrencyTests/Sequence+CurrencyTests.swift +++ b/Tests/CurrencyTests/Sequence+CurrencyTests.swift @@ -21,19 +21,19 @@ final class SequenceCurrencyTests: XCTestCase { } extension SequenceCurrencyTests { func test_sum() { - let amounts: [_New_USD] = [304.98, 19.02] + let amounts: [USD] = [304.98, 19.02] let sumTotal = amounts.sum() XCTAssertEqual(sumTotal.roundedAmount, 324) } func test_sum_withWhereClause() { - let amounts: [_New_USD] = [304.98, 9.02, 30.21] + let amounts: [USD] = [304.98, 9.02, 30.21] let sumTotal = amounts.sum(where: { $0.exactAmount > 20 }) XCTAssertEqual(sumTotal.roundedAmount, 335.19) } func test_sum_withMap() { - let prices: [_New_USD] = [3, 2.99, 5.98] + let prices: [USD] = [3, 2.99, 5.98] let totalTaxes = prices.sum { $0 * Decimal(0.09) } XCTAssertEqual(totalTaxes.roundedAmount, 1.08) }