defmodule Cldr.Calendar.Config do
@moduledoc """
Defines the configuration for a calendar.
See `Cldr.Calendar.new/3` for usage details.
"""
defstruct calendar: nil,
# A default backend for this
# calendar
cldr_backend: nil,
# Each quarter has three
# 'months` each of 13 weeks
# in either of a 4,4,5; 4,5,4
# of 5,4,4 layout
weeks_in_month: [4, 4, 5],
# Indicates if the anchor
# represents the beginning
# of the year or the end
begins_or_ends: :begins,
# Calendar begins on the
# :first, :last or :nearest
first_or_last: :first,
# Year begins on this day
# Use :first to mean the day
# day of the week on which the
# first day of the year occurs
# The functions `Cldr.Calendar.monday()`
# etc can be used
day_of_week: 1,
# Year begins in this Gregorian month
month_of_year: 1,
# The year of the last_day or first_day
# is either the year with the :majority
# of months or the :beginning year
# or :ending year
year: :majority,
# First week has at least
# this many days in current
# year
min_days_in_first_week: 1
@typedoc """
Defines the struct type for a calendar configuration
"""
@type t() :: %__MODULE__{
calendar: atom(),
cldr_backend: Cldr.backend() | nil,
weeks_in_month: list(pos_integer()),
begins_or_ends: :begins | :ends,
first_or_last: :first | :last,
day_of_week: Cldr.Calendar.day_of_week(),
month_of_year: pos_integer(),
year: :majority | :starts | :ends,
min_days_in_first_week: 1..7
}
@doc false
def extract_options(options) do
invalidate_old_options!(options)
detect_invalid_options!(options)
default_backend = Application.get_env(Cldr.Config.app_name(), :default_backend)
backend = Keyword.get(options, :backend, default_backend)
backend = if backend && Cldr.Code.ensure_compiled?(backend), do: backend, else: nil
%__MODULE__{
calendar: Keyword.get(options, :calendar),
cldr_backend: backend,
min_days_in_first_week: min_days_for_locale(backend, options),
day_of_week: first_day_for_locale(backend, options),
year: Keyword.get(options, :year, :majority),
month_of_year: Keyword.get(options, :month_of_year, 1),
first_or_last: Keyword.get(options, :first_or_last, :first),
begins_or_ends: Keyword.get(options, :begins_or_ends, :begins),
weeks_in_month: Keyword.get(options, :weeks_in_month, [4, 5, 4])
}
end
def min_days_for_locale(nil, options) do
default =
if locale = Keyword.get(options, :locale) do
Cldr.Calendar.min_days_for_locale(locale)
else
1
end
Keyword.get(options, :min_days_in_first_week, default)
end
def min_days_for_locale(backend, options) do
locale = Keyword.get(options, :locale, backend.get_locale())
Keyword.get(options, :min_days_in_first_week, Cldr.Calendar.min_days_for_locale(locale))
end
def first_day_for_locale(nil, options) do
default =
if locale = Keyword.get(options, :locale) do
Cldr.Calendar.first_day_for_locale(locale)
else
1
end
Keyword.get(options, :day_of_week, default)
end
def first_day_for_locale(backend, options) do
locale = Keyword.get(options, :locale, backend.get_locale())
Keyword.get(options, :day_of_week, Cldr.Calendar.first_day_for_locale(locale))
end
@valid_weeks_in_month [[4, 4, 5], [4, 5, 4], [5, 4, 4]]
@valid_year [:majority, :beginning, :ending]
@doc false
def validate_config(config, calendar_type) do
with :ok <-
validate_day(config, calendar_type),
:ok <-
assert(config.month_of_year in 1..12, month_error(config.month_of_year)),
:ok <-
assert(config.year in @valid_year, year_error(config.year)),
:ok <-
assert(
config.min_days_in_first_week in 1..7,
min_days_for_locale_error(config.min_days_in_first_week)
),
:ok <-
assert(
config.first_or_last in [:first, :last],
first_or_last_error(config.first_or_last)
),
:ok <-
assert(
config.begins_or_ends in [:begins, :ends],
begins_or_ends_error(config.begins_or_ends)
),
:ok <-
assert(
config.weeks_in_month in @valid_weeks_in_month,
weeks_in_month_error(config.weeks_in_month)
) do
{:ok, config}
end
end
@doc false
def validate_config!(config, calendar_type) do
case validate_config(config, calendar_type) do
{:ok, config} -> config
{:error, message} -> raise ArgumentError, message
end
end
defp invalidate_old_options!(options) do
if options[:day],
do: raise(ArgumentError, "Option :day is replaced with :day_of_week")
if options[:month],
do: raise(ArgumentError, "Option :month is replaced with :month_of_year")
if options[:min_days],
do: raise(ArgumentError, "Option :min_days is replaced with :min_days_in_first_week")
end
defp valid_options do
%__MODULE__{}
|> Map.delete(:__struct__)
|> Map.keys()
|> List.insert_at(0, :locale)
end
defp detect_invalid_options!(options) do
case Enum.filter(options, fn {key, _} -> key not in valid_options() end) do
[] ->
options
invalid_options ->
raise ArgumentError,
"Invalid options #{inspect(invalid_options)} found. Valid options are #{
inspect(valid_options())
}"
end
end
defp validate_day(config, :week) do
assert(config.day_of_week in 1..7, day_error(config.day_of_week))
end
defp validate_day(config, :month) do
assert(
config.day_of_week in 1..7 or config.day_of_week == :first,
day_error(config.day_of_week)
)
end
defp assert(true, _) do
:ok
end
defp assert(false, message) do
{:error, message}
end
defp day_error(day) do
":day_of_week must be in the range 1..7. Found #{inspect(day)}."
end
defp month_error(month) do
":month_of_year must be in the range 1..12. Found #{inspect(month)}."
end
defp year_error(year) do
":year must be either :beginning, :ending or :majority. Found #{inspect(year)}."
end
defp min_days_for_locale_error(min_days) do
":min_days_in_first_week must be in the range 1..7. Found #{inspect(min_days)}."
end
defp first_or_last_error(first_or_last) do
":first_or_last must be :first or :last. Found #{inspect(first_or_last)}."
end
defp begins_or_ends_error(begins_or_ends) do
":begins_or_ends must be :begins or :ends. Found #{inspect(begins_or_ends)}."
end
defp weeks_in_month_error(weeks_in_month) do
":weeks_in_month must be [4,4,5], [4,5,4] or [5,4,4]. Found #{inspect(weeks_in_month)}"
end
end