Tempus.jl
Quartz-inspired cron job scheduling for Julia: cron expressions (with optional seconds, month/day names, and @-aliases), overlap policies, retries, execution caps, timezone-aware schedules, and pluggable persistent state through AbstractStores.jl.
using Tempus
scheduler = Tempus.Scheduler() # in-memory state
Tempus.run!(scheduler) # dispatch loop runs in the background
push!(scheduler, Tempus.Job(
() -> println("Hello from Tempus!"),
"hello_job",
"* * * * *", # every minute
))
# ... later
close(scheduler)See the README for a walkthrough of state backends, job options, and the full cron syntax.
API Reference
Tempus.Tempus — Module
Tempus provides a cron-style job scheduling framework for Julia, inspired by Quartz in Java.
Features:
- Define jobs with cron-like scheduling expressions
- Supports job execution policies (overlap handling, retries, and failure strategies)
- Pluggable persistent state via AbstractStores.jl backends (memory, file, SQL, Redis)
- Concurrency-aware execution with configurable retry logic
- Supports disabling, enabling, and unscheduling jobs dynamically
- Thread-safe scheduling with a background execution loop
Tempus.Job — Type
JobRepresents a single job/unit of work. Can be scheduled to repeat.
Fields:
name::String: Unique identifier for the job.schedule::Union{Cron, Nothing}: The cron-style schedule expression.action: The function to execute when the job runs, behind an internal callback wrapper (call it asjob.action());nothingfor a job read from a store that was not added in this process (seeaddJob!).action_ref::Union{Nothing, String}: Fully qualified function reference (e.g."MyModule.my_handler"), used byrestoreJobs!.action_data::Union{Nothing, String}: JSON-encodedjob_params, passed to the action as keyword arguments:action(; JSON.parse(action_data)...).options::JobOptions: Execution options for retries, failures, and overlap handling.disabledAt::Union{DateTime, Nothing}: Timestamp when the job was disabled (if applicable).
Create jobs at run time (for example in a package's __init__), not during precompilation: the action wrapper is not valid in another Julia session. job_params are converted to keyword arguments from JSON at every call, which binaries compiled with juliac --trim cannot do; there, capture parameters in the action instead (Job(() -> handler(; value="x"), ...)).
Tempus.JobExecution — Type
JobExecutionRepresents an instance of a job execution.
Fields:
jobExecutionId::String: Unique identifier for this job execution.job::Job: The job being executed.scheduledStart::DateTime: When the job was scheduled to run.runConcurrently::Bool: Whether this execution is running concurrently with another.actualStart::DateTime: The actual start time.finish::DateTime: The completion time.status::Symbol: The execution result (:succeeded,:failed).
Tempus.JobExecutionRecord — Type
JobExecutionRecordWhat a Store persists for a finished JobExecution: its id, timing, and whether it succeeded (its status was :succeeded). The value an action returned and the exception it threw are kept only on the in-process JobExecution.
Tempus.JobOptions — Type
JobOptionsDefines options for job execution behavior.
Fields:
overlap_policy::Union{Symbol, Nothing}: Determines job execution behavior when the same job is already running (:skip,:queue,:concurrent).retries::Int: Number of retries allowed on failure.retry_delays::Union{Base.ExponentialBackOff, Nothing}: Delay strategy for retries (defaults to exponential backoff ifretries > 0).retry_check: Custom function(state, exception) -> Booldeciding whether a failed attempt is retried (thecheckargument ofBase.retry); stored as an internal callback wrapper. In a binary compiled withjuliac --trim, declare its exception argument@nospecialize.max_failed_executions::Union{Int, Nothing}: Number of consecutive failed executions that disables a job.max_executions::Union{Int, Nothing}: Maximum number of successful executions in the retained history.expires_at::Union{DateTime, Nothing}: Expiration time for a job.timezone::Union{Nothing, String}: IANA timezone name (e.g."America/Denver"). When set, the job's cron schedule is interpreted in this timezone.nothingmeans UTC.zone: Internal callback computing the next UTC time of a schedule intimezone;nothingwithout a timezone, and for options read back from a store record (seerestoreJobs!).
Tempus.JobRecord — Type
JobRecordWhat a Store persists for a Job: its definition and disabled state, without the action, which exists only in the process that added the job. schedule is the cron expression (nothing for one-shot jobs); the option fields are those of JobOptions except retry_check and zone, with overlap_policy as a string.
Tempus.Scheduler — Type
SchedulerThe main scheduling engine that executes jobs according to their schedules.
Fields:
lock::ReentrantLock: Ensures thread-safe access.jobExecutions::Vector{JobExecution}: List of scheduled job executions.store::S: Job storage backend (aStore); the scheduler type is parameterized on it so store calls are statically dispatched.jobExecutionFinished::Threads.Event: Signals all job executions have finished when shutting down.executingJobExecutions::Set{JobExecution}: Tracks currently executing jobs.running::Bool: Scheduler state (running/stopped).loopActive::Bool: Whether the dispatch-loop task has finished stopping.jobOptions::JobOptions: Default job execution options.max_concurrent_executions::Int: Limit on how many total executions can be running concurrently for this scheduler, defaults toThreads.nthreads()logging::Bool: Whether to emit log messages during scheduler operations, defaults totrue.
Tempus.Store — Type
StoreTwo typed AbstractStore views used by a scheduler, jobs by name (JobRecords) and bounded execution history by job name (JobExecutionRecords), plus the actions of the jobs added to this Store object in this process. Applications choose the shared backend.
Actions are not persisted. A scheduler runs a stored job only when a job of that name was added in this process with addJob! or push!; add each job at startup, or call restoreJobs! to resolve stored jobs' actions by name.
Tempus.Store — Method
Store(jobs, executions; history_limit=100)Create a store from separate typed job and execution-history backends.
Tempus.Store — Method
Store(backend; prefix="tempus/", history_limit=100)Create job and execution-history views over one AbstractStore backend. Namespacing lets Tempus share a physical store with other libraries and application state.
The backend must accept both JobRecord and Vector{JobExecutionRecord} values, i.e. be an AbstractStore{Any}. A serializing backend can use SerializedCodec or JSONCodec; JSON needs AbstractStores 0.2 or later, which decodes through the views' concrete types.
history_limit bounds the execution history kept per job. Note that max_executions/max_failed_executions are evaluated against that history, so a limit below either of them means the corresponding cap can never be reached.
Base.close — Method
close(scheduler::Scheduler; timeout::Real=5)Closes the scheduler, stopping job execution; waits up to timeout seconds (5 by default) for any currently executing jobs to finish before returning.
Base.push! — Method
push!(scheduler::Scheduler, job::Job)Adds a job to the scheduler and underlying Store, scheduling its next execution based on its cron schedule. Pushing a job whose name is already scheduled replaces the stored job and any queued (not yet running) executions.
Base.wait — Method
wait(scheduler::Scheduler)Waits until the dispatch loop and all started job tasks have stopped. Close the scheduler explicitly, or pass close_when_no_jobs=true to run! to stop the loop automatically when no executions are queued or running.
Returning does not mean every queued job ran: closing the scheduler or a dispatch error can leave unstarted executions queued. Background task errors are logged with @error; wait(scheduler) does not rethrow them.
Tempus.FileStore — Method
FileStore(directory; kw...)Create a Tempus store backed by an AbstractStores.FileStore. directory is a directory, not the single JSON file used by Tempus 2.
Tempus.InMemoryStore — Method
Create a process-local Tempus store.
Tempus.OneShotJob — Method
OneShotJob(action, name; kw...)A Job with no cron schedule that runs once, as soon as the scheduler picks it up, and is then disabled. A failed attempt is re-run (with the job's retry options applied within each attempt) until it succeeds or reaches max_failed_executions. Accepts the same keyword options as Job. max_executions is always set to 1.
Note the run-once bookkeeping is based on the job's stored execution history, so re-adding a one-shot job whose name has already succeeded will not run it again; use a fresh name (or purgeJob!) to re-run one.
Tempus.SQLiteStore — Method
SQLiteStore(connection; table="tempus_state", kw...)Compatibility constructor for a Tempus store backed by one AbstractStores.SQLStore table. The same Store(SQLStore(...)) form works for SQLite, Postgres, and other DBInterface drivers.
Tempus._function_ref — Method
Auto-extract fully qualified function reference string from a named function.
Tempus.addJob! — Method
addJob!(store, job)Add or replace a job by name. The job's definition is persisted; its action is kept in store for this process, so add every job again after a restart (or call restoreJobs!) for a scheduler to run it.
Tempus.disable! — Method
disable!(job::Job)Disables a job, preventing it from being scheduled for execution.
This mutates the Job object only. A store keeps its own record of the job, so use disableJob!(store, job) when the change must survive a restart.
Tempus.disableJob! — Method
disableJob!(store, job; at=Dates.now(UTC))Disable a stored job by reference or name. The update uses the backend atomic read-modify-write operation.
Tempus.enable! — Method
enable!(job::Job)Enables a previously disabled job, allowing it to be scheduled again.
Like disable!, this mutates the Job object only; call addJob!(store, job) afterwards to write the re-enabled job back to a persisting store.
Tempus.getJobs — Method
getJobs(store) -> Vector{Job}Return every stored job, regardless of disabled status. A job added to store in this process carries its action; any other stored job has action === nothing.
Tempus.getNMostRecentJobExecutions — Method
Return at most n JobExecutionRecords, newest first.
Tempus.getnext — Function
getnext(cron::Cron, timezone::String, from::DateTime=Dates.now(UTC)) -> DateTimeCompute the next trigger time for cron interpreted in timezone (IANA name, e.g. "America/Denver"). from is a UTC DateTime. Returns a UTC DateTime. DST handling: spring-forward gaps are skipped, fall-back ambiguities use the first occurrence.
Tempus.isdisabled — Method
isdisabled(job::Job) -> BoolReturns true if the job is currently disabled.
Tempus.nextJobExecution — Method
nextJobExecution(store::Store, job::Job) -> Union{JobExecution, Nothing}For a job persisted in store, check the job's status and execution history and return a JobExecution for the next time it should run, or nothing if the job shouldn't be scheduled again. As a side effect, jobs that have expired or reached their execution caps are disabled in the store.
Tempus.purgeJob! — Method
Remove a job and all execution history for that job.
Tempus.resolve_function — Method
resolve_function(ref::String) -> FunctionResolve a function from its fully qualified reference string (e.g. "MyModule.my_handler"). The module must be loaded before calling this function. This uses reflection, so it is unavailable in binaries compiled with juliac --trim.
Tempus.restoreJobs! — Method
restoreJobs!(store) -> Vector{Job}Give stored jobs without an action in this process their action back by resolving each job's action_ref with resolve_function, so a scheduler runs jobs a previous process stored without adding them again. Jobs without an action_ref (anonymous functions) are skipped. Returns the restored jobs.
This resolves functions by name through reflection, so it is unavailable in binaries compiled with juliac --trim; add every job with addJob! at startup there.
Tempus.run! — Method
run!(scheduler::Scheduler; close_when_no_jobs::Bool=false)Starts the scheduler, executing jobs at their scheduled times. The dispatch loop runs on a background task; run! returns the scheduler immediately. With close_when_no_jobs=true, the loop shuts down on its own once no executions are queued or running (see runJobs!). Throws if the scheduler is already running or a previous timed-out close still has loop or job tasks in flight.
Only stored jobs with an action in this process run (see addJob!); others are skipped with a warning.
A dispatch error stops the loop without canceling jobs that already started. Selected executions whose tasks were not started remain queued. Calling run! again rebuilds the queue from the stored jobs and their execution history.
Tempus.runJobs! — Method
runJobs!(store::Store, jobs; kw...)Add each job in jobs to store, run a scheduler with kw options, wait for all jobs to finish, then close the scheduler.
Tempus.scheduleNextExecution! — Method
scheduleNextExecution!(scheduler::Scheduler, job::Job)Schedule job's next execution, returning it, or nothing when no execution was scheduled: the job is done (see nextJobExecution), it has been removed from the store, it has no action in this process, an execution at the same time is already queued, or — for one-shot jobs, which are rescheduled only when an attempt fails — an execution is already queued or running. scheduler.lock must be held.
The new execution is appended without re-sorting (so callers iterating scheduler.jobExecutions or holding indexes into it stay valid); callers must restore scheduledStart order before the scheduler loop scans the list again.
Tempus.storeJobExecution! — Method
storeJobExecution!(store, execution)Prepend one execution's JobExecutionRecord to the job history and keep at most history_limit records. The update is atomic when the selected backend provides atomic modify!.
Tempus.unschedule! — Method
unschedule!(scheduler::Scheduler, job::Union{Job, AbstractString})Removes a job (by reference or name) from the scheduler and the underlying store, canceling any queued executions and deleting the job's execution history. An already running execution finishes but is not rescheduled.
See disable!/disableJob! to keep a job (and its history) around while preventing it from running.
Tempus.withscheduler — Method
withscheduler(f, args...; kw...)Creates a scheduler, runs a function f with it, then calls close.