Build job schedules with kotlinx-datetime and kotlinx-coroutines.
val tz = TimeZone.of("America/New_York")
Clock.System.schedulePulse(atSecond = 0).beat { instant ->
println("The time in $tz is ${instant.toLocalDateTime(tz)}.")
}
The time in America/New_York is 2023-10-07T19:49:00.
The time in America/New_York is 2023-10-07T19:50:00.
The time in America/New_York is 2023-10-07T19:51:00.
The time in America/New_York is 2023-10-07T19:52:00.
The time in America/New_York is 2023-10-07T19:53:00.
Cardiologist integrates with kotlinx-datetime to provide you scheduling based on Instant
, LocalDateTime
,
and LocalTime
, and integrates with kotlinx-coroutines to provide a suspending API to trigger pending jobs.
Simple suspending functions are available to delay until a given moment in time.
Clock.System.delayUntil(instant = Instant.DISTANT_FUTURE)
Clock.System.delayUntil(dateTime = LocalDateTime.MAX, timeZone = TimeZone.UTC)
...or suspending for a given period of time.
Clock.System.delayFor(period = DateTimePeriod(months = 1, days = 2), timeZone = TimeZone.UTC)
...or suspending until the next time a LocalTime
occurs in a certain time zone.
val midnight = LocalTime(hour = 0, minute = 0)
Clock.System.delayUntilNext(time = midnight, timeZone = TimeZone.UTC)
Repeating intervals are provided as a Pulse
, a type which holds a reccurence cadence.
val hourlyPulse: Pulse = Clock.System.intervalPulse(interval = 1.hours)
val dailyPulse: Pulse = Clock.System.intervalPulse(
period = DateTimePeriod(days = 1),
timeZone = TimeZone.UTC,
)
Pulse schedules can be built with a cron like API.
// Schedules a pulse to occur on the 5th of every month at 12:30 in UTC.
val scheduledPulse = Clock.System.schedulePulse(
timeZone = TimeZone.UTC,
atSecond = 0,
atMinute = 30,
atHour = 12,
onDayOfMonth = 5,
)
...and can be invoked by calling Pulse.beat
.
scheduledPulse.beat { instant -> println("$instant") }
Beating a pulse is a backpressure sensitive operation. If your job has not completed when the next pulse is scheduled to occur, it will by default be cancelled. Cardiologist provides two other modes to beat a pulse which allow jobs to run concurrently or apply backpressure.
import io.github.kevincianfarini.cardiologist.RecurringJobMode.*
// Cancels previous job when next job is scheduled to occur.
hourlyPulse.beat(mode = CancellingSequential) { instant -> longRunningOperation(instant) }
// Allows jobs to run concurrently if previous job is still active.
hourlyPulse.beat(mode = Concurrent) { instant -> longRunningOperation(instant) }
// Will not emit again until previous job finishes, applying backpressure.
hourlyPulse.beat(mode = DelayBetweenSequential) { instant -> longRunningOperation(instant) }
Cardiologist is an in process job scheduling library. It is meant to run concurrently to the rest of your process, not as a separate process. Use cases include job scheduling within a long-lived process like a server or a daemon.
// With ktor, for example.
fun main() = runBlocking { // this: CoroutineScope
this.embeddedServer(Netty, port = 8080) { /* omitted */ }.start(wait = false)
launch { recurringJob() }
}
private suspend fun recurringJob() {
Clock.System.schedulePulse(
onDayOfMonth = 1,
atHour = 0,
atMinute = 0,
atSecond = 0
).beat { instant -> someWork(instant) }
}
Cron jobs and Android's WorkManager leverage standalone processes and will launch your program as a separate processes. This is outside the scope of Cadiologist's goals.