Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 84 additions & 13 deletions lib/currency_formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,19 @@ defmodule CurrencyFormatter do
|> format(currency, opts)
end

def format(number_string, currency, opts) when is_binary(number_string) and is_binary(currency) do
def format(number_string, currency, opts)
when is_binary(number_string) and is_binary(currency) do
format = instructions(currency)

decimal =
format["subunit_to_unit"]
|> Integer.digits()
|> Kernel.length()

number_string
|> remove_non_numbers
|> add_subunit_separator
|> add_padding
|> add_subunit_separator(decimal - 1)
|> add_padding(decimal - 1)
|> split_units_and_subunits
|> handle_cents(format, opts)
|> set_symbol(format, opts)
Expand Down Expand Up @@ -274,17 +280,81 @@ defmodule CurrencyFormatter do
@spec remove_non_numbers(String.t()) :: String.t()
defp remove_non_numbers(string), do: String.replace(string, ~r/[^0-9-]/, "")

@spec add_subunit_separator(String.t()) :: String.t()
defp add_subunit_separator(string),
do: String.replace(string, ~r/^0*([0-9-]+)(\d{2})$/, "\\1,\\2")
@spec add_subunit_separator(String.t(), number) :: String.t()
defp add_subunit_separator(amount, decimal) do
String.replace(amount, ~r/^0*([0-9-]+)(\d{#{decimal}})$/, "\\1,\\2")
end

@spec add_padding(String.t(), number) :: String.t()
defp add_padding("", _decimal), do: "0,00"

defp add_padding("-" <> centified, decimal) when byte_size(centified) == 1 do
case decimal do
0 -> "-" <> centified
2 -> "-0,0" <> centified
3 -> "-0,00" <> centified
4 -> "-0,000" <> centified
end
end

defp add_padding(centified, decimal) when byte_size(centified) == 1 do
case decimal do
0 -> centified
2 -> "0,0" <> centified
3 -> "0,00" <> centified
4 -> "0,000" <> centified
end
end

defp add_padding("-," <> centified, decimal) when byte_size(centified) == 2 do
case decimal do
0 -> "-" <> centified
2 -> "-0," <> centified
3 -> "-0,0" <> centified
4 -> "-0,00" <> centified
end
end

defp add_padding(centified, decimal) when byte_size(centified) == 2 do
case decimal do
0 -> centified
2 -> "0," <> centified
3 -> "0,0" <> centified
4 -> "0,00" <> centified
end
end

defp add_padding("-" <> centified, decimal) when byte_size(centified) == 3 do
case decimal do
3 -> "-0," <> centified
4 -> "-0,0" <> centified
_ -> "-" <> centified
end
end

defp add_padding(centified, decimal) when byte_size(centified) == 3 do
case decimal do
3 -> "0," <> centified
4 -> "0,0" <> centified
_ -> centified
end
end

defp add_padding("-" <> centified, decimal) when byte_size(centified) == 4 do
case decimal do
4 -> "-0,0" <> centified
_ -> "-" <> centified
end
end

@spec add_padding(String.t()) :: String.t()
defp add_padding(""), do: "0,00"
defp add_padding("-" <> centified) when byte_size(centified) == 1, do: "-0,0" <> centified
defp add_padding(centified) when byte_size(centified) == 1, do: "0,0" <> centified
defp add_padding("-," <> centified) when byte_size(centified) == 2, do: "-0," <> centified
defp add_padding(centified) when byte_size(centified) == 2, do: "0," <> centified
defp add_padding(centified), do: centified
defp add_padding(centified, decimal) when byte_size(centified) == 4 do
case decimal do
4 -> "0," <> centified
_ -> centified
end
end

defp add_padding(centified, _decimal), do: centified

@spec split_units_and_subunits(binary) :: [binary]
defp split_units_and_subunits(string), do: String.split(string, ",", parts: 2)
Expand Down Expand Up @@ -327,6 +397,7 @@ defmodule CurrencyFormatter do
@spec set_symbol(String.t(), map, Keyword.t()) :: String.t()
defp set_symbol(number_string, %{"symbol_first" => true} = config, opts) do
symbol = get_symbol(config, opts)

if Keyword.get(opts, :html) do
wrap_in_spans(symbol: symbol, amount: number_string)
else
Expand Down
38 changes: 35 additions & 3 deletions test/currency_formatter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ defmodule CurrencyFormatterTest do
test "should correctly format in euro and dollars" do
assert "€0,01" == CurrencyFormatter.format(1, :eur)
assert "$0.01" == CurrencyFormatter.format(1, "USD")
assert "0,01Ft" == CurrencyFormatter.format(1, "huf")
assert "1,Ft" == CurrencyFormatter.format(1, "huf")
assert "$0.01" == CurrencyFormatter.format(1, :AUD)
end

Expand All @@ -29,6 +29,33 @@ defmodule CurrencyFormatterTest do
assert "$-0.53" == CurrencyFormatter.format(-53, "USD")
end

test "it formats various decimal currencies" do
assert "1,Ft" == CurrencyFormatter.format(1, "HUF")
assert "$0.01" == CurrencyFormatter.format(1, "USD")
assert "ب.د0.001" == CurrencyFormatter.format(1, "BHD")
assert "UF0,0001" == CurrencyFormatter.format(1, "CLF")

assert "12,Ft" == CurrencyFormatter.format(12, "HUF")
assert "$0.12" == CurrencyFormatter.format(12, "USD")
assert "ب.د0.012" == CurrencyFormatter.format(12, "BHD")
assert "UF0,0012" == CurrencyFormatter.format(12, "CLF")

assert "123,Ft" == CurrencyFormatter.format(123, "HUF")
assert "$1.23" == CurrencyFormatter.format(123, "USD")
assert "ب.د0.123" == CurrencyFormatter.format(123, "BHD")
assert "UF0,0123" == CurrencyFormatter.format(123, "CLF")

assert "1 234,Ft" == CurrencyFormatter.format(1234, "HUF")
assert "$12.34" == CurrencyFormatter.format(1234, "USD")
assert "ب.د1.234" == CurrencyFormatter.format(1234, "BHD")
assert "UF0,1234" == CurrencyFormatter.format(1234, "CLF")

assert "123 456,Ft" == CurrencyFormatter.format(123_456, "HUF")
assert "$1,234.56" == CurrencyFormatter.format(123_456, "USD")
assert "ب.د123.456" == CurrencyFormatter.format(123_456, "BHD")
assert "UF12,3456" == CurrencyFormatter.format(123_456, "CLF")
end

test "should format an amount_in_cents as integer to a price in dollars" do
assert "$0.01" == CurrencyFormatter.format(1, :usd)
assert "$0.12" == CurrencyFormatter.format(12, :usd)
Expand Down Expand Up @@ -72,6 +99,7 @@ defmodule CurrencyFormatterTest do
test "should return html" do
assert ~s[<span class="currency-formatter-symbol">€</span><span class="currency-formatter-amount">1.234</span>] ==
CurrencyFormatter.raw_html_format(123_400, :eur)

assert ~s[<span class="currency-formatter-symbol">€</span><span class="currency-formatter-amount">1.234,00</span>] ==
CurrencyFormatter.raw_html_format(123_400, :eur, keep_decimals: true)
end
Expand All @@ -98,11 +126,15 @@ defmodule CurrencyFormatterTest do
assert "US$12.34" == CurrencyFormatter.format(1234, :usd, disambiguate: true)
assert "C$12.34" == CurrencyFormatter.format(1234, :cad, disambiguate: true)
assert "C$12" == CurrencyFormatter.format(1200, :cad, disambiguate: true)
assert "C$12.00" == CurrencyFormatter.format(1200, :cad, disambiguate: true, keep_decimals: true)

assert "C$12.00" ==
CurrencyFormatter.format(1200, :cad, disambiguate: true, keep_decimals: true)

assert "$12.34" == CurrencyFormatter.format(1234, :usd, disambiguate: false)
assert "$12" == CurrencyFormatter.format(1200, :cad, disambiguate: false)
assert "$12.00" == CurrencyFormatter.format(1200, :cad, disambiguate: false, keep_decimals: true)

assert "$12.00" ==
CurrencyFormatter.format(1200, :cad, disambiguate: false, keep_decimals: true)
end

test "should return a map with formatting instructions" do
Expand Down