A canonical, typed representation of an RFC 5545 RRULE.
This is the input to Tempo.RRule.Expander.expand/3. Every
parser (Tempo.RRule.parse/2, the ical library's
%ICal.Recurrence{}, hand-built rules for tests) converts to
this shape before expansion runs. Keeping a single struct means
the expander has one input vocabulary to match against, and all
BY* fields carry their RFC-typed form: lists of integers with
negative values permitted where the RFC allows.
Fields
:freq— one of:second,:minute,:hour,:day,:week,:month,:year. Required.:interval— positive integer multiplier for the frequency. Default1.:count— terminate after this many occurrences. Mutually exclusive with:untilper RFC 5545.:until— terminate at or before thisTempo.t/0. Mutually exclusive with:count.:wkst— week-start day as an integer 1–7 (Monday–Sunday, ISO convention). Default1. Affects:byweeknocalculations.:bymonth— list of integers 1–12. Limit.:bymonthday— list of integers -31..31 (negatives count from the end of the month). Limit or expand per FREQ.:byyearday— list of integers -366..366. Limit or expand per FREQ.:byweekno— list of integers -53..53. Used only with:yearlyFREQ. Limit or expand.:byday— list of{ordinal_or_nil, weekday_1_to_7}tuples. With MONTHLY / YEARLY and no BYWEEKNO, ordinals select the Nth/-Nth weekday within the period. With other FREQs, ordinals are ignored (filter only).:byhour/:byminute/:bysecond— lists of integers. Limit or expand per FREQ.:bysetpos— list of integers; applied last to pick the Nth element of the per-period candidate set.:byyear— list of absolute years. A non-standard extension (RFC 5545 has noBYYEAR); used byTempo.Cronto honour a multi-year cron field such as2025,2027,2029. The expander materialises the cadence up to the last listed year and keeps only occurrences whose year is in the list.:bymonthday_nearest— list ofpos_integer()days or the atom:last. A non-standard extension carrying the cronW(nearest-weekday) modifier:15Wbecomes[15],LWbecomes[:last]. Each target snaps to the nearest weekday within the same month (Saturday → Friday, Sunday → Monday), never crossing a month boundary, so1Won a Saturday lands on the 3rd.:bymonthday_or_byday— a{monthdays, byday_entries}tuple. A non-standard extension carrying POSIX cron's day-of-month OR day-of-week union: when both fields are restricted (13 * 5— "the 13th or any Friday"), a candidate is kept if its day is inmonthdaysor its weekday matchesbyday_entries. Unlike the AND-composing:bymonthday+:byday, which would select only Friday-the-13ths.
Summary
Functions
Does the rule include any BY* modifier?
Project a rule's BY* filters onto the %Tempo{} selection carried in a
recurring interval's :repeat_rule.
Types
@type frequency() :: :second | :minute | :hour | :day | :week | :month | :year
@type t() :: %Tempo.RRule.Rule{ byday: [byday_entry()] | nil, byhour: [non_neg_integer()] | nil, byminute: [non_neg_integer()] | nil, bymonth: [integer()] | nil, bymonthday: [integer()] | nil, bymonthday_nearest: [pos_integer() | :last] | nil, bymonthday_or_byday: {[integer()], [byday_entry()]} | nil, bysecond: [non_neg_integer()] | nil, bysetpos: [integer()] | nil, byweekno: [integer()] | nil, byyear: [integer()] | nil, byyearday: [integer()] | nil, count: pos_integer() | nil, freq: frequency(), interval: pos_integer(), until: Tempo.t() | nil, wkst: weekday() }
@type weekday() :: 1..7
Functions
Does the rule include any BY* modifier?
true when any BY* field is non-nil. Used by the expander to
skip the BY-rule pipeline entirely for simple FREQ-only rules.
Examples
iex> Tempo.RRule.Rule.has_by_rules?(%Tempo.RRule.Rule{freq: :weekly})
false
iex> Tempo.RRule.Rule.has_by_rules?(%Tempo.RRule.Rule{freq: :weekly, byday: [{nil, 1}]})
true
Project a rule's BY* filters onto the %Tempo{} selection carried in a
recurring interval's :repeat_rule.
This is the single source of truth for the RRULE/cron → selection mapping:
both Tempo.RRule.parse/2 (over a parsed keyword list) and
Tempo.RRule.Expander (over a %Rule{}) build their selection here, so the
token vocabulary above cannot drift between the two paths.
The BY* filters are pushed coarsest-to-finest and the list is consolidated
(consecutive integers collapse to ranges) so the selection serialises to a
re-parseable ISO 8601 form. byday precedes the time elements because a
weekday after T…H…M is out of resolution order and will not round-trip.
Arguments
ruleis at/0.
Returns
- A
%Tempo{}carrying[selection: …], ornilwhen the rule has noBY*filter and a defaultWKST(the simple recurrence needs no repeat rule).
Examples
iex> Tempo.RRule.Rule.to_selection(%Tempo.RRule.Rule{freq: :monthly, byday: [{2, 1}]})
~o"L2I1KN"
iex> Tempo.RRule.Rule.to_selection(%Tempo.RRule.Rule{freq: :daily})
nil