defmodule Ulidex do @moduledoc """ This is an implementation of the ULID (Universally Unique Lexicographically Sortable Identifier) spec. """ alias Ulidex.Binary @typedoc """ A Crockford 32 encoded ULID. """ @type t() :: String.t() @doc """ Generates a Crockford 32 encoded ULID for the given timestamp which must be in milliseconds. If no timestamp is given it uses the current system time. ## Examples iex> ulid = Ulidex.generate() iex> <<_::size(208)>> = ulid """ @spec generate(integer()) :: binary() def generate(timestamp \\ System.system_time(:millisecond)) do timestamp |> Binary.generate() |> encode() end @doc """ Takes a 128 bit binary ULID and returns a Crockford 32 encoded ULID. Raises `Ulidex.Error.InvalidBinary` when the input is malformed. ## Examples iex> ulid = <<1, 128, 211, 67, 45, 254, 115, 142, 105, 252, 121, 217, 228, 222, 219, 155>> iex> Ulidex.encode(ulid) "01G39M6BFYEE76KZ3SV7JDXPWV" """ @spec encode(Binary.t()) :: t() def encode(ulid) when is_binary(ulid) and byte_size(ulid) == 16 do encoded = for <<(symbol::5 <- <<0::2, ulid::bitstring>>)>> do encode_symbol(symbol) end to_string(encoded) end def encode(_), do: raise(Ulidex.Error.InvalidBinary, "Must be a 128 bit binary") @doc """ Takes a Crockford 32 encoded ULID and returns a a 128 bit binary ULID. Raises `Ulidex.Error.InvalidUlidString` when the input is malformed. ## Examples iex> ulid = "01G3C1TW8AKE19BYDDR6996C1D" iex> Ulidex.decode(ulid) <<1, 128, 216, 29, 113, 10, 155, 130, 149, 249, 173, 193, 146, 147, 48, 45>> """ @spec decode(t()) :: Binary.t() def decode(ulid) when is_binary(ulid) and byte_size(ulid) == 26 do decoded = ulid |> to_charlist() |> Enum.reduce(<<>>, fn el, acc -> <> end) <<_padding::2, decoded::binary>> = decoded decoded end def decode(string), do: raise(Ulidex.Error.InvalidUlidString, string) defp encode_symbol(0), do: ?0 defp encode_symbol(1), do: ?1 defp encode_symbol(2), do: ?2 defp encode_symbol(3), do: ?3 defp encode_symbol(4), do: ?4 defp encode_symbol(5), do: ?5 defp encode_symbol(6), do: ?6 defp encode_symbol(7), do: ?7 defp encode_symbol(8), do: ?8 defp encode_symbol(9), do: ?9 defp encode_symbol(10), do: ?A defp encode_symbol(11), do: ?B defp encode_symbol(12), do: ?C defp encode_symbol(13), do: ?D defp encode_symbol(14), do: ?E defp encode_symbol(15), do: ?F defp encode_symbol(16), do: ?G defp encode_symbol(17), do: ?H defp encode_symbol(18), do: ?J defp encode_symbol(19), do: ?K defp encode_symbol(20), do: ?M defp encode_symbol(21), do: ?N defp encode_symbol(22), do: ?P defp encode_symbol(23), do: ?Q defp encode_symbol(24), do: ?R defp encode_symbol(25), do: ?S defp encode_symbol(26), do: ?T defp encode_symbol(27), do: ?V defp encode_symbol(28), do: ?W defp encode_symbol(29), do: ?X defp encode_symbol(30), do: ?Y defp encode_symbol(31), do: ?Z defp decode_symbol(?0), do: 0 defp decode_symbol(?o), do: 0 defp decode_symbol(?O), do: 0 defp decode_symbol(?1), do: 1 defp decode_symbol(?I), do: 1 defp decode_symbol(?i), do: 1 defp decode_symbol(?L), do: 1 defp decode_symbol(?l), do: 1 defp decode_symbol(?2), do: 2 defp decode_symbol(?3), do: 3 defp decode_symbol(?4), do: 4 defp decode_symbol(?5), do: 5 defp decode_symbol(?6), do: 6 defp decode_symbol(?7), do: 7 defp decode_symbol(?8), do: 8 defp decode_symbol(?9), do: 9 defp decode_symbol(?A), do: 10 defp decode_symbol(?a), do: 10 defp decode_symbol(?B), do: 11 defp decode_symbol(?b), do: 11 defp decode_symbol(?C), do: 12 defp decode_symbol(?c), do: 12 defp decode_symbol(?D), do: 13 defp decode_symbol(?d), do: 13 defp decode_symbol(?E), do: 14 defp decode_symbol(?e), do: 14 defp decode_symbol(?F), do: 15 defp decode_symbol(?f), do: 15 defp decode_symbol(?G), do: 16 defp decode_symbol(?g), do: 16 defp decode_symbol(?H), do: 17 defp decode_symbol(?h), do: 17 defp decode_symbol(?J), do: 18 defp decode_symbol(?j), do: 18 defp decode_symbol(?K), do: 19 defp decode_symbol(?k), do: 19 defp decode_symbol(?M), do: 20 defp decode_symbol(?m), do: 20 defp decode_symbol(?N), do: 21 defp decode_symbol(?n), do: 21 defp decode_symbol(?P), do: 22 defp decode_symbol(?p), do: 22 defp decode_symbol(?Q), do: 23 defp decode_symbol(?q), do: 23 defp decode_symbol(?R), do: 24 defp decode_symbol(?r), do: 24 defp decode_symbol(?S), do: 25 defp decode_symbol(?s), do: 25 defp decode_symbol(?T), do: 26 defp decode_symbol(?t), do: 26 defp decode_symbol(?V), do: 27 defp decode_symbol(?v), do: 27 defp decode_symbol(?W), do: 28 defp decode_symbol(?w), do: 28 defp decode_symbol(?X), do: 29 defp decode_symbol(?x), do: 29 defp decode_symbol(?Y), do: 30 defp decode_symbol(?y), do: 30 defp decode_symbol(?Z), do: 31 defp decode_symbol(?z), do: 31 end