From 28f84d4e3abff339383147ff8ac1931c155183b6 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 30 May 2021 17:15:32 +0600 Subject: [PATCH] rewrite mechanism of calculating of near time --- CHANGELOG.md | 4 +- .../dev/inmo/krontab/KronSchedulersMerging.kt | 4 +- .../dev/inmo/krontab/SchedulerShortcuts.kt | 4 +- .../kotlin/dev/inmo/krontab/StringParser.kt | 2 +- .../collection/CollectionKronScheduler.kt | 8 +- .../dev/inmo/krontab/internal/CronDateTime.kt | 265 ++++++++++-------- .../krontab/internal/CronDateTimeScheduler.kt | 10 +- .../internal/CronDateTimeSchedulerTz.kt | 11 +- .../internal/NearDateTimeCalculator.kt | 171 +++++++++++ .../dev/inmo/krontab/internal/Parser.kt | 2 +- .../dev/inmo/krontab/utils/DateTimeCopy.kt | 22 ++ .../dev/inmo/krontab/utils/TimeZoneTest.kt | 7 +- 12 files changed, 371 insertions(+), 139 deletions(-) create mode 100644 src/commonMain/kotlin/dev/inmo/krontab/internal/NearDateTimeCalculator.kt create mode 100644 src/commonMain/kotlin/dev/inmo/krontab/utils/DateTimeCopy.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0f03e..272ce4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ ## 0.6.1 * Versions - * `Klock`: `2.1.0` -> `2.1.2` + * `Klock`: `2.1.0` -> `2.1.2 + +` ## 0.6.0 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..802e765 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt @@ -4,103 +4,125 @@ import com.soywiz.klock.* import dev.inmo.krontab.KronScheduler /** - * @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( + NearDateTimeCalculatorMillis(arrayOf(0)), + seconds ?.let { NearDateTimeCalculatorSeconds(it) }, + minutes ?.let { NearDateTimeCalculatorMinutes(it) }, + hours ?.let { NearDateTimeCalculatorHours(it) }, + daysOfMonth ?.let { NearDateTimeCalculatorDays(it) }, + months ?.let { NearDateTimeCalculatorMonths(it) }, + years ?.let { NearDateTimeCalculatorYears(it) }, + daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(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 } +// +///** +// * 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 */ +// } +// 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) +// } +// +// months?.let { +// val left = it - current.month0 +// current += DateTimeSpan(years = if (left < 0) 1 else 0, months = left) +// } +// +// years?.let { +// if (current.yearInt != it) { +// return null +// } +// } +// +// return current +//} -internal fun createCronDateTimeList( +internal fun createCronDateTime( seconds: Array? = null, minutes: Array? = null, hours: Array? = null, @@ -108,38 +130,39 @@ 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) +// 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(daysOfMonth = currentTime) +// } +// +// month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> +// previousCronDateTime.copy(months = currentTime) +// } +// +// years ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int -> +// previousCronDateTime.copy(years = currentTime) +// } +// +// weekDays ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> +// previousCronDateTime.copy(daysOfWeek = currentTime) +// } +// +// return resultCronDateTimes.toList() } /** @@ -153,7 +176,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 +189,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..7992ab4 --- /dev/null +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/NearDateTimeCalculator.kt @@ -0,0 +1,171 @@ +package dev.inmo.krontab.internal + +import com.soywiz.klock.* +import dev.inmo.krontab.utils.copy +import kotlin.math.min + +internal class NearDateTimeCalculator( + private val times: Array, + private val partGetter: (DateTime) -> T, + private val partSetter: (DateTime, T) -> DateTime? +) where T : Comparable, T : Number { + /** + * @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be + * recalculated + */ + fun calculateNearTime( + relativelyTo: DateTime + ): Pair? { + val currentData = partGetter(relativelyTo) + val greaterOrEquals = times.firstOrNull { it >= currentData } + val newDateTime = if (greaterOrEquals == null) { + partSetter(relativelyTo, times.first()) ?: return null + } else { + partSetter(relativelyTo, greaterOrEquals) ?: return null + } + return if (newDateTime == relativelyTo) { + relativelyTo to false + } else { + newDateTime to true + } + } +} + +internal fun NearDateTimeCalculatorMillis( + times: Array +) = NearDateTimeCalculator( + 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 +) = NearDateTimeCalculator( + 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 +) = NearDateTimeCalculator( + 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 +) = NearDateTimeCalculator( + 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 +) = NearDateTimeCalculator( + 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 +) = NearDateTimeCalculator( + 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 +) = NearDateTimeCalculator( + times, + { it.dayOfWeek.index0.toByte() }, + { dateTime, newOne -> + val currentDayOfWeek = dateTime.dayOfWeek.index0 + (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 +) = NearDateTimeCalculator( + times, + { it.yearInt }, + { dateTime, newOne -> + val currentYear = dateTime.yearInt + (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/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 +}