All Projects → Flight-School → Money

Flight-School / Money

Licence: mit
A precise, type-safe representation of a monetary amount in a given currency

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Money

Money
PHP implementation of Fowler's Money pattern.
Stars: ✭ 3,868 (+373.44%)
Mutual labels:  currency, money
FinanceKit
FinanceKit is a Framework for iOS and Mac to build apps working with financial data, like money, currencies, stocks, portfolio, transactions and other concepts.
Stars: ✭ 15 (-98.16%)
Mutual labels:  money, currency
money
Crystal shard for dealing with money and currency conversion
Stars: ✭ 26 (-96.82%)
Mutual labels:  money, currency
Guide-to-Swift-Numbers-Sample-Code
Xcode Playground Sample Code for the Flight School Guide to Swift Numbers
Stars: ✭ 92 (-88.74%)
Mutual labels:  money, currency
Dinero.js
Create, calculate, and format money in JavaScript and TypeScript.
Stars: ✭ 5,286 (+547%)
Mutual labels:  currency, money
money-parser
Price and currency parsing utility
Stars: ✭ 26 (-96.82%)
Mutual labels:  money, currency
Vue Numeric
Input field component to display a formatted currency value based on Vue.js
Stars: ✭ 341 (-58.26%)
Mutual labels:  currency, money
binarium
Binarium cryptocurrency is the first one protected from ASICs
Stars: ✭ 14 (-98.29%)
Mutual labels:  money, currency
react-local-currency
💵 💴Shows the price of your services in the customer's currency 💶 💷
Stars: ✭ 21 (-97.43%)
Mutual labels:  money, currency
swift-currency
Type-safety and algorithms for working with money in Swift.
Stars: ✭ 88 (-89.23%)
Mutual labels:  money, currency
django-prices-openexchangerates
openexchangerates.org support for django-prices
Stars: ✭ 33 (-95.96%)
Mutual labels:  money, currency
Jsr354 Api
JSR 354 - Money and Currency API
Stars: ✭ 262 (-67.93%)
Mutual labels:  currency, money
stockholm
💵 Modern Python library for working with money and monetary amounts. Human friendly and flexible approach for development. 100% test coverage + built-in support for GraphQL and Protocol Buffers transports using current best-practices.
Stars: ✭ 26 (-96.82%)
Mutual labels:  money, currency
react-numeric
A react component for formatted number form fields
Stars: ✭ 30 (-96.33%)
Mutual labels:  money, currency
pesa
A JS money lib whose precision goes up to 11 (and beyond).
Stars: ✭ 38 (-95.35%)
Mutual labels:  money, currency
Narvalo.NET
Applied functional patterns for C#. Money and Currency types. MVP framework. (Obsolete)
Stars: ✭ 16 (-98.04%)
Mutual labels:  money, currency
nova-money-field
Money Field for Laravel Nova
Stars: ✭ 71 (-91.31%)
Mutual labels:  money, currency
bankster
Money Creation Made Easy
Stars: ✭ 30 (-96.33%)
Mutual labels:  money, currency
currency-converter
💰 Easily convert between 32 currencies
Stars: ✭ 16 (-98.04%)
Mutual labels:  money, currency
Laravel Money
Currency formatting and conversion package for Laravel
Stars: ✭ 261 (-68.05%)
Mutual labels:  currency, money

Money

Build Status License Swift Version Cocoapods platforms Cocoapods compatible Carthage compatible

A precise, type-safe representation of monetary amounts in a given currency.

This functionality is discussed in Chapter 3 of Flight School Guide to Swift Numbers.

Requirements

  • Swift 4.0+

Installation

Swift Package Manager

Add the Money package to your target dependencies in Package.swift:

import PackageDescription

let package = Package(
  name: "YourProject",
  dependencies: [
    .package(
        url: "https://github.com/Flight-School/Money",
        from: "1.2.1"
    ),
  ]
)

Then run the swift build command to build your project.

CocoaPods

You can install Money via CocoaPods, by adding the following line to your Podfile:

pod 'Money-FlightSchool', '~> 1.2.1'

Run the pod install command to download the library and integrate it into your Xcode project.

Note The module name for this library is "Money" --- that is, to use it, you add import Money to the top of your Swift code just as you would by any other installation method. The pod is called "Money-FlightSchool" because there's an existing pod with the name "Money".

Carthage

To use Money in your Xcode project using Carthage, specify it in Cartfile:

github "Flight-School/Money" ~> 1.2.1

Then run the carthage update command to build the framework, and drag the built Money.framework into your Xcode project.

Usage

Creating Monetary Amounts

The Money type has a required associated Currency type. These currency types are named according to their three letter ISO 4701 currency codes. You can initialize a monetary using a Decimal value:

let amount = Decimal(12)
let monetaryAmount = Money<USD>(amount)

You can also create monetary amounts using integer, floating-point, and string literals.

12 as Money<USD>
12.00 as Money<USD>
"12.00" as Money<USD>

Important: Swift floating-point literals are currently initialized using binary floating-point number type, which cannot precisely express certain values. As a workaround, monetary amounts initialized from a floating-point literal are rounded to the number of places of the minor currency unit. If you want to express a smaller fractional monetary amount, initialize from a string literal or Decimal value instead.

let preciseAmount: Money<USD> = "123.4567"
let roundedAmount: Money<USD> = 123.4567

preciseAmount.amount // 123.4567
roundedAmount.amount // 123.46

For more information, see https://bugs.swift.org/browse/SR-920.

Comparing Monetary Amounts

You can compare two monetary amounts with the same currency:

let amountInWallet: Money<USD> = 60.00
let price: Money<USD> = 19.99

amountInWallet >= price // true

Attempting to compare monetary amounts with different currencies results in a compiler error:

let dollarAmount: Money<USD> = 123.45
let euroAmount: Money<EUR> = 4567.89

dollarAmount == euroAmount // Error: Binary operator '==' cannot be applied

Adding, Subtracting, and Multiplying Monetary Amounts

Monetary amounts can be added, subtracted, and multiplied using the standard binary arithmetic operators (+, -, *):

let prices: [Money<USD>] = [2.19, 5.39, 20.99, 2.99, 1.99, 1.99, 0.99]
let subtotal = prices.reduce(0.00, +) // "$36.53"
let tax = 0.08 * subtotal // "$2.92"
let total = subtotal + tax // "$39.45"

Important: Multiplying a monetary amount by a floating-point number results in an amount rounded to the number of places of the minor currency unit. If you want to produce a smaller fractional monetary amount, multiply by a Decimal value instead.

Formatting Monetary Amounts

You can create a localized representation of a monetary amount using NumberFormatter. Set the currencyCode property of the formatter to the currency.code property of the Money value and pass the amount property to the formatter string(for:) method.

let allowance: Money<USD> = 10.00
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "fr-FR")
formatter.currencyCode = allowance.currency.code
formatter.string(for: allowance.amount) // "10,00 $US"

Encoding and Decoding Monetary Amounts

Encoding

By default, Money values are encoded as keyed containers, with amount encoded as a number value.

let value: Money<USD> = 123.45

let encoder = JSONEncoder()
let data = try encoder.encode(value)
String(data: data, encoding: .utf8) // #"{"amount":123.45,"currencyCode":"USD"}"#

To configure encoding behavior, set either the JSONEncoder.moneyEncodingOptions property or the CodingUserInfoKey.moneyEncodingOptions key in the encoder's userInfo property.

var encoder = JSONEncoder()
encoder.moneyEncodingOptions = [.omitCurrency, .encodeAmountAsString]

let data = try encoder.encode([value])
String(data: data, encoding: .utf8) // #"["123.45"]"#

Decoding

The default decoding behavior is flexible, supporting both keyed and single value containers, with string or number values for amount.

let json = #"""
[
    { "currencyCode": "USD", "amount": "100.00" },
    50.00,
    "10"
]
"""#.data(using: .utf8)!

let decoder = JSONDecoder()
let values = try decoder.decode([Money<USD>].self, from: json)
values.first?.amount // 100.00
values.last?.currency.code // "USD"

To configure decoding behavior, set either the JSONDecoder.moneyDecodingOptions property or the CodingUserInfoKey.moneyDecodingOptions key in the decoder's userInfo property.

var decoder = JSONDecoder()
decoder.moneyDecodingOptions = [.requireExplicitCurrency]

Important: Foundation decoders currently decode number values using a binary floating-point number type, which cannot precisely express certain values. As a workaround, you can specify the requireStringAmount decoding option to require monetary amounts to be decoded precisely from a string representation.

let json = #"""
{ "currencyCode": "USD", "amount": "27.31" }
"""#.data(using: .utf8)!

var decoder = JSONDecoder()

try decoder.decode(Money<USD>.self, from: json) // DecodingError

decoder.moneyDecodingOptions = [.requireStringAmount]
let preciseAmount = try decoder.decode(Money<USD>.self, from: json)
preciseAmount.amount // 27.31

Alternatively, you can the roundFloatingPointAmount decoding option to round decoded floating-point values to the number of places of the minor currency unit.

let json = #"""
{ "currencyCode": "USD", "amount": 27.31 }
"""#.data(using: .utf8)!

var decoder = JSONDecoder()

let impreciseAmount = try decoder.decode(Money<USD>.self, from: json)
impreciseAmount.amount // 27.30999999...

decoder.moneyDecodingOptions = [.roundFloatingPointAmount]
let roundedAmount = try decoder.decode(Money<USD>.self, from: json)
roundedAmount.amount // 27.31

For more information, see https://bugs.swift.org/browse/SR-7054.

Supporting Multiple Currencies

Consider a Product structure with a price property. If you only support a single currency, such as US Dollars, you would define price to be of type Money<USD>:

struct Product {
    var price: Money<USD>
}

If you want to support multiple currencies, however, you can't specify an explicit currency type in the property declaration. Instead, the Product would have to be defined as a generic type:

struct Product<Currency: CurrencyType> {
    var price: Money<Currency>
}

Unfortunately, this approach is unwieldy, as each type that interacts with Product would also need to be generic, and so on, until the entire code base is generic over the currency type.

class ViewController<Currency: CurrencyType> : UIViewController { ... } // 😭

A better solution would be to define a new Price protocol with requirements that match the Money type:

protocol Price {
    var amount: Decimal { get }
    var currency: CurrencyType.Type { get }
}

extension Money: Price {}

Doing this allows prices to be defined in multiple currencies without making Product generic over the currency type:

struct Product {
    var price: Price
}

let product = Product(price: 12.00 as Money<USD>)
product.price // "$12.00"

If you want to support only certain currencies, such as US Dollars and Euros, you can define a SupportedCurrency protocol and add conformance to each currency type through an extension:

protocol SupportedCurrency: CurrencyType {}
extension USD: SupportedCurrency {}
extension EUR: SupportedCurrency {}

extension Money: Price where Currency: SupportedCurrency {}

Now, attempting to create a Product with a price in an unsupported currency results in a compiler error:

Product(price: 100.00 as Money<EUR>)
Product(price: 100.00 as Money<GBP>) // Error

Supported Currencies

This package provides a Currency type for each of the currencies defined by the ISO 4217 standard with the exception of special codes, such as USN (US Dollar, Next day) and XBC (Bond Markets Unit European Unit of Account 9).

The source file defining the available currencies is generated from a CSV file using GYB. This data source is up-to-date with ISO 4217 Amendment Number 169, published on August 17, 2018.

You can regenerate Sources/Money/Currency.swift from Resources/iso4217.csv by installing GYB and running the make command from the terminal:

$ make

We don't currently have a mechanism to automatically update this data source. Please open an issue if you're aware of any new amendments made to ISO 4217.

Adding Custom Currencies

You can create your own custom currency types by defining an enumeration that conforms to the CurrencyType protocol. For example, here's how you might represent Bitcoin (BTC):

enum BTC: CurrencyType {
    static var name: String { return "Bitcoin" }
    static var code: String { return "BTC" }
    static var minorUnit: Int { return 8 }
}

let satoshi: Money<BTC> = 0.00000001

NumberFormatter only supports currencies defined by ISO 4217, so you'll have to configure the symbol, currency code, and any other necessary parameters:

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "₿"
formatter.currencyCode = "BTC"
formatter.maximumFractionDigits = 8

formatter.string(for: satoshi.amount) // ₿0.00000001

Showing Off with Emoji

If you're the type of person who enjoys putting clip art in your source code, here's a trick that'll really impress your teammates:

typealias 💵 = Money<USD>
typealias 💴 = Money<JPY>
typealias 💶 = Money<EUR>
typealias 💷 = Money<GBP>

let tubeFare: 💷 = 2.40 // "£2.40"

Alternatives to Consider

A type-safe Money structure like the one provided by this package can reduce the likelihood of certain kinds of programming errors. However, you may find the cost of using this abstraction to outweigh the benefits it can provide in your code base.

If that's the case, you might consider implementing your own simple Money type with a nested Currency enumeration like this:

struct Money {
   enum Currency: String {
      case USD, EUR, GBP, CNY // supported currencies here
   }

   var amount: Decimal
   var currency: Currency
}

It's ultimately up to you to decide what kind of abstraction is best for your particular use case. Whatever you choose, just make sure to represent monetary amounts using a Decimal type with an explicit currency.

License

MIT

Contact

Mattt (@mattt)

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].