diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt index b7e214a..5dc31c7 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronScheduler.kt @@ -1,7 +1,6 @@ package dev.inmo.krontab import com.soywiz.klock.DateTime -import dev.inmo.krontab.internal.toNearDateTime /** * This interface was created for abstraction of [next] operation. Currently, there is only diff --git a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt index 3e27445..e246e6b 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/KronSchedulersMerging.kt @@ -1,12 +1,8 @@ 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] diff --git a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt index ed641e6..e05a29d 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/SchedulerShortcuts.kt @@ -3,8 +3,6 @@ package dev.inmo.krontab import com.soywiz.klock.DateTime import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.internal.* -import dev.inmo.krontab.internal.CronDateTime -import dev.inmo.krontab.internal.CronDateTimeScheduler internal val anyCronDateTime by lazy { CronDateTime() diff --git a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt index a20bf93..fef6714 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/StringParser.kt @@ -1,6 +1,7 @@ package dev.inmo.krontab -import com.soywiz.klock.* +import com.soywiz.klock.TimezoneOffset +import com.soywiz.klock.minutes import dev.inmo.krontab.internal.* import dev.inmo.krontab.utils.Minutes @@ -19,9 +20,10 @@ typealias KrontabTemplate = String * * dayOfMonth * * month * * (optional) year - * * (optional) (can be placed before year) offset + * * (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00) offset + * * (optional) (can be placed anywhere after month) dayOfWeek * - * And each one have next format: + * And each one (except of offsets) have next format: * * `{number}[,{number},...]` or `*` * @@ -34,6 +36,9 @@ typealias KrontabTemplate = String * * 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] @@ -42,7 +47,7 @@ typealias KrontabTemplate = String * * 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]) - * * Offset (timezone) ranges can be found in [offsetRange] + * * WeekDay (timezone) ranges can be found in [dayOfWeekRange] * * Examples: * @@ -51,8 +56,10 @@ typealias KrontabTemplate = String * * "0/15 30 * * *" for every 15th seconds in a half of each hour * * "1 2 3 F,4,L 5" for triggering in near first second of second minute of third hour of fourth day of may * * "1 2 3 F,4,L 5 60o" for triggering in near first second of second minute of third hour of fourth day 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 fourth day 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 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 + * * "1 2 3 F,4,L 5 2021 60o 0-2w" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year if it will be in Sunday-Tuesday week days 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 @@ -64,15 +71,20 @@ fun createSimpleScheduler( incoming: KrontabTemplate ): KronScheduler { var offsetParsed: Int? = null + var dayOfWeekParsed: Array? = null var yearParsed: Array? = null val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also { listOfNotNull( it.getOrNull(5), - it.getOrNull(6) + it.getOrNull(6), + it.getOrNull(7) ).forEach { val offsetFromString = parseOffset(it) + val dayOfWeekFromString = parseWeekDay(it) offsetParsed = offsetParsed ?: offsetFromString + dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString when { + dayOfWeekFromString != null -> return@forEach offsetFromString == null && yearParsed == null -> { yearParsed = parseYears(it) } @@ -91,10 +103,10 @@ fun createSimpleScheduler( return offsetParsed ?.let { offset -> createKronSchedulerWithOffset( - secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, TimezoneOffset(offset.minutes) + secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed, TimezoneOffset(offset.minutes) ) } ?: createKronScheduler( - secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed + secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed ) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt index 84dcfec..768a127 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/SchedulerBuilder.kt @@ -1,6 +1,7 @@ package dev.inmo.krontab.builder -import com.soywiz.klock.* +import com.soywiz.klock.TimezoneOffset +import com.soywiz.klock.minutes import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.KronSchedulerTz import dev.inmo.krontab.internal.createKronScheduler @@ -43,6 +44,7 @@ class SchedulerBuilder( private var dayOfMonth: Array? = null, private var month: Array? = null, private var year: Array? = null, + private var dayOfWeek: Array? = null, private val offset: Minutes? = null ) { private fun > callAndReturn( @@ -105,6 +107,17 @@ class SchedulerBuilder( ) ?.toTypedArray() } + /** + * Starts an hours block + */ + fun dayOfWeek(block: WeekDaysBuilder.() -> Unit) { + dayOfWeek = callAndReturn( + dayOfWeek, + WeekDaysBuilder(), + block + ) ?.toTypedArray() + } + /** * Starts an months block */ @@ -134,6 +147,6 @@ class SchedulerBuilder( * @see dev.inmo.krontab.internal.createKronScheduler */ fun build(): KronScheduler = offset ?.let { - createKronSchedulerWithOffset(seconds, minutes, hours, dayOfMonth, month, year, TimezoneOffset(it.minutes)) - } ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year) + createKronSchedulerWithOffset(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, TimezoneOffset(it.minutes)) + } ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek) } diff --git a/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt b/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt index c4cc8a4..ad21c6d 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/builder/TimeBuilder.kt @@ -1,11 +1,10 @@ package dev.inmo.krontab.builder import dev.inmo.krontab.internal.* -import dev.inmo.krontab.utils.clamp /** * This class was created for incapsulation of builder work with specified [restrictionsRange]. For example, - * [include] function of [TimeBuilder] will always [clamp] incoming data using its [restrictionsRange] + * [include] function of [TimeBuilder] will always [coerceIn] incoming data using its [restrictionsRange] */ sealed class TimeBuilder ( private val restrictionsRange: IntRange, @@ -37,7 +36,7 @@ sealed class TimeBuilder ( */ @Suppress("MemberVisibilityCanBePrivate") infix fun include(array: Array) { - val clamped = array.map { it.clamp(restrictionsRange) } + (result ?: emptySet()) + val clamped = array.map { it.coerceIn(restrictionsRange) } + (result ?: emptySet()) result = clamped.toSet() } @@ -46,7 +45,7 @@ sealed class TimeBuilder ( */ @Suppress("unused") infix fun at(value: Int) { - result = (result ?: emptySet()) + value.clamp(restrictionsRange) + result = (result ?: emptySet()) + value.coerceIn(restrictionsRange) } @@ -70,7 +69,7 @@ sealed class TimeBuilder ( * @see [from] */ infix fun Int.every(delay: Int): Array { - val progression = clamp(restrictionsRange) .. restrictionsRange.last step delay + val progression = coerceIn(restrictionsRange) .. restrictionsRange.last step delay val result = progression.toSet().toTypedArray() this@TimeBuilder include result @@ -88,7 +87,7 @@ sealed class TimeBuilder ( */ @Suppress("MemberVisibilityCanBePrivate") infix fun Int.upTo(endIncluding: Int): Array { - val progression = clamp(restrictionsRange) .. endIncluding.clamp(restrictionsRange) + val progression = coerceIn(restrictionsRange) .. endIncluding.coerceIn(restrictionsRange) val result = progression.toSet().toTypedArray() this@TimeBuilder include result @@ -129,3 +128,4 @@ class HoursBuilder : TimeBuilder(hoursRange, intToByteConverter) class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange, intToByteConverter) class MonthsBuilder : TimeBuilder(monthRange, intToByteConverter) class YearsBuilder : TimeBuilder(yearRange, intToIntConverter) +class WeekDaysBuilder : TimeBuilder(dayOfWeekRange, intToByteConverter) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt index 10d3b70..97fe0c3 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/collection/CollectionKronScheduler.kt @@ -4,8 +4,6 @@ import com.soywiz.klock.DateTime import com.soywiz.klock.DateTimeTz import dev.inmo.krontab.* import dev.inmo.krontab.internal.* -import dev.inmo.krontab.internal.CronDateTimeScheduler -import dev.inmo.krontab.internal.toNearDateTime /** * This scheduler will be useful in case you want to unite several different [KronScheduler]s diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt index ae47a95..cc3667f 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTime.kt @@ -2,9 +2,10 @@ package dev.inmo.krontab.internal import com.soywiz.klock.* import dev.inmo.krontab.KronScheduler -import dev.inmo.krontab.utils.Minutes /** + * @param dayOfweek 0-6 + * @param year any int * @param month 0-11 * @param dayOfMonth 0-31 * @param hours 0-23 @@ -12,6 +13,7 @@ import dev.inmo.krontab.utils.Minutes * @param seconds 0-59 */ internal data class CronDateTime( + val dayOfweek: Byte? = null, val year: Int? = null, val month: Byte? = null, val dayOfMonth: Byte? = null, @@ -20,6 +22,7 @@ internal data class CronDateTime( val seconds: Byte? = 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) @@ -29,6 +32,7 @@ internal data class CronDateTime( } internal val klockDayOfMonth = dayOfMonth ?.plus(1) + internal val dayOfWeekInt: Int? = dayOfweek ?.toInt() } /** @@ -40,6 +44,22 @@ internal data class CronDateTime( 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 ?.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) @@ -86,7 +106,8 @@ internal fun createCronDateTimeList( hours: Array? = null, dayOfMonth: Array? = null, month: Array? = null, - years: Array? = null + years: Array? = null, + weekDays: Array? = null ): List { val resultCronDateTimes = mutableListOf(CronDateTime()) @@ -114,8 +135,8 @@ internal fun createCronDateTimeList( previousCronDateTime.copy(year = 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() @@ -130,8 +151,9 @@ internal fun createKronScheduler( hours: Array? = null, dayOfMonth: Array? = null, month: Array? = null, - years: Array? = null -): KronScheduler = CronDateTimeScheduler(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years)) + years: Array? = null, + weekDays: Array? = null +): KronScheduler = CronDateTimeScheduler(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays)) /** * @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data */ @@ -142,5 +164,6 @@ internal fun createKronSchedulerWithOffset( dayOfMonth: Array? = null, month: Array? = null, years: Array? = null, + weekDays: Array? = null, offset: TimezoneOffset -): KronScheduler = CronDateTimeSchedulerTz(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years), offset) +): KronScheduler = CronDateTimeSchedulerTz(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset) diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt index 23de605..751c179 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeScheduler.kt @@ -1,8 +1,9 @@ package dev.inmo.krontab.internal import com.soywiz.klock.DateTime -import dev.inmo.krontab.* +import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.collection.plus +import dev.inmo.krontab.getAnyNext /** * Cron-oriented realisation of [KronScheduler] diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt index c6f0e1f..adf74d4 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/CronDateTimeSchedulerTz.kt @@ -1,8 +1,9 @@ package dev.inmo.krontab.internal -import com.soywiz.klock.* -import dev.inmo.krontab.* -import dev.inmo.krontab.collection.plus +import com.soywiz.klock.DateTimeTz +import com.soywiz.klock.TimezoneOffset +import dev.inmo.krontab.KronScheduler +import dev.inmo.krontab.KronSchedulerTz /** * Cron-oriented realisation of [KronScheduler] with taking into account [offset] for list of [cronDateTimes] diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt index 871a60f..17c9be2 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Parser.kt @@ -1,7 +1,5 @@ package dev.inmo.krontab.internal -import dev.inmo.krontab.utils.clamp - typealias Converter = (Int) -> T internal val intToByteConverter: Converter = { it: Int -> it.toByte() } @@ -18,7 +16,7 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange, dataCon when { currentToken.contains("-") -> { val splitted = currentToken.split("-") - (splitted.first().toInt().clamp(dataRange) .. splitted[1].toInt().clamp(dataRange)).toList() + (splitted.first().toInt().coerceIn(dataRange) .. splitted[1].toInt().coerceIn(dataRange)).toList() } currentToken.contains("/") -> { val (start, step) = currentToken.split("/") @@ -26,18 +24,19 @@ private fun createSimpleScheduler(from: String, dataRange: IntRange, dataCon 0 } else { start.toInt() - }).clamp(dataRange) - val stepNum = step.toInt().clamp(dataRange) + }).coerceIn(dataRange) + val stepNum = step.toInt().coerceIn(dataRange) (startNum .. dataRange.last step stepNum).map { it } } currentToken == "*" -> return null - else -> listOf(currentToken.toInt().clamp(dataRange)) + else -> listOf(currentToken.toInt().coerceIn(dataRange)) } } return results.map(dataConverter) } +internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null } internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null } internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() } internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray() diff --git a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt index ec4359b..e0a03b7 100644 --- a/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt +++ b/src/commonMain/kotlin/dev/inmo/krontab/internal/Ranges.kt @@ -1,5 +1,6 @@ package dev.inmo.krontab.internal +internal val dayOfWeekRange = 0 .. 6 internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE internal val monthRange = 0 .. 11 internal val dayOfMonthRange = 0 .. 30 diff --git a/src/commonMain/kotlin/dev/inmo/krontab/utils/Data.kt b/src/commonMain/kotlin/dev/inmo/krontab/utils/Data.kt deleted file mode 100644 index f442496..0000000 --- a/src/commonMain/kotlin/dev/inmo/krontab/utils/Data.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.inmo.krontab.utils - -/** - * @return [min] in case if [this] less than [min]. Otherwise will check that [max] grant than [this] and return [this] - * if so or [max] otherwise - */ -internal fun Int.clamp(min: Int, max: Int): Int = if (this < min) min else if (this > max) max else this - -/** - * Wrapper function for [clamp] extension - */ -internal fun Int.clamp(range: IntRange): Int = clamp(range.first, range.last)