From 28d5665a1456ba772976ccd6caabeb59a281c92f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 29 Apr 2022 21:57:10 +0600 Subject: [PATCH] fix of #31 and several refactorings --- CHANGELOG.md | 2 + .../kotlin/dev/inmo/krontab/Executes.kt | 100 ++++++++---------- .../dev/inmo/krontab/SchedulerShortcuts.kt | 7 ++ .../kotlin/dev/inmo/krontab/StringParser.kt | 47 +++++--- .../inmo/krontab/builder/SchedulerBuilder.kt | 29 ++++- .../dev/inmo/krontab/builder/TimeBuilder.kt | 1 + .../dev/inmo/krontab/internal/CronDateTime.kt | 45 ++++++-- .../krontab/internal/CronDateTimeScheduler.kt | 1 + .../dev/inmo/krontab/internal/Defaults.kt | 4 + .../dev/inmo/krontab/internal/Parser.kt | 2 + .../dev/inmo/krontab/internal/Ranges.kt | 1 + .../dev/inmo/krontab/utils/SchedulerFlow.kt | 5 +- .../dev/inmo/krontab/utils/StringParseTest.kt | 44 +++++++- 13 files changed, 207 insertions(+), 81 deletions(-) create mode 100644 src/commonMain/kotlin/dev/inmo/krontab/internal/Defaults.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d6cf0d..695dd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Versions * `Coroutines`: `1.6.1` * `Klock`: `2.7.0` +* Deprecate `do*Local` due to their redundancy (use `do*` instead) +* Add support of milliseconds as optional parameter after month (fix of [#31](https://github.com/InsanusMokrassar/krontab/issues/31)) ## 0.7.1 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt b/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt index dc1de47..fc77cc7 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/Executes.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlin.coroutines.coroutineContext + /** * Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation. * @@ -13,13 +14,23 @@ import kotlin.coroutines.coroutineContext * * WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately */ -suspend inline fun KronScheduler.doOnceLocal(noinline block: suspend (DateTime) -> T): T { +suspend inline fun KronScheduler.doOnce(noinline block: suspend (DateTime) -> T): T { val time = nextOrNow().also { delay((it - DateTime.now()).millisecondsLong) } return block(time) } +/** + * 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 + */ +@Deprecated("Replaceable", ReplaceWith("doOnce", "dev.inmo.krontab.doOnce")) +suspend inline fun KronScheduler.doOnceLocal(noinline block: suspend (DateTime) -> T): T = doOnce(block) + /** * Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation. * @@ -37,25 +48,16 @@ suspend inline fun KronScheduler.doOnceTz(noinline block: suspend (DateTimeT } /** - * 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 = doOnceLocal { _ -> block() } - -/** - * Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it + * Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it * @see buildSchedule */ suspend inline fun doOnce( scheduleConfig: String, noinline block: suspend (DateTime) -> T -) = buildSchedule(scheduleConfig).doOnceLocal(block) +) = buildSchedule(scheduleConfig).doOnce(block) /** - * Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it + * Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it * @see buildSchedule */ suspend inline fun doOnceTz( @@ -63,24 +65,20 @@ suspend inline fun doOnceTz( noinline block: suspend (DateTimeTz) -> T ) = buildSchedule(scheduleConfig).doOnceTz(block) -/** - * Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it - * @see buildSchedule - */ -suspend inline fun doOnce( - scheduleConfig: String, - noinline block: suspend () -> T -) = doOnce(scheduleConfig) { _ -> block() } - /** * Will execute [block] while it will return true as a result of its calculation */ -suspend inline fun KronScheduler.doWhileLocal(noinline block: suspend (DateTime) -> Boolean) { +suspend inline fun KronScheduler.doWhile(noinline block: suspend (DateTime) -> Boolean) { do { delay(1L) - } while (doOnceLocal(block)) + } while (doOnce(block)) } +/** + * Will execute [block] while it will return true as a result of its calculation + */ +@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile")) +suspend inline fun KronScheduler.doWhileLocal(noinline block: suspend (DateTime) -> Boolean) = doWhile(block) /** * Will execute [block] while it will return true as a result of its calculation @@ -92,19 +90,25 @@ suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) } /** - * Will execute [block] while it will return true as a result of its calculation + * Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block] + * + * @see buildSchedule */ -suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean) = doWhileLocal { block() } +suspend inline fun doWhile( + scheduleConfig: String, + noinline block: suspend (DateTime) -> Boolean +) = buildSchedule(scheduleConfig).doWhile(block) /** * Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block] * * @see buildSchedule */ +@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile")) suspend inline fun doWhileLocal( scheduleConfig: String, noinline block: suspend (DateTime) -> Boolean -) = buildSchedule(scheduleConfig).doWhileLocal(block) +) = doWhile(scheduleConfig, block) /** * Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block] @@ -116,24 +120,19 @@ suspend inline fun doWhileTz( noinline block: suspend (DateTimeTz) -> Boolean ) = buildSchedule(scheduleConfig).doWhileTz(block) -/** - * Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block] - * - * @see buildSchedule - */ -suspend inline fun doWhile( - scheduleConfig: String, - noinline block: suspend () -> Boolean -) = doWhileLocal(scheduleConfig) { block() } - /** * Will execute [block] without any checking of result */ -suspend inline fun KronScheduler.doInfinityLocal(noinline block: suspend (DateTime) -> Unit) = doWhileLocal { +suspend inline fun KronScheduler.doInfinity(noinline block: suspend (DateTime) -> Unit) = doWhile { block(it) coroutineContext.isActive } +/** + * Will execute [block] without any checking of result + */ +@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity")) +suspend inline fun KronScheduler.doInfinityLocal(noinline block: suspend (DateTime) -> Unit) = doInfinity(block) /** * Will execute [block] without any checking of result @@ -144,22 +143,25 @@ suspend inline fun KronScheduler.doInfinityTz(noinline block: suspend (DateTimeT } /** - * Will execute [block] without any checking of result + * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block] + * + * @see buildSchedule */ -suspend inline fun KronScheduler.doInfinity(noinline block: suspend () -> Unit) = doWhile { - block() - coroutineContext.isActive -} +suspend inline fun doInfinity( + scheduleConfig: String, + noinline block: suspend (DateTime) -> Unit +) = buildSchedule(scheduleConfig).doInfinity(block) /** * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block] * * @see buildSchedule */ +@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity")) suspend inline fun doInfinityLocal( scheduleConfig: String, noinline block: suspend (DateTime) -> Unit -) = buildSchedule(scheduleConfig).doInfinityLocal(block) +) = doInfinity(scheduleConfig, block) /** * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block] @@ -170,13 +172,3 @@ suspend inline fun doInfinityTz( scheduleConfig: String, noinline block: suspend (DateTimeTz) -> Unit ) = buildSchedule(scheduleConfig).doInfinityTz(block) - -/** - * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block] - * - * @see buildSchedule - */ -suspend inline fun doInfinity( - scheduleConfig: String, - noinline block: suspend () -> Unit -) = buildSchedule(scheduleConfig).doInfinity(block) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt index d351b61..9dc6043 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt @@ -16,6 +16,13 @@ val AnyTimeScheduler: KronScheduler by lazy { CronDateTimeScheduler(anyCronDateTime) } +/** + * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one millisecond + */ +val EveryMillisecondScheduler: KronScheduler by lazy { + buildSchedule { milliseconds { 0 every 1 } } +} + /** * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one second */ diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index e2a1b99..9e1e6da 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -14,14 +14,15 @@ typealias KrontabTemplate = String /** * Parse [incoming] string and adapt according to next format: "* * * * *" where order of things: * - * * seconds - * * minutes - * * hours - * * dayOfMonth - * * month - * * (optional) year - * * (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00) offset - * * (optional) (can be placed anywhere after month) dayOfWeek + * * **seconds** + * * **minutes** + * * **hours** + * * **dayOfMonth** + * * **month** + * * **year** (optional) + * * **offset** (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00) + * * **dayOfWeek** (optional) (can be placed anywhere after month) + * * **milliseconds** (optional) (can be placed anywhere after month) (must be marked with `ms` at the end, for example: 500ms; 100-200ms) * * And each one (except of offsets) have next format: * @@ -48,18 +49,21 @@ typealias KrontabTemplate = String * * Months ranges can be found in [monthRange] * * Years ranges can be found in [yearRange] (in fact - any [Int]) * * WeekDay (timezone) ranges can be found in [dayOfWeekRange] + * * Milliseconds ranges can be found in [millisecondsRange] * * Examples: * * * "0/5 * * * *" for every five seconds triggering * * "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 * * * 500ms" for every 15th seconds in a half of each hour when milliseconds equal to 500 * * "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 60o" for triggering in near first second of second minute of third hour of fourth day of may with timezone UTC+01:00 * * "1 2 3 F,4,L 5 60o 0-2w" for triggering in near first second of second minute of third hour of fourth day of may in case if it will be in Sunday-Tuesday week days with timezone UTC+01:00 * * "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 * * "1 2 3 F,4,L 5 2021 60o" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year with timezone UTC+01:00 * * "1 2 3 F,4,L 5 2021 60o 0-2w" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 + * * "1 2 3 F,4,L 5 2021 60o 0-2w 500ms" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500 * * @return In case when offset parameter is absent in [incoming] will be used [createSimpleScheduler] method and * returned [CronDateTimeScheduler]. In case when offset parameter there is in [incoming] [KrontabTemplate] will be used @@ -73,18 +77,22 @@ fun createSimpleScheduler( var offsetParsed: Int? = null var dayOfWeekParsed: Array? = null var yearParsed: Array? = null + var millisecondsParsed: Array? = null val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also { listOfNotNull( it.getOrNull(5), it.getOrNull(6), - it.getOrNull(7) + it.getOrNull(7), + it.getOrNull(8) ).forEach { val offsetFromString = parseOffset(it) val dayOfWeekFromString = parseWeekDay(it) + val millisecondsFromString = parseMilliseconds(it) offsetParsed = offsetParsed ?: offsetFromString dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString + millisecondsParsed = millisecondsParsed ?: millisecondsFromString when { - dayOfWeekFromString != null || offsetFromString != null -> return@forEach + dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach yearParsed == null -> { yearParsed = parseYears(it) } @@ -100,10 +108,25 @@ fun createSimpleScheduler( return offsetParsed ?.let { offset -> createKronSchedulerWithOffset( - secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed, TimezoneOffset(offset.minutes) + secondsParsed, + minutesParsed, + hoursParsed, + dayOfMonthParsed, + monthParsed, + yearParsed, + dayOfWeekParsed, + TimezoneOffset(offset.minutes), + millisecondsParsed ?: millisecondsArrayDefault ) } ?: createKronScheduler( - secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed + secondsParsed, + minutesParsed, + hoursParsed, + dayOfMonthParsed, + monthParsed, + yearParsed, + dayOfWeekParsed, + millisecondsParsed ?: millisecondsArrayDefault ) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt index 768a127..6fb441e 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt @@ -4,6 +4,7 @@ import com.soywiz.klock.TimezoneOffset import com.soywiz.klock.minutes import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.KronSchedulerTz +import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.createKronScheduler import dev.inmo.krontab.internal.createKronSchedulerWithOffset import dev.inmo.krontab.utils.Minutes @@ -45,7 +46,8 @@ class SchedulerBuilder( private var month: Array? = null, private var year: Array? = null, private var dayOfWeek: Array? = null, - private val offset: Minutes? = null + private val offset: Minutes? = null, + private var milliseconds: Array? = null ) { private fun > callAndReturn( initial: Array?, @@ -63,6 +65,17 @@ class SchedulerBuilder( } ?: builderValue } + /** + * Starts an milliseconds block + */ + fun milliseconds(block: MillisecondsBuilder.() -> Unit) { + milliseconds = callAndReturn( + milliseconds, + MillisecondsBuilder(), + block + ) ?.toTypedArray() + } + /** * Starts an seconds block */ @@ -147,6 +160,16 @@ class SchedulerBuilder( * @see dev.inmo.krontab.internal.createKronScheduler */ fun build(): KronScheduler = offset ?.let { - createKronSchedulerWithOffset(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, TimezoneOffset(it.minutes)) - } ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek) + createKronSchedulerWithOffset( + seconds, + minutes, + hours, + dayOfMonth, + month, + year, + dayOfWeek, + TimezoneOffset(it.minutes), + milliseconds ?: millisecondsArrayDefault + ) + } ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, milliseconds ?: millisecondsArrayDefault) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt index ad21c6d..fa34d23 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt @@ -122,6 +122,7 @@ sealed class TimeBuilder ( internal fun build() = result ?.map(converter) } +class MillisecondsBuilder : TimeBuilder(millisecondsRange, intToShortConverter) class SecondsBuilder : TimeBuilder(secondsRange, intToByteConverter) class MinutesBuilder : TimeBuilder(minutesRange, intToByteConverter) class HoursBuilder : TimeBuilder(hoursRange, intToByteConverter) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt index bfc979e..729a784 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt @@ -20,7 +20,8 @@ internal data class CronDateTime( val daysOfMonth: Array? = null, val hours: Array? = null, val minutes: Array? = null, - val seconds: Array? = null + val seconds: Array? = null, + val milliseconds: Array? = millisecondsArrayDefault ) { init { check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true) @@ -30,12 +31,13 @@ internal data class CronDateTime( check(hours?.all { it in hoursRange } ?: true) check(minutes?.all { it in minutesRange } ?: true) check(seconds?.all { it in secondsRange } ?: true) + check(milliseconds?.all { it in millisecondsRange } ?: true) } internal val calculators = listOf( years ?.let { NearDateTimeCalculatorYears(it) }, daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) }, - NearDateTimeCalculatorMillis(arrayOf(0)), + milliseconds ?.let { NearDateTimeCalculatorMillis(it) }, seconds ?.let { NearDateTimeCalculatorSeconds(it) }, minutes ?.let { NearDateTimeCalculatorMinutes(it) }, hours ?.let { NearDateTimeCalculatorHours(it) }, @@ -65,9 +67,10 @@ internal fun createCronDateTime( dayOfMonth: Array? = null, month: Array? = null, years: Array? = null, - weekDays: Array? = null + weekDays: Array? = null, + milliseconds: Array? = millisecondsArrayDefault ): CronDateTime { - return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds) + return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds, milliseconds) } /** @@ -80,8 +83,20 @@ internal fun createKronScheduler( dayOfMonth: Array? = null, month: Array? = null, years: Array? = null, - weekDays: Array? = null -): KronScheduler = CronDateTimeScheduler(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays)) + weekDays: Array? = null, + milliseconds: Array? = millisecondsArrayDefault +): KronScheduler = CronDateTimeScheduler( + createCronDateTime( + seconds, + minutes, + hours, + dayOfMonth, + month, + years, + weekDays, + milliseconds + ) +) /** * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data */ @@ -93,8 +108,21 @@ internal fun createKronSchedulerWithOffset( month: Array? = null, years: Array? = null, weekDays: Array? = null, - offset: TimezoneOffset -): KronScheduler = CronDateTimeSchedulerTz(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset) + offset: TimezoneOffset, + milliseconds: Array? = millisecondsArrayDefault +): KronScheduler = CronDateTimeSchedulerTz( + createCronDateTime( + seconds, + minutes, + hours, + dayOfMonth, + month, + years, + weekDays, + milliseconds + ), + offset +) internal fun List.merge() = CronDateTime( flatMap { it.daysOfWeek ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, @@ -104,4 +132,5 @@ internal fun List.merge() = CronDateTime( flatMap { it.hours ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, flatMap { it.minutes ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, flatMap { it.seconds ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, + flatMap { it.milliseconds ?.toList() ?: listOf(0) }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, ) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt index 87dfdc9..c76195e 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt @@ -7,6 +7,7 @@ import dev.inmo.krontab.KronScheduler * Cron-oriented realisation of [KronScheduler] * * @see dev.inmo.krontab.AnyTimeScheduler + * @see dev.inmo.krontab.EveryMillisecondScheduler * @see dev.inmo.krontab.EverySecondScheduler * @see dev.inmo.krontab.EveryMinuteScheduler * @see dev.inmo.krontab.EveryHourScheduler diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Defaults.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Defaults.kt new file mode 100644 index 0000000..b1e534b --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Defaults.kt @@ -0,0 +1,4 @@ +package dev.inmo.krontab.internal + +internal const val millisecondsDefault: Short = 0 +internal val millisecondsArrayDefault: Array = arrayOf(millisecondsDefault) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt index a162d6c..c02cfa0 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt @@ -3,6 +3,7 @@ package dev.inmo.krontab.internal typealias Converter = (Int) -> T internal val intToByteConverter: Converter = { it: Int -> it.toByte() } +internal val intToShortConverter: Converter = { it: Int -> it.toShort() } internal val intToIntConverter: Converter = { it: Int -> it } private fun createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter): List? { val things = from.split(",") @@ -44,6 +45,7 @@ internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfM 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 parseMilliseconds(from: String?) = from ?.let { if (it.endsWith("ms")) createSimpleScheduler(from.removeSuffix("ms"), millisecondsRange, intToShortConverter) ?.toTypedArray() else null } internal fun Array.fillWith( whereToPut: MutableList, diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt index e0a03b7..6dab38d 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt @@ -7,3 +7,4 @@ internal val dayOfMonthRange = 0 .. 30 internal val hoursRange = 0 .. 23 internal val minutesRange = 0 .. 59 internal val secondsRange = minutesRange +internal val millisecondsRange = 0 .. 999 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt index b536f81..55f8989 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt @@ -6,6 +6,7 @@ import dev.inmo.krontab.* import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.isActive /** * This [Flow] will trigger emitting each near time which will be returned from [this] [KronScheduler] with attention to @@ -25,11 +26,11 @@ fun KronScheduler.asTzFlow(): Flow = channelFlow { * This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s * * @see channelFlow - * @see KronScheduler.doInfinityLocal + * @see KronScheduler.doInfinity */ @FlowPreview fun KronScheduler.asFlow(): Flow = channelFlow { - doInfinityLocal { + doInfinity { send(it) } } diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt index 64deafd..b4b0ffe 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt @@ -6,8 +6,8 @@ import dev.inmo.krontab.buildSchedule import kotlinx.coroutines.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.takeWhile -import kotlin.test.Test -import kotlin.test.assertEquals +import kotlin.math.floor +import kotlin.test.* @ExperimentalCoroutinesApi @FlowPreview @@ -29,6 +29,23 @@ class StringParseTest { assertEquals(mustBeCollected, collected) } } + @Test + fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() { + val kronScheduler = buildSchedule("*/1 * * * * 500ms") + + val flow = kronScheduler.asFlow() + + runTest { + val mustBeCollected = 10 + var collected = 0 + flow.takeWhile { + collected < mustBeCollected + }.collect { + collected++ + } + assertEquals(mustBeCollected, collected) + } + } @Test fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() { @@ -74,6 +91,29 @@ class StringParseTest { flow.takeWhile { ranges.isNotEmpty() }.collect { ranges.remove(it.seconds) collected++ + assertTrue(collected <= expectedCollects) + } + assertEquals(expectedCollects, collected) + } + } + @Test + fun testNextIsCorrectlyWorkEverySeveralMillisecondsRangeBuiltOnString() { + val rangesEnds = listOf(0, 200, 500, 750) + val kronScheduler = buildSchedule("* * * * * ${rangesEnds.joinToString(",") { "$it" }}ms") + + runTest { + val ranges = rangesEnds.toMutableList() + val expectedCollects = ranges.size + var collected = 0 + + var currentTime = DateTime.now() + while (ranges.isNotEmpty()) { + val nextTrigger = kronScheduler.next(currentTime) ?: error("Strangely unable to get next time") + + ranges.remove(nextTrigger.milliseconds) + collected++ + + currentTime = nextTrigger + 1.milliseconds } assertEquals(expectedCollects, collected) }