defmodule Holidefs do
@moduledoc """
Holidefs is a holiday OTP application for multiple locales that loads the
dates from definition files on the startup.
"""
alias Holidefs.Definition
alias Holidefs.Definition.Store
alias Holidefs.Holiday
alias Holidefs.Options
@type error_reasons :: :no_def | :invalid_date
@type locale_code :: atom | binary
@all_locales %{
at: "Austria",
au: "Australia",
be: "Belgium",
bg: "Bulgaria",
br: "Brazil",
ca: "Canada",
ch: "Switzerland",
co: "Colombia",
cy: "Cyprus",
cz: "Czech Republic",
de: "Germany",
dk: "Denmark",
ee: "Estonia",
es: "Spain",
fi: "Finland",
fr: "France",
gb: "United Kingdom",
ge: "Georgia",
hr: "Croatia",
hu: "Hungary",
ie: "Ireland",
it: "Italy",
kz: "Kazakhstan",
lt: "Lithuania",
my: "Malaysia",
mx: "Mexico",
nl: "Netherlands",
no: "Norway",
nz: "New Zealand",
ph: "Philippines",
pl: "Poland",
pt: "Portugal",
rs: "Serbia",
ru: "Russia",
se: "Sweden",
sg: "Singapore",
si: "Slovenia",
sk: "Slovakia",
us: "United States",
za: "South Africa",
fed: "Federal Reserve"
}
@locale_keys Application.compile_env(:holidefs, :locales, Map.keys(@all_locales))
@locales Map.take(@all_locales, @locale_keys)
@doc """
Returns a map of all the supported locales.
The key is the code and the value the name of the locale.
"""
@spec locales :: map
def locales, do: @locales
@doc """
Returns the language to translate the holiday names to.
"""
@spec get_language :: String.t()
def get_language do
Gettext.get_locale(Holidefs.Gettext)
end
@doc """
Sets the language to translate the holiday names to.
To use the native language names, you can set the language to `:orig`
"""
@spec set_language(atom | String.t()) :: nil
def set_language(locale) when is_atom(locale) do
locale
|> Atom.to_string()
|> set_language()
end
def set_language(locale) when is_binary(locale) do
Gettext.put_locale(Holidefs.Gettext, locale)
end
@doc """
Returns the list of regions from the given locale.
If succeed returns a `{:ok, regions}` tuple, otherwise
returns a `{:error, reason}` tuple.
"""
@spec get_regions(locale_code) :: {:ok, [String.t()]} | {:error, error_reasons}
def get_regions(locale) do
case Store.get_definition(locale) do
nil -> {:error, :no_def}
definition -> {:ok, Definition.get_regions(definition)}
end
end
@doc """
Returns all the holidays for the given locale on the given date.
If succeed returns a `{:ok, holidays}` tuple, otherwise
returns a `{:error, reason}` tuple.
"""
@spec on(locale_code, Date.t()) :: {:ok, [Holidefs.Holiday.t()]} | {:error, error_reasons}
@spec on(locale_code, Date.t(), Holidefs.Options.attrs()) ::
{:ok, [Holidefs.Holiday.t()]} | {:error, error_reasons}
def on(locale, date, opts \\ []) do
locale
|> Store.get_definition()
|> find_between(date, date, opts)
end
@doc """
Returns all the holidays for the given year.
If succeed returns a `{:ok, holidays}` tuple, otherwise
returns a `{:error, reason}` tuple
"""
@spec year(locale_code, integer) :: {:ok, [Holidefs.Holiday.t()]} | {:error, String.t()}
@spec year(locale_code, integer, Holidefs.Options.attrs()) ::
{:ok, [Holidefs.Holiday.t()]} | {:error, error_reasons}
def year(locale, year, opts \\ [])
def year(locale, year, opts) when is_integer(year) do
locale
|> Store.get_definition()
|> case do
nil ->
{:error, :no_def}
%Definition{} = definition ->
{:ok, all_year_holidays(definition, year, opts)}
end
end
def year(_, _, _) do
{:error, :invalid_date}
end
@spec all_year_holidays(Holidefs.Definition.t(), integer, Holidefs.Options.attrs()) :: [
Holidefs.Holiday.t()
]
defp all_year_holidays(
%Definition{code: code, rules: rules},
year,
%Options{include_informal?: include_informal?, regions: regions} = opts
) do
rules
|> Stream.filter(&(include_informal? or not &1.informal?))
|> Stream.filter(&(regions -- &1.regions != regions))
|> Stream.flat_map(&Holiday.from_rule(code, &1, year, opts))
|> Enum.sort_by(&Date.to_erl(&1.date))
end
defp all_year_holidays(definition, year, opts) when is_list(opts) or is_map(opts) do
all_year_holidays(definition, year, Options.build(opts, definition))
end
@doc """
Returns all the holidays for the given locale between start
and finish dates.
If succeed returns a `{:ok, holidays}` tuple, otherwise
returns a `{:error, reason}` tuple
"""
@spec between(locale_code, Date.t(), Date.t(), Holidefs.Options.attrs()) ::
{:ok, [Holidefs.Holiday.t()]} | {:error, error_reasons}
def between(locale, start, finish, opts \\ []) do
locale
|> Store.get_definition()
|> find_between(start, finish, opts)
end
defp find_between(nil, _, _, _) do
{:error, :no_def}
end
defp find_between(
definition,
%Date{} = start,
%Date{} = finish,
opts
) do
holidays =
start.year..finish.year
|> Stream.flat_map(&all_year_holidays(definition, &1, opts))
|> Stream.drop_while(&(Date.compare(&1.date, start) == :lt))
|> Enum.take_while(&(Date.compare(&1.date, finish) != :gt))
{:ok, holidays}
end
defp find_between(_, _, _, _) do
{:error, :invalid_date}
end
end