The rules of a picker: which days, ranges and times a person may choose.
The same rules go to the component, which disables what they refuse in
the browser, and to Koyomi.Changeset, which refuses it on the server.
So the two never disagree:
@rules Koyomi.Rules.new(min: :today, disabled_weekdays: [6, 7], max_days: 14)
<.date_range_picker field={@form[:stay]} rules={@rules} />
changeset
|> cast(params, [:stay])
|> Koyomi.Changeset.validate_range(:stay, @rules)Options
:min,:max- the first and the last day that may be chosen: aDate,:today, or{:today, days}for a day relative to today ({:today, 1}is tomorrow). For a date and time picker, also aDateTimeor aNaiveDateTime.:disabled_dates- days that may not be chosen (a list ofDate).:disabled_ranges- spans of days that may not be chosen (a list ofDate.Rangewith a step of 1, or{first, last}tuples).:disabled_weekdays- weekdays that may not be chosen, ISO numbers from 1 (Monday) to 7 (Sunday).:min_days,:max_days- the shortest and the longest range, in days, both ends counted: a range from Monday to Wednesday has 3 days.:allow_disabled_in_range- whentrue, a range may span disabled days (a stay that may cross a closed day).falseby default: a range that holds a disabled day is refused.:min_count,:max_count- the fewest and the most days of a multiple-date picker.:min_time,:max_time- the earliest and the latest time of day (Time), for the time and date and time pickers.:step- the minutes between the times that may be chosen, counted from midnight: with15, 09:15 may be chosen and 09:20 may not.:time_zone- the IANA time zone of:today("Etc/UTC"by default). A zone other than UTC needs a time zone database (seeCalendar).
:today is read again on each check and each render, so rules built
once (in a module attribute) stay right the next day.
Summary
Functions
Returns true when the day itself may be chosen: on or after :min,
on or before :max, and not disabled.
Checks a day. Returns :ok or {:error, error} (see error/0).
Checks the days of a multiple-date picker: each day, and the count.
Checks a date and time: its day as check_date/2 does, its time as
check_time/2 does, and the bounds as instants when :min or :max
is a DateTime or a NaiveDateTime.
Checks a range of days, both ends included: each end must be allowed,
the length must be within :min_days and :max_days, and no day inside
may be disabled unless :allow_disabled_in_range.
Checks a time of day against :min_time, :max_time and :step.
Returns true when the day is disabled by :disabled_dates,
:disabled_ranges or :disabled_weekdays (the bounds aside).
The last day that may be chosen, with :today resolved, or nil.
Merges more rules into rules: each option given replaces the one in
rules.
The first day that may be chosen, with :today resolved, or nil.
Builds rules from a keyword list (see the options above), or returns
rules as they are. nil gives rules that allow everything.
The rules as the browser reads them: a map with JSON values, :today
resolved and the camelCase keys of the picker. Empty rules are left out.
Today in the time zone of the rules.
Types
@type bound() :: Date.t() | DateTime.t() | NaiveDateTime.t() | :today | {:today, integer()} | nil
Why a value breaks the rules, with the values that a message needs:
{:before_min, min: Date | DateTime},{:after_max, max: ...}{:disabled, date: Date}- a disabled day.{:disabled_in_range, date: Date}- a disabled day inside a range.{:too_short, min_days: n},{:too_long, max_days: n}{:too_few, min_count: n},{:too_many, max_count: n}{:time_before_min, min_time: Time},{:time_after_max, max_time: Time}{:time_step, step: minutes}{:reversed, []}- a range that ends before it starts.
@type t() :: %Koyomi.Rules{ allow_disabled_in_range: boolean(), disabled_dates: [Date.t()], disabled_ranges: [{Date.t(), Date.t()}], disabled_weekdays: [1..7], max: bound(), max_count: pos_integer() | nil, max_days: pos_integer() | nil, max_time: Time.t() | nil, min: bound(), min_count: non_neg_integer() | nil, min_days: pos_integer() | nil, min_time: Time.t() | nil, step: pos_integer() | nil, time_zone: String.t() }
Functions
Returns true when the day itself may be chosen: on or after :min,
on or before :max, and not disabled.
Examples
iex> rules = Koyomi.Rules.new(disabled_weekdays: [6, 7])
iex> Koyomi.Rules.allowed?(rules, ~D[2027-03-15])
true
iex> Koyomi.Rules.allowed?(rules, ~D[2027-03-13])
false
Checks a day. Returns :ok or {:error, error} (see error/0).
Examples
iex> rules = Koyomi.Rules.new(min: ~D[2027-03-01])
iex> Koyomi.Rules.check_date(rules, ~D[2027-02-28])
{:error, {:before_min, min: ~D[2027-03-01]}}
Checks the days of a multiple-date picker: each day, and the count.
Examples
iex> Koyomi.Rules.check_dates([max_count: 2], [~D[2027-03-01], ~D[2027-03-02], ~D[2027-03-03]])
{:error, {:too_many, max_count: 2}}
@spec check_datetime(t() | keyword(), DateTime.t() | NaiveDateTime.t()) :: :ok | {:error, error()}
Checks a date and time: its day as check_date/2 does, its time as
check_time/2 does, and the bounds as instants when :min or :max
is a DateTime or a NaiveDateTime.
A DateTime is read in the time zone of the rules: its day and its
time of day are those of the wall clock there.
@spec check_range(t() | keyword(), Date.Range.t() | {Date.t(), Date.t()}) :: :ok | {:error, error()}
Checks a range of days, both ends included: each end must be allowed,
the length must be within :min_days and :max_days, and no day inside
may be disabled unless :allow_disabled_in_range.
Examples
iex> rules = Koyomi.Rules.new(max_days: 7)
iex> Koyomi.Rules.check_range(rules, Date.range(~D[2027-03-01], ~D[2027-03-07]))
:ok
iex> Koyomi.Rules.check_range(rules, Date.range(~D[2027-03-01], ~D[2027-03-08]))
{:error, {:too_long, max_days: 7}}
Checks a time of day against :min_time, :max_time and :step.
Examples
iex> Koyomi.Rules.check_time([step: 15], ~T[09:20:00])
{:error, {:time_step, step: 15}}
Returns true when the day is disabled by :disabled_dates,
:disabled_ranges or :disabled_weekdays (the bounds aside).
The last day that may be chosen, with :today resolved, or nil.
Merges more rules into rules: each option given replaces the one in
rules.
Examples
iex> rules = Koyomi.Rules.new(min: ~D[2027-03-01])
iex> Koyomi.Rules.merge(rules, max: ~D[2027-03-31]).max
~D[2027-03-31]
The first day that may be chosen, with :today resolved, or nil.
Examples
iex> Koyomi.Rules.min_date(Koyomi.Rules.new(min: ~D[2027-03-01]))
~D[2027-03-01]
Builds rules from a keyword list (see the options above), or returns
rules as they are. nil gives rules that allow everything.
Raises ArgumentError for an unknown option or a value of the wrong
kind, so that a mistake shows when the rules are built.
Examples
iex> rules = Koyomi.Rules.new(min: ~D[2027-03-01], disabled_weekdays: [7])
iex> rules.disabled_weekdays
[7]
iex> Koyomi.Rules.new(max_days: 0)
** (ArgumentError) Koyomi.Rules: :max_days must be a positive integer, got: 0
The rules as the browser reads them: a map with JSON values, :today
resolved and the camelCase keys of the picker. Empty rules are left out.
Examples
iex> Koyomi.Rules.to_client(Koyomi.Rules.new(min: ~D[2027-03-01], disabled_weekdays: [7]))
%{"min" => "2027-03-01", "disabledWeekdays" => [7]}
Today in the time zone of the rules.
Examples
iex> Koyomi.Rules.today(Koyomi.Rules.new([])) == Date.utc_today()
true