From 5844859369f8b476ea19df0d5c4bab56b9fab142 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 10 Apr 2021 00:28:08 +0600 Subject: [PATCH] rewrite system with offsets --- .../kotlin/dev/inmo/krontab/KronScheduler.kt | 7 --- .../dev/inmo/krontab/KronSchedulerTz.kt | 32 ++++++++++ .../dev/inmo/krontab/KronSchedulersMerging.kt | 15 ++++- .../dev/inmo/krontab/SchedulerShortcuts.kt | 1 - .../kotlin/dev/inmo/krontab/StringParser.kt | 47 +++++++++++++-- .../inmo/krontab/builder/SchedulerBuilder.kt | 48 ++++++++------- .../collection/CollectionKronScheduler.kt | 4 +- .../dev/inmo/krontab/internal/CronDateTime.kt | 59 +++++++++---------- .../krontab/internal/CronDateTimeScheduler.kt | 7 +-- .../internal/CronDateTimeSchedulerTz.kt | 30 ++++++++++ .../dev/inmo/krontab/internal/Ranges.kt | 5 -- .../dev/inmo/krontab/utils/StringParseTest.kt | 4 +- .../dev/inmo/krontab/utils/TimeZoneTest.kt | 1 + 13 files changed, 174 insertions(+), 86 deletions(-) create mode 100644 src/commonMain/kotlin/dev/inmo/krontab/KronSchedulerTz.kt create mode 100644 src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt index efd89ef..172536b 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt @@ -13,22 +13,15 @@ import dev.inmo.krontab.internal.toNearDateTime * @see dev.inmo.krontab.internal.CronDateTimeScheduler */ interface KronScheduler { - /** * @return Next [DateTime] when some action must be triggered according to settings of this instance * * @see dev.inmo.krontab.internal.CronDateTimeScheduler.next */ suspend fun next(relatively: DateTime = DateTime.now()): DateTime? - - suspend fun next(relatively: DateTimeTz): DateTimeTz? } suspend fun KronScheduler.nextOrRelative(relatively: DateTime = DateTime.now()): DateTime = next(relatively) ?: getAnyNext(relatively) -suspend fun KronScheduler.nextOrRelative(relatively: DateTimeTz): DateTimeTz = next(relatively) ?: getAnyNext(relatively) suspend fun KronScheduler.nextOrNow(): DateTime = DateTime.now().let { next(it) ?: getAnyNext(it) } -suspend fun KronScheduler.nextOrNowWithOffset(): DateTimeTz = DateTimeTz.nowLocal().let { - next(it) ?: getAnyNext(it) -} diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulerTz.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulerTz.kt new file mode 100644 index 0000000..65d812f --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulerTz.kt @@ -0,0 +1,32 @@ +package dev.inmo.krontab + +import com.soywiz.klock.DateTime +import com.soywiz.klock.DateTimeTz + +/** + * This interface extending [KronScheduler] to use [DateTimeTz] with taking into account offset of incoming time for + * [next] operation. + * + * @see dev.inmo.krontab.internal.CronDateTimeScheduler + * @see dev.inmo.krontab.KronScheduler + */ +interface KronSchedulerTz : KronScheduler { + suspend fun next(relatively: DateTimeTz): DateTimeTz? + + override suspend fun next(relatively: DateTime): DateTime? = next(relatively.localUnadjusted) ?.local +} + +suspend fun KronSchedulerTz.nextOrRelative(relatively: DateTimeTz): DateTimeTz = next(relatively) ?: getAnyNext( + relatively.local +).toOffsetUnadjusted(relatively.offset) +suspend fun KronSchedulerTz.nextOrNowWithOffset(): DateTimeTz = DateTimeTz.nowLocal().let { + next(it) ?: getAnyNext( + it.local + ).toOffsetUnadjusted(it.offset) +} + +suspend fun KronScheduler.next(relatively: DateTimeTz) = if (this is KronSchedulerTz) { + this.next(relatively) +} else { + this.next(relatively.local) ?.toOffsetUnadjusted(relatively.offset) +} \ No newline at end of file diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt index f5a682f..3e27445 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt @@ -1,8 +1,12 @@ package dev.inmo.krontab +import com.soywiz.klock.TimezoneOffset import dev.inmo.krontab.collection.CollectionKronScheduler +import dev.inmo.krontab.collection.includeAll +import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.CronDateTime import dev.inmo.krontab.internal.CronDateTimeScheduler +import dev.inmo.krontab.internal.CronDateTimeSchedulerTz /** * Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator] @@ -10,18 +14,23 @@ import dev.inmo.krontab.internal.CronDateTimeScheduler * @see CollectionKronScheduler * @see CollectionKronScheduler.include */ -fun Iterator.merge(): KronScheduler { +fun Iterator.merge(): CollectionKronScheduler { val cronDateTimes = mutableListOf() + val timezonedCronDateTimes = mutableListOf() val collectionScheduler = CollectionKronScheduler() forEach { when (it) { is CronDateTimeScheduler -> cronDateTimes.addAll(it.cronDateTimes) + is CronDateTimeSchedulerTz -> timezonedCronDateTimes.add(it) else -> collectionScheduler.include(it) } } if (cronDateTimes.isNotEmpty()) { collectionScheduler.include(CronDateTimeScheduler(cronDateTimes)) } + if (timezonedCronDateTimes.isNotEmpty()) { + collectionScheduler.includeAll(mergeCronDateTimeSchedulers(timezonedCronDateTimes)) + } return collectionScheduler } @@ -32,10 +41,10 @@ fun Iterator.merge(): KronScheduler { * @see CollectionKronScheduler.include */ @Suppress("NOTHING_TO_INLINE") -inline fun Iterable.merge(): KronScheduler = iterator().merge() +inline fun Iterable.merge(): CollectionKronScheduler = iterator().merge() /** * @return Vararg shortcut for [merge] */ @Suppress("NOTHING_TO_INLINE") -inline fun merge(vararg kronDateTimeSchedulers: KronScheduler) = kronDateTimeSchedulers.iterator().merge() +inline fun merge(vararg kronDateTimeSchedulers: KronScheduler): CollectionKronScheduler = kronDateTimeSchedulers.iterator().merge() diff --git a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt index aa5500b..f313d40 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt @@ -11,7 +11,6 @@ internal val anyCronDateTime by lazy { CronDateTime() } internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(relatively)!! -internal fun getAnyNext(relatively: DateTimeTz) = anyCronDateTime.toNearDateTime(relatively)!! /** * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index 0eb868f..287761f 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -1,6 +1,8 @@ package dev.inmo.krontab +import com.soywiz.klock.* import dev.inmo.krontab.internal.* +import dev.inmo.krontab.utils.Minutes /** * @see createSimpleScheduler @@ -52,9 +54,15 @@ typealias KrontabTemplate = String * * "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 * + * @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 + * [createKronSchedulerWithOffset] and returned [CronDateTimeSchedulerTz] + * * @see dev.inmo.krontab.internal.createKronScheduler */ -fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler { +fun createSimpleScheduler( + incoming: KrontabTemplate +): KronScheduler { var offsetParsed: Int? = null var yearParsed: Array? = null val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also { @@ -81,22 +89,53 @@ fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler { val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource) val monthParsed = parseMonths(monthSource) - return createKronScheduler( - secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, offsetParsed + return offsetParsed ?.let { offset -> + createKronSchedulerWithOffset( + secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, TimezoneOffset(offset.minutes) + ) + } ?: createKronScheduler( + secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed ) } +fun createSimpleScheduler( + incoming: KrontabTemplate, + defaultOffset: Minutes +): KronSchedulerTz { + val scheduler = createSimpleScheduler(incoming) + return if (scheduler is KronSchedulerTz) { + scheduler + } else { + CronDateTimeSchedulerTz( + (scheduler as CronDateTimeScheduler).cronDateTimes, + TimezoneOffset(defaultOffset.minutes) + ) + } +} + /** * Shortcut for [createSimpleScheduler] */ fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming) +/** + * Shortcut for [createSimpleScheduler] + */ +fun buildSchedule(incoming: KrontabTemplate, defaultOffset: Minutes): KronSchedulerTz = createSimpleScheduler(incoming, defaultOffset) /** * Shortcut for [buildSchedule] */ fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this) +/** + * Shortcut for [buildSchedule] + */ +fun KrontabTemplate.toSchedule(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset) /** * Shortcut for [buildSchedule] */ -fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this) \ No newline at end of file +fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this) +/** + * Shortcut for [buildSchedule] + */ +fun KrontabTemplate.toKronScheduler(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset) \ 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 2f477d0..84dcfec 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt @@ -1,9 +1,10 @@ package dev.inmo.krontab.builder -import com.soywiz.klock.TimeSpan -import com.soywiz.klock.TimezoneOffset +import com.soywiz.klock.* import dev.inmo.krontab.KronScheduler +import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.internal.createKronScheduler +import dev.inmo.krontab.internal.createKronSchedulerWithOffset import dev.inmo.krontab.utils.Minutes /** @@ -19,6 +20,22 @@ fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler { return builder.build() } +/** + * Will help to create an instance of [KronScheduler] + * + * @see dev.inmo.krontab.createSimpleScheduler + */ +fun buildSchedule( + offset: Minutes, + settingsBlock: SchedulerBuilder.() -> Unit +): KronSchedulerTz { + val builder = SchedulerBuilder(offset = offset) + + builder.settingsBlock() + + return builder.build() as KronSchedulerTz +} + class SchedulerBuilder( private var seconds: Array? = null, private var minutes: Array? = null, @@ -26,7 +43,7 @@ class SchedulerBuilder( private var dayOfMonth: Array? = null, private var month: Array? = null, private var year: Array? = null, - var offset: Minutes? = null + private val offset: Minutes? = null ) { private fun > callAndReturn( initial: Array?, @@ -110,32 +127,13 @@ class SchedulerBuilder( ) ?.toTypedArray() } - /** - * Setter of [offset] property - */ - fun offset(offset: Minutes?) { - this.offset = offset - } - - /** - * Setter of [offset] property - */ - fun offset(offset: TimeSpan?) { - this.offset = offset ?.minutes ?.toInt() - } - - /** - * Setter of [offset] property - */ - fun offset(offset: TimezoneOffset?) { - this.offset = offset ?.totalMinutesInt - } - /** * @return Completely built and independent [KronScheduler] * * @see dev.inmo.krontab.createSimpleScheduler * @see dev.inmo.krontab.internal.createKronScheduler */ - fun build(): KronScheduler = createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year) + fun build(): KronScheduler = offset ?.let { + createKronSchedulerWithOffset(seconds, minutes, hours, dayOfMonth, month, year, TimezoneOffset(it.minutes)) + } ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt index 62fbf8c..a4ab265 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt @@ -12,7 +12,7 @@ import dev.inmo.krontab.internal.toNearDateTime */ data class CollectionKronScheduler internal constructor( internal val schedulers: MutableList -) : KronScheduler { +) : KronSchedulerTz { internal constructor() : this(mutableListOf()) /** @@ -51,6 +51,6 @@ data class CollectionKronScheduler internal constructor( } override suspend fun next(relatively: DateTimeTz): DateTimeTz { - return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively) + return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively.local).toOffsetUnadjusted(relatively.offset) } } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt index 4b1d312..ae47a95 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt @@ -12,7 +12,6 @@ import dev.inmo.krontab.utils.Minutes * @param seconds 0-59 */ internal data class CronDateTime( - val offset: Int? = null, val year: Int? = null, val month: Byte? = null, val dayOfMonth: Byte? = null, @@ -30,7 +29,6 @@ internal data class CronDateTime( } internal val klockDayOfMonth = dayOfMonth ?.plus(1) - internal val klockOffset = offset ?.minutes ?.offset } /** @@ -82,35 +80,14 @@ internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now() return current } -/** - * THIS METHOD WILL TAKE CARE ABOUT [offset] PARAMETER. It was decided due to the fact that we unable to get - * real timezone offset from simple [DateTime] - * - * @return The near [DateTime] which happens after [relativelyTo] or will be equal to [relativelyTo] - */ -internal fun CronDateTime.toNearDateTime( - relativelyTo: DateTimeTz -): DateTimeTz? { - val klockOffset = klockOffset - return if (klockOffset != null) { - toNearDateTime(relativelyTo.toOffset(klockOffset).local) ?.toOffsetUnadjusted(klockOffset) ?.toOffset(relativelyTo.offset) - } else { - toNearDateTime(relativelyTo.local) ?.toOffsetUnadjusted(relativelyTo.offset) - } -} - -/** - * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data - */ -internal fun createKronScheduler( +internal fun createCronDateTimeList( seconds: Array? = null, minutes: Array? = null, hours: Array? = null, dayOfMonth: Array? = null, month: Array? = null, - years: Array? = null, - offset: Minutes? = null -): KronScheduler { + years: Array? = null +): List { val resultCronDateTimes = mutableListOf(CronDateTime()) seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> @@ -141,9 +118,29 @@ internal fun createKronScheduler( previousCronDateTime.copy(year = currentTime) } - offset ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int -> - previousCronDateTime.copy(year = currentTime) - } - - return CronDateTimeScheduler(resultCronDateTimes.toList()) + return resultCronDateTimes.toList() } + +/** + * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data + */ +internal fun createKronScheduler( + seconds: Array? = null, + minutes: Array? = null, + hours: Array? = null, + dayOfMonth: Array? = null, + month: Array? = null, + years: Array? = null +): KronScheduler = CronDateTimeScheduler(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years)) +/** + * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data + */ +internal fun createKronSchedulerWithOffset( + seconds: Array? = null, + minutes: Array? = null, + hours: Array? = null, + dayOfMonth: Array? = null, + month: Array? = null, + years: Array? = null, + offset: TimezoneOffset +): KronScheduler = CronDateTimeSchedulerTz(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years), offset) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt index ede3fca..f4ae554 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt @@ -1,7 +1,6 @@ package dev.inmo.krontab.internal -import com.soywiz.klock.DateTime -import com.soywiz.klock.DateTimeTz +import com.soywiz.klock.* import dev.inmo.krontab.* import dev.inmo.krontab.collection.plus @@ -30,10 +29,6 @@ internal data class CronDateTimeScheduler internal constructor( override suspend fun next(relatively: DateTime): DateTime { return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull() ?: getAnyNext(relatively) } - - override suspend fun next(relatively: DateTimeTz): DateTimeTz { - return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull() ?: getAnyNext(relatively) - } } internal fun mergeCronDateTimeSchedulers(schedulers: List) = CronDateTimeScheduler( diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt new file mode 100644 index 0000000..86c71c5 --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt @@ -0,0 +1,30 @@ +package dev.inmo.krontab.internal + +import com.soywiz.klock.* +import dev.inmo.krontab.* +import dev.inmo.krontab.collection.plus + +/** + * Cron-oriented realisation of [KronScheduler] with taking into account [offset] for list of [cronDateTimes] + * + * @see CronDateTime + */ +internal data class CronDateTimeSchedulerTz internal constructor( + internal val cronDateTimes: List, + internal val offset: TimezoneOffset +) : KronSchedulerTz { + override suspend fun next(relatively: DateTimeTz): DateTimeTz? { + val dateTimeWithActualOffset = relatively.utc.toOffset(offset).local + return cronDateTimes.mapNotNull { + it.toNearDateTime(dateTimeWithActualOffset) + }.minOrNull() ?.toOffset(relatively.offset) + } +} + +internal fun mergeCronDateTimeSchedulers( + schedulers: List +) = schedulers.groupBy { + it.offset +}.map { (offset, schedulers) -> + CronDateTimeSchedulerTz(schedulers.flatMap { it.cronDateTimes }, offset) +} diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt index 232cf13..ec4359b 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt @@ -6,8 +6,3 @@ internal val dayOfMonthRange = 0 .. 30 internal val hoursRange = 0 .. 23 internal val minutesRange = 0 .. 59 internal val secondsRange = minutesRange - -/** - * From 0 - 1439 minutes (1440 == 24 hours, that is the same as 0 in terms of timezones) - */ -internal val offsetRange = 0 until (hoursRange.count() * minutesRange.count()) diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt index fdc0146..4333187 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt @@ -1,6 +1,7 @@ package dev.inmo.krontab.utils import com.soywiz.klock.DateTimeTz +import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.buildSchedule import dev.inmo.krontab.internal.offsetRange import kotlinx.coroutines.* @@ -84,9 +85,8 @@ class StringParseTest { runTest { for (i in offsetRange) { - val kronScheduler = buildSchedule("* * 10 * * ${i}o") + val kronScheduler = buildSchedule("* * 10 * * ${i}o") as KronSchedulerTz val next = kronScheduler.next(now) - } } } diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt index 0b3bc2b..481e86a 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt @@ -2,6 +2,7 @@ package dev.inmo.krontab.utils import com.soywiz.klock.* import dev.inmo.krontab.builder.buildSchedule +import dev.inmo.krontab.next import kotlin.test.Test import kotlin.test.assertEquals