A range of values of an inner type, held as an Ash.Range.
attribute :stay, Ash.Type.Range, constraints: [inner_type: :date]Inner types
| inner type | precision | resolution | without a resolution |
|---|---|---|---|
:integer | 1 | a positive integer | cast to [) |
:date | a day | a Duration of days or longer | cast to [) |
:naive_datetime | a second | a Duration | kept as given |
:datetime | a second, or a microsecond with precision: :microsecond | a Duration | kept as given |
The precision is the smallest difference between two values. Cast to inclusive-exclusive,
[), [1, 3] becomes [1, 4); kept as given, (a, b] stays (a, b]. With a
resolution, every range is cast to [).
Constraints
:inner_type- Required. The type of the range's values: one of:integer,:date,:naive_datetimeor:datetime, or anAsh.Type.NewTypeof one, such as:utc_datetime_usec. Values are ordered the same way in Elixir and in the data layer. Each has a precision: see Inner types, above.:inner_constraints(keyword/0) - Constraints of the inner type, applied to each bound, limit and anchor. The default value is[].:lower(keyword/0) - Constraints on the range's lower bound, which isnilwhen the range is unbounded below. The default value is[].:required?(boolean/0) - The range must not be unbounded below. The default value isfalse.:inclusive?(boolean/0) -truerequires an inclusive lower bound,falsean exclusive one. A range unbounded below passes either. An:integeror:daterange, or one with aresolution, is cast to[), so onlytrueis allowed.:limit(term/0) - The least value the lower bound may take:limit: ~D[2025-07-01]keeps a range from starting before 1 July 2025. The range cannot then be unbounded below. Written as the lower bound,:startis this limit, or unbounded without one, so[:start, :end)is every value between the limits. With aresolution, it must be on the grid.
:upper(keyword/0) - Constraints on the range's upper bound, which isnilwhen the range is unbounded above. The default value is[].:required?(boolean/0) - The range must not be unbounded above. The default value isfalse.:inclusive?(boolean/0) -truerequires an inclusive upper bound,falsean exclusive one. A range unbounded above passes either. An:integeror:daterange, or one with aresolution, is cast to[), so onlyfalseis allowed.:limit(term/0) - The greatest value the upper bound may take:limit: ~D[2026-07-01]keeps a range from ending after 1 July 2026. An inclusive upper bound must be below it. The range cannot then be unbounded above. Written as the upper bound,:endis this limit, or unbounded without one. With aresolution, it must be on the grid.
:allow_empty?(boolean/0) - Allow an empty range, one holding no values (seeAsh.Range.empty/0). The default value isfalse.:resolution(term/0) - The length of a period: a positiveDuration, or a positive integer for an:integerrange.Duration.new!(week: 2)divides a:daterange into fortnights. It is a whole number of the precision, in months and years or in weeks and shorter, not both.
The periods' boundaries are the grid, and the range's bounds and limits must be on it. A range with a resolution is cast to[). Without one, a period is one unit of the precision.:anchor(term/0) - A value on the grid, aligning the periods, within the limits or not:~D[2025-06-23]starts fortnights on that Monday. Without one, the grid starts at0or at0001-01-01T00:00:00Z, so weeks start on Monday. Required for a resolution in months or years, on the 28th of a month or earlier.
ADateTimein a time zone aligns periods of a day or longer to its time of day in that zone, so a day is 23, 24 or 25 hours long. Its time of day must fall in no gap or overlap of the zone.
PostgreSQL
ash_postgres stores a range as a native range: :integer as int8range, :date as
daterange, :naive_datetime as tsrange and :datetime as tstzrange.
Summary
Functions
The inner type, looking through any Ash.Type.NewType.
Returns the period containing value. Takes constraints from Ash.Type.init/2.
Returns the length of a period: the resolution, or one unit of the precision without
one. Takes constraints from Ash.Type.init/2.
Functions
@spec base_type(Ash.Type.t()) :: Ash.Type.t()
The inner type, looking through any Ash.Type.NewType.
@spec period(term(), Keyword.t()) :: {:ok, Ash.Range.t()} | {:error, String.t()}
Returns the period containing value. Takes constraints from Ash.Type.init/2.
iex> {:ok, constraints} =
...> Ash.Type.init(Ash.Type.Range,
...> inner_type: :date,
...> resolution: Duration.new!(week: 2),
...> anchor: ~D[2025-06-23]
...> )
iex> Ash.Type.Range.period(~D[2025-06-25], constraints)
{:ok, %Ash.Range{lower: ~D[2025-06-23], upper: ~D[2025-07-07], bounds: :"[)"}}
@spec resolution(Keyword.t()) :: Duration.t() | pos_integer()
Returns the length of a period: the resolution, or one unit of the precision without
one. Takes constraints from Ash.Type.init/2.
iex> {:ok, constraints} = Ash.Type.init(Ash.Type.Range, inner_type: :utc_datetime)
iex> Ash.Type.Range.resolution(constraints)
%Duration{second: 1}