mirror of
https://github.com/InsanusMokrassar/krontab.git
synced 2024-11-25 19:48:49 +00:00
add years, KrontabWrapper, new function to create scheduler from template and update readme
This commit is contained in:
parent
476239faa6
commit
365adb1016
22
README.md
22
README.md
@ -53,18 +53,18 @@ For old version of Gradle, instead of `implementation` word developers must use
|
||||
Developers can use more simple way to configure repeat times is string. String configuring
|
||||
like a `crontab`, but with a little bit different meanings:
|
||||
```
|
||||
/-------- Seconds
|
||||
| /------ Minutes
|
||||
| | /---- Hours
|
||||
| | | /-- Days of months
|
||||
| | | | / Months
|
||||
| | | | |
|
||||
* * * * *
|
||||
/---------- Seconds
|
||||
| /-------- Minutes
|
||||
| | /------ Hours
|
||||
| | | /---- Days of months
|
||||
| | | | /-- Months
|
||||
| | | | | / (optional) Year
|
||||
* * * * * *
|
||||
```
|
||||
|
||||
It is different with original `crontab` syntax for the reason, that expected that in practice developers
|
||||
will use seconds and minutes with more probability than months (for example). In fact, developers will use something
|
||||
like:
|
||||
will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use
|
||||
something like:
|
||||
|
||||
```kotlin
|
||||
doWhile("/5 * * * *") {
|
||||
@ -73,7 +73,7 @@ doWhile("/5 * * * *") {
|
||||
}
|
||||
```
|
||||
|
||||
Or more version:
|
||||
An other version:
|
||||
|
||||
```kotlin
|
||||
doInfinity("/5 * * * *") {
|
||||
@ -85,7 +85,7 @@ Both of examples will print `Called` message every five seconds.
|
||||
|
||||
### Config via builder
|
||||
|
||||
Also this library currently supports DSL for creating the same goals:
|
||||
Also, this library currently supports DSL for creating the same goals:
|
||||
|
||||
```kotlin
|
||||
val kronScheduler = buildSchedule {
|
||||
|
@ -7,9 +7,13 @@ import kotlinx.coroutines.delay
|
||||
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
|
||||
*
|
||||
* WARNING!!! If you want to launch it in parallel, you must do this explicitly.
|
||||
*
|
||||
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately
|
||||
*/
|
||||
suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T {
|
||||
delay((next() - DateTime.now()).millisecondsLong)
|
||||
next() ?.let {
|
||||
delay((it - DateTime.now()).millisecondsLong)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.krontab.internal.toNearDateTime
|
||||
|
||||
/**
|
||||
* This interface was created for abstraction of [next] operation. Currently, there is only
|
||||
@ -17,5 +18,7 @@ interface KronScheduler {
|
||||
*
|
||||
* @see dev.inmo.krontab.internal.CronDateTimeScheduler.next
|
||||
*/
|
||||
suspend fun next(relatively: DateTime = DateTime.now()): DateTime
|
||||
suspend fun next(relatively: DateTime = DateTime.now()): DateTime?
|
||||
}
|
||||
|
||||
suspend fun KronScheduler.forceNext(relatively: DateTime = DateTime.now()): DateTime = next(relatively) ?: getAnyNext(relatively)
|
||||
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
data class KrontabTemplateWrapper(
|
||||
val template: KrontabTemplate
|
||||
) : KronScheduler by template.toKronScheduler()
|
@ -1,12 +1,15 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.krontab.builder.buildSchedule
|
||||
import dev.inmo.krontab.internal.*
|
||||
import dev.inmo.krontab.internal.CronDateTime
|
||||
import dev.inmo.krontab.internal.CronDateTimeScheduler
|
||||
|
||||
internal val anyCronDateTime by lazy {
|
||||
CronDateTime()
|
||||
}
|
||||
internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(relatively)!!
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now]
|
||||
@ -48,4 +51,11 @@ val EveryDayOfMonthScheduler: KronScheduler by lazy {
|
||||
*/
|
||||
val EveryMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule { months { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one year
|
||||
*/
|
||||
val EveryYearScheduler: KronScheduler by lazy {
|
||||
buildSchedule { years { 0 every 1 } }
|
||||
}
|
@ -16,6 +16,7 @@ typealias KrontabTemplate = String
|
||||
* * hours
|
||||
* * dayOfMonth
|
||||
* * month
|
||||
* * (optional) year
|
||||
*
|
||||
* And each one have next format:
|
||||
*
|
||||
@ -37,6 +38,7 @@ typealias KrontabTemplate = String
|
||||
* * Hours ranges can be found in [hoursRange]
|
||||
* * Days of month ranges can be found in [dayOfMonthRange]
|
||||
* * Months ranges can be found in [monthRange]
|
||||
* * Years ranges can be found in [yearRange] (in fact - any [Int])
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
@ -44,20 +46,25 @@ typealias KrontabTemplate = String
|
||||
* * "0/5,L * * * *" for every five seconds triggering and on 59 second
|
||||
* * "0/15 30 * * *" for every 15th seconds in a half of each hour
|
||||
* * "1 2 3 F,4,L 5" for triggering in near first second of second minute of third hour of fourth day of may
|
||||
* * "1 2 3 F,4,L 5 2021" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year
|
||||
*
|
||||
* @see dev.inmo.krontab.internal.createKronScheduler
|
||||
*/
|
||||
fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler {
|
||||
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ")
|
||||
val yearSource: String?
|
||||
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also {
|
||||
yearSource = it.getOrNull(5)
|
||||
}
|
||||
|
||||
val secondsParsed = parseSeconds(secondsSource)
|
||||
val minutesParsed = parseMinutes(minutesSource)
|
||||
val hoursParsed = parseHours(hoursSource)
|
||||
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
||||
val monthParsed = parseMonths(monthSource)
|
||||
val yearParsed = parseYears(yearSource)
|
||||
|
||||
return createKronScheduler(
|
||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed
|
||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed
|
||||
)
|
||||
}
|
||||
|
||||
@ -69,4 +76,9 @@ fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleSchedu
|
||||
/**
|
||||
* Shortcut for [buildSchedule]
|
||||
*/
|
||||
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
|
||||
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
|
||||
|
||||
/**
|
||||
* Shortcut for [buildSchedule]
|
||||
*/
|
||||
fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this)
|
@ -21,20 +21,21 @@ class SchedulerBuilder(
|
||||
private var minutes: Array<Byte>? = null,
|
||||
private var hours: Array<Byte>? = null,
|
||||
private var dayOfMonth: Array<Byte>? = null,
|
||||
private var month: Array<Byte>? = null
|
||||
private var month: Array<Byte>? = null,
|
||||
private var year: Array<Int>? = null
|
||||
) {
|
||||
private fun <T : TimeBuilder> callAndReturn(
|
||||
initial: Array<Byte>?,
|
||||
private fun <I, T : TimeBuilder<I>> callAndReturn(
|
||||
initial: Array<I>?,
|
||||
builder: T,
|
||||
block: T.() -> Unit
|
||||
): Array<Byte>? {
|
||||
): List<I>? {
|
||||
builder.block()
|
||||
|
||||
val builderValue = builder.build()
|
||||
|
||||
return initial ?.let {
|
||||
builderValue ?.let { _ ->
|
||||
(it + builderValue).distinct().toTypedArray()
|
||||
(it + builderValue).distinct()
|
||||
} ?: builderValue
|
||||
} ?: builderValue
|
||||
}
|
||||
@ -47,7 +48,7 @@ class SchedulerBuilder(
|
||||
seconds,
|
||||
SecondsBuilder(),
|
||||
block
|
||||
)
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,7 +59,7 @@ class SchedulerBuilder(
|
||||
minutes,
|
||||
MinutesBuilder(),
|
||||
block
|
||||
)
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,7 +70,7 @@ class SchedulerBuilder(
|
||||
hours,
|
||||
HoursBuilder(),
|
||||
block
|
||||
)
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,7 +81,7 @@ class SchedulerBuilder(
|
||||
dayOfMonth,
|
||||
DaysOfMonthBuilder(),
|
||||
block
|
||||
)
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,7 +92,18 @@ class SchedulerBuilder(
|
||||
month,
|
||||
MonthsBuilder(),
|
||||
block
|
||||
)
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an year block
|
||||
*/
|
||||
fun years(block: YearsBuilder.() -> Unit) {
|
||||
year = callAndReturn(
|
||||
year,
|
||||
YearsBuilder(),
|
||||
block
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,5 +112,5 @@ class SchedulerBuilder(
|
||||
* @see dev.inmo.krontab.createSimpleScheduler
|
||||
* @see dev.inmo.krontab.internal.createKronScheduler
|
||||
*/
|
||||
fun build(): KronScheduler = createKronScheduler(seconds, minutes, hours, dayOfMonth, month)
|
||||
fun build(): KronScheduler = createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year)
|
||||
}
|
||||
|
@ -7,8 +7,9 @@ import dev.inmo.krontab.utils.clamp
|
||||
* This class was created for incapsulation of builder work with specified [restrictionsRange]. For example,
|
||||
* [include] function of [TimeBuilder] will always [clamp] incoming data using its [restrictionsRange]
|
||||
*/
|
||||
sealed class TimeBuilder (
|
||||
private val restrictionsRange: IntRange
|
||||
sealed class TimeBuilder<T : Number> (
|
||||
private val restrictionsRange: IntRange,
|
||||
private val converter: Converter<T>
|
||||
) {
|
||||
private var result: Set<Int>? = null
|
||||
|
||||
@ -119,11 +120,12 @@ sealed class TimeBuilder (
|
||||
*/
|
||||
fun includeFirst() = at(restrictionsRange.first)
|
||||
|
||||
internal fun build() = result ?.map { it.toByte() } ?.toTypedArray()
|
||||
internal fun build() = result ?.map(converter)
|
||||
}
|
||||
|
||||
class SecondsBuilder : TimeBuilder(secondsRange)
|
||||
class MinutesBuilder : TimeBuilder(minutesRange)
|
||||
class HoursBuilder : TimeBuilder(hoursRange)
|
||||
class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange)
|
||||
class MonthsBuilder : TimeBuilder(monthRange)
|
||||
class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
|
||||
class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
|
||||
class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
|
||||
class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
|
||||
class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
|
||||
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)
|
||||
|
@ -1,8 +1,7 @@
|
||||
package dev.inmo.krontab.collection
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.anyCronDateTime
|
||||
import dev.inmo.krontab.*
|
||||
import dev.inmo.krontab.internal.*
|
||||
import dev.inmo.krontab.internal.CronDateTimeScheduler
|
||||
import dev.inmo.krontab.internal.toNearDateTime
|
||||
@ -47,6 +46,6 @@ data class CollectionKronScheduler internal constructor(
|
||||
}
|
||||
|
||||
override suspend fun next(relatively: DateTime): DateTime {
|
||||
return schedulers.minOfOrNull { it.next(relatively) } ?: anyCronDateTime.toNearDateTime(relatively)
|
||||
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively)
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import dev.inmo.krontab.KronScheduler
|
||||
* @param seconds 0-59
|
||||
*/
|
||||
internal data class CronDateTime(
|
||||
val year: Int? = null,
|
||||
val month: Byte? = null,
|
||||
val dayOfMonth: Byte? = null,
|
||||
val hours: Byte? = null,
|
||||
@ -19,6 +20,7 @@ internal data class CronDateTime(
|
||||
val seconds: Byte? = null
|
||||
) {
|
||||
init {
|
||||
check(year ?.let { it in yearRange } ?: true)
|
||||
check(month ?.let { it in monthRange } ?: true)
|
||||
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true)
|
||||
check(hours?.let { it in hoursRange } ?: true)
|
||||
@ -32,7 +34,7 @@ internal data class CronDateTime(
|
||||
/**
|
||||
* @return The near [DateTime] which happens after [relativelyTo] or will be equal to [relativelyTo]
|
||||
*/
|
||||
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime {
|
||||
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? {
|
||||
var current = relativelyTo
|
||||
|
||||
seconds?.let {
|
||||
@ -63,7 +65,13 @@ internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()
|
||||
|
||||
month ?.let {
|
||||
val left = it - current.month0
|
||||
current += DateTimeSpan(months = if (left < 0) 1 else 0, days = left)
|
||||
current += DateTimeSpan(years = if (left < 0) 1 else 0, months = left)
|
||||
}
|
||||
|
||||
year ?.let {
|
||||
if (current.yearInt != it) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
@ -77,7 +85,8 @@ internal fun createKronScheduler(
|
||||
minutes: Array<Byte>? = null,
|
||||
hours: Array<Byte>? = null,
|
||||
dayOfMonth: Array<Byte>? = null,
|
||||
month: Array<Byte>? = null
|
||||
month: Array<Byte>? = null,
|
||||
years: Array<Int>? = null
|
||||
): KronScheduler {
|
||||
val resultCronDateTimes = mutableListOf(CronDateTime())
|
||||
|
||||
@ -101,5 +110,9 @@ internal fun createKronScheduler(
|
||||
previousCronDateTime.copy(month = currentTime)
|
||||
}
|
||||
|
||||
years ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int ->
|
||||
previousCronDateTime.copy(year = currentTime)
|
||||
}
|
||||
|
||||
return CronDateTimeScheduler(resultCronDateTimes.toList())
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.anyCronDateTime
|
||||
import dev.inmo.krontab.*
|
||||
import dev.inmo.krontab.collection.plus
|
||||
|
||||
/**
|
||||
@ -14,6 +13,7 @@ import dev.inmo.krontab.collection.plus
|
||||
* @see dev.inmo.krontab.EveryHourScheduler
|
||||
* @see dev.inmo.krontab.EveryDayOfMonthScheduler
|
||||
* @see dev.inmo.krontab.EveryMonthScheduler
|
||||
* @see dev.inmo.krontab.EveryYearScheduler
|
||||
*
|
||||
* @see dev.inmo.krontab.builder.buildSchedule
|
||||
* @see dev.inmo.krontab.builder.SchedulerBuilder
|
||||
@ -22,12 +22,12 @@ internal data class CronDateTimeScheduler internal constructor(
|
||||
internal val cronDateTimes: List<CronDateTime>
|
||||
) : KronScheduler {
|
||||
/**
|
||||
* @return Near date using [cronDateTimes] list and getting the [Iterable.min] one
|
||||
* @return Near date using [cronDateTimes] list and getting the [Iterable.minByOrNull] one
|
||||
*
|
||||
* @see toNearDateTime
|
||||
*/
|
||||
override suspend fun next(relatively: DateTime): DateTime {
|
||||
return cronDateTimes.map { it.toNearDateTime(relatively) }.minOrNull() ?: anyCronDateTime.toNearDateTime(relatively)
|
||||
return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull() ?: getAnyNext(relatively)
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ internal fun mergeCronDateTimeSchedulers(schedulers: List<CronDateTimeScheduler>
|
||||
|
||||
/**
|
||||
* @return New instance of [CronDateTimeScheduler] with all unique [CronDateTimeScheduler.cronDateTimes] of
|
||||
* [kronDateTimeSchedulers] included
|
||||
* [kronSchedulers] included
|
||||
*/
|
||||
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
|
||||
fun merge(kronSchedulers: List<KronScheduler>) = kronSchedulers.apply { dev.inmo.krontab.merge() }
|
||||
|
@ -2,7 +2,11 @@ package dev.inmo.krontab.internal
|
||||
|
||||
import dev.inmo.krontab.utils.clamp
|
||||
|
||||
private fun createSimpleScheduler(from: String, dataRange: IntRange): Array<Byte>? {
|
||||
typealias Converter<T> = (Int) -> T
|
||||
|
||||
internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() }
|
||||
internal val intToIntConverter: Converter<Int> = { it: Int -> it }
|
||||
private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter<T>): List<T>? {
|
||||
val things = from.split(",")
|
||||
|
||||
val results = things.flatMap {
|
||||
@ -31,18 +35,19 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange): Array<Byte
|
||||
}
|
||||
}
|
||||
|
||||
return results.map { it.toByte() }.toTypedArray()
|
||||
return results.map(dataConverter)
|
||||
}
|
||||
|
||||
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange)
|
||||
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange)
|
||||
internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange)
|
||||
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange)
|
||||
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange)
|
||||
internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }
|
||||
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange, intToByteConverter) ?.toTypedArray()
|
||||
|
||||
internal fun Array<Byte>.fillWith(
|
||||
internal fun <T> Array<T>.fillWith(
|
||||
whereToPut: MutableList<CronDateTime>,
|
||||
createFactory: (CronDateTime, Byte) -> CronDateTime
|
||||
createFactory: (CronDateTime, T) -> CronDateTime
|
||||
) {
|
||||
val previousValues = whereToPut.toList()
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE
|
||||
internal val monthRange = 0 .. 11
|
||||
internal val dayOfMonthRange = 0 .. 30
|
||||
internal val hoursRange = 0 .. 23
|
||||
|
@ -17,7 +17,7 @@ class SchedulerFlow(
|
||||
override suspend fun collectSafely(collector: FlowCollector<DateTime>) {
|
||||
while (true) {
|
||||
val now = DateTime.now()
|
||||
val nextTime = scheduler.next(now)
|
||||
val nextTime = scheduler.next(now) ?: break
|
||||
val sleepDelay = (nextTime - now).millisecondsLong
|
||||
delay(sleepDelay)
|
||||
collector.emit(nextTime)
|
||||
|
Loading…
Reference in New Issue
Block a user