Packages
oban
2.3.3
2.23.0
2.22.1
2.22.0
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.4
2.19.3
2.19.2
2.19.1
2.19.0
2.18.3
2.18.2
2.18.1
2.18.0
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.3
2.16.2
2.16.1
2.16.0
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.2
2.14.1
2.14.0
2.13.6
2.13.5
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.3
2.11.2
2.11.1
2.11.0
2.10.1
2.10.0
retired
2.9.2
2.9.1
2.9.0
2.8.0
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.4
2.3.3
2.3.2
2.3.1
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
1.2.0
1.1.0
1.0.0
1.0.0-rc.2
1.0.0-rc.1
0.12.1
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
Robust job processing, backed by modern PostgreSQL, SQLite3, and MySQL.
Current section
Files
Jump to
Current section
Files
lib/oban/crontab/cron.ex
defmodule Oban.Crontab.Cron do
@moduledoc false
alias Oban.Crontab.Parser
@type expression :: [:*] | list(non_neg_integer())
@type t :: %__MODULE__{
minutes: expression(),
hours: expression(),
days: expression(),
months: expression(),
weekdays: expression(),
reboot: boolean()
}
@part_ranges %{
minutes: {0, 59},
hours: {0, 23},
days: {1, 31},
months: {1, 12},
weekdays: {0, 6}
}
defstruct minutes: [:*], hours: [:*], days: [:*], months: [:*], weekdays: [:*], reboot: false
@spec now?(cron :: t(), datetime :: DateTime.t()) :: boolean()
def now?(cron, datetime \\ DateTime.utc_now())
def now?(%__MODULE__{reboot: true}, _datetime), do: false
def now?(%__MODULE__{} = cron, datetime) do
cron
|> Map.from_struct()
|> Map.drop([:reboot])
|> Enum.all?(fn {part, values} ->
Enum.any?(values, &matches_rule?(part, &1, datetime))
end)
end
defp matches_rule?(_part, :*, _date_time), do: true
defp matches_rule?(:minutes, minute, datetime), do: minute == datetime.minute
defp matches_rule?(:hours, hour, datetime), do: hour == datetime.hour
defp matches_rule?(:days, day, datetime), do: day == datetime.day
defp matches_rule?(:months, month, datetime), do: month == datetime.month
defp matches_rule?(:weekdays, weekday, datetime), do: weekday == day_of_week(datetime)
defp day_of_week(datetime) do
datetime
|> Date.day_of_week()
|> Integer.mod(7)
end
@doc """
Parses a crontab expression into a %Cron{} struct.
The parser can handle common expressions that use minutes, hours, days, months and weekdays,
along with ranges and steps. It also supports common extensions, also called nicknames.
Raises an `ArgumentError` if the expression cannot be parsed.
## Nicknames
- @yearly: Run once a year, "0 0 1 1 *".
- @annually: same as @yearly
- @monthly: Run once a month, "0 0 1 * *".
- @weekly: Run once a week, "0 0 * * 0".
- @daily: Run once a day, "0 0 * * *".
- @midnight: same as @daily
- @hourly: Run once an hour, "0 * * * *".
- @reboot: Run once at boot
## Examples
iex> parse!("@hourly")
%Cron{}
iex> parse!("0 * * * *")
%Cron{}
iex> parse!("60 * * * *")
** (ArgumentError)
"""
@spec parse!(input :: binary()) :: t()
def parse!("@annually"), do: parse!("@yearly")
def parse!("@yearly"), do: parse!("0 0 1 1 *")
def parse!("@monthly"), do: parse!("0 0 1 * *")
def parse!("@weekly"), do: parse!("0 0 * * 0")
def parse!("@midnight"), do: parse!("@daily")
def parse!("@daily"), do: parse!("0 0 * * *")
def parse!("@hourly"), do: parse!("0 * * * *")
def parse!("@reboot"), do: struct!(__MODULE__, reboot: true)
def parse!(input) when is_binary(input) do
input
|> String.trim()
|> Parser.cron()
|> case do
{:ok, parsed, _, _, _, _} ->
struct!(__MODULE__, expand(parsed))
{:error, message, _, _, _, _} ->
raise ArgumentError, message
end
end
defp expand(parsed) when is_list(parsed), do: Enum.map(parsed, &expand/1)
defp expand({part, expressions}) do
{min, max} = Map.get(@part_ranges, part)
expanded =
expressions
|> Enum.flat_map(&expand(&1, min, max))
|> :lists.usort()
{part, expanded}
end
defp expand({:wild, _value}, _min, _max), do: [:*]
defp expand({:literal, value}, min, max) when value in min..max, do: [value]
defp expand({:step, [{:wild, _}, value]}, min, max) when value > 0 and value in min..max do
for step <- min..max, rem(step, value) == 0, do: step
end
defp expand({:step, [{:range, [first, last]}, value]}, min, max)
when first >= min and last <= max and last > first and value <= last - first do
for step <- first..last, rem(step, value) == 0, do: step
end
defp expand({:range, [first, last]}, min, max) when first >= min and last <= max do
for step <- first..last, do: step
end
defp expand({_type, value}, min, max) do
raise ArgumentError, "Unexpected value #{inspect(value)} outside of range #{min}..#{max}"
end
@spec reboot?(cron :: t()) :: boolean()
def reboot?(%__MODULE__{reboot: reboot}), do: reboot
end