Skip to main content

lib/date_time_tools.ex

defmodule WarframeWorldstateDataElixirTools.DateTimeTools do
  @seconds_in_day 86_400
  @days_in_week 7

  @doc """
  Returns a tuple with the start and end of the day from an input timestamp.
  All timestamps are assumed to be UTC.

  ## Examples

      iex> dt = ~U[2024-01-15 14:30:00Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.daily_reset(dt)
      {~U[2024-01-15 00:00:00Z], ~U[2024-01-15 23:59:59Z]}

  """
  def daily_reset(now) when is_struct(now) do
    start_of_day = %{now | hour: 0, minute: 0, second: 0, microsecond: {0, 0}}
    end_of_day = DateTime.add(start_of_day, @seconds_in_day - 1, :second)
    {start_of_day, end_of_day}
  end

  @doc """
  Returns a tuple with the start of Monday and end of Sunday for the week
  containing the input timestamp. All timestamps are assumed to be UTC.

  ## Examples

      iex> dt = ~U[2024-01-17 14:30:00Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.weekly_reset(dt)
      {~U[2024-01-15 00:00:00Z], ~U[2024-01-21 23:59:59Z]}

      iex> dt = ~U[2024-01-15 00:00:00Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.weekly_reset(dt)
      {~U[2024-01-15 00:00:00Z], ~U[2024-01-21 23:59:59Z]}

      iex> dt = ~U[2024-01-21 23:59:59Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.weekly_reset(dt)
      {~U[2024-01-15 00:00:00Z], ~U[2024-01-21 23:59:59Z]}

  """
  def weekly_reset(now) when is_struct(now) do
    day_of_week = Date.day_of_week(now)

    start_of_week =
      now
      |> DateTime.add(-(day_of_week - 1) * @seconds_in_day, :second)
      |> then(&%{&1 | hour: 0, minute: 0, second: 0, microsecond: {0, 0}})

    end_of_week =
      DateTime.add(start_of_week, @days_in_week * @seconds_in_day - 1, :second)

    {start_of_week, end_of_week}
  end

  @doc """
  Returns milliseconds from now to `timestamp` (`timestamp - now`).
  Positive = future, negative = past.

  ## Examples

      iex> now = ~U[2024-01-15 12:00:00Z]
      iex> ts = ~U[2024-01-15 12:00:05Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.from_now(ts, fn -> now end)
      5000

      iex> now = ~U[2024-01-15 12:00:00Z]
      iex> ts = ~U[2024-01-15 11:59:55Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.from_now(ts, fn -> now end)
      -5000

  """
  def from_now(timestamp, now_fn \\ &DateTime.utc_now/0)
      when is_struct(timestamp) do
    DateTime.diff(timestamp, now_fn.(), :millisecond)
  end

  @doc """
  Returns milliseconds from `timestamp` to now (`now - timestamp`).
  Positive = past, negative = future.

  ## Examples

      iex> now = ~U[2024-01-15 12:00:00Z]
      iex> ts = ~U[2024-01-15 11:59:55Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.to_now(ts, fn -> now end)
      5000

      iex> now = ~U[2024-01-15 12:00:00Z]
      iex> ts = ~U[2024-01-15 12:00:05Z]
      iex> WarframeWorldstateDataElixirTools.DateTimeTools.to_now(ts, fn -> now end)
      -5000

  """
  def to_now(timestamp, now_fn \\ &DateTime.utc_now/0)
      when is_struct(timestamp) do
    DateTime.diff(now_fn.(), timestamp, :millisecond)
  end
end