diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aef33b..b4dff22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 2.1.0 + +* Versions + * `Kotlin`: `1.8.22` + * `Klock`: `4.0.3` +* New value class `KrontabConfig`. It is preferable way to create `KronScheduler` instead of + `KrontabTemplate` since this update +* You may configure krontab with builders using simple `KronScheduler` invoke extension +* New useful extensions like `KronScheduler.daily` +* `KrontabTemplateWrapper` is obsolete in favor to `KrontabConfig` + ## 2.0.0 * Versions 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 639f93d..56fa1b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,12 +9,13 @@ android.useAndroidX=true android.enableJetifier=false -kotlin_version=1.8.21 +kotlin_version=1.8.22 kotlin_coroutines_version=1.6.4 +kotlin_serialization_version=1.5.1 dokka_version=1.8.20 -klockVersion=4.0.1 +klockVersion=4.0.3 ## Github reease @@ -35,5 +36,5 @@ androidx_work_version=2.8.1 ## Common -version=2.0.0 -android_code_version=26 +version=2.1.0 +android_code_version=27 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/KrontabConfig.kt b/src/commonMain/kotlin/dev/inmo/krontab/KrontabConfig.kt new file mode 100644 index 0000000..d0aa227 --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/KrontabConfig.kt @@ -0,0 +1,165 @@ +package dev.inmo.krontab + +import dev.inmo.krontab.internal.CronDateTimeScheduler +import dev.inmo.krontab.internal.CronDateTimeSchedulerTz +import dev.inmo.krontab.internal.createKronScheduler +import dev.inmo.krontab.internal.createKronSchedulerWithOffset +import dev.inmo.krontab.internal.millisecondsArrayDefault +import dev.inmo.krontab.internal.parseDaysOfMonth +import dev.inmo.krontab.internal.parseHours +import dev.inmo.krontab.internal.parseMilliseconds +import dev.inmo.krontab.internal.parseMinutes +import dev.inmo.krontab.internal.parseMonths +import dev.inmo.krontab.internal.parseOffset +import dev.inmo.krontab.internal.parseSeconds +import dev.inmo.krontab.internal.parseWeekDay +import dev.inmo.krontab.internal.parseYears +import dev.inmo.krontab.utils.Minutes +import korlibs.time.TimezoneOffset +import korlibs.time.minutes +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +/** + * 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) + ) + } + } +} diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt b/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt index 0cd06f6..d439c59 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KrontabTemplateWrapper.kt @@ -5,6 +5,10 @@ package dev.inmo.krontab * [dev.inmo.krontab.internal.CronDateTimeScheduler] due to the fact that [toKronScheduler] will return it under the * hood */ +@Deprecated( + "It is useless wrapper for KrontabTemplate. Use KrontabConfig instead", + ReplaceWith("KrontabConfig(template)", "dev.inmo.krontab.KrontabConfig") +) data class KrontabTemplateWrapper( val template: KrontabTemplate ) : KronScheduler by template.toKronScheduler() @@ -15,4 +19,8 @@ data class KrontabTemplateWrapper( * @see [toKronScheduler] * @see [KrontabTemplateWrapper] */ +@Deprecated( + "Will be removed in near major update with KrontabTemplateWrapper", + ReplaceWith("this.krontabConfig", "dev.inmo.krontab.krontabConfig") +) fun KrontabTemplate.wrapAsKronScheduler() = KrontabTemplateWrapper(this) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt index 00dea44..ad88b58 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt @@ -10,57 +10,99 @@ internal val anyCronDateTime by lazy { internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(relatively)!! /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + * [KronScheduler.next] will always return [korlibs.time.DateTime.now] */ val AnyTimeScheduler: KronScheduler by lazy { CronDateTimeScheduler(anyCronDateTime) } /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one millisecond + * [KronScheduler.next] will always return [korlibs.time.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 [korlibs.time.DateTime.now] + one second */ val EverySecondScheduler: KronScheduler by lazy { buildSchedule { seconds { 0 every 1 } } } /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one minute + * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one minute */ val EveryMinuteScheduler: KronScheduler by lazy { buildSchedule { minutes { 0 every 1 } } } /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one hour + * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one hour */ val EveryHourScheduler: KronScheduler by lazy { buildSchedule { hours { 0 every 1 } } } /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one day + * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one day */ val EveryDayOfMonthScheduler: KronScheduler by lazy { buildSchedule { dayOfMonth { 0 every 1 } } } /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one month + * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one month */ val EveryMonthScheduler: KronScheduler by lazy { buildSchedule { months { 0 every 1 } } } /** - * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one year + * [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one year */ val EveryYearScheduler: KronScheduler by lazy { buildSchedule { years { 0 every 1 } } } + +/** + * Shortcut for [EveryMillisecondScheduler] + */ +inline val KronScheduler.Companion.everyMillisecond + get() = EveryMillisecondScheduler + +/** + * Shortcut for [EverySecondScheduler] + */ +inline val KronScheduler.Companion.everySecond + get() = EverySecondScheduler + +/** + * Shortcut for [EveryMinuteScheduler] + */ +inline val KronScheduler.Companion.everyMinute + get() = EveryMinuteScheduler + +/** + * Shortcut for [EveryHourScheduler] + */ +inline val KronScheduler.Companion.hourly + get() = EveryHourScheduler + +/** + * Shortcut for [EveryDayOfMonthScheduler] + */ +inline val KronScheduler.Companion.daily + get() = EveryDayOfMonthScheduler + +/** + * Shortcut for [EveryMonthScheduler] + */ +inline val KronScheduler.Companion.monthly + get() = EveryMonthScheduler + +/** + * Shortcut for [EveryYearScheduler] + */ +inline val KronScheduler.Companion.annually + get() = EveryYearScheduler diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index 3e4c5bf..292fbe5 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -1,7 +1,5 @@ package dev.inmo.krontab -import korlibs.time.TimezoneOffset -import korlibs.time.minutes import dev.inmo.krontab.internal.* import dev.inmo.krontab.utils.Minutes @@ -11,6 +9,8 @@ import dev.inmo.krontab.utils.Minutes */ typealias KrontabTemplate = String +inline fun KrontabTemplate.krontabConfig() = KrontabConfig(this) + /** * Parse [incoming] string and adapt according to next format: "* * * * *" where order of things: * @@ -70,80 +70,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..681f2c6 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] + * + * Since 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] + * + * Since 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,