mirror of
https://github.com/InsanusMokrassar/krontab.git
synced 2024-11-26 03:58:50 +00:00
commit
ba548969e1
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.4.31` -> `1.4.32`
|
||||||
|
* Supporting of weekdays
|
||||||
|
* Supporting of timezones
|
||||||
|
* Any `KronScheduler` now can be used for calling `next` with `DateTimeTz`
|
||||||
|
* New type `KronSchedulerTz`
|
||||||
|
* `SchedulerFlow` has been deprecated
|
||||||
|
* New extension `asTzFlow` and small changes in `asFlow` logic
|
||||||
|
* `merge` extensions now return `CollectionKronScheduler` instead of just `KronScheduler`
|
||||||
|
|
||||||
## 0.5.1
|
## 0.5.1
|
||||||
|
|
||||||
* Versions
|
* Versions
|
||||||
|
43
README.md
43
README.md
@ -14,6 +14,8 @@ runtime of applications.
|
|||||||
| [ How to use: Config from string ](#config-from-string) |
|
| [ How to use: Config from string ](#config-from-string) |
|
||||||
| [ How to use: Config via builder (DSL preview) ](#config-via-builder) |
|
| [ How to use: Config via builder (DSL preview) ](#config-via-builder) |
|
||||||
| [ How to use: KronScheduler as a Flow ](#KronScheduler-as-a-Flow) |
|
| [ How to use: KronScheduler as a Flow ](#KronScheduler-as-a-Flow) |
|
||||||
|
| [ How to use: Offsets ](#Offsets) |
|
||||||
|
| [ How to use: Note about week days ](#Note-about-week-days) |
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
@ -52,14 +54,17 @@ For old version of Gradle, instead of `implementation` word developers must use
|
|||||||
|
|
||||||
Developers can use more simple way to configure repeat times is string. String configuring
|
Developers can use more simple way to configure repeat times is string. String configuring
|
||||||
like a `crontab`, but with a little bit different meanings:
|
like a `crontab`, but with a little bit different meanings:
|
||||||
|
|
||||||
```
|
```
|
||||||
/---------- Seconds
|
/--------------- Seconds
|
||||||
| /-------- Minutes
|
| /------------- Minutes
|
||||||
| | /------ Hours
|
| | /----------- Hours
|
||||||
| | | /---- Days of months
|
| | | /--------- Days of months
|
||||||
| | | | /-- Months
|
| | | | /------- Months
|
||||||
| | | | | / (optional) Year
|
| | | | | /----- (optional) Year
|
||||||
* * * * * *
|
| | | | | | /--- (optional) Timezone offset
|
||||||
|
| | | | | | | / (optional) Week days
|
||||||
|
* * * * * * 0o *w
|
||||||
```
|
```
|
||||||
|
|
||||||
It is different with original `crontab` syntax for the reason, that expected that in practice developers
|
It is different with original `crontab` syntax for the reason, that expected that in practice developers
|
||||||
@ -152,3 +157,27 @@ flow.takeWhile {
|
|||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Offsets
|
||||||
|
|
||||||
|
Offsets in this library works via passing parameter ending with `o` in any place after `month` config. Currently
|
||||||
|
there is only one format supported for offsets: minutes of offsets. To use time zones you will need to call `next`
|
||||||
|
method with `DateTimeTz` argument or `nextTimeZoned` method with any `KronScheduler` instance, but in case if this
|
||||||
|
scheduler is not instance of `KronSchedulerTz` it will works like you passed just `DateTime`.
|
||||||
|
|
||||||
|
Besides, in case you wish to use time zones explicitly, you will need to get `KronSchedulerTz`. It is possible by:
|
||||||
|
|
||||||
|
* Using `createSimpleScheduler`/`buildSchedule`/`KrontabTemplate#toSchedule`/`KrontabTemplate#toKronScheduler` methods
|
||||||
|
with passing `defaultOffset` parameter
|
||||||
|
* Using `SchedulerBuilder#build`/`createSimpleScheduler`/`buildSchedule`/`KrontabTemplate#toSchedule`/`KrontabTemplate#toKronScheduler`
|
||||||
|
methods with casting to `KronSchedulerTz` in case you are pretty sure that it is timezoned `KronScheduler`
|
||||||
|
* Creating your own implementation of `KronSchedulerTz`
|
||||||
|
|
||||||
|
### Note about week days
|
||||||
|
|
||||||
|
Unlike original CRON, here week days:
|
||||||
|
|
||||||
|
* Works as `AND`: cron date time will search first day which will pass requirement according all parameters including
|
||||||
|
week days
|
||||||
|
* You may use any related to numbers syntax with week days: `0-3w`, `0,1,2,3w`, etc.
|
||||||
|
* Week days (like years and offsets) are optional and can be placed anywhere after `month`
|
||||||
|
12
build.gradle
12
build.gradle
@ -18,6 +18,18 @@ plugins {
|
|||||||
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
|
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
|
||||||
id "org.jetbrains.dokka" version "$dokka_version"
|
id "org.jetbrains.dokka" version "$dokka_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||||
|
allprojects {
|
||||||
|
if (it != rootProject.findProject("docs")) {
|
||||||
|
tasks.whenTaskAdded { task ->
|
||||||
|
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
|
||||||
|
task.enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
apply plugin: "com.android.library"
|
apply plugin: "com.android.library"
|
||||||
|
|
||||||
project.version = "$version"
|
project.version = "$version"
|
||||||
|
@ -8,10 +8,10 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=false
|
android.enableJetifier=false
|
||||||
|
|
||||||
|
|
||||||
kotlin_version=1.4.31
|
kotlin_version=1.4.32
|
||||||
kotlin_coroutines_version=1.4.3
|
kotlin_coroutines_version=1.4.3
|
||||||
|
|
||||||
dokka_version=1.4.20
|
dokka_version=1.4.30
|
||||||
|
|
||||||
klockVersion=2.0.7
|
klockVersion=2.0.7
|
||||||
|
|
||||||
@ -33,6 +33,6 @@ androidx_work_version=2.5.0
|
|||||||
|
|
||||||
## Common
|
## Common
|
||||||
|
|
||||||
version=0.5.1
|
version=0.5.2
|
||||||
android_code_version=2
|
android_code_version=3
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package dev.inmo.krontab
|
package dev.inmo.krontab
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
import dev.inmo.krontab.internal.toNearDateTime
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface was created for abstraction of [next] operation. Currently, there is only
|
* This interface was created for abstraction of [next] operation. Currently, there is only
|
||||||
|
34
src/commonMain/kotlin/dev/inmo/krontab/KronSchedulerTz.kt
Normal file
34
src/commonMain/kotlin/dev/inmo/krontab/KronSchedulerTz.kt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun KronScheduler.nextTimeZoned() = next(DateTime.now().local)
|
@ -1,8 +1,8 @@
|
|||||||
package dev.inmo.krontab
|
package dev.inmo.krontab
|
||||||
|
|
||||||
import dev.inmo.krontab.collection.CollectionKronScheduler
|
import dev.inmo.krontab.collection.CollectionKronScheduler
|
||||||
import dev.inmo.krontab.internal.CronDateTime
|
import dev.inmo.krontab.collection.includeAll
|
||||||
import dev.inmo.krontab.internal.CronDateTimeScheduler
|
import dev.inmo.krontab.internal.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator]
|
* Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator]
|
||||||
@ -10,18 +10,23 @@ import dev.inmo.krontab.internal.CronDateTimeScheduler
|
|||||||
* @see CollectionKronScheduler
|
* @see CollectionKronScheduler
|
||||||
* @see CollectionKronScheduler.include
|
* @see CollectionKronScheduler.include
|
||||||
*/
|
*/
|
||||||
fun Iterator<KronScheduler>.merge(): KronScheduler {
|
fun Iterator<KronScheduler>.merge(): CollectionKronScheduler {
|
||||||
val cronDateTimes = mutableListOf<CronDateTime>()
|
val cronDateTimes = mutableListOf<CronDateTime>()
|
||||||
|
val timezonedCronDateTimes = mutableListOf<CronDateTimeSchedulerTz>()
|
||||||
val collectionScheduler = CollectionKronScheduler()
|
val collectionScheduler = CollectionKronScheduler()
|
||||||
forEach {
|
forEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
is CronDateTimeScheduler -> cronDateTimes.addAll(it.cronDateTimes)
|
is CronDateTimeScheduler -> cronDateTimes.addAll(it.cronDateTimes)
|
||||||
|
is CronDateTimeSchedulerTz -> timezonedCronDateTimes.add(it)
|
||||||
else -> collectionScheduler.include(it)
|
else -> collectionScheduler.include(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cronDateTimes.isNotEmpty()) {
|
if (cronDateTimes.isNotEmpty()) {
|
||||||
collectionScheduler.include(CronDateTimeScheduler(cronDateTimes))
|
collectionScheduler.include(CronDateTimeScheduler(cronDateTimes))
|
||||||
}
|
}
|
||||||
|
if (timezonedCronDateTimes.isNotEmpty()) {
|
||||||
|
collectionScheduler.includeAll(mergeCronDateTimeSchedulers(timezonedCronDateTimes))
|
||||||
|
}
|
||||||
return collectionScheduler
|
return collectionScheduler
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,10 +37,10 @@ fun Iterator<KronScheduler>.merge(): KronScheduler {
|
|||||||
* @see CollectionKronScheduler.include
|
* @see CollectionKronScheduler.include
|
||||||
*/
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun Iterable<KronScheduler>.merge(): KronScheduler = iterator().merge()
|
inline fun Iterable<KronScheduler>.merge(): CollectionKronScheduler = iterator().merge()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Vararg shortcut for [merge]
|
* @return Vararg shortcut for [merge]
|
||||||
*/
|
*/
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun merge(vararg kronDateTimeSchedulers: KronScheduler) = kronDateTimeSchedulers.iterator().merge()
|
inline fun merge(vararg kronDateTimeSchedulers: KronScheduler): CollectionKronScheduler = kronDateTimeSchedulers.iterator().merge()
|
||||||
|
@ -3,8 +3,6 @@ package dev.inmo.krontab
|
|||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
import dev.inmo.krontab.builder.buildSchedule
|
import dev.inmo.krontab.builder.buildSchedule
|
||||||
import dev.inmo.krontab.internal.*
|
import dev.inmo.krontab.internal.*
|
||||||
import dev.inmo.krontab.internal.CronDateTime
|
|
||||||
import dev.inmo.krontab.internal.CronDateTimeScheduler
|
|
||||||
|
|
||||||
internal val anyCronDateTime by lazy {
|
internal val anyCronDateTime by lazy {
|
||||||
CronDateTime()
|
CronDateTime()
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package dev.inmo.krontab
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import com.soywiz.klock.TimezoneOffset
|
||||||
|
import com.soywiz.klock.minutes
|
||||||
import dev.inmo.krontab.internal.*
|
import dev.inmo.krontab.internal.*
|
||||||
|
import dev.inmo.krontab.utils.Minutes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see createSimpleScheduler
|
* @see createSimpleScheduler
|
||||||
@ -17,8 +20,10 @@ typealias KrontabTemplate = String
|
|||||||
* * dayOfMonth
|
* * dayOfMonth
|
||||||
* * month
|
* * month
|
||||||
* * (optional) year
|
* * (optional) year
|
||||||
|
* * (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 `*`
|
* `{number}[,{number},...]` or `*`
|
||||||
*
|
*
|
||||||
@ -31,6 +36,9 @@ typealias KrontabTemplate = String
|
|||||||
* * F
|
* * F
|
||||||
* * L
|
* * 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:
|
* Additional info about ranges can be found in follow accordance:
|
||||||
*
|
*
|
||||||
* * Seconds ranges can be found in [secondsRange]
|
* * Seconds ranges can be found in [secondsRange]
|
||||||
@ -39,6 +47,7 @@ typealias KrontabTemplate = String
|
|||||||
* * Days of month ranges can be found in [dayOfMonthRange]
|
* * Days of month ranges can be found in [dayOfMonthRange]
|
||||||
* * Months ranges can be found in [monthRange]
|
* * Months ranges can be found in [monthRange]
|
||||||
* * Years ranges can be found in [yearRange] (in fact - any [Int])
|
* * Years ranges can be found in [yearRange] (in fact - any [Int])
|
||||||
|
* * WeekDay (timezone) ranges can be found in [dayOfWeekRange]
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
*
|
*
|
||||||
@ -46,14 +55,41 @@ typealias KrontabTemplate = String
|
|||||||
* * "0/5,L * * * *" for every five seconds triggering and on 59 second
|
* * "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 * * *" 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" 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" 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
|
||||||
|
* [createKronSchedulerWithOffset] and returned [CronDateTimeSchedulerTz]
|
||||||
*
|
*
|
||||||
* @see dev.inmo.krontab.internal.createKronScheduler
|
* @see dev.inmo.krontab.internal.createKronScheduler
|
||||||
*/
|
*/
|
||||||
fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler {
|
fun createSimpleScheduler(
|
||||||
val yearSource: String?
|
incoming: KrontabTemplate
|
||||||
|
): KronScheduler {
|
||||||
|
var offsetParsed: Int? = null
|
||||||
|
var dayOfWeekParsed: Array<Byte>? = null
|
||||||
|
var yearParsed: Array<Int>? = null
|
||||||
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also {
|
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also {
|
||||||
yearSource = it.getOrNull(5)
|
listOfNotNull(
|
||||||
|
it.getOrNull(5),
|
||||||
|
it.getOrNull(6),
|
||||||
|
it.getOrNull(7)
|
||||||
|
).forEach {
|
||||||
|
val offsetFromString = parseOffset(it)
|
||||||
|
val dayOfWeekFromString = parseWeekDay(it)
|
||||||
|
offsetParsed = offsetParsed ?: offsetFromString
|
||||||
|
dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString
|
||||||
|
when {
|
||||||
|
dayOfWeekFromString != null || offsetFromString != null -> return@forEach
|
||||||
|
yearParsed == null -> {
|
||||||
|
yearParsed = parseYears(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val secondsParsed = parseSeconds(secondsSource)
|
val secondsParsed = parseSeconds(secondsSource)
|
||||||
@ -61,24 +97,54 @@ fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler {
|
|||||||
val hoursParsed = parseHours(hoursSource)
|
val hoursParsed = parseHours(hoursSource)
|
||||||
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
||||||
val monthParsed = parseMonths(monthSource)
|
val monthParsed = parseMonths(monthSource)
|
||||||
val yearParsed = parseYears(yearSource)
|
|
||||||
|
|
||||||
return createKronScheduler(
|
return offsetParsed ?.let { offset ->
|
||||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed
|
createKronSchedulerWithOffset(
|
||||||
|
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed, TimezoneOffset(offset.minutes)
|
||||||
|
)
|
||||||
|
} ?: createKronScheduler(
|
||||||
|
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
* Shortcut for [createSimpleScheduler]
|
||||||
*/
|
*/
|
||||||
fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming)
|
fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming)
|
||||||
|
/**
|
||||||
|
* Shortcut for [createSimpleScheduler]
|
||||||
|
*/
|
||||||
|
fun buildSchedule(incoming: KrontabTemplate, defaultOffset: Minutes): KronSchedulerTz = createSimpleScheduler(incoming, defaultOffset)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for [buildSchedule]
|
* Shortcut for [buildSchedule]
|
||||||
*/
|
*/
|
||||||
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
|
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
|
||||||
|
/**
|
||||||
|
* Shortcut for [buildSchedule]
|
||||||
|
*/
|
||||||
|
fun KrontabTemplate.toSchedule(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut for [buildSchedule]
|
* Shortcut for [buildSchedule]
|
||||||
*/
|
*/
|
||||||
fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this)
|
fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this)
|
||||||
|
/**
|
||||||
|
* Shortcut for [buildSchedule]
|
||||||
|
*/
|
||||||
|
fun KrontabTemplate.toKronScheduler(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset)
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
package dev.inmo.krontab.builder
|
package dev.inmo.krontab.builder
|
||||||
|
|
||||||
|
import com.soywiz.klock.TimezoneOffset
|
||||||
|
import com.soywiz.klock.minutes
|
||||||
import dev.inmo.krontab.KronScheduler
|
import dev.inmo.krontab.KronScheduler
|
||||||
|
import dev.inmo.krontab.KronSchedulerTz
|
||||||
import dev.inmo.krontab.internal.createKronScheduler
|
import dev.inmo.krontab.internal.createKronScheduler
|
||||||
|
import dev.inmo.krontab.internal.createKronSchedulerWithOffset
|
||||||
|
import dev.inmo.krontab.utils.Minutes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will help to create an instance of [KronScheduler]
|
* Will help to create an instance of [KronScheduler]
|
||||||
@ -16,13 +21,31 @@ fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
|||||||
return builder.build()
|
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(
|
class SchedulerBuilder(
|
||||||
private var seconds: Array<Byte>? = null,
|
private var seconds: Array<Byte>? = null,
|
||||||
private var minutes: Array<Byte>? = null,
|
private var minutes: Array<Byte>? = null,
|
||||||
private var hours: Array<Byte>? = null,
|
private var hours: Array<Byte>? = null,
|
||||||
private var dayOfMonth: Array<Byte>? = null,
|
private var dayOfMonth: Array<Byte>? = null,
|
||||||
private var month: Array<Byte>? = null,
|
private var month: Array<Byte>? = null,
|
||||||
private var year: Array<Int>? = null
|
private var year: Array<Int>? = null,
|
||||||
|
private var dayOfWeek: Array<Byte>? = null,
|
||||||
|
private val offset: Minutes? = null
|
||||||
) {
|
) {
|
||||||
private fun <I, T : TimeBuilder<I>> callAndReturn(
|
private fun <I, T : TimeBuilder<I>> callAndReturn(
|
||||||
initial: Array<I>?,
|
initial: Array<I>?,
|
||||||
@ -84,6 +107,17 @@ class SchedulerBuilder(
|
|||||||
) ?.toTypedArray()
|
) ?.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an hours block
|
||||||
|
*/
|
||||||
|
fun dayOfWeek(block: WeekDaysBuilder.() -> Unit) {
|
||||||
|
dayOfWeek = callAndReturn(
|
||||||
|
dayOfWeek,
|
||||||
|
WeekDaysBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts an months block
|
* Starts an months block
|
||||||
*/
|
*/
|
||||||
@ -112,5 +146,7 @@ class SchedulerBuilder(
|
|||||||
* @see dev.inmo.krontab.createSimpleScheduler
|
* @see dev.inmo.krontab.createSimpleScheduler
|
||||||
* @see dev.inmo.krontab.internal.createKronScheduler
|
* @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, dayOfWeek, TimezoneOffset(it.minutes))
|
||||||
|
} ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package dev.inmo.krontab.builder
|
package dev.inmo.krontab.builder
|
||||||
|
|
||||||
import dev.inmo.krontab.internal.*
|
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,
|
* 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<T : Number> (
|
sealed class TimeBuilder<T : Number> (
|
||||||
private val restrictionsRange: IntRange,
|
private val restrictionsRange: IntRange,
|
||||||
@ -37,7 +36,7 @@ sealed class TimeBuilder<T : Number> (
|
|||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
infix fun include(array: Array<Int>) {
|
infix fun include(array: Array<Int>) {
|
||||||
val clamped = array.map { it.clamp(restrictionsRange) } + (result ?: emptySet())
|
val clamped = array.map { it.coerceIn(restrictionsRange) } + (result ?: emptySet())
|
||||||
result = clamped.toSet()
|
result = clamped.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ sealed class TimeBuilder<T : Number> (
|
|||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
infix fun at(value: Int) {
|
infix fun at(value: Int) {
|
||||||
result = (result ?: emptySet()) + value.clamp(restrictionsRange)
|
result = (result ?: emptySet()) + value.coerceIn(restrictionsRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ sealed class TimeBuilder<T : Number> (
|
|||||||
* @see [from]
|
* @see [from]
|
||||||
*/
|
*/
|
||||||
infix fun Int.every(delay: Int): Array<Int> {
|
infix fun Int.every(delay: Int): Array<Int> {
|
||||||
val progression = clamp(restrictionsRange) .. restrictionsRange.last step delay
|
val progression = coerceIn(restrictionsRange) .. restrictionsRange.last step delay
|
||||||
val result = progression.toSet().toTypedArray()
|
val result = progression.toSet().toTypedArray()
|
||||||
|
|
||||||
this@TimeBuilder include result
|
this@TimeBuilder include result
|
||||||
@ -88,7 +87,7 @@ sealed class TimeBuilder<T : Number> (
|
|||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
infix fun Int.upTo(endIncluding: Int): Array<Int> {
|
infix fun Int.upTo(endIncluding: Int): Array<Int> {
|
||||||
val progression = clamp(restrictionsRange) .. endIncluding.clamp(restrictionsRange)
|
val progression = coerceIn(restrictionsRange) .. endIncluding.coerceIn(restrictionsRange)
|
||||||
val result = progression.toSet().toTypedArray()
|
val result = progression.toSet().toTypedArray()
|
||||||
|
|
||||||
this@TimeBuilder include result
|
this@TimeBuilder include result
|
||||||
@ -129,3 +128,4 @@ class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
|
|||||||
class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
|
class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
|
||||||
class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
|
class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
|
||||||
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)
|
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)
|
||||||
|
class WeekDaysBuilder : TimeBuilder<Byte>(dayOfWeekRange, intToByteConverter)
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
package dev.inmo.krontab.collection
|
package dev.inmo.krontab.collection
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
|
import com.soywiz.klock.DateTimeTz
|
||||||
import dev.inmo.krontab.*
|
import dev.inmo.krontab.*
|
||||||
import dev.inmo.krontab.internal.*
|
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
|
* This scheduler will be useful in case you want to unite several different [KronScheduler]s
|
||||||
*/
|
*/
|
||||||
data class CollectionKronScheduler internal constructor(
|
data class CollectionKronScheduler internal constructor(
|
||||||
internal val schedulers: MutableList<KronScheduler>
|
internal val schedulers: MutableList<KronScheduler>
|
||||||
) : KronScheduler {
|
) : KronSchedulerTz {
|
||||||
internal constructor() : this(mutableListOf())
|
internal constructor() : this(mutableListOf())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,6 +37,18 @@ data class CollectionKronScheduler internal constructor(
|
|||||||
mergeCronDateTimeSchedulers(resultCronDateTimes)
|
mergeCronDateTimeSchedulers(resultCronDateTimes)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is CronDateTimeSchedulerTz -> {
|
||||||
|
val newCronDateTimes = kronScheduler.cronDateTimes.toMutableList()
|
||||||
|
val cronDateTimes = schedulers.removeAll {
|
||||||
|
if (it is CronDateTimeSchedulerTz && it.offset == kronScheduler.offset) {
|
||||||
|
newCronDateTimes.addAll(it.cronDateTimes)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schedulers.add(CronDateTimeSchedulerTz(newCronDateTimes.toList(), kronScheduler.offset))
|
||||||
|
}
|
||||||
is CollectionKronScheduler -> kronScheduler.schedulers.forEach {
|
is CollectionKronScheduler -> kronScheduler.schedulers.forEach {
|
||||||
include(it)
|
include(it)
|
||||||
}
|
}
|
||||||
@ -48,4 +59,8 @@ data class CollectionKronScheduler internal constructor(
|
|||||||
override suspend fun next(relatively: DateTime): DateTime {
|
override suspend fun next(relatively: DateTime): DateTime {
|
||||||
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively)
|
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun next(relatively: DateTimeTz): DateTimeTz {
|
||||||
|
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively.local).toOffsetUnadjusted(relatively.offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package dev.inmo.krontab.internal
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.*
|
||||||
import com.soywiz.klock.DateTimeSpan
|
|
||||||
import dev.inmo.krontab.KronScheduler
|
import dev.inmo.krontab.KronScheduler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param dayOfweek 0-6
|
||||||
|
* @param year any int
|
||||||
* @param month 0-11
|
* @param month 0-11
|
||||||
* @param dayOfMonth 0-31
|
* @param dayOfMonth 0-31
|
||||||
* @param hours 0-23
|
* @param hours 0-23
|
||||||
@ -12,6 +13,7 @@ import dev.inmo.krontab.KronScheduler
|
|||||||
* @param seconds 0-59
|
* @param seconds 0-59
|
||||||
*/
|
*/
|
||||||
internal data class CronDateTime(
|
internal data class CronDateTime(
|
||||||
|
val dayOfweek: Byte? = null,
|
||||||
val year: Int? = null,
|
val year: Int? = null,
|
||||||
val month: Byte? = null,
|
val month: Byte? = null,
|
||||||
val dayOfMonth: Byte? = null,
|
val dayOfMonth: Byte? = null,
|
||||||
@ -20,6 +22,7 @@ internal data class CronDateTime(
|
|||||||
val seconds: Byte? = null
|
val seconds: Byte? = null
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
|
check(dayOfweek ?.let { it in dayOfWeekRange } ?: true)
|
||||||
check(year ?.let { it in yearRange } ?: true)
|
check(year ?.let { it in yearRange } ?: true)
|
||||||
check(month ?.let { it in monthRange } ?: true)
|
check(month ?.let { it in monthRange } ?: true)
|
||||||
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true)
|
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true)
|
||||||
@ -29,14 +32,34 @@ internal data class CronDateTime(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal val klockDayOfMonth = dayOfMonth ?.plus(1)
|
internal val klockDayOfMonth = dayOfMonth ?.plus(1)
|
||||||
|
internal val dayOfWeekInt: Int? = dayOfweek ?.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* THIS METHOD WILL <b>NOT</b> 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]
|
* @return The near [DateTime] which happens after [relativelyTo] or will be equal to [relativelyTo]
|
||||||
*/
|
*/
|
||||||
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? {
|
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? {
|
||||||
var current = relativelyTo
|
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 {
|
seconds?.let {
|
||||||
val left = it - current.seconds
|
val left = it - current.seconds
|
||||||
current += DateTimeSpan(minutes = if (left <= 0) 1 else 0, seconds = left)
|
current += DateTimeSpan(minutes = if (left <= 0) 1 else 0, seconds = left)
|
||||||
@ -77,17 +100,15 @@ internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()
|
|||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
internal fun createCronDateTimeList(
|
||||||
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
|
||||||
*/
|
|
||||||
internal fun createKronScheduler(
|
|
||||||
seconds: Array<Byte>? = null,
|
seconds: Array<Byte>? = null,
|
||||||
minutes: Array<Byte>? = null,
|
minutes: Array<Byte>? = null,
|
||||||
hours: Array<Byte>? = null,
|
hours: Array<Byte>? = null,
|
||||||
dayOfMonth: Array<Byte>? = null,
|
dayOfMonth: Array<Byte>? = null,
|
||||||
month: Array<Byte>? = null,
|
month: Array<Byte>? = null,
|
||||||
years: Array<Int>? = null
|
years: Array<Int>? = null,
|
||||||
): KronScheduler {
|
weekDays: Array<Byte>? = null
|
||||||
|
): List<CronDateTime> {
|
||||||
val resultCronDateTimes = mutableListOf(CronDateTime())
|
val resultCronDateTimes = mutableListOf(CronDateTime())
|
||||||
|
|
||||||
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||||
@ -114,5 +135,35 @@ internal fun createKronScheduler(
|
|||||||
previousCronDateTime.copy(year = currentTime)
|
previousCronDateTime.copy(year = currentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CronDateTimeScheduler(resultCronDateTimes.toList())
|
weekDays ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
||||||
|
previousCronDateTime.copy(dayOfweek = currentTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultCronDateTimes.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
||||||
|
*/
|
||||||
|
internal fun createKronScheduler(
|
||||||
|
seconds: Array<Byte>? = null,
|
||||||
|
minutes: Array<Byte>? = null,
|
||||||
|
hours: Array<Byte>? = null,
|
||||||
|
dayOfMonth: Array<Byte>? = null,
|
||||||
|
month: Array<Byte>? = null,
|
||||||
|
years: Array<Int>? = null,
|
||||||
|
weekDays: Array<Byte>? = null
|
||||||
|
): KronScheduler = CronDateTimeScheduler(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays))
|
||||||
|
/**
|
||||||
|
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
||||||
|
*/
|
||||||
|
internal fun createKronSchedulerWithOffset(
|
||||||
|
seconds: Array<Byte>? = null,
|
||||||
|
minutes: Array<Byte>? = null,
|
||||||
|
hours: Array<Byte>? = null,
|
||||||
|
dayOfMonth: Array<Byte>? = null,
|
||||||
|
month: Array<Byte>? = null,
|
||||||
|
years: Array<Int>? = null,
|
||||||
|
weekDays: Array<Byte>? = null,
|
||||||
|
offset: TimezoneOffset
|
||||||
|
): KronScheduler = CronDateTimeSchedulerTz(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.krontab.internal
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
import dev.inmo.krontab.*
|
import dev.inmo.krontab.KronScheduler
|
||||||
import dev.inmo.krontab.collection.plus
|
import dev.inmo.krontab.collection.plus
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,8 +26,8 @@ internal data class CronDateTimeScheduler internal constructor(
|
|||||||
*
|
*
|
||||||
* @see toNearDateTime
|
* @see toNearDateTime
|
||||||
*/
|
*/
|
||||||
override suspend fun next(relatively: DateTime): DateTime {
|
override suspend fun next(relatively: DateTime): DateTime? {
|
||||||
return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull() ?: getAnyNext(relatively)
|
return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
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]
|
||||||
|
*
|
||||||
|
* @see CronDateTime
|
||||||
|
*/
|
||||||
|
internal data class CronDateTimeSchedulerTz internal constructor(
|
||||||
|
internal val cronDateTimes: List<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mergeCronDateTimeSchedulers(
|
||||||
|
schedulers: List<CronDateTimeSchedulerTz>
|
||||||
|
) = schedulers.groupBy {
|
||||||
|
it.offset
|
||||||
|
}.map { (offset, schedulers) ->
|
||||||
|
CronDateTimeSchedulerTz(schedulers.flatMap { it.cronDateTimes }, offset)
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package dev.inmo.krontab.internal
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
import dev.inmo.krontab.utils.clamp
|
|
||||||
|
|
||||||
typealias Converter<T> = (Int) -> T
|
typealias Converter<T> = (Int) -> T
|
||||||
|
|
||||||
internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() }
|
internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() }
|
||||||
@ -18,7 +16,7 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
|
|||||||
when {
|
when {
|
||||||
currentToken.contains("-") -> {
|
currentToken.contains("-") -> {
|
||||||
val splitted = currentToken.split("-")
|
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("/") -> {
|
currentToken.contains("/") -> {
|
||||||
val (start, step) = currentToken.split("/")
|
val (start, step) = currentToken.split("/")
|
||||||
@ -26,18 +24,20 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
|
|||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
start.toInt()
|
start.toInt()
|
||||||
}).clamp(dataRange)
|
}).coerceIn(dataRange)
|
||||||
val stepNum = step.toInt().clamp(dataRange)
|
val stepNum = step.toInt().coerceIn(dataRange)
|
||||||
(startNum .. dataRange.last step stepNum).map { it }
|
(startNum .. dataRange.last step stepNum).map { it }
|
||||||
}
|
}
|
||||||
currentToken == "*" -> return null
|
currentToken == "*" -> return null
|
||||||
else -> listOf(currentToken.toInt().clamp(dataRange))
|
else -> listOf(currentToken.toInt().coerceIn(dataRange))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.map(dataConverter)
|
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 parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }
|
||||||
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray()
|
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray()
|
||||||
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray()
|
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray()
|
||||||
@ -60,3 +60,16 @@ internal fun <T> Array<T>.fillWith(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun <T> T.fillWith(
|
||||||
|
whereToPut: MutableList<CronDateTime>,
|
||||||
|
createFactory: (CronDateTime, T) -> CronDateTime
|
||||||
|
) {
|
||||||
|
val previousValues = whereToPut.toList()
|
||||||
|
|
||||||
|
whereToPut.clear()
|
||||||
|
|
||||||
|
previousValues.forEach { previousValue ->
|
||||||
|
whereToPut.add(createFactory(previousValue, this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.krontab.internal
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
internal val dayOfWeekRange = 0 .. 6
|
||||||
internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE
|
internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE
|
||||||
internal val monthRange = 0 .. 11
|
internal val monthRange = 0 .. 11
|
||||||
internal val dayOfMonthRange = 0 .. 30
|
internal val dayOfMonthRange = 0 .. 30
|
||||||
|
@ -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)
|
|
@ -1,14 +1,39 @@
|
|||||||
package dev.inmo.krontab.utils
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
|
import com.soywiz.klock.DateTimeTz
|
||||||
import dev.inmo.krontab.KronScheduler
|
import dev.inmo.krontab.KronScheduler
|
||||||
import kotlinx.coroutines.FlowPreview
|
import dev.inmo.krontab.next
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This [Flow] will trigger emitting each near time which will be returned from [this] [KronScheduler] with attention to
|
||||||
|
* time zones
|
||||||
|
*
|
||||||
|
* @see channelFlow
|
||||||
|
*/
|
||||||
@FlowPreview
|
@FlowPreview
|
||||||
fun KronScheduler.asFlow(): Flow<DateTime> = SchedulerFlow(this)
|
fun KronScheduler.asTzFlow(): Flow<DateTimeTz> = channelFlow {
|
||||||
|
while (isActive) {
|
||||||
|
val now = DateTime.now().local
|
||||||
|
val nextTime = next(now) ?: break
|
||||||
|
val sleepDelay = (nextTime - DateTime.now().local).millisecondsLong
|
||||||
|
delay(sleepDelay)
|
||||||
|
send(nextTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s
|
||||||
|
*/
|
||||||
|
@FlowPreview
|
||||||
|
fun KronScheduler.asFlow(): Flow<DateTime> = asTzFlow().map { it.local }
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
"It is not recommended to use this class in future. This functionality will be removed soon",
|
||||||
|
ReplaceWith("asFlow", "dev.inmo.krontab.utils.asFlow")
|
||||||
|
)
|
||||||
@FlowPreview
|
@FlowPreview
|
||||||
class SchedulerFlow(
|
class SchedulerFlow(
|
||||||
private val scheduler: KronScheduler
|
private val scheduler: KronScheduler
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
typealias Minutes = Int
|
@ -1,5 +1,7 @@
|
|||||||
package dev.inmo.krontab.utils
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
import com.soywiz.klock.*
|
||||||
|
import dev.inmo.krontab.KronSchedulerTz
|
||||||
import dev.inmo.krontab.buildSchedule
|
import dev.inmo.krontab.buildSchedule
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
@ -76,4 +78,19 @@ class StringParseTest {
|
|||||||
assertEquals(expectedCollects, collected)
|
assertEquals(expectedCollects, collected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
fun testThatTimezoneCorrectlyDeserialized() {
|
||||||
|
val now = DateTimeTz.nowLocal()
|
||||||
|
|
||||||
|
runTest {
|
||||||
|
for (i in 0 .. 1339) {
|
||||||
|
val expectedInCurrentOffset = now.toOffset(TimezoneOffset(i.minutes)) + 1.hours
|
||||||
|
val kronScheduler = buildSchedule(
|
||||||
|
"${expectedInCurrentOffset.seconds} ${expectedInCurrentOffset.minutes} ${expectedInCurrentOffset.hours} * * ${i}o"
|
||||||
|
) as KronSchedulerTz
|
||||||
|
val next = kronScheduler.next(now)
|
||||||
|
assertEquals(expectedInCurrentOffset.toOffset(now.offset), next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
27
src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt
Normal file
27
src/commonTest/kotlin/dev/inmo/krontab/utils/TimeZoneTest.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class TimeZoneTest {
|
||||||
|
@Test
|
||||||
|
fun testDifferentTimeZonesReturnsDifferentTimes() {
|
||||||
|
val scheduler = buildSchedule { seconds { every(1) } }
|
||||||
|
val baseDate = DateTime.now().startOfWeek
|
||||||
|
runTest {
|
||||||
|
for (i in 0 until 7) {
|
||||||
|
val now = baseDate + i.days
|
||||||
|
for (j in 0 .. 24) {
|
||||||
|
val nowTz = now.toOffset(j.hours)
|
||||||
|
val next = scheduler.next(nowTz)!!
|
||||||
|
assertEquals(
|
||||||
|
(nowTz + 1.seconds).utc.unixMillisLong, next.utc.unixMillisLong
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/commonTest/kotlin/dev/inmo/krontab/utils/WeekDaysTest.kt
Normal file
38
src/commonTest/kotlin/dev/inmo/krontab/utils/WeekDaysTest.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
import com.soywiz.klock.*
|
||||||
|
import dev.inmo.krontab.builder.buildSchedule
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class WeekDaysTest {
|
||||||
|
@Test
|
||||||
|
fun testThatWeekDaysSchedulingWorks() {
|
||||||
|
val startDateTime = DateTime.now().startOfDay
|
||||||
|
val weekDay = startDateTime.dayOfWeek.index0
|
||||||
|
val testDays = 400
|
||||||
|
val scheduler = buildSchedule {
|
||||||
|
dayOfWeek {
|
||||||
|
at(weekDay)
|
||||||
|
}
|
||||||
|
years {
|
||||||
|
at(startDateTime.yearInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runTest {
|
||||||
|
for (day in 0 until testDays) {
|
||||||
|
val currentDateTime = startDateTime + day.days
|
||||||
|
val next = scheduler.next(currentDateTime)
|
||||||
|
val expected = when {
|
||||||
|
day % 7 == 0 -> currentDateTime
|
||||||
|
else -> startDateTime + ceil(day.toFloat() / 7).weeks
|
||||||
|
}
|
||||||
|
if (expected.yearInt != startDateTime.yearInt) {
|
||||||
|
assertNull(next)
|
||||||
|
} else {
|
||||||
|
assertEquals(expected, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user