lib/azan/time_component.ex

defmodule Azan.TimeComponent do
  @moduledoc """
  Documentation for `TimeComponent`.
  """

  use TypedStruct

  typedstruct do
    field :hour, integer()
    field :minute, integer()
    field :second, integer()
  end

  @doc ~S"""
  Create a new `TimeComponent` from an hour with fractional part.

       ## Examples

        iex> TimeComponent.new(12.5)
        %TimeComponent{hour: 12, minute: 30, second: 0}
  """
  def new({:error, _}) do
    {:error, :invalid_time_component}
  end

  def new(num) do
    hour = num |> floor()
    minute = ((num - hour) * 60) |> floor()
    second = ((num - hour - minute / 60) * 3600) |> floor()
    %__MODULE__{hour: hour, minute: minute, second: second}
  end

  @doc ~S"""
  Convert a `TimeComponent` to a string in the format of "HH:MM".

       ## Examples

        iex> TimeComponent.new(12.5) |> TimeComponent.to_hm_string()
        "12:30"
  """
  def to_hm_string(%__MODULE__{hour: hour, minute: minute, second: second}) do
    hour_string = hour |> Integer.to_string()

    minute_string =
      (minute + round(second / 60)) |> Integer.to_string() |> String.pad_leading(2, "0")

    hour_string <> ":" <> minute_string
  end

  @doc ~S"""
  Create a DateTime from `TimeComponent`

       ## Examples

        iex> TimeComponent.new(12.5) |> TimeComponent.create_utc_datetime(2015, 1, 1)
        ~U[2015-01-01 12:30:00Z]
  """

  def create_utc_datetime({:error, _reason}, _date), do: {:error, :invalid_date}

  def create_utc_datetime(%__MODULE__{hour: hour, minute: minute, second: second}, %Date{
        year: year,
        month: month,
        day: day
      }) do
    create_utc_datetime(
      %__MODULE__{hour: hour, minute: minute, second: second},
      year,
      month,
      day
    )
  end

  def create_utc_datetime({:error, _reason}, _year, _month, _day), do: {:error, :invalid_date}

  def create_utc_datetime(
        %__MODULE__{hour: hour, minute: minute, second: second},
        year,
        month,
        day
      )
      when hour > 23 do
    day_offset = hour |> div(24)
    hour = hour |> rem(24)

    create_utc_datetime(
      %__MODULE__{hour: hour, minute: minute, second: second},
      year,
      month,
      day
    )
    |> Timex.shift(days: day_offset)
  end

  def create_utc_datetime(
        %__MODULE__{hour: hour, minute: minute, second: second},
        year,
        month,
        day
      )
      when hour < 0 and hour > -23 do
    hour = hour + 24

    create_utc_datetime(
      %__MODULE__{hour: hour, minute: minute, second: second},
      year,
      month,
      day
    )
    |> Timex.shift(days: -1)
  end

  def create_utc_datetime(
        %__MODULE__{hour: hour, minute: minute, second: second},
        year,
        month,
        day
      )
      when hour < -23 do
    day_offset = hour |> div(24)
    hour = hour + 24

    create_utc_datetime(
      %__MODULE__{hour: hour, minute: minute, second: second},
      year,
      month,
      day
    )
    |> Timex.shift(days: day_offset)
  end

  def create_utc_datetime(
        %__MODULE__{hour: hour, minute: minute, second: second},
        year,
        month,
        day
      ) do
    {{year, month, day}, {hour, minute, second}} |> Timex.to_datetime("UTC")
  end
end