currencies (empty) → 0.1.0.0
raw patch · 9 files changed
+417/−0 lines, 9 filesdep +basedep +currenciesdep +hspecsetup-changed
Dependencies added: base, currencies, hspec, text
Files
- LICENSE +30/−0
- README.md +3/−0
- Setup.hs +2/−0
- currencies.cabal +45/−0
- src/Data/Currency/Amounts.hs +53/−0
- src/Data/Currency/Currencies.hs +89/−0
- src/Data/Currency/Pretty.hs +132/−0
- test/Data/Currency/PrettySpec.hs +62/−0
- test/Spec.hs +1/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Daniel Campoverde [alx741] (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Daniel Campoverde [alx741] nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,3 @@+[](https://circleci.com/gh/alx741/currencies)++# currencies
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ currencies.cabal view
@@ -0,0 +1,45 @@+name: currencies+version: 0.1.0.0+synopsis: Currencies representation, pretty printing and conversion+description: ISO 4217 Currencies representation, pretty printing and conversion:+ .+ * Represent monetary amouts of a particular currency in a type-safe manner+ .+ * Convert amounts between different currencies+ .+ * Print human readable amouts+homepage: https://github.com/alx741/currencies#readme+license: BSD3+license-file: LICENSE+author: Daniel Campoverde [alx741]+maintainer: alx@sillybytes.net+copyright: 2017 Daniel Campoverde [alx741]+category: Data+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Data.Currency.Currencies+ , Data.Currency.Amounts+ , Data.Currency.Pretty+ build-depends: base >= 4.7 && < 5+ , text >= 1.2 && < 2+ default-language: Haskell2010++test-suite currencies-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ other-modules: Data.Currency.PrettySpec+ build-depends: base+ , currencies+ , hspec >= 2.0 && < 3+ , text >= 1.2 && < 2+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/alx741/currencies
+ src/Data/Currency/Amounts.hs view
@@ -0,0 +1,53 @@+-- | Operations upon monetary 'Amount's+--+-- Converting amounts+--+-- >>> convert USD (Amount EUR 23482.34)+-- Amount USD 27709.1612+--+-- >>> prettyPrint $ convert USD (Amount EUR 23482.34)+-- "USD 27,709.16"+--+-- Comparing amounts+--+-- >>> compareAmounts (Amount EUR 1000) (Amount BTC 1)+-- LT++module Data.Currency.Amounts+ ( -- * Monetary Amount+ Amount(..)++ -- * Converting+ , toUSD+ , fromUSD+ , convert++ -- * Comparing+ , compareAmounts++ , module Data.Currency.Currencies+ ) where++import Data.Currency.Currencies++-- | Monetary amounts+data Amount c = Amount c Double deriving (Show, Read, Eq)++instance (Currency c) => Ord (Amount c) where+ (Amount _ v1) <= (Amount _ v2) = v1 <= v2++-- | Convert an 'Amount' of an arbitrary currency to 'USD'+toUSD :: (Currency c) => Amount c -> Amount USD+toUSD (Amount c amount) = Amount USD $ amount * exchangeUSD c++-- | Convert an 'Amount' from 'USD' to a given 'Currency'+fromUSD :: (Currency c) => c -> Amount USD -> Amount c+fromUSD c (Amount USD amount) = Amount c $ amount / exchangeUSD c++-- | Convert an 'Amount' to a given 'Currency'+convert :: (Currency c', Currency c) => c' -> Amount c -> Amount c'+convert c' amount = fromUSD c' $ toUSD amount++-- | Compare two 'Amount's of arbitrary currencies+compareAmounts :: (Currency c1, Currency c2) => Amount c1 -> Amount c2 -> Ordering+compareAmounts a1 a2 = compare (toUSD a1) (toUSD a2)
+ src/Data/Currency/Currencies.hs view
@@ -0,0 +1,89 @@+-- | ISO 4217 compliant and other currencies++module Data.Currency.Currencies+ ( -- * Currency Class+ Currency(..)+ , CurrencyType(..)++ -- * Currencies+ , BTC(..)+ , CLP(..)+ , EUR(..)+ , USD(..)+ ) where++class (Show c, Eq c) => Currency c where+ currencyType :: c -> CurrencyType+ -- | ISO 4217 Currency Code+ isoCode :: c -> String+ -- | ISO 4217 Currency Numeric Code+ isoNumericCode :: c -> String+ -- | Number of digits after the decimal separator+ decimalDigits :: c -> Int+ -- | Currency UTF-8 symbol+ symbol :: c -> String+ -- | Exchange rate with US Dollar (USD)+ exchangeUSD :: c -> Double+ -- | ISO 3166-1 alpha-2 Country codes where the currency is used+ countries :: c -> [String]++data CurrencyType+ = Circulating -- ^ Currencies recognized as legal tender+ | Local -- ^ Currencies with validity only in particular geographical localities+ | Supranational -- ^ Currencies for procedural purposes and precious metals (X currencies)+ | Cryptocurrency -- ^ Digital, cryptography based currencies+ | Fictional -- ^ Currencies used in games, movies, novels, and other fictional setups+ deriving (Show, Read, Eq)+++-- | Bitcoin+data BTC = BTC deriving (Show, Read, Eq)++-- | Chilean Peso+data CLP = CLP deriving (Show, Read, Eq)++-- | European Union Euro+data EUR = EUR deriving (Show, Read, Eq)++-- | US Dollar+data USD = USD deriving (Show, Read, Eq)+++instance Currency BTC where+ currencyType _ = Cryptocurrency+ isoCode = show+ isoNumericCode _ = ""+ decimalDigits _ = 8+ symbol _ = "B"+ exchangeUSD _ = 4237.88+ countries _ = []++instance Currency CLP where+ currencyType _ = Circulating+ isoCode = show+ isoNumericCode _ = "152"+ decimalDigits _ = 0+ symbol _ = "$"+ exchangeUSD _ = 0.0015+ countries _ = ["CL"]++instance Currency EUR where+ currencyType _ = Circulating+ isoCode = show+ isoNumericCode _ = "978"+ decimalDigits _ = 2+ symbol _ = "€"+ exchangeUSD _ = 1.18+ countries _ = ["AD", "AT", "BE", "CY", "EE", "FI", "FR", "DE", "GR"+ , "GP", "IE", "IT", "LV", "LT", "LU", "MT", "MQ", "YT", "MC"+ , "ME", "NL", "PT", "RE", "BL", "PM", "SM", "SK", "SI", "ES"]++instance Currency USD where+ currencyType _ = Circulating+ isoCode = show+ isoNumericCode _ = "840"+ decimalDigits _ = 2+ symbol _ = "$"+ exchangeUSD _ = 1.0+ countries _ = ["US", "AS", "BB", "BM", "IO", "VG", "BQ", "EC", "MH"+ , "FM", "MP", "PW", "PA", "PR", "TL", "TC", "VI"]
+ src/Data/Currency/Pretty.hs view
@@ -0,0 +1,132 @@+-- | Pretty print monetary amounts like:+--+-- * /$ 34.50/+--+-- * /USD 3,456.29/+--+-- * /€ 32 433 938.23/+--+-- Using default printing settings+--+-- >>> prettyPrint (Amount USD 2342.2)+-- "USD 2,342.20"+-- >>> prettyPrint (Amount EUR 45827.346)+-- "EUR 45,827.35"+--+-- Using custom printing settings+--+-- >>> prettyPrintWith (defaultConfig { useCurrencySymbol = True }) (Amount USD 2342.2)+-- "$ 2,342.20"+-- >>> prettyPrintWith (defaultConfig { useCurrencySymbol = True }) (Amount EUR 2342.2)+-- "€ 2,342.20"+-- >>> prettyPrintWith (defaultConfig { showDecimals = False }) (Amount USD 25.50)+-- "USD 25"+--+-- For more printing settings see 'PrettyConfig'++module Data.Currency.Pretty+ ( -- * Pretty printing+ prettyPrint+ , prettyPrintWith++ -- * Configuration+ , PrettyConfig(..)+ , defaultConfig++ , module Data.Currency.Amounts+ ) where++import Text.Printf+import Data.Currency.Amounts+import Data.Monoid ((<>))++-- | Pretty print a monetary amount using 'defaultConfig'+prettyPrint :: (Currency c) => Amount c -> String+prettyPrint = prettyPrintWith defaultConfig++-- | Pretty print a monetary amount with a custom 'PrettyConfig' configuration+prettyPrintWith :: (Currency c) => PrettyConfig -> Amount c -> String+prettyPrintWith cnf (Amount currency amount) =+ prefixSymbol currency cnf+ $ prefixCode currency cnf+ $ changeDecimalSep currency cnf+ $ largeAmountSeparate currency cnf+ $ toDecimalString currency cnf amount++prefixSymbol :: (Currency c) => c -> PrettyConfig -> String -> String+prefixSymbol currency cnf val+ | useCurrencySymbol cnf = symbol currency <> " " <> val+ | otherwise = val++prefixCode :: (Currency c) => c -> PrettyConfig -> String -> String+prefixCode currency cnf val+ | useCurrencySymbol cnf = val+ | suffixIsoCode cnf = val <> " " <> isoCode currency+ | otherwise = isoCode currency <> " " <> val++changeDecimalSep :: (Currency c) => c -> PrettyConfig -> String -> String+changeDecimalSep currency cnf = replaceFst '.' (decimalSeparator cnf)+ where+ replaceFst :: Char -> Char -> String -> String+ replaceFst c c' [] = []+ replaceFst c c' (s:ss)+ | s == c = c' : ss+ | otherwise = s : replaceFst c c' ss++largeAmountSeparate :: (Currency c) => c -> PrettyConfig -> String -> String+largeAmountSeparate currency cnf amount+ | compactFourDigitAmounts cnf = if length integer <= 4 then amount else separated ++ decimal+ | otherwise = separated ++ decimal+ where+ (integer, decimal) = span (/= '.') amount+ separated = reverse $ intersperseN 3 (largeAmountSeparator cnf) $ reverse integer++toDecimalString :: (Currency c) => c -> PrettyConfig -> Double -> String+toDecimalString currency cnf amount+ | showDecimals cnf = printf format amount+ | otherwise = takeWhile (/= '.') $ printf "%.1f" amount+ where format = "%." <> show (decimalDigits currency) <> "f"++intersperseN :: Eq a => Int -> a -> [a] -> [a]+intersperseN n s ss+ | null remainder = ss+ | otherwise = (take ++ [s]) ++ intersperseN n s remainder+ where (take, remainder) = splitAt n ss+++data PrettyConfig = PrettyConfig+ { showDecimals :: Bool+ -- | Print four digits amounts as+ -- /USD 1000,00/ instead of /USD 1,000.00/+ , compactFourDigitAmounts :: Bool+ -- | Replace the currency ISO code with its symbol to produce+ -- /$ 23.50/ instead of /USD 23.50/+ , useCurrencySymbol :: Bool+ -- | Use the currency ISO code as suffix to produce+ -- /23.50 USD/ instead of /USD 23.50/+ , suffixIsoCode :: Bool+ , largeAmountSeparator :: Char+ , decimalSeparator :: Char+ } deriving (Show)+++-- | Default 'PrettyConfig' used in 'prettyPrint'+--+-- * Show decimals+--+-- * Compact four digit amounts+--+-- * Use ISO code+--+-- * Separate large amounts with comma+--+-- * Separate decimals with dot+defaultConfig :: PrettyConfig+defaultConfig = PrettyConfig+ { showDecimals = True+ , compactFourDigitAmounts = True+ , useCurrencySymbol = False+ , suffixIsoCode = False+ , largeAmountSeparator = ','+ , decimalSeparator = '.'+ }
+ test/Data/Currency/PrettySpec.hs view
@@ -0,0 +1,62 @@+module Data.Currency.PrettySpec where++import Test.Hspec+import Data.Currency.Pretty+import Data.Currency.Amounts++spec :: Spec+spec = do+ describe "prettyPrint" $ do+ it "prints human readable monetary amounts" $ do+ prettyPrint (Amount USD 23.50) `shouldBe` "USD 23.50"+ prettyPrint (Amount USD 540.256) `shouldBe` "USD 540.26"+ prettyPrint (Amount USD 300.251) `shouldBe` "USD 300.25"+ prettyPrint (Amount USD 3.4) `shouldBe` "USD 3.40"+ prettyPrint (Amount EUR 15.589) `shouldBe` "EUR 15.59"++ it "respects currency decimal digits" $ do+ prettyPrint (Amount CLP 345.35) `shouldBe` "CLP 345"+ prettyPrint (Amount CLP 23.53) `shouldBe` "CLP 24"++ it "can use the currency code as suffix" $ do+ let config = defaultConfig { suffixIsoCode = True }+ prettyPrintWith config (Amount USD 23.50) `shouldBe` "23.50 USD"+ prettyPrintWith config (Amount CLP 345.35) `shouldBe` "345 CLP"++ it "can omit decimals" $ do+ let config = defaultConfig { showDecimals = False }+ prettyPrintWith config (Amount USD 23.50) `shouldBe` "USD 23"+ prettyPrintWith config (Amount USD 534.25) `shouldBe` "USD 534"+ prettyPrintWith config (Amount EUR 15.589) `shouldBe` "EUR 15"++ it "can use the currency symbol" $ do+ let config = defaultConfig { useCurrencySymbol = True }+ prettyPrintWith config (Amount USD 23.50) `shouldBe` "$ 23.50"+ prettyPrintWith config (Amount EUR 15.589) `shouldBe` "€ 15.59"+ prettyPrintWith config (Amount BTC 0.5898) `shouldBe` "B 0.58980000"++ it "can use a custom decimal separator" $ do+ let config = defaultConfig { decimalSeparator = ',' }+ prettyPrintWith config (Amount USD 23.50) `shouldBe` "USD 23,50"+ prettyPrintWith config (Amount EUR 15.589) `shouldBe` "EUR 15,59"+ prettyPrintWith config (Amount USD 23.0) `shouldBe` "USD 23,00"++ it "uses large amounts separators" $ do+ prettyPrint (Amount USD 32323.50) `shouldBe` "USD 32,323.50"+ prettyPrint (Amount EUR 3827115.259) `shouldBe` "EUR 3,827,115.26"++ it "can use a custom large amounts separator" $ do+ let config = defaultConfig { largeAmountSeparator = ' ' }+ prettyPrintWith config (Amount USD 32323.50) `shouldBe` "USD 32 323.50"+ prettyPrintWith config (Amount EUR 3827115.259) `shouldBe` "EUR 3 827 115.26"++ it "can avoid separating 4 digit amounts" $ do+ prettyPrint (Amount USD 2323.50) `shouldBe` "USD 2323.50"+ prettyPrint (Amount EUR 4629.25) `shouldBe` "EUR 4629.25"+ prettyPrint (Amount USD 23875.00) `shouldBe` "USD 23,875.00"++ it "can force 4-digit amounts separation" $ do+ let config = defaultConfig { compactFourDigitAmounts = False}+ prettyPrintWith config (Amount USD 2323.50) `shouldBe` "USD 2,323.50"+ prettyPrintWith config (Amount EUR 4629.25) `shouldBe` "EUR 4,629.25"+ prettyPrintWith config (Amount USD 23875.00) `shouldBe` "USD 23,875.00"
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}