defmodule Azan.PolarCircleResolution do
@moduledoc """
Documentation for `PolarCircleResolution`.
"""
@latitude_variation_step 0.5
@unsafe_location 65
alias Azan.{
Coordinate,
DateUtils,
MathUtils,
SolarTime
}
require Logger
use TypedStruct
typedstruct do
field :date, Date.t()
field :tomorrow, Date.t()
field :coordinate, Coordinate.t()
field :solar_time, SolarTime.t()
field :tomorrow_solar_time, SolarTime.t()
end
def is_valid_solar_time(%SolarTime{sunrise: sunrise, sunset: sunset})
when is_number(sunrise) and is_number(sunset),
do: true
def is_valid_solar_time(%SolarTime{}), do: false
def aqrab_yaum_resolver(
coordinate,
date,
days_added \\ 1,
direction \\ 1
)
def aqrab_yaum_resolver(_coordinate, _date, days_added, _direction)
when days_added > ceil(365 / 2),
do: nil
def aqrab_yaum_resolver(
%Coordinate{} = coordinate,
%Date{} = date,
days_added,
direction
) do
with {:ok, epoch_date} <-
date |> Timex.shift(days: days_added * direction) |> DateUtils.wrap_timex_error(),
{:ok, tomorrow} <- epoch_date |> Timex.shift(days: 1) |> DateUtils.wrap_timex_error() do
solar_time = SolarTime.new(epoch_date, coordinate)
tomorrow_solar_time = SolarTime.new(tomorrow, coordinate)
case is_valid_solar_time(solar_time) && is_valid_solar_time(tomorrow_solar_time) do
true ->
%__MODULE__{
date: date,
tomorrow: tomorrow,
coordinate: coordinate,
solar_time: solar_time,
tomorrow_solar_time: tomorrow_solar_time
}
_ ->
days_offset = if direction > 0, do: 0, else: 1
days_added = days_added + days_offset
aqrab_yaum_resolver(coordinate, date, days_added, -direction)
end
else
{:error, reason} -> raise reason
end
end
def aqrab_balad_resolver(%Coordinate{} = coordinate, %Date{} = date, latitude) do
new_coordinate = %{coordinate | latitude: latitude}
solar_time = date |> SolarTime.new(new_coordinate)
tomorrow = date |> Timex.shift(days: 1)
tomorrow_solar_time = tomorrow |> SolarTime.new(new_coordinate)
case is_valid_solar_time(solar_time) && is_valid_solar_time(tomorrow_solar_time) do
true ->
%__MODULE__{
date: date,
tomorrow: tomorrow,
coordinate: new_coordinate,
solar_time: solar_time,
tomorrow_solar_time: tomorrow_solar_time
}
false ->
if abs(latitude) < @unsafe_location,
do: nil,
else:
aqrab_balad_resolver(
coordinate,
date,
latitude - MathUtils.sign(latitude) * @latitude_variation_step
)
end
end
def polar_circle_resolved_values(:aqrab_yaum, %Date{} = date, %Coordinate{} = coordinate) do
resolved = coordinate |> aqrab_yaum_resolver(date)
default = default_polar_circle_resolve_values(date, coordinate)
resolved || default
end
def polar_circle_resolved_values(
:aqrab_balad,
%Date{} = date,
%Coordinate{latitude: latitude} = coordinate
) do
coordinate
|> aqrab_balad_resolver(date, latitude - MathUtils.sign(latitude) * @latitude_variation_step) ||
default_polar_circle_resolve_values(date, coordinate)
end
def polar_circle_resolved_values(:unresolved, %Date{} = date, %Coordinate{} = coordinate) do
tomorrow = date |> Timex.shift(days: 1)
%__MODULE__{
date: date,
tomorrow: tomorrow,
coordinate: coordinate,
solar_time: SolarTime.new(date, coordinate),
tomorrow_solar_time: SolarTime.new(tomorrow, coordinate)
}
end
def default_polar_circle_resolve_values(date, coordinate) do
:unresolved |> polar_circle_resolved_values(date, coordinate)
end
end