Server-side validation for parsed date and date-range values.
Pure Elixir, no Ecto dependency. The Ecto changeset bridge is
in Localize.Inputs.Date.Changeset.
Summary
Functions
Validates a parsed Date.t/0 against bounds, weekday
restrictions, and required-ness.
Validates a parsed Date.Range.t/0 against bounds, span,
weekend restrictions, and required-ness.
Functions
@spec validate_date(term(), Keyword.t()) :: :ok | {:error, Localize.Inputs.ValidationError.t()}
Validates a parsed Date.t/0 against bounds, weekday
restrictions, and required-ness.
Arguments
valueis aDate.t/0ornil.optionsis a keyword list of options.
Options
:required— whentrue,nilis rejected.:min— minimum allowed date (Dateor ISO-8601 string).:max— maximum allowed date.:not_weekend— whentrue, rejects Saturday and Sunday. To customise which weekdays count as "weekend" per locale, pass:weekend_daysas a list of 1..7 (1 = Monday).:on_or_after— alias for:min. When both are given, the stricter (later) bound wins.:on_or_before— alias for:max. When both are given, the stricter (earlier) bound wins.:business_days_only— whentrue, rejects any date falling on the locale's weekend (per:weekend_daysor the default[6, 7]= Sat/Sun). Equivalent to:not_weekendbut with the more business-domain naming. Future extensions may also reject locale-aware public holidays via a:holidaysoption (not yet wired).
Returns
:okwhen every check passes.{:error, %Localize.Inputs.ValidationError{errors: [{atom(), String.t()}]}}with one entry per failing check, in the order:required,:min,:max,:weekend.
Examples
iex> Localize.Inputs.Date.Validator.validate_date(~D[2026-05-16], min: ~D[2026-01-01])
:ok
iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
...> Localize.Inputs.Date.Validator.validate_date(~D[2025-01-01], min: ~D[2026-01-01])
iex> Keyword.get(errors, :min) =~ "2026-01-01"
true
iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
...> Localize.Inputs.Date.Validator.validate_date(nil, required: true)
iex> errors
[{:required, "is required"}]
@spec validate_date_range(term(), Keyword.t()) :: :ok | {:error, Localize.Inputs.ValidationError.t()}
Validates a parsed Date.Range.t/0 against bounds, span,
weekend restrictions, and required-ness.
Arguments
valueis aDate.Range.t/0ornil.optionsis a keyword list of options.
Options
:required— whentrue,nilis rejected.:min,:max— bounds that both endpoints must satisfy.:min_span— minimum span in days (inclusive of both endpoints, so~D[2026-01-01]..~D[2026-01-03]has span 3).:max_span— maximum span in days.:disallow_inverted— whentrue, rejects descending ranges. The range parser already rejects inverted ranges by default; this is here for direct validator use.
Returns
:okwhen every check passes.{:error, ValidationError.t()}with errors keyed by:required,:min,:max,:min_span,:max_span,:inverted.
Examples
iex> range = Date.range(~D[2026-05-01], ~D[2026-05-07])
iex> Localize.Inputs.Date.Validator.validate_date_range(range, min_span: 5, max_span: 10)
:ok
iex> range = Date.range(~D[2026-05-01], ~D[2026-05-03])
iex> {:error, %Localize.Inputs.ValidationError{errors: errors}} =
...> Localize.Inputs.Date.Validator.validate_date_range(range, min_span: 5)
iex> Keyword.get(errors, :min_span) =~ "5"
true