a
|> time
|> traveller

I want to freeze time for my test. So I came up with the following:

defmodule Chronos do
  alias __MODULE__, as: Chronos

  use Agent

  @mty_tz "America/Monterrey"
  @time_format "%Y-%m-%dT%H:%M:%S"

  @spec start_link(list()) :: {:error, any()} | {:ok, pid()}
  def start_link([]) do
    Agent.start_link(
      fn ->
        %{is_frozen: false, frozen_value: nil}
      end,
      name: Chronos
    )
  end

  @spec now() :: DateTime.t()
  def now do
    get_timestamp()
    |> Timex.from_unix()
    |> shift_localtime
  catch
    :exit, {:noproc, _} ->
      Timex.now(@mty_tz)
      |> shift_localtime
  end

  @spec get_timestamp() :: integer()
  def get_timestamp do
    state = Agent.get(Chronos, fn state -> state end)

    if state[:is_frozen] do
      state[:frozen_value]
    else
      :os.system_time(:second)
    end
  end

  @spec freeze() :: atom()
  def freeze do
    freeze(:os.system_time(:second))
  end

  @spec freeze(integer()) :: atom()
  def freeze(timestamp) do
    Agent.update(Chronos, fn _state ->
      %{is_frozen: true, frozen_value: timestamp}
    end)
  end

  @spec unfreeze() :: atom()
  def unfreeze do
    Agent.update(Chronos, fn _state ->
      %{is_frozen: false, frozen_value: nil}
    end)
  end

  defp shift_localtime(date_time) do
    date_time
    |> Timex.format!(@time_format, :strftime)
    |> Timex.parse!(@time_format, :strftime)
    |> Timex.to_datetime()
  end
end

The tiny module depends on Timex. Here are some use cases:

### Within your test, define

defp freeze_time(_context) do
  # 2021-08-12 22:25:00-06:00 -06
  Chronos.freeze(1_628_828_700)
end

### And use it at your setup

setup [:freeze_time]

### Now, wherever you call for `Chronos.now()` you will get the frozen datetime.

iex(1)> Chronos.now()
~U[2021-08-13 04:25:00Z]

And now I finished this tiny example.


—


to go back