add years, KrontabWrapper, new function to create scheduler from template and update readme

This commit is contained in:
InsanusMokrassar 2021-01-02 21:35:08 +06:00
parent 476239faa6
commit 365adb1016
14 changed files with 122 additions and 56 deletions

View File

@ -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 Developers can use more simple way to configure repeat times is string. String configuring
like a `crontab`, but with a little bit different meanings: like a `crontab`, but with a little bit different meanings:
``` ```
/-------- Seconds /---------- Seconds
| /------ Minutes | /-------- Minutes
| | /---- Hours | | /------ Hours
| | | /-- Days of months | | | /---- Days of months
| | | | / Months | | | | /-- Months
| | | | | | | | | | / (optional) Year
* * * * * * * * * * *
``` ```
It is different with original `crontab` syntax for the reason, that expected that in practice developers 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 will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use
like: something like:
```kotlin ```kotlin
doWhile("/5 * * * *") { doWhile("/5 * * * *") {
@ -73,7 +73,7 @@ doWhile("/5 * * * *") {
} }
``` ```
Or more version: An other version:
```kotlin ```kotlin
doInfinity("/5 * * * *") { doInfinity("/5 * * * *") {
@ -85,7 +85,7 @@ Both of examples will print `Called` message every five seconds.
### Config via builder ### 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 ```kotlin
val kronScheduler = buildSchedule { val kronScheduler = buildSchedule {

View File

@ -7,9 +7,13 @@ import kotlinx.coroutines.delay
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation. * 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!!! 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 { 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() return block()
} }

View File

@ -1,6 +1,7 @@
package dev.inmo.krontab package dev.inmo.krontab
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.internal.toNearDateTime
/** /**
* This interface was created for abstraction of [next] operation. Currently, there is only * 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 * @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)

View File

@ -0,0 +1,5 @@
package dev.inmo.krontab
data class KrontabTemplateWrapper(
val template: KrontabTemplate
) : KronScheduler by template.toKronScheduler()

View File

@ -1,12 +1,15 @@
package dev.inmo.krontab package dev.inmo.krontab
import com.soywiz.klock.DateTime
import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.builder.buildSchedule
import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.CronDateTime import dev.inmo.krontab.internal.CronDateTime
import dev.inmo.krontab.internal.CronDateTimeScheduler import dev.inmo.krontab.internal.CronDateTimeScheduler
internal val anyCronDateTime by lazy { internal val anyCronDateTime by lazy {
CronDateTime() CronDateTime()
} }
internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(relatively)!!
/** /**
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now]
@ -48,4 +51,11 @@ val EveryDayOfMonthScheduler: KronScheduler by lazy {
*/ */
val EveryMonthScheduler: KronScheduler by lazy { val EveryMonthScheduler: KronScheduler by lazy {
buildSchedule { months { 0 every 1 } } 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 } }
} }

View File

@ -16,6 +16,7 @@ typealias KrontabTemplate = String
* * hours * * hours
* * dayOfMonth * * dayOfMonth
* * month * * month
* * (optional) year
* *
* And each one have next format: * And each one have next format:
* *
@ -37,6 +38,7 @@ typealias KrontabTemplate = String
* * Hours ranges can be found in [hoursRange] * * Hours ranges can be found in [hoursRange]
* * Days of month ranges can be found in [dayOfMonthRange] * * Days of month ranges can be found in [dayOfMonthRange]
* * Months ranges can be found in [monthRange] * * Months ranges can be found in [monthRange]
* * Years ranges can be found in [yearRange] (in fact - any [Int])
* *
* Examples: * Examples:
* *
@ -44,20 +46,25 @@ typealias KrontabTemplate = String
* * "0/5,L * * * *" for every five seconds triggering and on 59 second * * "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 * * "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" 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 * @see dev.inmo.krontab.internal.createKronScheduler
*/ */
fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler { 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 secondsParsed = parseSeconds(secondsSource)
val minutesParsed = parseMinutes(minutesSource) val minutesParsed = parseMinutes(minutesSource)
val hoursParsed = parseHours(hoursSource) val hoursParsed = parseHours(hoursSource)
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource) val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
val monthParsed = parseMonths(monthSource) val monthParsed = parseMonths(monthSource)
val yearParsed = parseYears(yearSource)
return createKronScheduler( 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] * Shortcut for [buildSchedule]
*/ */
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this) fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
/**
* Shortcut for [buildSchedule]
*/
fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this)

View File

@ -21,20 +21,21 @@ class SchedulerBuilder(
private var minutes: Array<Byte>? = null, private var minutes: Array<Byte>? = null,
private var hours: Array<Byte>? = null, private var hours: Array<Byte>? = null,
private var dayOfMonth: 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( private fun <I, T : TimeBuilder<I>> callAndReturn(
initial: Array<Byte>?, initial: Array<I>?,
builder: T, builder: T,
block: T.() -> Unit block: T.() -> Unit
): Array<Byte>? { ): List<I>? {
builder.block() builder.block()
val builderValue = builder.build() val builderValue = builder.build()
return initial ?.let { return initial ?.let {
builderValue ?.let { _ -> builderValue ?.let { _ ->
(it + builderValue).distinct().toTypedArray() (it + builderValue).distinct()
} ?: builderValue } ?: builderValue
} ?: builderValue } ?: builderValue
} }
@ -47,7 +48,7 @@ class SchedulerBuilder(
seconds, seconds,
SecondsBuilder(), SecondsBuilder(),
block block
) ) ?.toTypedArray()
} }
/** /**
@ -58,7 +59,7 @@ class SchedulerBuilder(
minutes, minutes,
MinutesBuilder(), MinutesBuilder(),
block block
) ) ?.toTypedArray()
} }
/** /**
@ -69,7 +70,7 @@ class SchedulerBuilder(
hours, hours,
HoursBuilder(), HoursBuilder(),
block block
) ) ?.toTypedArray()
} }
/** /**
@ -80,7 +81,7 @@ class SchedulerBuilder(
dayOfMonth, dayOfMonth,
DaysOfMonthBuilder(), DaysOfMonthBuilder(),
block block
) ) ?.toTypedArray()
} }
/** /**
@ -91,7 +92,18 @@ class SchedulerBuilder(
month, month,
MonthsBuilder(), MonthsBuilder(),
block 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.createSimpleScheduler
* @see dev.inmo.krontab.internal.createKronScheduler * @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)
} }

View File

@ -7,8 +7,9 @@ import dev.inmo.krontab.utils.clamp
* This class was created for incapsulation of builder work with specified [restrictionsRange]. For example, * 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] * [include] function of [TimeBuilder] will always [clamp] incoming data using its [restrictionsRange]
*/ */
sealed class TimeBuilder ( sealed class TimeBuilder<T : Number> (
private val restrictionsRange: IntRange private val restrictionsRange: IntRange,
private val converter: Converter<T>
) { ) {
private var result: Set<Int>? = null private var result: Set<Int>? = null
@ -119,11 +120,12 @@ sealed class TimeBuilder (
*/ */
fun includeFirst() = at(restrictionsRange.first) fun includeFirst() = at(restrictionsRange.first)
internal fun build() = result ?.map { it.toByte() } ?.toTypedArray() internal fun build() = result ?.map(converter)
} }
class SecondsBuilder : TimeBuilder(secondsRange) class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
class MinutesBuilder : TimeBuilder(minutesRange) class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
class HoursBuilder : TimeBuilder(hoursRange) class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange) class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
class MonthsBuilder : TimeBuilder(monthRange) class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)

View File

@ -1,8 +1,7 @@
package dev.inmo.krontab.collection package dev.inmo.krontab.collection
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.*
import dev.inmo.krontab.anyCronDateTime
import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.CronDateTimeScheduler import dev.inmo.krontab.internal.CronDateTimeScheduler
import dev.inmo.krontab.internal.toNearDateTime import dev.inmo.krontab.internal.toNearDateTime
@ -47,6 +46,6 @@ data class CollectionKronScheduler internal constructor(
} }
override suspend fun next(relatively: DateTime): DateTime { 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)
} }
} }

View File

@ -12,6 +12,7 @@ import dev.inmo.krontab.KronScheduler
* @param seconds 0-59 * @param seconds 0-59
*/ */
internal data class CronDateTime( internal data class CronDateTime(
val year: Int? = null,
val month: Byte? = null, val month: Byte? = null,
val dayOfMonth: Byte? = null, val dayOfMonth: Byte? = null,
val hours: Byte? = null, val hours: Byte? = null,
@ -19,6 +20,7 @@ internal data class CronDateTime(
val seconds: Byte? = null val seconds: Byte? = null
) { ) {
init { init {
check(year ?.let { it in yearRange } ?: true)
check(month ?.let { it in monthRange } ?: true) check(month ?.let { it in monthRange } ?: true)
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true) check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true)
check(hours?.let { it in hoursRange } ?: 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] * @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 var current = relativelyTo
seconds?.let { seconds?.let {
@ -63,7 +65,13 @@ internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()
month ?.let { month ?.let {
val left = it - current.month0 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 return current
@ -77,7 +85,8 @@ internal fun createKronScheduler(
minutes: Array<Byte>? = null, minutes: Array<Byte>? = null,
hours: Array<Byte>? = null, hours: Array<Byte>? = null,
dayOfMonth: Array<Byte>? = null, dayOfMonth: Array<Byte>? = null,
month: Array<Byte>? = null month: Array<Byte>? = null,
years: Array<Int>? = null
): KronScheduler { ): KronScheduler {
val resultCronDateTimes = mutableListOf(CronDateTime()) val resultCronDateTimes = mutableListOf(CronDateTime())
@ -101,5 +110,9 @@ internal fun createKronScheduler(
previousCronDateTime.copy(month = currentTime) previousCronDateTime.copy(month = currentTime)
} }
years ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int ->
previousCronDateTime.copy(year = currentTime)
}
return CronDateTimeScheduler(resultCronDateTimes.toList()) return CronDateTimeScheduler(resultCronDateTimes.toList())
} }

View File

@ -1,8 +1,7 @@
package dev.inmo.krontab.internal package dev.inmo.krontab.internal
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.*
import dev.inmo.krontab.anyCronDateTime
import dev.inmo.krontab.collection.plus 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.EveryHourScheduler
* @see dev.inmo.krontab.EveryDayOfMonthScheduler * @see dev.inmo.krontab.EveryDayOfMonthScheduler
* @see dev.inmo.krontab.EveryMonthScheduler * @see dev.inmo.krontab.EveryMonthScheduler
* @see dev.inmo.krontab.EveryYearScheduler
* *
* @see dev.inmo.krontab.builder.buildSchedule * @see dev.inmo.krontab.builder.buildSchedule
* @see dev.inmo.krontab.builder.SchedulerBuilder * @see dev.inmo.krontab.builder.SchedulerBuilder
@ -22,12 +22,12 @@ internal data class CronDateTimeScheduler internal constructor(
internal val cronDateTimes: List<CronDateTime> internal val cronDateTimes: List<CronDateTime>
) : KronScheduler { ) : 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 * @see toNearDateTime
*/ */
override suspend fun next(relatively: DateTime): DateTime { 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 * @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")) @Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
fun merge(kronSchedulers: List<KronScheduler>) = kronSchedulers.apply { dev.inmo.krontab.merge() } fun merge(kronSchedulers: List<KronScheduler>) = kronSchedulers.apply { dev.inmo.krontab.merge() }

View File

@ -2,7 +2,11 @@ package dev.inmo.krontab.internal
import dev.inmo.krontab.utils.clamp 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 things = from.split(",")
val results = things.flatMap { 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 parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange) internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray()
internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange) internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray()
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange) internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange, intToByteConverter) ?.toTypedArray()
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange) 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>, whereToPut: MutableList<CronDateTime>,
createFactory: (CronDateTime, Byte) -> CronDateTime createFactory: (CronDateTime, T) -> CronDateTime
) { ) {
val previousValues = whereToPut.toList() val previousValues = whereToPut.toList()

View File

@ -1,5 +1,6 @@
package dev.inmo.krontab.internal package dev.inmo.krontab.internal
internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE
internal val monthRange = 0 .. 11 internal val monthRange = 0 .. 11
internal val dayOfMonthRange = 0 .. 30 internal val dayOfMonthRange = 0 .. 30
internal val hoursRange = 0 .. 23 internal val hoursRange = 0 .. 23

View File

@ -17,7 +17,7 @@ class SchedulerFlow(
override suspend fun collectSafely(collector: FlowCollector<DateTime>) { override suspend fun collectSafely(collector: FlowCollector<DateTime>) {
while (true) { while (true) {
val now = DateTime.now() val now = DateTime.now()
val nextTime = scheduler.next(now) val nextTime = scheduler.next(now) ?: break
val sleepDelay = (nextTime - now).millisecondsLong val sleepDelay = (nextTime - now).millisecondsLong
delay(sleepDelay) delay(sleepDelay)
collector.emit(nextTime) collector.emit(nextTime)