Current section
Files
Jump to
Current section
Files
lib/druuid.ex
defmodule Druuid do
@moduledoc """
Druuid is used to generate datetime relative uuids that fit into a
postgres bigint. It does this by reserving the higher order bits for
the datetime and the lower order bits for some entropy.
## Getting Started
The primary function is `Druuid.gen/1`. This will return a new uuid
based on the 1970 epoch:
```elixir
Druuid.gen() #=> 12283551831556210035
```
You may wish to choose an epoch offset to make this number smaller if
you don't need to worry about dates before a certain point. `Druuid.epoch_offset/1`
allows you to pass in an erlang datetime tuple as the offset point and `Druuid.gen/1`
can take this as an optional argument. For example, consider using Jan 1st, 2016 at midnight
as an anchor point:
```elixir
Druuid.epoch_offset({{2016,1,1},{0,0,0}})
|> Druuid.gen #=> 106596357117955988
```
"""
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 calculates an epoch offset from the given datetime
## Parameters
- `offset_datetime` erlang datetime tuple from which to calculate the offset
## Examples
iex> Druuid.epoch_offset({{2016, 1, 1}, {0, 0, 0}})
1451606400
"""
@spec epoch_offset(Tuple) :: integer
def epoch_offset(offset_datetime) do
offset_datetime
|> :calendar.datetime_to_gregorian_seconds
|> -(@epoch)
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)
8388608000
"""
@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.
defp uniform do
: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