diff --git a/CHANGELOG.md b/CHANGELOG.md index d6be6ef..000541e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ * Versions * `Kotlin`: `1.8.22` * `Klock`: `4.0.3` +* New value class `KrontabConfig`. Since this update, it is preferable way to create `KronScheduler` instead of + `KrontabTemplate` +* You may configure krontab with builders using simple `KronScheduler` invoke extension ## 2.0.0 diff --git a/build.gradle b/build.gradle index 936e8ea..3362158 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" classpath "com.android.tools.build:gradle:$android_gradle_version" @@ -16,6 +17,7 @@ buildscript { plugins { id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version" + id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version" id "org.jetbrains.dokka" version "$dokka_version" } @@ -66,6 +68,7 @@ kotlin { dependencies { implementation kotlin('stdlib') api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" + api "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization_version" api "com.soywiz.korlibs.klock:klock:$klockVersion" } diff --git a/gradle.properties b/gradle.properties index d57fa15..bedd771 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,6 +11,7 @@ android.enableJetifier=false kotlin_version=1.8.22 kotlin_coroutines_version=1.6.4 +kotlin_serialization_version=1.5.1 dokka_version=1.8.20 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt index 289bb50..974c52d 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt @@ -18,6 +18,8 @@ interface KronScheduler { * @see dev.inmo.krontab.internal.CronDateTimeScheduler.next */ suspend fun next(relatively: DateTime = DateTime.now()): DateTime? + + companion object } suspend fun KronScheduler.nextOrRelative(relatively: DateTime = DateTime.now()): DateTime = next(relatively) ?: getAnyNext(relatively) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index 3e4c5bf..79a71c3 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -4,6 +4,8 @@ import korlibs.time.TimezoneOffset import korlibs.time.minutes import dev.inmo.krontab.internal.* import dev.inmo.krontab.utils.Minutes +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline /** * @see createSimpleScheduler @@ -11,6 +13,150 @@ import dev.inmo.krontab.utils.Minutes */ typealias KrontabTemplate = String +/** + * This value class contains [KrontabTemplate] + * + * * **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: + * + * `{number}[,{number},...]` or `*` + * + * and {number} here is one of + * + * * {int}-{int} + * * {int}/{int} + * * */{int} + * * {int} + * * F + * * L + * + * Week days must be marked with `w` at the end, and starts with 0 which means Sunday. For example, 0w == Sunday. With + * weeks you can use syntax like with any number like seconds, for example: 0-2w means Sunday-Tuesday + * + * Additional info about ranges can be found in follow accordance: + * + * * Seconds ranges can be found in [secondsRange] + * * Minutes ranges can be found in [minutesRange] + * * 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]) + * * 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 first, fifth and last days of may + * * "1 2 3 F,4,L 5 60o" for triggering in near first second of second minute of third hour of first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 + * + * @see dev.inmo.krontab.internal.createKronScheduler + */ +@Serializable +@JvmInline +value class KrontabConfig( + @Suppress("MemberVisibilityCanBePrivate") + val template: KrontabTemplate +) { + /** + * Creates __new__ [KronScheduler] based on a [template] + * + * @return In case when offset parameter is absent in [template] will be used [createSimpleScheduler] method and + * returned [CronDateTimeScheduler]. In case when offset parameter there is in [template] [KrontabTemplate] will be used + * [createKronSchedulerWithOffset] and returned [CronDateTimeSchedulerTz] + */ + fun scheduler(): KronScheduler { + var offsetParsed: Int? = null + var dayOfWeekParsed: Array? = null + var yearParsed: Array? = null + var millisecondsParsed: Array? = null + val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template.split(" ").also { + listOfNotNull( + it.getOrNull(5), + it.getOrNull(6), + 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 || millisecondsFromString != null -> return@forEach + yearParsed == null -> { + yearParsed = parseYears(it) + } + } + } + } + + val secondsParsed = parseSeconds(secondsSource) + val minutesParsed = parseMinutes(minutesSource) + val hoursParsed = parseHours(hoursSource) + val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource) + val monthParsed = parseMonths(monthSource) + + return offsetParsed ?.let { offset -> + createKronSchedulerWithOffset( + secondsParsed, + minutesParsed, + hoursParsed, + dayOfMonthParsed, + monthParsed, + yearParsed, + dayOfWeekParsed, + TimezoneOffset(offset.minutes), + millisecondsParsed ?: millisecondsArrayDefault + ) + } ?: createKronScheduler( + secondsParsed, + minutesParsed, + hoursParsed, + dayOfMonthParsed, + monthParsed, + yearParsed, + dayOfWeekParsed, + millisecondsParsed ?: millisecondsArrayDefault + ) + } + + /** + * Creates base [KronScheduler] using [scheduler] function. In case when returned [KronScheduler] is [KronSchedulerTz], + * it will be returned as is. Otherwise, will be created new [CronDateTimeSchedulerTz] with [defaultOffset] as + * offset + */ + fun scheduler(defaultOffset: Minutes): KronSchedulerTz { + val scheduler = scheduler() + return if (scheduler is KronSchedulerTz) { + scheduler + } else { + CronDateTimeSchedulerTz( + (scheduler as CronDateTimeScheduler).cronDateTime, + TimezoneOffset(defaultOffset.minutes) + ) + } + } +} + /** * Parse [incoming] string and adapt according to next format: "* * * * *" where order of things: * @@ -70,80 +216,16 @@ typealias KrontabTemplate = String * [createKronSchedulerWithOffset] and returned [CronDateTimeSchedulerTz] * * @see dev.inmo.krontab.internal.createKronScheduler + * @see KrontabConfig.scheduler */ fun createSimpleScheduler( incoming: KrontabTemplate -): KronScheduler { - 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(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 || millisecondsFromString != null -> return@forEach - yearParsed == null -> { - yearParsed = parseYears(it) - } - } - } - } - - val secondsParsed = parseSeconds(secondsSource) - val minutesParsed = parseMinutes(minutesSource) - val hoursParsed = parseHours(hoursSource) - val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource) - val monthParsed = parseMonths(monthSource) - - return offsetParsed ?.let { offset -> - createKronSchedulerWithOffset( - secondsParsed, - minutesParsed, - hoursParsed, - dayOfMonthParsed, - monthParsed, - yearParsed, - dayOfWeekParsed, - TimezoneOffset(offset.minutes), - millisecondsParsed ?: millisecondsArrayDefault - ) - } ?: createKronScheduler( - secondsParsed, - minutesParsed, - hoursParsed, - dayOfMonthParsed, - monthParsed, - yearParsed, - dayOfWeekParsed, - millisecondsParsed ?: millisecondsArrayDefault - ) -} +): KronScheduler = KrontabConfig(incoming).scheduler() fun createSimpleScheduler( incoming: KrontabTemplate, defaultOffset: Minutes -): KronSchedulerTz { - val scheduler = createSimpleScheduler(incoming) - return if (scheduler is KronSchedulerTz) { - scheduler - } else { - CronDateTimeSchedulerTz( - (scheduler as CronDateTimeScheduler).cronDateTime, - TimezoneOffset(defaultOffset.minutes) - ) - } -} +): KronSchedulerTz = KrontabConfig(incoming).scheduler(defaultOffset) /** * Shortcut for [createSimpleScheduler] diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt index a930da5..db60fa4 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt @@ -14,7 +14,7 @@ import dev.inmo.krontab.utils.Minutes * * @see dev.inmo.krontab.createSimpleScheduler */ -fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler { +inline fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler { val builder = SchedulerBuilder() builder.settingsBlock() @@ -27,7 +27,7 @@ fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler { * * @see dev.inmo.krontab.createSimpleScheduler */ -fun buildSchedule( +inline fun buildSchedule( offset: Minutes, settingsBlock: SchedulerBuilder.() -> Unit ): KronSchedulerTz { @@ -38,6 +38,27 @@ fun buildSchedule( return builder.build() as KronSchedulerTz } +/** + * Creates new [KronScheduler] with [settingsBlock] + * + * Due to the fact that it is inline function, you may break execution of [settingsBlock] + * at any time + */ +inline operator fun KronScheduler.Companion.invoke( + offset: Minutes, + settingsBlock: SchedulerBuilder.() -> Unit +): KronSchedulerTz = buildSchedule(offset, settingsBlock) + +/** + * Creates new [KronScheduler] with [settingsBlock] + * + * Due to the fact that it is inline function, you may break execution of [settingsBlock] + * at any time + */ +inline operator fun KronScheduler.Companion.invoke( + settingsBlock: SchedulerBuilder.() -> Unit +): KronScheduler = buildSchedule(settingsBlock) + class SchedulerBuilder( private var seconds: Array? = null, private var minutes: Array? = null,