Skip to content

Commit 5d657ed

Browse files
Initial implementation of MonetaryAmount
0 parents  commit 5d657ed

20 files changed

+2169
-0
lines changed

.gitignore

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## Build generated
6+
build/
7+
DerivedData/
8+
9+
## Various settings
10+
*.pbxuser
11+
!default.pbxuser
12+
*.mode1v3
13+
!default.mode1v3
14+
*.mode2v3
15+
!default.mode2v3
16+
*.perspectivev3
17+
!default.perspectivev3
18+
xcuserdata/
19+
20+
## Other
21+
*.moved-aside
22+
*.xccheckout
23+
*.xcscmblueprint
24+
**/.DS_Store
25+
26+
## Obj-C/Swift specific
27+
*.hmap
28+
*.ipa
29+
*.dSYM.zip
30+
*.dSYM
31+
32+
## Playgrounds
33+
timeline.xctimeline
34+
playground.xcworkspace
35+
36+
# Swift Package Manager
37+
#
38+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
39+
# Packages/
40+
# Package.pins
41+
Package.resolved
42+
.build/
43+
MonetaryAmount.xcodeproj/
44+
45+
# CocoaPods
46+
#
47+
# We recommend against adding the Pods directory to your .gitignore. However
48+
# you should judge for yourself, the pros and cons are mentioned at:
49+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
50+
#
51+
# Pods/
52+
#
53+
# Add this line if you want to avoid checking in source code from the Xcode workspace
54+
# *.xcworkspace
55+
56+
# Carthage
57+
#
58+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
59+
# Carthage/Checkouts
60+
61+
Carthage/Build
62+
63+
# fastlane
64+
#
65+
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
66+
# screenshots whenever they are needed.
67+
# For more information about the recommended setup visit:
68+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
69+
70+
fastlane/report.xml
71+
fastlane/Preview.html
72+
fastlane/screenshots/**/*.png
73+
fastlane/test_output
74+
75+
# Code Injection
76+
#
77+
# After new code Injection tools there's a generated folder /iOSInjectionProject
78+
# https://github.com/johnno1962/injectionforxcode
79+
80+
iOSInjectionProject/
81+

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018 SoftwareEngineerChris
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MonetaryAmount.podspec

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Pod::Spec.new do |s|
2+
s.name = 'MonetaryAmount'
3+
s.version = '1.0.0'
4+
s.summary = 'A Swift representation of Money and ISO 4217 Currencies and the manipulation of them'
5+
6+
s.description = <<-DESC
7+
A Swift representation of money / monetary amounts and ISO 4217 currency designations. Supports manipulation
8+
and combination of monetary amounts of a single or multiple currency. Handles minor units (the exponent, e.g. cents for USD)
9+
for each currency as specified in the ISO 4217 standard. Simple manipulation includes consolidating / combining
10+
amounts. For example with MonetaryAmount, GBP10.50 + USD5.70 + GBP6.50 + USD8.10 consolidated would result in
11+
GBP17.00 and USD13.80. MonetaryAmount does not do any FX or conversion of currencies, it keeps each individual
12+
currency subtotal separate.
13+
DESC
14+
15+
s.homepage = 'https://github.com/SoftwareEngineerChris/MonetaryAmount'
16+
s.license = { :type => 'MIT', :file => 'LICENSE' }
17+
s.author = { 'SoftwareEngineerChris' => '[email protected]' }
18+
s.source = { :git => 'https://github.com/SoftwareEngineerChris/MonetaryAmount.git', :tag => s.version.to_s }
19+
20+
s.dependency 'RoundedDecimal', '~> 1.1.0'
21+
22+
s.ios.deployment_target = '10.0'
23+
s.osx.deployment_target = '10.9'
24+
s.watchos.deployment_target = '3.0'
25+
s.tvos.deployment_target = '10'
26+
s.swift_version = '5.0'
27+
28+
s.source_files = 'Sources/MonetaryAmount/*'
29+
end

Package.resolved

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// swift-tools-version:5.0
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "MonetaryAmount",
7+
products: [
8+
.library(
9+
name: "MonetaryAmount",
10+
type: .static,
11+
targets: ["MonetaryAmount"]),
12+
.executable(
13+
name: "CurrenciesGenerator",
14+
targets: ["CurrenciesGenerator"]
15+
)
16+
],
17+
dependencies: [
18+
.package(url: "https://github.com/SoftwareEngineerChris/RoundedDecimal.git", from: "2.1.0"),
19+
.package(url: "https://github.com/stencilproject/Stencil.git", from: "0.13.0")
20+
],
21+
targets: [
22+
.target(
23+
name: "MonetaryAmount",
24+
dependencies: ["RoundedDecimal"]),
25+
.testTarget(
26+
name: "MonetaryAmountTests",
27+
dependencies: ["MonetaryAmount"]),
28+
.target(
29+
name: "CurrenciesGenerator",
30+
dependencies: ["Stencil"]),
31+
]
32+
)

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# MonetaryAmount
2+
3+
A Swift representation of money / monetary amounts and _ISO 4217_ currency designations. Supports manipulation
4+
and combination of monetary amounts of a single currency or multiple currencies.
5+
6+
Handles minor units (the exponent, e.g. cents for USD) for each currency as specified in the ISO 4217 standard.
7+
Simple manipulation includes consolidating / combining amounts.
8+
9+
`MonetaryAmount` values that have the _same_ `Currency` will be added together, `MonetaryAmount` values with
10+
_unique_ `Currency` values will be added to the result on their own.
11+
12+
### Usage Example
13+
14+
```swift
15+
let moneyA = 12.00.in(.USD)
16+
let moneyB = 18.00.in(.USD)
17+
let moneyC = 6.00.in(.GBP)
18+
19+
// result would equal [30.00.in(.USD), 6.00.in(.GBP)]
20+
let result = [moneyA, moneyB, moneyC].consolidating(moneyB)
21+
```
22+
23+
The underlying value for the amount is represented by a `DynamicRoundedDecimal` which
24+
itself uses the Swift `Decimal` type. `DynamicRoundedDecimal` handles rounding internally
25+
as declared by the given currency's `minorUnit`.
26+
27+
### Construction
28+
29+
```swift
30+
// moneyA and moneyB are equal and represent US$28.53
31+
let moneyA = MonetaryAmount(currency: Currency.USD, value: Decimal(string: "28.529372")!)
32+
let moneyB = 28.53.in(.USD)
33+
```
34+
35+
#### Note
36+
`MonetaryAmount` does not do any FX or conversion of currencies, it keeps each individual
37+
currency subtotal separate.
38+
39+
#### See Also
40+
[Wikipedia Entry for ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)
41+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// main.m
3+
// MonetaryAmount
4+
//
5+
// Created by Chris Hargreaves on 10/09/2019.
6+
// Copyright © 2019 Software Engineering Limited. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Stencil
11+
12+
private struct CurrencyGenerator {
13+
14+
static func main() {
15+
// Read all stdin up-front to read CSV data
16+
guard let inputCSV = String(bytes: FileHandle.standardInput.readDataToEndOfFile(), encoding: .utf8) else {
17+
reportError("Unable to read input. Please pipe into stdin.")
18+
}
19+
20+
// Pull the currencies out of the CSV data
21+
let currencies = CurrencyGenerator.currencyData(csvCurrencyData: inputCSV)
22+
23+
// Create the Stencil template context
24+
let context = [ "currencies": currencies, "generatedDate": Date() ] as [String : Any]
25+
26+
// Load the Stencil template file from disk
27+
let environment = Environment(loader: FileSystemLoader(paths: ["templates/"]))
28+
29+
// Render the template with the template context
30+
guard let rendered = try? environment.renderTemplate(name: "Currencies.swift", context: context) else {
31+
reportError("Unable to read Currencies.swift template file")
32+
}
33+
34+
// Pump the rendered string back out to the stdout
35+
FileHandle.standardOutput.write(rendered.data(using: .utf8)!)
36+
}
37+
38+
private enum CurrencyDataColumn: Int, CaseIterable {
39+
40+
case alphaCurrencyCode
41+
case minorUnit
42+
case currencyName
43+
}
44+
45+
private struct CurrencyData: Equatable, Hashable {
46+
47+
let currencyName: String
48+
let alphaCurrencyCode: String
49+
let minorUnit: Int
50+
}
51+
52+
private static func currencyData(csvCurrencyData: String) -> [CurrencyData] {
53+
54+
let currencyData = csvCurrencyData
55+
.components(separatedBy: "\n")
56+
.map { $0.components(separatedBy: ",") }
57+
.compactMap { rawData -> CurrencyData? in
58+
59+
guard rawData.count == CurrencyDataColumn.allCases.count else {
60+
return nil
61+
}
62+
63+
return CurrencyData(
64+
currencyName: rawData[CurrencyDataColumn.currencyName.rawValue],
65+
alphaCurrencyCode: rawData[CurrencyDataColumn.alphaCurrencyCode.rawValue],
66+
minorUnit: Int(rawData[CurrencyDataColumn.minorUnit.rawValue])!
67+
)
68+
}
69+
70+
return Array(Set(currencyData))
71+
.sorted { $0.alphaCurrencyCode < $1.alphaCurrencyCode }
72+
}
73+
74+
private static func reportError(_ message: String) -> Never {
75+
FileHandle.standardError.write(message.data(using: .utf8)!)
76+
exit(-1)
77+
}
78+
}
79+
80+
// MARK: Program Start
81+
82+
CurrencyGenerator.main()

0 commit comments

Comments
 (0)