View Source ecron (ecron v1.1.0)
ecron
enables dynamic scheduling of tasks using crontab-style expressions.
It provides a flexible API for managing recurring jobs, one-time tasks,
and time-based message delivery.
Summary
Job Management
Activates a previously deactivated job from the default registry.
The job can be deactivated using deactivate/1
.
Activates a previously deactivated job from the specified registry.
The job will resume from the current time.
The job can be deactivated using deactivate/2
.
Adds a new crontab job with specified parameters. Jobs exceeding their limits are automatically removed.
Deactivates an existing job temporarily from the default registry.
The job can be reactivated using activate/1
.
Deactivates an existing job temporarily from the specified registry.
The job can be reactivated using activate/2
.
Deletes an existing job from the default registry.
If the job does not exist, it will be ignored.
Use delete/2
to delete a job from a specified registry.
Deletes an existing job from the specified registry.
If the job does not exist, it will be ignored.
Use delete/1
to delete a job from the default registry.
Reloads all tasks manually. Useful when system time has changed significantly.
Reloads all tasks manually. Useful when system time has changed significantly.
Create a new registry.
Same as start_link(Register, [])
.
Starts an new registry.
Timer Functions
Create a one-time timer that sends a message when the crontab spec is triggered.
Sends a message to a process repeatedly based on a crontab schedule from the default registry.
Same as send_interval(Spec, Pid, Message, #{register => ecron_local})
.
Sends a message to a process repeatedly based on a crontab schedule from the default registry.
Same as send_interval(ecron_local, make_ref(), Spec, Pid, Message, unlimited, unlimited, [])
.
Evaluates Pid ! Message
repeatedly after crontab schedule milliseconds.
Debugging & Testing
Parses a crontab specification and returns the next N trigger times. Useful for debugging and validating crontab expressions.
Retrieves statistics for both default and global registered jobs.
Retrieves statistics for default registry.
Use statistic/2
to get statistics for a specific registry.
Retrieves statistics for a specific job from the specified registry.
Use statistic/1
to get statistics for the default registry.
Deprecated Functions
Same as add(ecron_local, JobName, Spec, MFA)
.
ecron_local
is the default register name, always exists.
Adds a new crontab job with specified parameters. Jobs exceeding their limits are automatically removed.
Same as add_with_count(ecron_local, make_ref(), Spec, MFA, RunCount)
.
ecron_local
is the default register name, always exists.
Add a job into default register with start and end time.
If you want to use the specified register, use add_with_time/6
.
Add a job into specified register with start and end time.
If you want to use the default register, use add_with_time/5
.
Sends a message to a process repeatedly based on a crontab schedule from the specified registry.
Same as send_interval(Register, Name, Spec, Pid, Message, unlimited, unlimited, [])
.
Sends a message to a process repeatedly based on a crontab schedule.
Job Management
-spec activate(name()) -> ok | {error, not_found}.
Activates a previously deactivated job from the default registry.
The job can be deactivated using deactivate/1
.
Activates a previously deactivated job from the specified registry.
The job will resume from the current time.
The job can be deactivated using deactivate/2
.
Parameters:
Register
- Process name where job is registeredJobName
- Name of job to activate
Returns: ok
| {error, not_found}
-spec create(name(), crontab_spec(), mfargs()) -> ecron_result().
Same as create(JobName, Spec, MFA, #{})
.
Examples
UniqueJobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, UniqueJobName} = ecron:create(UniqueJobName, "0 4 * * *", MFA),
ecron:statistic(UniqueJobName).
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.create(job_name, "0 4 * * *", mfa)
:ecron.statistic(job_name)
-spec create(name(), crontab_spec(), mfargs(), options()) -> ecron_result().
Adds a new crontab job with specified parameters. Jobs exceeding their limits are automatically removed.
Parameters:
JobName
- Unique identifier for the job. Returns{error, already_exist}
if duplicateSpec
- Crontab expression defining execution scheduleMFA
-{Module, Function, Args}
to execute when triggeredOpts
- Options map:- register:
atom()
- The process name where the job will be registered (default:ecron_local
) - start_time:
{Hour,Min,Sec}|unlimited
- Start time for job execution (default:unlimited
) - end_time:
{Hour,Min,Sec}|unlimited
- End time for job execution (default:unlimited
) - singleton:
boolean()
- If true, prevents concurrent execution (default:false
) - max_count:
pos_integer()|unlimited
- Maximum number of executions allowed (default:unlimited
). It will be automatically removed When a job reaches its max_count limit. - max_runtime_ms:
pos_integer()|unlimited
- Maximum runtime in milliseconds per execution (default:unlimited
)
- register:
Returns: {ok, JobName}
| {error, already_exist}
| {error, parse_error(), term()}
Examples
Register = my_job_register,
{ok, _Pid} = ecron:start_link(Register, []),
JobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, JobName} = ecron:create(JobName, "0 4 * * *", MFA, #{
start_time => {1, 0, 0},
end_time => unlimited,
singleton => false,
max_count => unlimited,
max_runtime_ms => 1000,
register => Register
}),
ecron:statistic(Register, JobName).
register = :my_job_register
{:ok, _Pid} = :ecron.start_link(register, [])
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.create(job_name, "0 4 * * *", mfa, %{
:start_time => {1, 0, 0},
:end_time => :unlimited,
:singleton => false,
:max_count => :unlimited,
:max_runtime_ms => 1000,
:register => register
})
:ecron.statistic(register, job_name)
Singleton
When a job's singleton option is set to true, the system checks if there is already an instance of the job running before starting a new execution. If an instance is already running (old pid is alive), the new execution will be skipped.
TimeRange
The start time must be less than the maximum value in the spec, and the end time must be greater than the minimum value in the spec.
For example: With spec 0 1-12/1 * * *
the max value is 12 and min value is 1,
so start time must be less than {12,0,0} and end time must be greater than {1,0,0}, with start < end.
If we can not find the next schedule time in the next 5 years, return 'cant find next schedule time in the next 5 years'.
ecron:create(invalid_job, "* 0,13 * * *", {io, format, [test]}, #{
start_time => {1,0,0},
end_time => {12,0,0}
}).
{error,invalid_time,
#{reason => "cant find next schedule time in the next 5 years",
spec => "* 0,13 * * *",
start_time => {1,0,0},
end_time => {12,0,0}}}
-spec deactivate(name()) -> ok | {error, not_found}.
Deactivates an existing job temporarily from the default registry.
The job can be reactivated using activate/1
.
Deactivates an existing job temporarily from the specified registry.
The job can be reactivated using activate/2
.
Parameters:
Register
- Process name where job is registeredJobName
- Name of job to deactivate
Returns: ok
| {error, not_found}
-spec delete(name()) -> ok.
Deletes an existing job from the default registry.
If the job does not exist, it will be ignored.
Use delete/2
to delete a job from a specified registry.
Returns: ok
Deletes an existing job from the specified registry.
If the job does not exist, it will be ignored.
Use delete/1
to delete a job from the default registry.
Parameters:
Register
- Process name where job is registeredJobName
- Name of job to delete
Returns: ok
-spec reload() -> ok.
Reloads all tasks manually. Useful when system time has changed significantly.
This will:
- Recalculate next execution times for all jobs
- Reset internal timers
- Apply to both default and global registries
Returns: ok
-spec reload(register()) -> ok.
Reloads all tasks manually. Useful when system time has changed significantly.
Parameters:
Register
- Process name where job is registered
Returns: ok
-spec start_link(register() | {local | global, register()}) -> {ok, pid()} | {error, term()} | ignore.
Create a new registry.
Same as start_link(Register, [])
.
Parameters:
Register
- Process name where job is registered
Returns: {ok, Pid}
| {error, Reason}
-spec start_link(atom() | {local | global, atom()}, [{name(), crontab_spec(), mfargs()}]) -> {ok, pid()} | {error, term()} | ignore.
Starts an new registry.
Parameters:
Name
- Process name where job is registeredJobSpec
- Crontab expression to parse list
Returns: {ok, Pid}
| {error, Reason}
Timer Functions
-spec send_after(crontab_spec(), pid() | atom(), term()) -> {ok, reference()} | {error, parse_error(), term()}.
Create a one-time timer that sends a message when the crontab spec is triggered.
Parameters:
Spec
- Crontab expression defining when to triggerDest
- Destination pid() or registered name to receive messageMessage
- Term to send when timer triggers
Notes:
- Similar to
erlang:send_after/3
but uses crontab format - Dest pid must be local
- Maximum time value is 4294967295 milliseconds
- Timer auto-cancels if destination process dies
Returns: {ok, reference()}
| {error, parse_error(), term()}
Examples
ecron:send_after("*/3 * * * * *", self(), hello_world).
:ecron.send_after("*/3 * * * * *", self(), :hello_world)
Statistic
This is one-time timer, so it not seen in statistic/0
result.
Use erlang:cancel_timer/1
to cancel, not ecron:delete/1
-spec send_interval(crontab_spec(), term()) -> ecron_result().
Sends a message to a process repeatedly based on a crontab schedule from the default registry.
Same as send_interval(Spec, Pid, Message, #{register => ecron_local})
.
-spec send_interval(crontab_spec(), pid(), term()) -> ecron_result().
Sends a message to a process repeatedly based on a crontab schedule from the default registry.
Same as send_interval(ecron_local, make_ref(), Spec, Pid, Message, unlimited, unlimited, [])
.
-spec send_interval(crontab_spec(), pid(), term(), options()) -> ecron_result().
Evaluates Pid ! Message
repeatedly after crontab schedule milliseconds.
Parameters:
Spec
- Crontab expression defining when to triggerPid
- Destination process ID or registered nameMessage
- Term to send on each triggerOpts
- Same options ascreate/4
Returns: {ok, reference()}
| {error, parse_error(), term()}
Examples
ecron:send_interval("*/3 * * * * *", self(), hello_world).
:ecron.send_interval("*/3 * * * * *", self(), :hello_world)
Statistic
This is repeatable timer, so it seen in statistic/0
result.
Use ecron:delete/1
to cancel, not erlang:cancel_timer/1
Debugging & Testing
-spec parse_spec(crontab_spec(), pos_integer()) -> {ok, #{type => cron | every, crontab => crontab(), next => [rfc3339_string()]}} | {error, atom(), term()}.
Parses a crontab specification and returns the next N trigger times. Useful for debugging and validating crontab expressions.
Parameters:
Spec
- Crontab expression to parseNum
- Number of future trigger times to calculate
Returns: {ok, #{type => cron|every, crontab => parsed_spec, next => [rfc3339_string()]}}
|
{error, parse_error(), term()}
-spec statistic() -> [statistic()].
Retrieves statistics for both default and global registered jobs.
Returns: List of statistics for all jobs in both local and global registries.
Each statistic entry contains the same information as statistic/2
.
UniqueJobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, UniqueJobName} = ecron:create(UniqueJobName, "0 4 * * *", MFA),
ecron:statistic(UniqueJobName).
[#{name => every_4am_job,node => nonode@nohost,ok => 0,
status => activate,type => cron,
next =>
["2025-02-19T04:00:00+08:00","2025-02-20T04:00:00+08:00",
"2025-02-21T04:00:00+08:00","2025-02-22T04:00:00+08:00",
...
],
opts => [{singleton,false}, {max_count,unlimited}, {max_runtime_ms,unlimited}],
mfa => {io,format,["Run at 04:00 everyday.~n",[]]},
aborted => 0,crashed => 0,skipped => 0, run_microsecond => [],
start_time => {0,0,0},
end_time => {23,59,59},
crontab =>
#{second => [0],
month => '*',
minute => [0],
hour => [4],
day_of_month => '*',day_of_week => '*'},
results => []}]
Retrieves statistics for default registry.
Use statistic/2
to get statistics for a specific registry.
Returns: List of statistics for all jobs in local registries.
Each statistic entry contains the same information as statistic/2
.
Retrieves statistics for a specific job from the specified registry.
Use statistic/1
to get statistics for the default registry.
Parameters:
Register
- Process name where job is registeredJobName
- Name of job to get statistics for
Returns: {ok, statistic()}
| {error, not_found}
Where statistic() contains:
- Job configuration
- Execution counts (ok/crashed/aborted/skipped)
- Latest results
- Run times
- Next scheduled runs
1> ecron:statistic(basic). [#{name => basic,node => nonode@nohost,ok => 0, status => activate,type => cron, next => ["2025-02-20T22:15:00+08:00"...], opts => [{singleton,false},{max_count,unlimited},{max_runtime_ms,unlimited}], mfa => {io,format,['Runs on 0, 15, 30, 45 minutes~n']}, aborted => 0,crashed => 0,skipped => 0, start_time => {0,0,0}, end_time => {23,59,59}, run_microsecond => [], crontab => #{second => [0], month => '*', minute => [0,15,30,45], hour => '*',day_of_month => '*',day_of_week => '*'}, results => []}]
ok
: successful job.crashed
: crashed job.skipped
: singleton(true) job has been skipped due to the previous task still running.aborted
: job has been aborted due to exceedingmax_runtime_ms
.
-type statistic() :: #{name => name(), node => node(), type => cron | every, crontab => crontab(), status => deactivate | activate, crashed => non_neg_integer(), ok => non_neg_integer(), aborted => non_neg_integer(), opts => option_list(), results => [term()], run_microsecond => [pos_integer()], start_time => rfc3339_string() | unlimited, end_time => rfc3339_string() | unlimited, next => [calendar:datetime()]}.
Deprecated Functions
-spec add(name(), crontab_spec(), mfargs()) -> ecron_result().
Same as add(ecron_local, JobName, Spec, MFA)
.
ecron_local
is the default register name, always exists.
Examples
JobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, JobName} = ecron:add(JobName, "0 4 * * *", MFA),
ecron:statistic(JobName).
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.add(job_name, "0 4 * * *", mfa)
:ecron.statistic(job_name)
-spec add(register(), name(), crontab_spec(), mfargs()) -> ecron_result().
Same as add(Register, JobName, Spec, MFA, unlimited, unlimited, [])
.
Examples
Register = my_cronjob_register,
{ok, _Pid} = ecron:start_link(Register, []),
JobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, JobName} = ecron:add(Register, JobName, "0 4 * * *", MFA),
ecron:statistic(Register, JobName).
register = :my_cronjob_register
{:ok, _Pid} = :ecron.start_link(register, [])
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.add(register, job_name, "0 4 * * *", mfa)
:ecron.statistic(register, job_name)
-spec add(name(), crontab_spec(), mfargs(), start_at(), end_at(), option_list()) -> ecron_result().
Same as add(ecron_local, JobName, Spec, MFA, Start, End, Opts)
.
-spec add(register(), name(), crontab_spec(), mfargs(), start_at(), end_at(), option_list()) -> ecron_result().
Adds a new crontab job with specified parameters. Jobs exceeding their limits are automatically removed.
Parameters:
Register
- The process name where the job will be registeredJobName
- Unique identifier for the job. Returns{error, already_exist}
if duplicateSpec
- Crontab expression defining execution scheduleMFA
-{Module, Function, Args}
to execute when triggeredStart
- Start time{Hour,Min,Sec}
orunlimited
for immediate startEnd
- End time{Hour,Min,Sec}
orunlimited
for no endOpts
- Options list:{singleton, boolean()}
- If true (default), prevents concurrent execution{max_count, pos_integer() | unlimited}
- Maximum executions allowed{max_runtime_ms, pos_integer() | unlimited}
- Maximum runtime milliseconds allowed for each execution
Returns: {ok, JobName}
| {error, already_exist}
| {error, parse_error(), term()}
Examples
Register = my_cronjob_register,
{ok, _Pid} = ecron:start_link(Register, []),
JobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, JobName} = ecron:add(Register, JobName, "0 4 * * *", MFA, {1, 0, 0}, unlimited, [{singleton, true}]),
ecron:statistic(Register, JobName).
register = :my_cronjob_register
{:ok, _Pid} = :ecron.start_link(register, [])
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.add(register, job_name, "0 4 * * *", mfa, {1, 0, 0}, :unlimited, [singleton: true])
:ecron.statistic(register, job_name)
Singleton
When a job's singleton option is set to true, the system checks if there is already an instance of the job running before starting a new execution. If an instance is already running(old pid is alive), the new execution will be skipped.
-spec add_with_count(crontab_spec(), mfargs(), pos_integer()) -> ecron_result().
Same as add_with_count(ecron_local, make_ref(), Spec, MFA, RunCount)
.
ecron_local
is the default register name, always exists.
Examples
The job will be auto deleted after running 10 times.
JobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, JobName} = ecron:add_with_count(JobName, "0 4 * * *", MFA, 10),
ecron:statistic(JobName).
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.add_with_count(job_name, "0 4 * * *", mfa, 10)
:ecron.statistic(job_name)
-spec add_with_count(register(), name(), crontab_spec(), mfargs(), pos_integer()) -> ecron_result().
Same as add(Register, JobName, Spec, MFA, unlimited, unlimited, [{max_count, RunCount}])
.
Examples
The job will be auto deleted after running 10 times.
Register = my_cronjob_register,
{ok, _Pid} = ecron:start_link(Register, []),
JobName = every_4am_job,
MFA = {io, format, ["Run at 04:00 everyday.~n", []]},
{ok, JobName} = ecron:add_with_count(Register, JobName, "0 4 * * *", MFA, 10),
ecron:statistic(Register, JobName).
register = :my_cronjob_register
{:ok, _Pid} = :ecron.start_link(register, [])
job_name = :every_4am_job
mfa = {IO, :puts, ["Run at 04:00 everyday.\n"]}
{:ok, ^job_name} = :ecron.add_with_count(register, job_name, "0 4 * * *", mfa, 10)
:ecron.statistic(register, job_name)
-spec add_with_time(name(), crontab_spec(), mfargs(), start_at(), end_at()) -> ecron_result().
Add a job into default register with start and end time.
If you want to use the specified register, use add_with_time/6
.
Examples
The job will be auto skipped if the current time is not between 04:00 and 12:00 everyday.
JobName = on_hour_job,
MFA = {io, format, ["Run at 04:00-12:00 on the hour.~n", []]},
{ok, JobName} = ecron:add_with_time(JobName, "0 1-12/1 * * *", MFA, {4, 0, 0}, {12, 0, 0}),
ecron:statistic(JobName).
job_name = :on_hour_job
mfa = {IO, :puts, ["Run at 04:00-12:00 on the hour.\n"]}
{:ok, ^job_name} = :ecron.add_with_time(job_name, "0 1-12/1 * * *", mfa, {4, 0, 0}, {12, 0, 0})
:ecron.statistic(job_name)
TimeRange
The start time must be less than the maximum value in the spec, and the end time must be greater than the minimum value in the spec.
For example: With spec 0 1-12/1 * * *
the max value is 12 and min value is 1,
so start time must be less than {12,0,0} and end time must be greater than {1,0,0}, with start < end.
If we can not find the next schedule time in the next 5 years, return cant find next schedule time in the next 5 years
.
ecron:add_with_time(invalid_job, "* 0,13 * * *", {io, format, ["test"]},{1,0,0},{12,0,0}).
{error,invalid_time,
#{reason => "cant find next schedule time in the next 5 years",
start => {1,0,0},
stop => {12,0,0},
spec => "* 0,13 * * *"}}
-spec add_with_time(register(), name(), crontab_spec(), mfargs(), start_at(), end_at()) -> ecron_result().
Add a job into specified register with start and end time.
If you want to use the default register, use add_with_time/5
.
Examples
The job will be auto skipped if the current time is not between 04:00 and 12:00 everyday.
Register = my_cronjob_register,
{ok, _Pid} = ecron:start_link(Register, []),
JobName = on_hour_job,
MFA = {io, format, ["Run at 04:00-12:00 on the hour.~n", []]},
{ok, JobName} = ecron:add_with_time(Register, JobName, "0 1-12/1 * * *", MFA, {4, 0, 0}, {12, 0, 0}),
ecron:statistic(Register, JobName).
register = :my_cronjob_register
{:ok, _Pid} = :ecron.start_link(register, [])
job_name = :on_hour_job
mfa = {IO, :puts, ["Run at 04:00-12:00 on the hour.\n"]}
{:ok, ^job_name} = :ecron.add_with_time(register, job_name, "0 1-12/1 * * *", mfa, {4, 0, 0}, {12, 0, 0})
:ecron.statistic(register, job_name)
TimeRange
The start time must be less than the maximum value in the spec, and the end time must be greater than the minimum value in the spec.
For example: With spec 0 1-12/1 * * *
the max value is 12 and min value is 1,
so start time must be less than {12,0,0} and end time must be greater than {1,0,0}, with start < end.
If we can not find the next schedule time in the next 5 years, return cant find next schedule time in the next 5 years
.
ecron:add_with_time(invalid_job, "* 0,13 * * *", {io, format, ["test"]},{1,0,0},{12,0,0}).
{error,invalid_time,
#{reason => "cant find next schedule time in the next 5 years",
start => {1,0,0},
stop => {12,0,0},
spec => "* 0,13 * * *"}}
-spec send_interval(register(), name(), crontab_spec(), pid(), term()) -> ecron_result().
Sends a message to a process repeatedly based on a crontab schedule from the specified registry.
Same as send_interval(Register, Name, Spec, Pid, Message, unlimited, unlimited, [])
.
-spec send_interval(register(), name(), crontab_spec(), term(), start_at(), end_at(), option_list()) -> ecron_result().
Same as send_interval(register(), name(), Spec, self(), Message, Start, End, Option)
.
-spec send_interval(register(), name(), crontab_spec(), pid(), term(), start_at(), end_at(), option_list()) -> ecron_result().
Sends a message to a process repeatedly based on a crontab schedule.
Parameters
Register
- Process name where job will be registeredJobName
- Unique identifier for the jobSpec
- Crontab expression defining execution schedulePid
- Destination process ID or registered nameMessage
- Term to send on each triggerStart
- Start time{Hour,Min,Sec}
orunlimited
End
- End time{Hour,Min,Sec}
orunlimited
Option
- Same options asadd/7
Returns: {ok, reference()}
| {error, parse_error(), term()}
Types
-type crontab() ::
#{second => '*' | [0..59 | {0..58, 1..59}, ...],
minute => '*' | [0..59 | {0..58, 1..59}, ...],
hour => '*' | [0..23, ...],
month => '*' | [1..12 | {1..11, 2..12}, ...],
day_of_month => '*' | [1..31 | {1..30, 2..31}, ...],
day_of_week => '*' | [0..6 | {0..5, 1..6}, ...]}.
-type ecron_result() :: {ok, name()} | {error, parse_error(), term()} | {error, already_exist}.
-type end_at() :: unlimited | calendar:time().
-type name() :: term().
-type option_list() :: [{singleton, boolean()} | {max_count, pos_integer() | unlimited} | {max_runtime_ms, pos_integer() | unlimited}].
-type options() :: #{singleton => boolean(), max_count => pos_integer() | unlimited, max_runtime_ms => pos_integer() | unlimited, start_time => start_at(), end_time => end_at(), register => register()}.
-type parse_error() ::
invalid_time | invalid_opts | invalid_spec | month | day_of_month | day_of_week | hour |
minute | second.
-type register() :: atom().
-type start_at() :: unlimited | calendar:time().