defmodule Druuid do use Bitwise epoch = {{1970, 1, 1}, {0, 0, 0}} @epoch :calendar.datetime_to_gregorian_seconds(epoch) @doc """ This function generates a druuid id given an optional epoch_offset. ## Parameters - `epoch_offset`: (optional) integer value that offsets the 1970 epoch. Defaults to `0`. """ @spec gen(integer) :: integer def gen(epoch_offset \\ 0) do gen_from_values(epoch_offset, uniform, timestamp) end @doc """ This function determinstically generates the druuid id from the variables given. Unless you have a specific reason to override one of these, you probably want to use the Druuid.gen/1 function. ## Parameters - `epoch_offset`: integer value that offsets the 1970 epoch. Defaults to `0`. - `rand`: float value representing a random sample b/w 0 and 1 from a uniform distribution. - `ts`: integer timestamp representing the current time in seconds from the epoch. ## Examples iex> Druuid.gen_from_values(0, 0.0, 1) 8_388_608_000 """ @spec gen_from_values(integer, float, integer) :: integer def gen_from_values(epoch_offset, rand, ts) do ms = (ts - epoch_offset) * 1.0e+3 |> round rand = rand * 1.0e+16 |> round id = ms <<< (64 - 41) v = :math.pow(2, (64 - 41)) |> round id ||| rem(rand, v) end # Returns a uniform random number b/w 0.0 and 1.0. # This should be changed to only seed the rng 1 time defp uniform do << a :: 32, b :: 32, c :: 32 >> = :crypto.rand_bytes(12) :random.seed(a,b,c) :random.uniform end # Returns an integer representing the seconds since the epoch. defp timestamp do :calendar.universal_time |> :calendar.datetime_to_gregorian_seconds |> -(@epoch) end end