-module(automata@schedule).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/automata/schedule.gleam").
-export([from_cron/1, from_rrule/2, from_every/2, from_once/1, step/1, matches/2, iterator_after/2, next_after/2]).
-export_type([schedule_error/0, schedule/0, every_plan/0, occurrence_iterator/0, iter_step/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type schedule_error() :: {every_interval_must_be_positive, integer()} |
{invalid_r_rule_anchor, automata@schedule@ast:date_time()}.
-opaque schedule() :: {cron_schedule, automata@cron@normalize:cron_plan()} |
{r_rule_schedule, automata@rrule@normalize:r_rule_plan()} |
{every_schedule, every_plan()} |
{once_schedule, automata@schedule@ast:date_time()}.
-type every_plan() :: {every_plan, automata@schedule@ast:date_time(), integer()}.
-opaque occurrence_iterator() :: {cron_occurrences,
automata@cron@iterator:cron_iterator()} |
{r_rule_occurrences, automata@rrule@iterator:r_rule_iterator()} |
{every_occurrences, every_plan(), automata@schedule@ast:date_time()} |
{once_occurrences, automata@schedule@ast:date_time(), boolean()}.
-type iter_step() :: {yield,
automata@schedule@ast:valid_date_time(),
occurrence_iterator()} |
done.
-file("src/automata/schedule.gleam", 76).
?DOC(
" Build a `Schedule` from a validated cron expression.\n"
"\n"
" Cron schedules are infinite; iterators never return `Done` on\n"
" well-formed input. The return type is `Result` so that this\n"
" constructor shares one shape with `from_rrule`, `from_every`, and\n"
" `from_once`, letting generic helpers treat the four uniformly. The\n"
" current implementation cannot fail (a `ValidCron` cannot produce a\n"
" normalisation error), so callers can `let assert Ok(_) = ...` with\n"
" confidence.\n"
).
-spec from_cron(automata@cron@validator:valid_cron()) -> {ok, schedule()} |
{error, schedule_error()}.
from_cron(Spec) ->
{ok, {cron_schedule, automata@cron@normalize:normalize(Spec)}}.
-file("src/automata/schedule.gleam", 87).
?DOC(
" Build a `Schedule` from a validated RRULE plus an anchor.\n"
"\n"
" The anchor seeds defaults for unspecified `BYHOUR` / `BYMINUTE` /\n"
" `BYDAY` / `BYMONTH` / `BYMONTHDAY` and contributes the second\n"
" component for every yielded occurrence.\n"
).
-spec from_rrule(
automata@rrule@validator:valid_r_rule(),
automata@schedule@ast:valid_date_time()
) -> {ok, schedule()} | {error, schedule_error()}.
from_rrule(Spec, Anchor) ->
case automata@rrule@normalize:normalize(
Spec,
automata@schedule@ast:valid_datetime_value(Anchor)
) of
{ok, Plan} ->
{ok, {r_rule_schedule, Plan}};
{error, {invalid_anchor, At}} ->
{error, {invalid_r_rule_anchor, At}}
end.
-file("src/automata/schedule.gleam", 107).
?DOC(
" Build an interval `Schedule` that fires `interval_seconds` apart\n"
" starting at `anchor` (inclusive).\n"
"\n"
" Returns `EveryIntervalMustBePositive` when the interval is zero or\n"
" negative.\n"
).
-spec from_every(integer(), automata@schedule@ast:valid_date_time()) -> {ok,
schedule()} |
{error, schedule_error()}.
from_every(Interval_seconds, Anchor) ->
case Interval_seconds =< 0 of
true ->
{error, {every_interval_must_be_positive, Interval_seconds}};
false ->
{ok,
{every_schedule,
{every_plan,
automata@schedule@ast:valid_datetime_value(Anchor),
Interval_seconds}}}
end.
-file("src/automata/schedule.gleam", 130).
?DOC(
" Build a one-shot `Schedule` that fires exactly once at `at`.\n"
"\n"
" The return type is `Result` so that this constructor shares one\n"
" shape with `from_rrule`, `from_every`, and `from_cron`. The current\n"
" implementation cannot fail (the `at` argument is already a\n"
" `ValidDateTime`), so callers can `let assert Ok(_) = ...` with\n"
" confidence.\n"
).
-spec from_once(automata@schedule@ast:valid_date_time()) -> {ok, schedule()} |
{error, schedule_error()}.
from_once(At) ->
{ok, {once_schedule, automata@schedule@ast:valid_datetime_value(At)}}.
-file("src/automata/schedule.gleam", 165).
?DOC(" Advance an iterator by one occurrence.\n").
-spec step(occurrence_iterator()) -> iter_step().
step(Iterator) ->
case Iterator of
{cron_occurrences, Cursor} ->
case automata@cron@iterator:step(Cursor) of
{yield, At, Next} ->
{yield, At, {cron_occurrences, Next}};
done ->
done
end;
{r_rule_occurrences, Cursor@1} ->
case automata@rrule@iterator:step(Cursor@1) of
{yield, At@1, Next@1} ->
{yield, At@1, {r_rule_occurrences, Next@1}};
done ->
done
end;
{every_occurrences, Plan, Cursor@2} ->
{yield,
automata@schedule@ast:unsafe_assume_valid(Cursor@2),
{every_occurrences,
Plan,
automata@internal@calendar:add_seconds(
Cursor@2,
erlang:element(3, Plan)
)}};
{once_occurrences, At@2, true} ->
{yield,
automata@schedule@ast:unsafe_assume_valid(At@2),
{once_occurrences, At@2, false}};
{once_occurrences, _, false} ->
done
end.
-file("src/automata/schedule.gleam", 260).
-spec once_eligible(
automata@schedule@ast:date_time(),
automata@schedule@ast:boundary()
) -> boolean().
once_eligible(At, Boundary) ->
case Boundary of
{inclusive, Valid} ->
automata@internal@calendar:less_or_equal(
automata@schedule@ast:valid_datetime_value(Valid),
At
);
{exclusive, Valid@1} ->
automata@internal@calendar:less_than(
automata@schedule@ast:valid_datetime_value(Valid@1),
At
)
end.
-file("src/automata/schedule.gleam", 269).
-spec modulo(integer(), integer()) -> integer().
modulo(Dividend, Divisor) ->
_pipe = gleam@int:modulo(Dividend, Divisor),
gleam@result:unwrap(_pipe, 0).
-file("src/automata/schedule.gleam", 215).
-spec every_matches(every_plan(), automata@schedule@ast:date_time()) -> boolean().
every_matches(Plan, At) ->
case automata@internal@calendar:less_than(At, erlang:element(2, Plan)) of
true ->
false;
false ->
Delta = automata@internal@calendar:seconds_between(
erlang:element(2, Plan),
At
),
modulo(Delta, erlang:element(3, Plan)) =:= 0
end.
-file("src/automata/schedule.gleam", 137).
?DOC(
" Return `True` when `at` is an occurrence of `schedule`.\n"
"\n"
" Pure: same inputs always produce the same output.\n"
).
-spec matches(schedule(), automata@schedule@ast:valid_date_time()) -> boolean().
matches(Schedule, At) ->
Raw = automata@schedule@ast:valid_datetime_value(At),
case Schedule of
{cron_schedule, Plan} ->
automata@cron@evaluator:matches(Plan, Raw);
{r_rule_schedule, Plan@1} ->
automata@rrule@evaluator:matches(Plan@1, Raw);
{every_schedule, Plan@2} ->
every_matches(Plan@2, Raw);
{once_schedule, When} ->
automata@internal@calendar:equals(When, Raw)
end.
-file("src/automata/schedule.gleam", 244).
-spec every_align_cursor(
every_plan(),
automata@schedule@ast:date_time(),
boolean()
) -> automata@schedule@ast:date_time().
every_align_cursor(Plan, Target, Exclusive) ->
Delta = automata@internal@calendar:seconds_between(
erlang:element(2, Plan),
Target
),
Interval = erlang:element(3, Plan),
Remainder = modulo(Delta, Interval),
Advance = case {Exclusive, Remainder} of
{true, _} ->
(Delta - Remainder) + Interval;
{false, 0} ->
Delta;
{false, _} ->
(Delta - Remainder) + Interval
end,
automata@internal@calendar:add_seconds(erlang:element(2, Plan), Advance).
-file("src/automata/schedule.gleam", 225).
-spec every_start_cursor(every_plan(), automata@schedule@ast:boundary()) -> automata@schedule@ast:date_time().
every_start_cursor(Plan, Boundary) ->
case Boundary of
{inclusive, Valid} ->
Target = automata@schedule@ast:valid_datetime_value(Valid),
case automata@internal@calendar:less_or_equal(
Target,
erlang:element(2, Plan)
) of
true ->
erlang:element(2, Plan);
false ->
every_align_cursor(Plan, Target, false)
end;
{exclusive, Valid@1} ->
Target@1 = automata@schedule@ast:valid_datetime_value(Valid@1),
case automata@internal@calendar:less_than(
Target@1,
erlang:element(2, Plan)
) of
true ->
erlang:element(2, Plan);
false ->
every_align_cursor(Plan, Target@1, true)
end
end.
-file("src/automata/schedule.gleam", 148).
?DOC(" Build an iterator over occurrences strictly satisfying `boundary`.\n").
-spec iterator_after(schedule(), automata@schedule@ast:boundary()) -> occurrence_iterator().
iterator_after(Schedule, Boundary) ->
case Schedule of
{cron_schedule, Plan} ->
{cron_occurrences, automata@cron@iterator:'after'(Plan, Boundary)};
{r_rule_schedule, Plan@1} ->
{r_rule_occurrences,
automata@rrule@iterator:'after'(Plan@1, Boundary)};
{every_schedule, Plan@2} ->
{every_occurrences, Plan@2, every_start_cursor(Plan@2, Boundary)};
{once_schedule, When} ->
{once_occurrences, When, once_eligible(When, Boundary)}
end.
-file("src/automata/schedule.gleam", 200).
?DOC(
" Return the next occurrence strictly after `after`, if any.\n"
"\n"
" Equivalent to stepping `iterator_after(schedule, Exclusive(after))`\n"
" once.\n"
).
-spec next_after(schedule(), automata@schedule@ast:valid_date_time()) -> gleam@option:option(automata@schedule@ast:valid_date_time()).
next_after(Schedule, After) ->
case step(iterator_after(Schedule, {exclusive, After})) of
{yield, At, _} ->
{some, At};
done ->
none
end.