diff --git a/.github/workflows/publishing_packages.yml b/.github/workflows/publishing_packages.yml index 9af32ba..df9d39f 100644 --- a/.github/workflows/publishing_packages.yml +++ b/.github/workflows/publishing_packages.yml @@ -16,7 +16,7 @@ jobs: - name: prebuild run: ./gradlew clean build - name: Publish package - run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signMetadataPublication -x signAndroidDebugPublication -x signAndroidReleasePublication + run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signMetadataPublication -x signAndroidReleasePublication env: GITHUBPACKAGES_USER: ${{ github.actor }} GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bde7df..671ed32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.6.1 + +* Versions + * `Klock`: `2.1.0` -> `2.1.2` +* Rewriting of default mechanism of `KronScheduler`s + ## 0.6.0 * Versions diff --git a/gradle.properties b/gradle.properties index d68cd69..22a107f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ kotlin_coroutines_version=1.5.0 dokka_version=1.4.32 -klockVersion=2.1.0 +klockVersion=2.1.2 ## Github reease @@ -33,6 +33,6 @@ androidx_work_version=2.5.0 ## Common -version=0.6.0 -android_code_version=4 +version=0.6.1 +android_code_version=5 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt index e246e6b..b62b712 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt @@ -16,13 +16,13 @@ fun Iterator.merge(): CollectionKronScheduler { val collectionScheduler = CollectionKronScheduler() forEach { when (it) { - is CronDateTimeScheduler -> cronDateTimes.addAll(it.cronDateTimes) + is CronDateTimeScheduler -> cronDateTimes.add(it.cronDateTime) is CronDateTimeSchedulerTz -> timezonedCronDateTimes.add(it) else -> collectionScheduler.include(it) } } if (cronDateTimes.isNotEmpty()) { - collectionScheduler.include(CronDateTimeScheduler(cronDateTimes)) + collectionScheduler.include(CronDateTimeScheduler(cronDateTimes.merge())) } if (timezonedCronDateTimes.isNotEmpty()) { collectionScheduler.includeAll(mergeCronDateTimeSchedulers(timezonedCronDateTimes)) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt index e05a29d..d351b61 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt @@ -13,7 +13,7 @@ internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(r * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] */ val AnyTimeScheduler: KronScheduler by lazy { - CronDateTimeScheduler(listOf(anyCronDateTime)) + CronDateTimeScheduler(anyCronDateTime) } /** @@ -56,4 +56,4 @@ val EveryMonthScheduler: KronScheduler by lazy { */ val EveryYearScheduler: KronScheduler by lazy { buildSchedule { years { 0 every 1 } } -} \ No newline at end of file +} diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index b9f29ad..e2a1b99 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -116,7 +116,7 @@ fun createSimpleScheduler( scheduler } else { CronDateTimeSchedulerTz( - (scheduler as CronDateTimeScheduler).cronDateTimes, + (scheduler as CronDateTimeScheduler).cronDateTime, TimezoneOffset(defaultOffset.minutes) ) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt index 97fe0c3..64fc73a 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt @@ -38,16 +38,16 @@ data class CollectionKronScheduler internal constructor( ) } is CronDateTimeSchedulerTz -> { - val newCronDateTimes = kronScheduler.cronDateTimes.toMutableList() - val cronDateTimes = schedulers.removeAll { + val newCronDateTimes = mutableListOf(kronScheduler.cronDateTime) + schedulers.removeAll { if (it is CronDateTimeSchedulerTz && it.offset == kronScheduler.offset) { - newCronDateTimes.addAll(it.cronDateTimes) + newCronDateTimes.add(it.cronDateTime) true } else { false } } - schedulers.add(CronDateTimeSchedulerTz(newCronDateTimes.toList(), kronScheduler.offset)) + schedulers.add(CronDateTimeSchedulerTz(newCronDateTimes.merge(), kronScheduler.offset)) } is CollectionKronScheduler -> kronScheduler.schedulers.forEach { include(it) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt index 49cf462..350a066 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt @@ -2,105 +2,63 @@ package dev.inmo.krontab.internal import com.soywiz.klock.* import dev.inmo.krontab.KronScheduler +import dev.inmo.krontab.utils.copy /** - * @param dayOfweek 0-6 - * @param year any int - * @param month 0-11 - * @param dayOfMonth 0-31 + * @param daysOfWeek 0-6 + * @param years any int + * @param months 0-11 + * @param daysOfMonth 0-31 * @param hours 0-23 * @param minutes 0-59 * @param seconds 0-59 */ internal data class CronDateTime( - val dayOfweek: Byte? = null, - val year: Int? = null, - val month: Byte? = null, - val dayOfMonth: Byte? = null, - val hours: Byte? = null, - val minutes: Byte? = null, - val seconds: Byte? = null + val daysOfWeek: Array? = null, + val years: Array? = null, + val months: Array? = null, + val daysOfMonth: Array? = null, + val hours: Array? = null, + val minutes: Array? = null, + val seconds: Array? = null ) { init { - check(dayOfweek ?.let { it in dayOfWeekRange } ?: true) - check(year ?.let { it in yearRange } ?: true) - check(month ?.let { it in monthRange } ?: true) - check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true) - check(hours?.let { it in hoursRange } ?: true) - check(minutes?.let { it in minutesRange } ?: true) - check(seconds?.let { it in secondsRange } ?: true) + check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true) + check(years?.all { it in yearRange } ?: true) + check(months?.all { it in monthRange } ?: true) + check(daysOfMonth ?.all { it in dayOfMonthRange } ?: true) + check(hours?.all { it in hoursRange } ?: true) + check(minutes?.all { it in minutesRange } ?: true) + check(seconds?.all { it in secondsRange } ?: true) } - internal val klockDayOfMonth = dayOfMonth ?.plus(1) - internal val dayOfWeekInt: Int? = dayOfweek ?.toInt() -} + internal val calculators = listOf( + years ?.let { NearDateTimeCalculatorYears(it) }, + daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) }, + NearDateTimeCalculatorMillis(arrayOf(0)), + seconds ?.let { NearDateTimeCalculatorSeconds(it) }, + minutes ?.let { NearDateTimeCalculatorMinutes(it) }, + hours ?.let { NearDateTimeCalculatorHours(it) }, + daysOfMonth ?.let { NearDateTimeCalculatorDays(it) }, + months ?.let { NearDateTimeCalculatorMonths(it) }, + ) -/** - * THIS METHOD WILL NOT 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: DateTime = DateTime.now()): DateTime? { - var current = relativelyTo - - val weekDay = dayOfWeekInt - if (weekDay != null && current.dayOfWeek.index0 != weekDay) { - do { - var diff = weekDay - current.dayOfWeek.index0 - if (diff < 0) { - diff += 7 /* days in week */ + internal fun toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? { + var current = relativelyTo + whileLoop@while (true) { + for (calculator in calculators) { + val (calculated, requireRecalculation) = (calculator ?: continue).calculateNearTime(current) ?: return null + current = calculated + if (requireRecalculation) { + continue@whileLoop + } } - current = (current + diff.days).startOfDay - - val next = toNearDateTime(current) - if (next == null || next.dayOfWeek.index0 == weekDay) { - return next - } - } while (true) - } - - seconds?.let { - val left = it - current.seconds - current += DateTimeSpan(minutes = if (left <= 0) 1 else 0, seconds = left) - } - - minutes?.let { - val left = it - current.minutes - current += DateTimeSpan(hours = if (left < 0) 1 else 0, minutes = left) - } - - hours?.let { - val left = it - current.hours - current += DateTimeSpan(days = if (left < 0) 1 else 0, hours = left) - } - - klockDayOfMonth ?.let { - val left = (it - current.dayOfMonth).let { diff -> - if (diff > 0 && current.endOfMonth.run { it > dayOfMonth && current.dayOfMonth == dayOfMonth }) { - 0 - } else { - diff - } - } - current += DateTimeSpan(months = if (left < 0) 1 else 0, days = left) - } - - month ?.let { - val left = it - current.month0 - current += DateTimeSpan(years = if (left < 0) 1 else 0, months = left) - } - - year ?.let { - if (current.yearInt != it) { - return null + return current } } - - return current } -internal fun createCronDateTimeList( +internal fun createCronDateTime( seconds: Array? = null, minutes: Array? = null, hours: Array? = null, @@ -108,38 +66,8 @@ internal fun createCronDateTimeList( month: Array? = null, years: Array? = null, weekDays: Array? = null -): List { - val resultCronDateTimes = mutableListOf(CronDateTime()) - - seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> - previousCronDateTime.copy(seconds = currentTime) - } - - minutes ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> - previousCronDateTime.copy(minutes = currentTime) - } - - hours ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> - previousCronDateTime.copy(hours = currentTime) - } - - dayOfMonth ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> - previousCronDateTime.copy(dayOfMonth = currentTime) - } - - month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> - previousCronDateTime.copy(month = currentTime) - } - - years ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int -> - previousCronDateTime.copy(year = currentTime) - } - - weekDays ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> - previousCronDateTime.copy(dayOfweek = currentTime) - } - - return resultCronDateTimes.toList() +): CronDateTime { + return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds) } /** @@ -153,7 +81,7 @@ internal fun createKronScheduler( month: Array? = null, years: Array? = null, weekDays: Array? = null -): KronScheduler = CronDateTimeScheduler(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays)) +): KronScheduler = CronDateTimeScheduler(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays)) /** * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data */ @@ -166,4 +94,14 @@ internal fun createKronSchedulerWithOffset( years: Array? = null, weekDays: Array? = null, offset: TimezoneOffset -): KronScheduler = CronDateTimeSchedulerTz(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset) +): KronScheduler = CronDateTimeSchedulerTz(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset) + +internal fun List.merge() = CronDateTime( + flatMap { it.daysOfWeek ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, + flatMap { it.years ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, + flatMap { it.months ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() }, + flatMap { it.daysOfMonth ?.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.seconds ?.toList() ?: emptyList() }.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 b4b3bcf..620a5de 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt @@ -19,7 +19,7 @@ import dev.inmo.krontab.collection.plus * @see dev.inmo.krontab.builder.SchedulerBuilder */ internal data class CronDateTimeScheduler internal constructor( - internal val cronDateTimes: List + internal val cronDateTime: CronDateTime ) : KronScheduler { /** * @return Near date using [cronDateTimes] list and getting the [Iterable.minByOrNull] one @@ -27,12 +27,14 @@ internal data class CronDateTimeScheduler internal constructor( * @see toNearDateTime */ override suspend fun next(relatively: DateTime): DateTime? { - return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull() + return cronDateTime.toNearDateTime(relatively) } } -internal fun mergeCronDateTimeSchedulers(schedulers: List) = CronDateTimeScheduler( - schedulers.flatMap { it.cronDateTimes } +internal fun mergeCronDateTimeSchedulers( + schedulers: List +): CronDateTimeScheduler = CronDateTimeScheduler( + schedulers.map { it.cronDateTime }.merge() ) /** diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt index adf74d4..8a6ca26 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt @@ -11,14 +11,12 @@ import dev.inmo.krontab.KronSchedulerTz * @see CronDateTime */ internal data class CronDateTimeSchedulerTz internal constructor( - internal val cronDateTimes: List, + internal val cronDateTime: CronDateTime, internal val offset: TimezoneOffset ) : KronSchedulerTz { override suspend fun next(relatively: DateTimeTz): DateTimeTz? { val dateTimeWithActualOffset = relatively.toOffset(offset).local - return cronDateTimes.mapNotNull { - it.toNearDateTime(dateTimeWithActualOffset) - }.minOrNull() ?.toOffsetUnadjusted(offset) ?.toOffset(relatively.offset) + return cronDateTime.toNearDateTime(dateTimeWithActualOffset) ?.toOffsetUnadjusted(offset) ?.toOffset(relatively.offset) } } @@ -27,5 +25,8 @@ internal fun mergeCronDateTimeSchedulers( ) = schedulers.groupBy { it.offset }.map { (offset, schedulers) -> - CronDateTimeSchedulerTz(schedulers.flatMap { it.cronDateTimes }, offset) + CronDateTimeSchedulerTz( + schedulers.map { it.cronDateTime }.merge(), + offset + ) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/NearDateTimeCalculator.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/NearDateTimeCalculator.kt new file mode 100644 index 0000000..714a3f6 --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/NearDateTimeCalculator.kt @@ -0,0 +1,183 @@ +package dev.inmo.krontab.internal + +import com.soywiz.klock.* +import dev.inmo.krontab.utils.copy +import kotlin.math.min + +fun interface NearDateTimeCalculator { + /** + * @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be + * recalculated + */ + fun calculateNearTime( + relativelyTo: DateTime + ): Pair? +} + +internal class CommonNearDateTimeCalculator( + private val times: Array, + private val partGetter: (DateTime) -> T, + private val partSetter: (DateTime, T) -> DateTime? +) : NearDateTimeCalculator where T : Comparable, T : Number { + /** + * @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be + * recalculated + */ + override fun calculateNearTime( + relativelyTo: DateTime + ): Pair? { + val currentData = partGetter(relativelyTo) + val greaterOrEquals = times.firstOrNull { it >= currentData } + val newDateTime = when (greaterOrEquals) { + null -> partSetter(relativelyTo, times.first()) ?: return null + currentData -> relativelyTo + else -> partSetter(relativelyTo, greaterOrEquals) ?: return null + } + return if (newDateTime == relativelyTo) { + relativelyTo to false + } else { + newDateTime to true + } + } +} + +internal fun NearDateTimeCalculatorMillis( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.milliseconds.toShort() }, + { dateTime, newOne -> + (if (newOne < dateTime.milliseconds) { + dateTime.plus(1.seconds) + } else { + dateTime + }).copy(milliseconds = newOne.toInt()) + } +) + +internal fun NearDateTimeCalculatorSeconds( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.seconds.toByte() }, + { dateTime, newOne -> + (if (newOne < dateTime.seconds) { + dateTime.plus(1.minutes) + } else { + dateTime + }).copy(second = newOne.toInt(), milliseconds = 0) + } +) + +internal fun NearDateTimeCalculatorMinutes( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.minutes.toByte() }, + { dateTime, newOne -> + (if (newOne < dateTime.minutes) { + dateTime.plus(1.hours) + } else { + dateTime + }).copy(minute = newOne.toInt(), second = 0, milliseconds = 0) + } +) + +internal fun NearDateTimeCalculatorHours( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.hours.toByte() }, + { dateTime, newOne -> + (if (newOne < dateTime.hours) { + dateTime.plus(1.days) + } else { + dateTime + }).copy(hour = newOne.toInt(), minute = 0, second = 0, milliseconds = 0) + } +) + +internal fun NearDateTimeCalculatorDays( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.dayOfMonth.toByte() }, + { dateTime, newOne -> + (if (newOne < dateTime.dayOfMonth) { + dateTime.plus(1.months) + } else { + dateTime + }).copy( + dayOfMonth = min(dateTime.month.days(dateTime.year), newOne.toInt() + 1), // index1 + hour = 0, + minute = 0, + second = 0, + milliseconds = 0 + ) + } +) + +internal fun NearDateTimeCalculatorMonths( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.dayOfMonth.toByte() }, + { dateTime, newOne -> + (if (newOne < dateTime.month0) { + dateTime.plus(1.years) + } else { + dateTime + }).copy( + month = newOne.toInt() + 1, // index1 + dayOfMonth = 1, // index1 + hour = 0, + minute = 0, + second = 0, + milliseconds = 0 + ) + } +) + +internal fun NearDateTimeCalculatorWeekDays( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.dayOfWeek.index0.toByte() }, + { dateTime, newOne -> + val currentDayOfWeek = dateTime.dayOfWeek.index0 + if (newOne.toInt() == currentDayOfWeek) return@CommonNearDateTimeCalculator dateTime + (if (newOne < currentDayOfWeek) { + dateTime.plus(7.days - (currentDayOfWeek - newOne).days) + } else { + dateTime.plus(newOne.toInt().days - currentDayOfWeek.days) + }).copy( + hour = 0, + minute = 0, + second = 0, + milliseconds = 0 + ) + } +) + +internal fun NearDateTimeCalculatorYears( + times: Array +) = CommonNearDateTimeCalculator( + times, + { it.yearInt }, + { dateTime, newOne -> + val currentYear = dateTime.yearInt + if (newOne == currentYear) return@CommonNearDateTimeCalculator dateTime + (if (newOne < currentYear) { + null + } else { + dateTime.plus(newOne.years - currentYear.years) + }) ?.copy( + month = 1, // index1 + dayOfMonth = 1, // index1 + hour = 0, + minute = 0, + second = 0, + milliseconds = 0 + ) + } +) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt index 17c9be2..a162d6c 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt @@ -8,7 +8,7 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange, dataCon val things = from.split(",") val results = things.flatMap { - val currentToken = it.toLowerCase().replace( + val currentToken = it.lowercase().replace( "f", dataRange.first.toString() ).replace( "l", dataRange.last.toString() diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/DateTimeCopy.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/DateTimeCopy.kt new file mode 100644 index 0000000..c3666fb --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/utils/DateTimeCopy.kt @@ -0,0 +1,22 @@ +package dev.inmo.krontab.utils + +import com.soywiz.klock.* +import kotlin.math.min + +fun DateTime.copy( + year: Int = yearInt, + month: Int = month1, + dayOfMonth: Int = this.dayOfMonth, + hour: Int = hours, + minute: Int = minutes, + second: Int = seconds, + milliseconds: Int = this.milliseconds +) = DateTime( + year, + month, + min(Month(month).days(yearInt), dayOfMonth), + hour, + minute, + second, + milliseconds +) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt index 81738f4..1382d86 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/utils/SchedulerFlow.kt @@ -15,9 +15,16 @@ import kotlinx.coroutines.flow.* */ @FlowPreview fun KronScheduler.asTzFlow(): Flow = channelFlow { + var previousTime = DateTime.nowLocal() while (isActive) { - val now = DateTime.now().local + val now = DateTime.nowLocal() val nextTime = next(now) ?: break + if (previousTime == nextTime) { + delay(1L) // skip 1ms + continue + } else { + previousTime = nextTime + } val sleepDelay = (nextTime - DateTime.now().local).millisecondsLong delay(sleepDelay) send(nextTime) @@ -48,4 +55,4 @@ class SchedulerFlow( collector.emit(nextTime) } } -} \ No newline at end of file +} diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt index 0ddc638..64deafd 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/StringParseTest.kt @@ -67,8 +67,8 @@ class StringParseTest { val flow = kronScheduler.asFlow() runTest { - val ranges = rangesEnds.map { it.first .. it.second }.flatten().toMutableList() - val expectedCollects = rangesEnds.sumOf { it.second - it.first + 1 } + val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList() + val expectedCollects = ranges.size var collected = 0 flow.takeWhile { ranges.isNotEmpty() }.collect { @@ -80,7 +80,7 @@ class StringParseTest { } @Test fun testThatTimezoneCorrectlyDeserialized() { - val now = DateTimeTz.nowLocal() + val now = DateTime.now().copy(milliseconds = 0).local runTest { for (i in 0 .. 1339) { diff --git a/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt b/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt index 4663174..97e03e9 100644 --- a/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt +++ b/src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt @@ -10,7 +10,8 @@ class TimeZoneTest { @Test fun testDifferentTimeZonesReturnsDifferentTimes() { val scheduler = buildSchedule { seconds { every(1) } } - val baseDate = DateTime.now().startOfWeek + val additionalMilliseconds = 100.milliseconds + val baseDate = DateTime.now().startOfWeek.copy(milliseconds = additionalMilliseconds.millisecondsInt) runTest { for (i in 0 until 7) { val now = baseDate + i.days @@ -18,10 +19,10 @@ class TimeZoneTest { val nowTz = now.toOffset(j.hours) val next = scheduler.next(nowTz)!! assertEquals( - (nowTz + 1.seconds).utc.unixMillisLong, next.utc.unixMillisLong + (nowTz + 1.seconds - additionalMilliseconds).utc.unixMillisLong, next.utc.unixMillisLong ) } } } } -} \ No newline at end of file +}