Current section
Files
Jump to
Current section
Files
lib/moeda.ex
defmodule Moeda do
@moduledoc """
[](https://travis-ci.org/ramondelemos/ex_dinheiro?branch=master)
[](https://coveralls.io/github/ramondelemos/ex_dinheiro?branch=master)
"""
alias Moeda.Moedas
defstruct [:nome, :simbolo, :codigo, :codigo_iso, :expoente]
@typedoc """
Type that represents `Moeda` struct with:
:nome as String.t that represents the name of the currency.
:simbolo as String.t that represents symbol of the currency.
:codigo as String.t that represents the ISO 4217 code.
:codigo_iso as integer that represents the country code.
:expoente as integer that represents the exponent of the currency.
"""
@type t :: %Moeda{
nome: String.t(),
simbolo: String.t(),
codigo: String.t(),
codigo_iso: integer,
expoente: integer
}
@spec find(String.t() | atom) :: map | nil
@doc """
Return a map from an atom or string that represents an ISO 4217 code.
## Examples
iex> Moeda.find(:BRL)
%Moeda{nome: "Brazilian Real", simbolo: 'R$', codigo: "BRL", codigo_iso: 986, expoente: 2}
iex> Moeda.find("BRL")
%Moeda{nome: "Brazilian Real", simbolo: 'R$', codigo: "BRL", codigo_iso: 986, expoente: 2}
iex> Moeda.find("")
nil
Its function ignore case sensitive.
## Examples
iex> Moeda.find(:brl)
%Moeda{nome: "Brazilian Real", simbolo: 'R$', codigo: "BRL", codigo_iso: 986, expoente: 2}
iex> Moeda.find("brl")
%Moeda{nome: "Brazilian Real", simbolo: 'R$', codigo: "BRL", codigo_iso: 986, expoente: 2}
Is possible to work with no official ISO currency code adding it in the system Mix config.
## Examples
iex> Moeda.find(:XBT)
nil
iex> moedas = %{ XBT: %Moeda{nome: "Bitcoin", simbolo: '฿', codigo: "XBT", codigo_iso: 0, expoente: 8} }
iex> Application.put_env(:ex_dinheiro, :unofficial_currencies, moedas)
iex> Moeda.find("xbt")
%Moeda{nome: "Bitcoin", simbolo: '฿', codigo: "XBT", codigo_iso: 0, expoente: 8}
Is possible to override some official ISO currency code adding it in the system Mix config.
## Examples
iex> Moeda.find(:BRL)
%Moeda{nome: "Brazilian Real", simbolo: 'R$', codigo: "BRL", codigo_iso: 986, expoente: 2}
iex> moedas = %{ BRL: %Moeda{nome: "Moeda do Brasil", simbolo: 'BR$', codigo: "BRL", codigo_iso: 986, expoente: 4}, USD: %Moeda{nome: "Moeda do EUA", simbolo: 'US$', codigo: "USD", codigo_iso: 986, expoente: 3} }
iex> Application.put_env(:ex_dinheiro, :unofficial_currencies, moedas)
iex> Moeda.find(:BRL)
%Moeda{nome: "Moeda do Brasil", simbolo: 'BR$', codigo: "BRL", codigo_iso: 986, expoente: 4}
iex> Moeda.find(:USD)
%Moeda{nome: "Moeda do EUA", simbolo: 'US$', codigo: "USD", codigo_iso: 986, expoente: 3}
"""
def find(codigo) when is_atom(codigo) do
codigo
|> Atom.to_string()
|> String.upcase()
|> String.to_atom()
|> do_find
end
def find(codigo) when is_binary(codigo) do
codigo
|> String.upcase()
|> String.to_atom()
|> do_find
end
defp do_find(codigo) do
unofficial_currencies =
Application.get_env(:ex_dinheiro, :unofficial_currencies, %{})
currency = unofficial_currencies[codigo]
if currency do
currency
else
currencies = Moedas.get_moedas()
currencies[codigo]
end
end
@spec get_atom(String.t() | atom) :: atom | nil
@doc """
Return an atom from a value that represents an ISO 4217 code.
## Examples
iex> Moeda.get_atom(:BRL)
:BRL
iex> Moeda.get_atom("BRL")
:BRL
iex> Moeda.get_atom("")
nil
Its function ignore case sensitive.
## Examples
iex> Moeda.get_atom(:brl)
:BRL
iex> Moeda.get_atom("brl")
:BRL
"""
def get_atom(codigo) do
moeda = find(codigo)
if moeda do
moeda.codigo |> String.upcase() |> String.to_atom()
else
nil
end
end
@spec get_factor(String.t() | atom) :: float | nil
@doc """
Return a multiplication factor from an ISO 4217 code.
## Examples
iex> Moeda.get_factor(:BRL)
100.0
iex> Moeda.get_factor("BRL")
100.0
iex> Moeda.get_factor("")
nil
Its function ignore case sensitive.
## Examples
iex> Moeda.get_factor(:brl)
100.0
iex> Moeda.get_factor("brl")
100.0
"""
def get_factor(codigo) do
moeda = find(codigo)
if moeda do
:math.pow(10, moeda.expoente)
else
nil
end
end
@spec to_string(String.t() | atom, float, Keywords.t()) :: String.t()
@doc """
Return a formated string from a ISO 4217 code and a float value.
## Examples
iex> Moeda.to_string(:BRL, 100.0)
"R$ 100,00"
iex> Moeda.to_string("BRL", 1000.5)
"R$ 1.000,50"
iex> Moeda.to_string(:BRL, -1.0)
"R$ -1,00"
Its function ignore case sensitive.
## Examples
iex> Moeda.to_string(:bRl, 100.0)
"R$ 100,00"
iex> Moeda.to_string("BrL", 1000.5)
"R$ 1.000,50"
Using options-style parameters you can change the behavior of the function.
- `thousand_separator` - default `"."`, set the thousand separator.
- `decimal_separator` - default `","`, set the decimal separator.
- `display_currency_symbol` - default `true`, put to `false` to hide de currency symbol.
- `display_currency_code` - default `false`, put to `true` to display de currency ISO 4217 code.
## Exemples
iex> Moeda.to_string(:USD, 1000.5, thousand_separator: ",", decimal_separator: ".")
"$ 1,000.50"
iex> Moeda.to_string(:USD, 1000.5, display_currency_symbol: false)
"1.000,50"
iex> Moeda.to_string(:USD, 1000.5, display_currency_code: true)
"$ 1.000,50 USD"
iex> Moeda.to_string(:USD, 1000.5, display_currency_code: true, display_currency_symbol: false)
"1.000,50 USD"
The default values also can be set in the system Mix config.
## Example:
iex> Application.put_env(:ex_dinheiro, :thousand_separator, ",")
iex> Application.put_env(:ex_dinheiro, :decimal_separator, ".")
iex> Moeda.to_string(:USD, 1000.5)
"$ 1,000.50"
iex> Application.put_env(:ex_dinheiro, :display_currency_symbol, false)
iex> Moeda.to_string(:USD, 5000.5)
"5,000.50"
iex> Application.put_env(:ex_dinheiro, :display_currency_code, true)
iex> Moeda.to_string(:USD, 10000.0)
"10,000.00 USD"
The options-style parameters override values in the system Mix config.
## Example:
iex> Application.put_env(:ex_dinheiro, :thousand_separator, ",")
iex> Application.put_env(:ex_dinheiro, :decimal_separator, ".")
iex> Moeda.to_string(:USD, 1000.5)
"$ 1,000.50"
iex> Moeda.to_string(:BRL, 1000.5, thousand_separator: ".", decimal_separator: ",")
"R$ 1.000,50"
"""
def to_string(moeda, valor, opts \\ []) do
m = Moeda.find(moeda)
unless m,
do:
raise(
ArgumentError,
message: "'#{moeda}' does not represent an ISO 4217 code."
)
unless is_float(valor),
do: raise(ArgumentError, message: "Value '#{valor}' must be float.")
{thousand_separator, decimal_separator, display_currency_symbol,
display_currency_code} = get_config(opts)
parts =
valor
|> :erlang.float_to_binary(decimals: m.expoente)
|> String.split(".")
thousands =
parts
|> List.first()
|> String.reverse()
|> String.codepoints()
|> format_thousands(thousand_separator)
|> String.reverse()
decimals =
if m.expoente > 0 do
Enum.join([decimal_separator, List.last(parts)])
else
""
end
currency_symbol =
if display_currency_symbol do
m.simbolo
else
""
end
currency_code =
if display_currency_code do
m.codigo
else
""
end
[currency_symbol, " ", thousands, decimals, " ", currency_code]
|> Enum.join()
|> String.trim()
end
defp get_config(opts) do
conf_thousand_separator =
Application.get_env(:ex_dinheiro, :thousand_separator, ".")
conf_decimal_separator =
Application.get_env(:ex_dinheiro, :decimal_separator, ",")
conf_display_currency_symbol =
Application.get_env(:ex_dinheiro, :display_currency_symbol, true)
conf_display_currency_code =
Application.get_env(:ex_dinheiro, :display_currency_code, false)
thousand_separator =
Keyword.get(opts, :thousand_separator, conf_thousand_separator)
decimal_separator =
Keyword.get(opts, :decimal_separator, conf_decimal_separator)
display_currency_symbol =
Keyword.get(opts, :display_currency_symbol, conf_display_currency_symbol)
display_currency_code =
Keyword.get(opts, :display_currency_code, conf_display_currency_code)
{thousand_separator, decimal_separator, display_currency_symbol,
display_currency_code}
end
defp format_thousands([head | tail], separator, opts \\ []) do
position = Keyword.get(opts, :position, 1)
num =
if rem(position, 3) == 0 and head != "-" and tail != [] do
Enum.join([head, separator])
else
head
end
if tail != [] do
[num, format_thousands(tail, separator, position: position + 1)]
|> Enum.join()
else
num
end
end
end