Current section
Files
Jump to
Current section
Files
lib/ex_nric.ex
defmodule ExNric do
@moduledoc """
Documentation for ExNric.
"""
@doc """
Validates Singapore NRIC
## Examples
iex> ExNric.validate("S7343684B")
{:ok, "S7343684B"}
iex> ExNric.validate("T9901744E")
{:ok, "T9901744E"}
iex> ExNric.validate("F4365949U")
{:ok, "F4365949U"}
iex> ExNric.validate("G0047750N")
{:ok, "G0047750N"}
iex> ExNric.validate("G0047750U")
{:error, :checksum_error}
iex> ExNric.validate("X")
{:error, :invalid_format}
"""
@spec validate(String.t()) ::
{:ok, String.t()} | {:error, :checksum_error} | {:error, :invalid_format}
def validate(nric) when is_binary(nric) do
nric_pattern = ~r/^([STFG])(\d{7})([A-Z])$/
case Regex.run(nric_pattern, nric) do
[_, prefix, digits, checksum] ->
calculated_checksum = calculate_checksum(prefix, numberize(digits))
do_check(calculated_checksum, checksum, nric)
_ ->
{:error, :invalid_format}
end
end
defp do_check(calculated_checksum, checksum, nric) do
if calculated_checksum == checksum do
{:ok, nric}
else
{:error, :checksum_error}
end
end
defp numberize(digits) do
digits |> String.codepoints() |> Enum.map(&String.to_integer/1)
end
defp calculate_checksum("S", digits), do: calculate_checksum_uin(digits, 0)
defp calculate_checksum("T", digits), do: calculate_checksum_uin(digits, 4)
defp calculate_checksum("F", digits), do: calculate_checksum_fin(digits, 0)
defp calculate_checksum("G", digits), do: calculate_checksum_fin(digits, 4)
defp calculate_checksum_uin(digits, d0) do
d = digits |> calculate_d(d0) |> Integer.mod(11)
ds() |> Enum.zip(uin_checksums()) |> Enum.into(%{}) |> Map.get(d)
end
defp calculate_checksum_fin(digits, d0) do
d = digits |> calculate_d(d0) |> Integer.mod(11)
ds() |> Enum.zip(fin_checksums()) |> Enum.into(%{}) |> Map.get(d)
end
defp calculate_d(digits, d0) do
d0 + (digits |> Enum.zip(weights()) |> Enum.map(fn {a, b} -> a * b end) |> Enum.sum())
end
defp weights do
[2, 7, 6, 5, 4, 3, 2]
end
defp ds do
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
end
defp uin_checksums do
"A B C D E F G H I Z J" |> String.split()
end
defp fin_checksums do
"K L M N P Q R T U W X" |> String.split()
end
end