fix of #31 and several refactorings

This commit is contained in:
InsanusMokrassar 2022-04-29 21:57:10 +06:00
parent f98fd5ddb7
commit 28d5665a14
13 changed files with 207 additions and 81 deletions

View File

@ -5,6 +5,8 @@
* Versions * Versions
* `Coroutines`: `1.6.1` * `Coroutines`: `1.6.1`
* `Klock`: `2.7.0` * `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 ## 0.7.1

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
/** /**
* 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.
* *
@ -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 * WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately
*/ */
suspend inline fun <T> KronScheduler.doOnceLocal(noinline block: suspend (DateTime) -> T): T { suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend (DateTime) -> T): T {
val time = nextOrNow().also { val time = nextOrNow().also {
delay((it - DateTime.now()).millisecondsLong) delay((it - DateTime.now()).millisecondsLong)
} }
return block(time) 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 <T> KronScheduler.doOnceLocal(noinline block: suspend (DateTime) -> T): T = doOnce(block)
/** /**
* 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.
* *
@ -37,25 +48,16 @@ suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeT
} }
/** /**
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation. * Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it
*
* 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 = doOnceLocal { _ -> block() }
/**
* Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it
* @see buildSchedule * @see buildSchedule
*/ */
suspend inline fun <T> doOnce( suspend inline fun <T> doOnce(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend (DateTime) -> T 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 * @see buildSchedule
*/ */
suspend inline fun <T> doOnceTz( suspend inline fun <T> doOnceTz(
@ -63,24 +65,20 @@ suspend inline fun <T> doOnceTz(
noinline block: suspend (DateTimeTz) -> T noinline block: suspend (DateTimeTz) -> T
) = buildSchedule(scheduleConfig).doOnceTz(block) ) = buildSchedule(scheduleConfig).doOnceTz(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it
* @see buildSchedule
*/
suspend inline fun <T> doOnce(
scheduleConfig: String,
noinline block: suspend () -> T
) = doOnce(scheduleConfig) { _ -> block() }
/** /**
* Will execute [block] while it will return true as a result of its calculation * 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 { do {
delay(1L) 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 * 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] * Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
* *
* @see buildSchedule * @see buildSchedule
*/ */
@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile"))
suspend inline fun doWhileLocal( suspend inline fun doWhileLocal(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend (DateTime) -> Boolean noinline block: suspend (DateTime) -> Boolean
) = buildSchedule(scheduleConfig).doWhileLocal(block) ) = doWhile(scheduleConfig, block)
/** /**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block] * Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
@ -116,24 +120,19 @@ suspend inline fun doWhileTz(
noinline block: suspend (DateTimeTz) -> Boolean noinline block: suspend (DateTimeTz) -> Boolean
) = buildSchedule(scheduleConfig).doWhileTz(block) ) = 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 * 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) block(it)
coroutineContext.isActive 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 * 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 { suspend inline fun doInfinity(
block() scheduleConfig: String,
coroutineContext.isActive noinline block: suspend (DateTime) -> Unit
} ) = buildSchedule(scheduleConfig).doInfinity(block)
/** /**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block] * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
* *
* @see buildSchedule * @see buildSchedule
*/ */
@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity"))
suspend inline fun doInfinityLocal( suspend inline fun doInfinityLocal(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend (DateTime) -> Unit noinline block: suspend (DateTime) -> Unit
) = buildSchedule(scheduleConfig).doInfinityLocal(block) ) = doInfinity(scheduleConfig, block)
/** /**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block] * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
@ -170,13 +172,3 @@ suspend inline fun doInfinityTz(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend (DateTimeTz) -> Unit noinline block: suspend (DateTimeTz) -> Unit
) = buildSchedule(scheduleConfig).doInfinityTz(block) ) = 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)

View File

@ -16,6 +16,13 @@ val AnyTimeScheduler: KronScheduler by lazy {
CronDateTimeScheduler(anyCronDateTime) 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 * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one second
*/ */

View File

@ -14,14 +14,15 @@ typealias KrontabTemplate = String
/** /**
* Parse [incoming] string and adapt according to next format: "* * * * *" where order of things: * Parse [incoming] string and adapt according to next format: "* * * * *" where order of things:
* *
* * seconds * * **seconds**
* * minutes * * **minutes**
* * hours * * **hours**
* * dayOfMonth * * **dayOfMonth**
* * month * * **month**
* * (optional) year * * **year** (optional)
* * (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00) offset * * **offset** (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00)
* * (optional) (can be placed anywhere after month) dayOfWeek * * **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: * And each one (except of offsets) have next format:
* *
@ -48,18 +49,21 @@ typealias KrontabTemplate = String
* * 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]) * * Years ranges can be found in [yearRange] (in fact - any [Int])
* * WeekDay (timezone) ranges can be found in [dayOfWeekRange] * * WeekDay (timezone) ranges can be found in [dayOfWeekRange]
* * Milliseconds ranges can be found in [millisecondsRange]
* *
* Examples: * Examples:
* *
* * "0/5 * * * *" for every five seconds triggering * * "0/5 * * * *" for every five seconds triggering
* * "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
* * "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" 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" 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 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" 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" 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" 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 * @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 * 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 offsetParsed: Int? = null
var dayOfWeekParsed: Array<Byte>? = null var dayOfWeekParsed: Array<Byte>? = null
var yearParsed: Array<Int>? = null var yearParsed: Array<Int>? = null
var millisecondsParsed: Array<Short>? = null
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also { val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also {
listOfNotNull( listOfNotNull(
it.getOrNull(5), it.getOrNull(5),
it.getOrNull(6), it.getOrNull(6),
it.getOrNull(7) it.getOrNull(7),
it.getOrNull(8)
).forEach { ).forEach {
val offsetFromString = parseOffset(it) val offsetFromString = parseOffset(it)
val dayOfWeekFromString = parseWeekDay(it) val dayOfWeekFromString = parseWeekDay(it)
val millisecondsFromString = parseMilliseconds(it)
offsetParsed = offsetParsed ?: offsetFromString offsetParsed = offsetParsed ?: offsetFromString
dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString
millisecondsParsed = millisecondsParsed ?: millisecondsFromString
when { when {
dayOfWeekFromString != null || offsetFromString != null -> return@forEach dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach
yearParsed == null -> { yearParsed == null -> {
yearParsed = parseYears(it) yearParsed = parseYears(it)
} }
@ -100,10 +108,25 @@ fun createSimpleScheduler(
return offsetParsed ?.let { offset -> return offsetParsed ?.let { offset ->
createKronSchedulerWithOffset( createKronSchedulerWithOffset(
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed, TimezoneOffset(offset.minutes) secondsParsed,
minutesParsed,
hoursParsed,
dayOfMonthParsed,
monthParsed,
yearParsed,
dayOfWeekParsed,
TimezoneOffset(offset.minutes),
millisecondsParsed ?: millisecondsArrayDefault
) )
} ?: createKronScheduler( } ?: createKronScheduler(
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed secondsParsed,
minutesParsed,
hoursParsed,
dayOfMonthParsed,
monthParsed,
yearParsed,
dayOfWeekParsed,
millisecondsParsed ?: millisecondsArrayDefault
) )
} }

View File

@ -4,6 +4,7 @@ import com.soywiz.klock.TimezoneOffset
import com.soywiz.klock.minutes import com.soywiz.klock.minutes
import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.KronScheduler
import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.KronSchedulerTz
import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.createKronScheduler import dev.inmo.krontab.internal.createKronScheduler
import dev.inmo.krontab.internal.createKronSchedulerWithOffset import dev.inmo.krontab.internal.createKronSchedulerWithOffset
import dev.inmo.krontab.utils.Minutes import dev.inmo.krontab.utils.Minutes
@ -45,7 +46,8 @@ class SchedulerBuilder(
private var month: Array<Byte>? = null, private var month: Array<Byte>? = null,
private var year: Array<Int>? = null, private var year: Array<Int>? = null,
private var dayOfWeek: Array<Byte>? = null, private var dayOfWeek: Array<Byte>? = null,
private val offset: Minutes? = null private val offset: Minutes? = null,
private var milliseconds: Array<Short>? = null
) { ) {
private fun <I, T : TimeBuilder<I>> callAndReturn( private fun <I, T : TimeBuilder<I>> callAndReturn(
initial: Array<I>?, initial: Array<I>?,
@ -63,6 +65,17 @@ class SchedulerBuilder(
} ?: builderValue } ?: builderValue
} }
/**
* Starts an milliseconds block
*/
fun milliseconds(block: MillisecondsBuilder.() -> Unit) {
milliseconds = callAndReturn(
milliseconds,
MillisecondsBuilder(),
block
) ?.toTypedArray()
}
/** /**
* Starts an seconds block * Starts an seconds block
*/ */
@ -147,6 +160,16 @@ class SchedulerBuilder(
* @see dev.inmo.krontab.internal.createKronScheduler * @see dev.inmo.krontab.internal.createKronScheduler
*/ */
fun build(): KronScheduler = offset ?.let { fun build(): KronScheduler = offset ?.let {
createKronSchedulerWithOffset(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, TimezoneOffset(it.minutes)) createKronSchedulerWithOffset(
} ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek) seconds,
minutes,
hours,
dayOfMonth,
month,
year,
dayOfWeek,
TimezoneOffset(it.minutes),
milliseconds ?: millisecondsArrayDefault
)
} ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, milliseconds ?: millisecondsArrayDefault)
} }

View File

@ -122,6 +122,7 @@ sealed class TimeBuilder<T : Number> (
internal fun build() = result ?.map(converter) internal fun build() = result ?.map(converter)
} }
class MillisecondsBuilder : TimeBuilder<Short>(millisecondsRange, intToShortConverter)
class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter) class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter) class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter) class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)

View File

@ -20,7 +20,8 @@ internal data class CronDateTime(
val daysOfMonth: Array<Byte>? = null, val daysOfMonth: Array<Byte>? = null,
val hours: Array<Byte>? = null, val hours: Array<Byte>? = null,
val minutes: Array<Byte>? = null, val minutes: Array<Byte>? = null,
val seconds: Array<Byte>? = null val seconds: Array<Byte>? = null,
val milliseconds: Array<Short>? = millisecondsArrayDefault
) { ) {
init { init {
check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true) check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true)
@ -30,12 +31,13 @@ internal data class CronDateTime(
check(hours?.all { it in hoursRange } ?: true) check(hours?.all { it in hoursRange } ?: true)
check(minutes?.all { it in minutesRange } ?: true) check(minutes?.all { it in minutesRange } ?: true)
check(seconds?.all { it in secondsRange } ?: true) check(seconds?.all { it in secondsRange } ?: true)
check(milliseconds?.all { it in millisecondsRange } ?: true)
} }
internal val calculators = listOf( internal val calculators = listOf(
years ?.let { NearDateTimeCalculatorYears(it) }, years ?.let { NearDateTimeCalculatorYears(it) },
daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) }, daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) },
NearDateTimeCalculatorMillis(arrayOf(0)), milliseconds ?.let { NearDateTimeCalculatorMillis(it) },
seconds ?.let { NearDateTimeCalculatorSeconds(it) }, seconds ?.let { NearDateTimeCalculatorSeconds(it) },
minutes ?.let { NearDateTimeCalculatorMinutes(it) }, minutes ?.let { NearDateTimeCalculatorMinutes(it) },
hours ?.let { NearDateTimeCalculatorHours(it) }, hours ?.let { NearDateTimeCalculatorHours(it) },
@ -65,9 +67,10 @@ internal fun createCronDateTime(
dayOfMonth: Array<Byte>? = null, dayOfMonth: Array<Byte>? = null,
month: Array<Byte>? = null, month: Array<Byte>? = null,
years: Array<Int>? = null, years: Array<Int>? = null,
weekDays: Array<Byte>? = null weekDays: Array<Byte>? = null,
milliseconds: Array<Short>? = millisecondsArrayDefault
): CronDateTime { ): 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<Byte>? = null, dayOfMonth: Array<Byte>? = null,
month: Array<Byte>? = null, month: Array<Byte>? = null,
years: Array<Int>? = null, years: Array<Int>? = null,
weekDays: Array<Byte>? = null weekDays: Array<Byte>? = null,
): KronScheduler = CronDateTimeScheduler(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays)) milliseconds: Array<Short>? = millisecondsArrayDefault
): KronScheduler = CronDateTimeScheduler(
createCronDateTime(
seconds,
minutes,
hours,
dayOfMonth,
month,
years,
weekDays,
milliseconds
)
)
/** /**
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
*/ */
@ -93,8 +108,21 @@ internal fun createKronSchedulerWithOffset(
month: Array<Byte>? = null, month: Array<Byte>? = null,
years: Array<Int>? = null, years: Array<Int>? = null,
weekDays: Array<Byte>? = null, weekDays: Array<Byte>? = null,
offset: TimezoneOffset offset: TimezoneOffset,
): KronScheduler = CronDateTimeSchedulerTz(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset) milliseconds: Array<Short>? = millisecondsArrayDefault
): KronScheduler = CronDateTimeSchedulerTz(
createCronDateTime(
seconds,
minutes,
hours,
dayOfMonth,
month,
years,
weekDays,
milliseconds
),
offset
)
internal fun List<CronDateTime>.merge() = CronDateTime( internal fun List<CronDateTime>.merge() = CronDateTime(
flatMap { it.daysOfWeek ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, flatMap { it.daysOfWeek ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
@ -104,4 +132,5 @@ internal fun List<CronDateTime>.merge() = CronDateTime(
flatMap { it.hours ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, flatMap { it.hours ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
flatMap { it.minutes ?.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.seconds ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
flatMap { it.milliseconds ?.toList() ?: listOf(0) }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
) )

View File

@ -7,6 +7,7 @@ import dev.inmo.krontab.KronScheduler
* Cron-oriented realisation of [KronScheduler] * Cron-oriented realisation of [KronScheduler]
* *
* @see dev.inmo.krontab.AnyTimeScheduler * @see dev.inmo.krontab.AnyTimeScheduler
* @see dev.inmo.krontab.EveryMillisecondScheduler
* @see dev.inmo.krontab.EverySecondScheduler * @see dev.inmo.krontab.EverySecondScheduler
* @see dev.inmo.krontab.EveryMinuteScheduler * @see dev.inmo.krontab.EveryMinuteScheduler
* @see dev.inmo.krontab.EveryHourScheduler * @see dev.inmo.krontab.EveryHourScheduler

View File

@ -0,0 +1,4 @@
package dev.inmo.krontab.internal
internal const val millisecondsDefault: Short = 0
internal val millisecondsArrayDefault: Array<Short> = arrayOf(millisecondsDefault)

View File

@ -3,6 +3,7 @@ package dev.inmo.krontab.internal
typealias Converter<T> = (Int) -> T typealias Converter<T> = (Int) -> T
internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() } internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() }
internal val intToShortConverter: Converter<Short> = { it: Int -> it.toShort() }
internal val intToIntConverter: Converter<Int> = { it: Int -> it } internal val intToIntConverter: Converter<Int> = { it: Int -> it }
private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter<T>): List<T>? { private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter<T>): List<T>? {
val things = from.split(",") 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 parseHours(from: String) = createSimpleScheduler(from, hoursRange, intToByteConverter) ?.toTypedArray()
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange, intToByteConverter) ?.toTypedArray() internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange, intToByteConverter) ?.toTypedArray()
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange, 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 <T> Array<T>.fillWith( internal fun <T> Array<T>.fillWith(
whereToPut: MutableList<CronDateTime>, whereToPut: MutableList<CronDateTime>,

View File

@ -7,3 +7,4 @@ internal val dayOfMonthRange = 0 .. 30
internal val hoursRange = 0 .. 23 internal val hoursRange = 0 .. 23
internal val minutesRange = 0 .. 59 internal val minutesRange = 0 .. 59
internal val secondsRange = minutesRange internal val secondsRange = minutesRange
internal val millisecondsRange = 0 .. 999

View File

@ -6,6 +6,7 @@ import dev.inmo.krontab.*
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow 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 * 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<DateTimeTz> = channelFlow {
* This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s * This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s
* *
* @see channelFlow * @see channelFlow
* @see KronScheduler.doInfinityLocal * @see KronScheduler.doInfinity
*/ */
@FlowPreview @FlowPreview
fun KronScheduler.asFlow(): Flow<DateTime> = channelFlow { fun KronScheduler.asFlow(): Flow<DateTime> = channelFlow {
doInfinityLocal { doInfinity {
send(it) send(it)
} }
} }

View File

@ -6,8 +6,8 @@ import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlin.test.Test import kotlin.math.floor
import kotlin.test.assertEquals import kotlin.test.*
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@FlowPreview @FlowPreview
@ -29,6 +29,23 @@ class StringParseTest {
assertEquals(mustBeCollected, collected) 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 @Test
fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
@ -74,6 +91,29 @@ class StringParseTest {
flow.takeWhile { ranges.isNotEmpty() }.collect { flow.takeWhile { ranges.isNotEmpty() }.collect {
ranges.remove(it.seconds) ranges.remove(it.seconds)
collected++ 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) assertEquals(expectedCollects, collected)
} }