Packages
canada_sin
0.1.0
Elixir package that validates a Canadian SIN and can get the SIN's province
Current section
Files
Jump to
Current section
Files
lib/canada_sin.ex
defmodule CanadaSin do
@moduledoc """
Documentation for CanadaSin.
"""
@provinces %{
0 => [],
1 => [:NB, :NF, :NS, :PE],
2 => [:QC],
3 => [:QC],
4 => [:ON],
5 => [:ON],
6 => [:AB, :MB, :SK, :NT, :NU],
7 => [:BC, :YU],
8 => [],
9 => [:temporary]
}
@doc """
Validates if a SIN matches Canadian's rules
## Examples
iex> CanadaSin.valid? "130 692 544"
true
iex> CanadaSin.valid? 130692544
true
"""
@spec valid?(integer) :: boolean
def valid?(number) when is_integer(number), do: valid?(Integer.to_string(number))
@sin_length 9
@spec valid?(String.t()) :: boolean
def valid?(string) do
sanitized = sanitize(string)
if String.length(sanitized) == @sin_length do
rem(compute(sanitized), 10) == 0
else
false
end
end
@spec compute(String.t()) :: integer
defp compute(string) do
numbers =
string
|> String.codepoints()
|> Enum.map(fn char -> safe_integer_parse(char) end)
|> Enum.with_index()
Enum.reduce(numbers, 0, &compute_char/2)
end
@multipliers [1, 2, 1, 2, 1, 2, 1, 2, 1]
@spec compute_char({integer, integer}, integer) :: integer
defp compute_char({number, index}, acc) do
multiplier = Enum.at(@multipliers, index)
acc + sum_of_units(multiplier * number)
end
@spec sum_of_units(integer) :: integer
defp sum_of_units(number) when number < 10, do: number
defp sum_of_units(number) do
number
|> Integer.to_string()
|> String.codepoints()
|> Enum.map(&safe_integer_parse/1)
|> Enum.reduce(0, fn value, acc -> acc + value end)
end
@doc """
Gets the province from the first digit
## Examples
iex> CanadaSin.province "130 692 544"
[:NB, :NF, :NS, :PE]
iex> CanadaSin.province 230692544
[:QC]
"""
@spec province(integer) :: [atom]
def province(number) when is_integer(number), do: province(Integer.to_string(number))
@spec province(String.t()) :: [atom]
def province(string) when is_bitstring(string) do
Map.get(@provinces, get_province_code(string))
end
@spec get_province_code(String.t()) :: integer
defp get_province_code(string) do
string
|> sanitize
|> String.first()
|> safe_integer_parse
end
defp sanitize(string) do
string
|> String.trim()
|> String.replace(~r/\D/, "")
end
defp safe_integer_parse(number) do
case Integer.parse(number) do
{integer, _} -> integer
:error -> 0
end
end
end