# `Localize.Inputs.Date.Validator`
[🔗](https://github.com/elixir-localize/localize_datetime_inputs/blob/v0.1.1/lib/localize/inputs/date/validator.ex#L1)

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`.

# `validate_date`

```elixir
@spec validate_date(term(), Keyword.t()) ::
  :ok | {:error, Localize.Inputs.ValidationError.t()}
```

Validates a parsed `t:Date.t/0` against bounds, weekday
restrictions, and required-ness.

### Arguments

* `value` is a `t:Date.t/0` or `nil`.

* `options` is a keyword list of options.

### Options

* `:required` — when `true`, `nil` is rejected.

* `:min` — minimum allowed date (`Date` or ISO-8601 string).

* `:max` — maximum allowed date.

* `:not_weekend` — when `true`, rejects Saturday and Sunday.
  To customise which weekdays count as "weekend" per
  locale, pass `:weekend_days` as 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` — when `true`, rejects any date
  falling on the locale's weekend (per `:weekend_days` or
  the default `[6, 7]` = Sat/Sun). Equivalent to
  `:not_weekend` but with the more business-domain
  naming. Future extensions may also reject locale-aware
  public holidays via a `:holidays` option (not yet
  wired).

### Returns

* `:ok` when 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"}]

# `validate_date_range`

```elixir
@spec validate_date_range(term(), Keyword.t()) ::
  :ok | {:error, Localize.Inputs.ValidationError.t()}
```

Validates a parsed `t:Date.Range.t/0` against bounds, span,
weekend restrictions, and required-ness.

### Arguments

* `value` is a `t:Date.Range.t/0` or `nil`.

* `options` is a keyword list of options.

### Options

* `:required` — when `true`, `nil` is 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` — when `true`, rejects descending
  ranges. The range parser already rejects inverted ranges
  by default; this is here for direct validator use.

### Returns

* `:ok` when 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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
