diff --git a/README.md b/README.md index 9e37851..376cb2e 100644 --- a/README.md +++ b/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 { diff --git a/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt b/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt index 18daaff..f3077a9 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt @@ -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 KronScheduler.doOnce(noinline block: suspend () -> T): T { - delay((next() - DateTime.now()).millisecondsLong) + next() ?.let { + delay((it - DateTime.now()).millisecondsLong) + } return block() } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt index 4c5385c..b511b3f 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt @@ -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) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt b/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt new file mode 100644 index 0000000..480211f --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt @@ -0,0 +1,5 @@ +package dev.inmo.krontab + +data class KrontabTemplateWrapper( + val template: KrontabTemplate +) : KronScheduler by template.toKronScheduler() diff --git a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt index c3cadc7..ed641e6 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt @@ -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 } } } \ No newline at end of file diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index 845ad5d..d41c5b4 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -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) \ No newline at end of file +fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this) + +/** + * Shortcut for [buildSchedule] + */ +fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this) \ No newline at end of file diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt index d1db013..acc1334 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt @@ -21,20 +21,21 @@ class SchedulerBuilder( private var minutes: Array? = null, private var hours: Array? = null, private var dayOfMonth: Array? = null, - private var month: Array? = null + private var month: Array? = null, + private var year: Array? = null ) { - private fun callAndReturn( - initial: Array?, + private fun > callAndReturn( + initial: Array?, builder: T, block: T.() -> Unit - ): Array? { + ): List? { 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) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt index a250cbe..c4cc8a4 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt @@ -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 ( + private val restrictionsRange: IntRange, + private val converter: Converter ) { private var result: Set? = 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(secondsRange, intToByteConverter) +class MinutesBuilder : TimeBuilder(minutesRange, intToByteConverter) +class HoursBuilder : TimeBuilder(hoursRange, intToByteConverter) +class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange, intToByteConverter) +class MonthsBuilder : TimeBuilder(monthRange, intToByteConverter) +class YearsBuilder : TimeBuilder(yearRange, intToIntConverter) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt index 978140c..1f552b2 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt @@ -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) } } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt index ed89c96..269cd9b 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt @@ -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? = null, hours: Array? = null, dayOfMonth: Array? = null, - month: Array? = null + month: Array? = null, + years: Array? = 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()) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt index 485fb3c..23de605 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt @@ -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 ) : 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 /** * @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) = kronSchedulers.apply { dev.inmo.krontab.merge() } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt index 04b71ff..f2f8648 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt @@ -2,7 +2,11 @@ package dev.inmo.krontab.internal import dev.inmo.krontab.utils.clamp -private fun createSimpleScheduler(from: String, dataRange: IntRange): Array? { +typealias Converter = (Int) -> T + +internal val intToByteConverter: Converter = { it: Int -> it.toByte() } +internal val intToIntConverter: Converter = { it: Int -> it } +private fun createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter): List? { val things = from.split(",") val results = things.flatMap { @@ -31,18 +35,19 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange): Array.fillWith( +internal fun Array.fillWith( whereToPut: MutableList, - createFactory: (CronDateTime, Byte) -> CronDateTime + createFactory: (CronDateTime, T) -> CronDateTime ) { val previousValues = whereToPut.toList() diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt index 58586f1..ec4359b 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt @@ -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 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt index eca31f2..14cc761 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt @@ -17,7 +17,7 @@ class SchedulerFlow( override suspend fun collectSafely(collector: FlowCollector) { 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)