Compare commits

..

49 Commits
0.6.0 ... 0.6.5

Author SHA1 Message Date
97752e1ac0 optimize imports 2021-09-26 13:06:22 +06:00
276ecaac27 overwrite do* extensions 2021-09-26 13:05:59 +06:00
d36e406b59 now flows are using doInfinity 2021-09-26 11:49:01 +06:00
4dc65bf09a delay in doWhile and flows with doWhile instead their own logic 2021-09-25 19:28:49 +06:00
539b5fbcd8 0.6.5 2021-09-25 18:51:51 +06:00
e4f417877d Update kdocs.yml 2021-09-23 13:22:27 +06:00
4f6054c0d9 Update build.gradle 2021-09-23 13:18:40 +06:00
88dbfee370 Update README.md 2021-09-22 20:57:21 +06:00
5af29c2246 Merge pull request #21 from InsanusMokrassar/0.6.4
0.6.4
2021-09-22 20:52:29 +06:00
6aa40ad463 fix publication workflow script 2021-09-22 20:26:12 +06:00
3062f839af fix for publishing packages 2021-09-22 20:20:13 +06:00
001756ab9f add fix for android build 2021-09-22 20:12:50 +06:00
b7ebd0ac1c update workflows 2021-09-22 20:08:06 +06:00
ece767e4d0 update android compile sdk version and build tools 2021-09-22 20:04:08 +06:00
222542b38f update version of build gradle tool 2021-09-22 19:46:35 +06:00
5798774b2d update androidx work dependency 2021-09-22 19:39:18 +06:00
32bf5c0641 update dependencies 2021-09-22 19:34:04 +06:00
5916f3eebb start 0.6.4 2021-09-22 19:29:54 +06:00
df01a66b5a Merge pull request #20 from InsanusMokrassar/0.6.3
0.6.3
2021-08-30 20:26:10 +06:00
a622168297 update dependencies 2021-08-30 18:51:17 +06:00
9202eabf90 start 0.6.3 2021-08-30 18:28:12 +06:00
bc612604eb Update README.md 2021-08-07 17:11:10 +06:00
8808c00dc2 Update README.md 2021-08-07 16:40:24 +06:00
f3db5fd2e4 update kdocs yaml 2021-08-07 16:26:06 +06:00
b66284b6e5 update kdocs script 2021-08-07 16:25:35 +06:00
2a16f3c588 update kdocs pdenendency 2021-08-07 16:20:43 +06:00
6d055739fe Update dokka.gradle 2021-08-07 16:10:59 +06:00
92ae81d7a7 Create kdocs.yml 2021-08-07 16:07:40 +06:00
9e9c09e364 Merge pull request #19 from InsanusMokrassar/0.6.2
0.6.2
2021-07-30 11:02:45 +06:00
a8be548a72 Update build.gradle 2021-07-29 19:35:07 +06:00
6ec77b4e6f Remove bintray from repos 2021-07-29 19:33:35 +06:00
0ecb34709b update workflows 2021-07-28 14:41:27 +06:00
0f973adb9f fill changelog, remove legacy compiler for js 2021-07-28 14:31:30 +06:00
abc7a63d2e Update gradle.properties 2021-07-28 11:37:43 +06:00
fbab0e232f Update README.md 2021-07-28 11:29:31 +06:00
57fa3a30a6 Update dependencies 2021-07-28 11:27:17 +06:00
f50564abe2 start 0.6.2 2021-07-28 11:24:38 +06:00
8276ebc7ec Update README.md 2021-07-28 11:23:55 +06:00
5a6084c573 Merge pull request #18 from InsanusMokrassar/0.6.1
0.6.1
2021-06-03 13:40:06 +06:00
437206e915 remove old comments 2021-06-03 12:55:16 +06:00
0ecc6f0028 upfill of changelog 2021-06-03 12:52:39 +06:00
fec17f949f fixes in tests 2021-06-03 12:37:13 +06:00
c3ed1d024c fix tests 2021-06-03 12:16:39 +06:00
b4790b892a partial fix 2021-05-30 18:53:54 +06:00
28f84d4e3a rewrite mechanism of calculating of near time 2021-05-30 18:53:54 +06:00
22ef00fe8f Update publishing_packages.yml 2021-05-30 09:00:57 +06:00
c509d35b00 update klock 2021-05-29 17:30:28 +06:00
3de66a7274 start 0.6.1 2021-05-29 17:25:40 +06:00
1f0387363d Merge pull request #17 from InsanusMokrassar/0.6.0
0.6.0
2021-05-25 15:48:02 +06:00
22 changed files with 600 additions and 215 deletions

24
.github/workflows/kdocs.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Publish KDocs
on:
push:
branches:
- master
jobs:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Fix android 31.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Build
run: ./gradlew dokkaHtml
- name: Publish KDocs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./build/dokka/html
publish_branch: kdocs

View File

@@ -1,4 +1,3 @@
name: Publish package to GitHub Packages
on: [push]
jobs:
@@ -9,6 +8,9 @@ jobs:
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Fix android 31.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/31.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
- name: Update version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
@@ -16,7 +18,8 @@ jobs:
- name: prebuild
run: ./gradlew clean build
- name: Publish package
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signMetadataPublication -x signAndroidDebugPublication -x signAndroidReleasePublication
continue-on-error: true
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidReleasePublication -x signAndroidDebugPublication -x signAndroidReleasePublication
env:
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,5 +1,40 @@
# Changelog
## 0.6.5
* Flows now use `doWhile` functions
* `doWhile` now use additional delay (for 1 ms) for cases when `block` executing too fast
* New extensions for `KronScheduler`: `do(Once/While/Infinity)Local`/`do(Once/While/Infinity)Tz`
## 0.6.4
* Versions
* `Kotlin`: `1.5.31`
* `Coroutines`: `1.5.2`
* `Klock`: `2.4.2`
* `Androidx Work`: `2.6.0`
## 0.6.3
* Versions
* `Kotlin`: `1.5.30`
* `Klock`: `2.4.0`
## 0.6.2
**LEGACY COMPILER VARIANT FOR JS IS NOT AVAILABLE SINCE THIS UPDATE**
* Versions
* `Kotlin`: `1.5.21`
* `Coroutines`: `1.5.1`
* `Klock`: `2.3.1`
## 0.6.1
* Versions
* `Klock`: `2.1.0` -> `2.1.2`
* Rewriting of default mechanism of `KronScheduler`s
## 0.6.0
* Versions

View File

@@ -1,22 +1,12 @@
# krontab
[![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab-mpp/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab-mpp/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
[![Build Status](https://travis-ci.com/InsanusMokrassar/krontab.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/krontab)
[![Build Status](https://github.com/InsanusMokrassar/krontab/actions/workflows/publishing_packages.yml/badge.svg)](https://github.com/InsanusMokrassar/krontab/actions/workflows/publishing_packages.yml)
[![KDocs](https://raw.githubusercontent.com/InsanusMokrassar/badges/master/kdocs.svg)](https://krontab.inmo.dev/index.html)
Library was created to give oppotunity to launch some things from time to time according to some schedule in
runtime of applications.
| Table of content |
|---|
| [ How to use ](#how-to-use) |
| [ How to use: Including in project ](#including-in-project) |
| [ How to use: Config from string ](#config-from-string) |
| [ 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: Offsets ](#Offsets) |
| [ How to use: Note about week days ](#Note-about-week-days) |
## How to use
There are several ways to configure and use this library:
@@ -46,7 +36,7 @@ implementation "dev.inmo:krontab:$krontab_version"
Next version is the latest currently for the library:
[ ![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab-mpp/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab-mpp/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
For old version of Gradle, instead of `implementation` word developers must use `compile`.
@@ -133,6 +123,38 @@ kronScheduler.doInfinity {
All of these examples will do the same things: print `Called` message every five seconds.
### do\* functions
With regular `doOnce`/`doWhile`/`doInfinity` there are two types of their variations: **local** and **timezoned**. Local
variations (`doOnceLocal`/`doWhileLocal`/`doInfinityLocal`) will pass `DateTime` as an argument into the block:
```kotlin
doInfinityLocal("/5 * * * *") {
println(it) // will print current date time
}
```
Timezoned variations (`doOnceTz`/`doWhileTz`/`doInfinityTz`) will do the same thing but pass as an argument `DateTimeTz`:
```kotlin
doInfinityTz("/5 * * * * 0o") {
println(it) // will print current date time in UTC
}
```
It is useful in cases when you need to get the time of calling and avoid extra calls to system time.
#### Helpful table for
| | No args | Local `DateTime` | Local `DateTimeTz` with offset of `KronScheduler` |
|---| ------- | ---------------- | ------------------------------------------------- |
| **Call only near time** | doOnce | doOnceLocal | doOnceTz |
| **Call while condition is true** | doWhile | doWhileLocal | doWhileTz |
| **Work infinity*** | doInfinity | doInfinityLocal | doInfinityTz |
*Here there is an important notice, that `Work infinity` is not exactly `infinity`. Actually, that means that `do while
coroutine is alive` and in fact executing will be stopped when coroutine became cancelled.
### KronScheduler as a Flow
Any `KronScheduler`can e converted to a `Flow<DateTime` using extension `asFlow`:
@@ -163,7 +185,7 @@ flow.takeWhile {
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`.
scheduler is not instance of `KronSchedulerTz` it will work 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:

View File

@@ -1,7 +1,7 @@
buildscript {
repositories {
mavenLocal()
jcenter()
// jcenter()
mavenCentral()
google()
}
@@ -10,7 +10,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:4.2.2'
}
}
@@ -40,9 +40,9 @@ apply from: "github_release.gradle"
repositories {
mavenLocal()
jcenter()
// jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
// maven { url "https://kotlin.bintray.com/kotlinx" }
google()
}
@@ -50,7 +50,7 @@ apply from: './dokka.gradle'
kotlin {
jvm()
js(BOTH) {
js(IR) {
browser()
nodejs()
}
@@ -132,3 +132,9 @@ android {
}
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}

View File

@@ -15,31 +15,40 @@ dokkaHtml {
}
moduleName.set("kdocs")
dokkaSourceSets {
configureEach {
skipDeprecated.set(true)
includeNonPublic.set(true)
reportUndocumented.set(true)
switch (true) {
case project.hasProperty("DOKKA_PATH"):
outputDirectory = new File(project.property("DOKKA_PATH").toString())
break
case System.getenv("DOKKA_PATH") != null:
outputDirectory = new File(System.getenv("DOKKA_PATH"))
break
}
sourceLink {
localDirectory.set(file("./"))
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/krontab/blob/master/"))
remoteLineSuffix.set("#L")
dokkaSourceSets {
configureEach {
skipDeprecated.set(true)
sourceLink {
localDirectory.set(file("./"))
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/krontab/blob/master/"))
remoteLineSuffix.set("#L")
}
}
}
named("commonMain") {
sourceRoot { path = "src/commonMain" }
}
named("commonMain") {
sourceRoots.setFrom(findSourcesWithName("commonMain"))
}
//
// named("jsMain") {
// sourceRoot { path = "src/jsMain" }
// }
// named("jsMain") {
// sourceRoot { path = "src/jsMain" }
// }
//
// named("jvmMain") {
// sourceRoot { path = "src/jvmMain" }
// }
// named("jvmMain") {
// sourceRoot { path = "src/jvmMain" }
// }
}
}
}

View File

@@ -8,12 +8,12 @@ android.useAndroidX=true
android.enableJetifier=false
kotlin_version=1.5.10
kotlin_coroutines_version=1.5.0
kotlin_version=1.5.31
kotlin_coroutines_version=1.5.2
dokka_version=1.4.32
dokka_version=1.5.30
klockVersion=2.1.0
klockVersion=2.4.2
## Github reease
@@ -22,17 +22,16 @@ github_release_plugin_version=2.2.12
## Android
android_minSdkVersion=19
android_compileSdkVersion=30
android_buildToolsVersion=30.0.3
dexcount_version=2.0.0
android_compileSdkVersion=31
android_buildToolsVersion=31.0.0
dexcount_version=3.0.0
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
androidx_work_version=2.5.0
androidx_work_version=2.6.0
## Common
version=0.6.0
android_code_version=4
version=0.6.5
android_code_version=9

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip

View File

@@ -1,7 +1,10 @@
package dev.inmo.krontab
import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlin.coroutines.coroutineContext
/**
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
@@ -10,29 +13,109 @@ import kotlinx.coroutines.delay
*
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately
*/
suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T {
next() ?.let {
suspend inline fun <T> KronScheduler.doOnceLocal(noinline block: suspend (DateTime) -> T): T {
val time = nextOrNow().also {
delay((it - DateTime.now()).millisecondsLong)
}
return block()
return block(time)
}
/**
* Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
*
* WARNING!!! If you want to launch it in parallel, you must do this explicitly.
*
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediatelly
*/
suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeTz) -> T): T {
val time = when (this) {
is KronSchedulerTz -> nextOrNowWithOffset()
else -> nextOrNow().local
}
delay((time - DateTimeTz.nowLocal()).millisecondsLong)
return block(time)
}
/**
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
*
* WARNING!!! If you want to launch it in parallel, you must do this explicitly.
*
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately
*/
suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T = doOnceLocal { _ -> block() }
/**
* Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it
* @see buildSchedule
*/
suspend inline fun <T> doOnce(
scheduleConfig: String,
noinline block: suspend (DateTime) -> T
) = buildSchedule(scheduleConfig).doOnceLocal(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it
* @see buildSchedule
*/
suspend inline fun <T> doOnceTz(
scheduleConfig: String,
noinline block: suspend (DateTimeTz) -> T
) = buildSchedule(scheduleConfig).doOnceTz(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doOnceLocal] on it
* @see buildSchedule
*/
suspend inline fun <T> doOnce(
scheduleConfig: String,
noinline block: suspend () -> T
) = buildSchedule(scheduleConfig).doOnce(block)
) = doOnce(scheduleConfig) { _ -> block() }
/**
* Will execute [block] while it will return true as a result of its calculation
*/
suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean) {
do { val doNext = doOnce(block) } while (doNext)
suspend inline fun KronScheduler.doWhileLocal(noinline block: suspend (DateTime) -> Boolean) {
do {
delay(1L)
} while (doOnceLocal(block))
}
/**
* Will execute [block] while it will return true as a result of its calculation
*/
suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) -> Boolean) {
do {
delay(1L)
} while (doOnceTz(block))
}
/**
* Will execute [block] while it will return true as a result of its calculation
*/
suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean) = doWhileLocal { block() }
/**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
*
* @see buildSchedule
*/
suspend inline fun doWhileLocal(
scheduleConfig: String,
noinline block: suspend (DateTime) -> Boolean
) = buildSchedule(scheduleConfig).doWhileLocal(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
*
* @see buildSchedule
*/
suspend inline fun doWhileTz(
scheduleConfig: String,
noinline block: suspend (DateTimeTz) -> Boolean
) = buildSchedule(scheduleConfig).doWhileTz(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
*
@@ -41,15 +124,53 @@ suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean)
suspend inline fun doWhile(
scheduleConfig: String,
noinline block: suspend () -> Boolean
) = buildSchedule(scheduleConfig).doWhile(block)
) = doWhileLocal(scheduleConfig) { block() }
/**
* Will execute [block] without any checking of result
*/
suspend inline fun KronScheduler.doInfinityLocal(noinline block: suspend (DateTime) -> Unit) = doWhileLocal {
block(it)
coroutineContext.isActive
}
/**
* Will execute [block] without any checking of result
*/
suspend inline fun KronScheduler.doInfinityTz(noinline block: suspend (DateTimeTz) -> Unit) = doWhileTz {
block(it)
coroutineContext.isActive
}
/**
* Will execute [block] without any checking of result
*/
suspend inline fun KronScheduler.doInfinity(noinline block: suspend () -> Unit) = doWhile {
block()
true
coroutineContext.isActive
}
/**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
*
* @see buildSchedule
*/
suspend inline fun doInfinityLocal(
scheduleConfig: String,
noinline block: suspend (DateTime) -> Unit
) = buildSchedule(scheduleConfig).doInfinityLocal(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
*
* @see buildSchedule
*/
suspend inline fun doInfinityTz(
scheduleConfig: String,
noinline block: suspend (DateTimeTz) -> Unit
) = buildSchedule(scheduleConfig).doInfinityTz(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
*

View File

@@ -16,13 +16,13 @@ fun Iterator<KronScheduler>.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))

View File

@@ -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 } }
}
}

View File

@@ -116,7 +116,7 @@ fun createSimpleScheduler(
scheduler
} else {
CronDateTimeSchedulerTz(
(scheduler as CronDateTimeScheduler).cronDateTimes,
(scheduler as CronDateTimeScheduler).cronDateTime,
TimezoneOffset(defaultOffset.minutes)
)
}

View File

@@ -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)

View File

@@ -1,106 +1,64 @@
package dev.inmo.krontab.internal
import com.soywiz.klock.*
import com.soywiz.klock.DateTime
import com.soywiz.klock.TimezoneOffset
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<Byte>? = null,
val years: Array<Int>? = null,
val months: Array<Byte>? = null,
val daysOfMonth: Array<Byte>? = null,
val hours: Array<Byte>? = null,
val minutes: Array<Byte>? = null,
val seconds: Array<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)
check(hours?.let { it in hoursRange } ?: true)
check(minutes?.let { it in minutesRange } ?: true)
check(seconds?.let { it in secondsRange } ?: true)
check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true)
check(years?.all { it in yearRange } ?: true)
check(months?.all { it in monthRange } ?: true)
check(daysOfMonth ?.all { it in dayOfMonthRange } ?: true)
check(hours?.all { it in hoursRange } ?: true)
check(minutes?.all { it in minutesRange } ?: true)
check(seconds?.all { it in secondsRange } ?: true)
}
internal val klockDayOfMonth = dayOfMonth ?.plus(1)
internal val dayOfWeekInt: Int? = dayOfweek ?.toInt()
}
internal val calculators = listOf(
years ?.let { NearDateTimeCalculatorYears(it) },
daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) },
NearDateTimeCalculatorMillis(arrayOf(0)),
seconds ?.let { NearDateTimeCalculatorSeconds(it) },
minutes ?.let { NearDateTimeCalculatorMinutes(it) },
hours ?.let { NearDateTimeCalculatorHours(it) },
daysOfMonth ?.let { NearDateTimeCalculatorDays(it) },
months ?.let { NearDateTimeCalculatorMonths(it) },
)
/**
* THIS METHOD WILL <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]
*/
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? {
var current = relativelyTo
val weekDay = dayOfWeekInt
if (weekDay != null && current.dayOfWeek.index0 != weekDay) {
do {
var diff = weekDay - current.dayOfWeek.index0
if (diff < 0) {
diff += 7 /* days in week */
internal fun toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? {
var current = relativelyTo
whileLoop@while (true) {
for (calculator in calculators) {
val (calculated, requireRecalculation) = (calculator ?: continue).calculateNearTime(current) ?: return null
current = calculated
if (requireRecalculation) {
continue@whileLoop
}
}
current = (current + diff.days).startOfDay
val next = toNearDateTime(current)
if (next == null || next.dayOfWeek.index0 == weekDay) {
return next
}
} while (true)
}
seconds?.let {
val left = it - current.seconds
current += DateTimeSpan(minutes = if (left <= 0) 1 else 0, seconds = left)
}
minutes?.let {
val left = it - current.minutes
current += DateTimeSpan(hours = if (left < 0) 1 else 0, minutes = left)
}
hours?.let {
val left = it - current.hours
current += DateTimeSpan(days = if (left < 0) 1 else 0, hours = left)
}
klockDayOfMonth ?.let {
val left = (it - current.dayOfMonth).let { diff ->
if (diff > 0 && current.endOfMonth.run { it > dayOfMonth && current.dayOfMonth == dayOfMonth }) {
0
} else {
diff
}
}
current += DateTimeSpan(months = if (left < 0) 1 else 0, days = left)
}
month ?.let {
val left = it - current.month0
current += DateTimeSpan(years = if (left < 0) 1 else 0, months = left)
}
year ?.let {
if (current.yearInt != it) {
return null
return current
}
}
return current
}
internal fun createCronDateTimeList(
internal fun createCronDateTime(
seconds: Array<Byte>? = null,
minutes: Array<Byte>? = null,
hours: Array<Byte>? = null,
@@ -108,38 +66,8 @@ internal fun createCronDateTimeList(
month: Array<Byte>? = null,
years: Array<Int>? = null,
weekDays: Array<Byte>? = null
): List<CronDateTime> {
val resultCronDateTimes = mutableListOf(CronDateTime())
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(seconds = currentTime)
}
minutes ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(minutes = currentTime)
}
hours ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(hours = currentTime)
}
dayOfMonth ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(dayOfMonth = currentTime)
}
month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(month = currentTime)
}
years ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int ->
previousCronDateTime.copy(year = currentTime)
}
weekDays ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(dayOfweek = currentTime)
}
return resultCronDateTimes.toList()
): CronDateTime {
return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds)
}
/**
@@ -153,7 +81,7 @@ internal fun createKronScheduler(
month: Array<Byte>? = null,
years: Array<Int>? = null,
weekDays: Array<Byte>? = null
): KronScheduler = CronDateTimeScheduler(createCronDateTimeList(seconds, minutes, hours, dayOfMonth, month, years, weekDays))
): KronScheduler = CronDateTimeScheduler(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays))
/**
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
*/
@@ -166,4 +94,14 @@ internal fun createKronSchedulerWithOffset(
years: Array<Int>? = null,
weekDays: Array<Byte>? = 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<CronDateTime>.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() },
)

View File

@@ -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<CronDateTime>
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>) = CronDateTimeScheduler(
schedulers.flatMap { it.cronDateTimes }
internal fun mergeCronDateTimeSchedulers(
schedulers: List<CronDateTimeScheduler>
): CronDateTimeScheduler = CronDateTimeScheduler(
schedulers.map { it.cronDateTime }.merge()
)
/**

View File

@@ -11,14 +11,12 @@ import dev.inmo.krontab.KronSchedulerTz
* @see CronDateTime
*/
internal data class CronDateTimeSchedulerTz internal constructor(
internal val cronDateTimes: List<CronDateTime>,
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
)
}

View File

@@ -0,0 +1,183 @@
package dev.inmo.krontab.internal
import com.soywiz.klock.*
import dev.inmo.krontab.utils.copy
import kotlin.math.min
fun interface NearDateTimeCalculator {
/**
* @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be
* recalculated
*/
fun calculateNearTime(
relativelyTo: DateTime
): Pair<DateTime, Boolean>?
}
internal class CommonNearDateTimeCalculator<T>(
private val times: Array<T>,
private val partGetter: (DateTime) -> T,
private val partSetter: (DateTime, T) -> DateTime?
) : NearDateTimeCalculator where T : Comparable<T>, T : Number {
/**
* @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be
* recalculated
*/
override fun calculateNearTime(
relativelyTo: DateTime
): Pair<DateTime, Boolean>? {
val currentData = partGetter(relativelyTo)
val greaterOrEquals = times.firstOrNull { it >= currentData }
val newDateTime = when (greaterOrEquals) {
null -> partSetter(relativelyTo, times.first()) ?: return null
currentData -> relativelyTo
else -> partSetter(relativelyTo, greaterOrEquals) ?: return null
}
return if (newDateTime == relativelyTo) {
relativelyTo to false
} else {
newDateTime to true
}
}
}
internal fun NearDateTimeCalculatorMillis(
times: Array<Short>
) = CommonNearDateTimeCalculator(
times,
{ it.milliseconds.toShort() },
{ dateTime, newOne ->
(if (newOne < dateTime.milliseconds) {
dateTime.plus(1.seconds)
} else {
dateTime
}).copy(milliseconds = newOne.toInt())
}
)
internal fun NearDateTimeCalculatorSeconds(
times: Array<Byte>
) = CommonNearDateTimeCalculator(
times,
{ it.seconds.toByte() },
{ dateTime, newOne ->
(if (newOne < dateTime.seconds) {
dateTime.plus(1.minutes)
} else {
dateTime
}).copy(second = newOne.toInt(), milliseconds = 0)
}
)
internal fun NearDateTimeCalculatorMinutes(
times: Array<Byte>
) = CommonNearDateTimeCalculator(
times,
{ it.minutes.toByte() },
{ dateTime, newOne ->
(if (newOne < dateTime.minutes) {
dateTime.plus(1.hours)
} else {
dateTime
}).copy(minute = newOne.toInt(), second = 0, milliseconds = 0)
}
)
internal fun NearDateTimeCalculatorHours(
times: Array<Byte>
) = CommonNearDateTimeCalculator(
times,
{ it.hours.toByte() },
{ dateTime, newOne ->
(if (newOne < dateTime.hours) {
dateTime.plus(1.days)
} else {
dateTime
}).copy(hour = newOne.toInt(), minute = 0, second = 0, milliseconds = 0)
}
)
internal fun NearDateTimeCalculatorDays(
times: Array<Byte>
) = CommonNearDateTimeCalculator(
times,
{ it.dayOfMonth.toByte() },
{ dateTime, newOne ->
(if (newOne < dateTime.dayOfMonth) {
dateTime.plus(1.months)
} else {
dateTime
}).copy(
dayOfMonth = min(dateTime.month.days(dateTime.year), newOne.toInt() + 1), // index1
hour = 0,
minute = 0,
second = 0,
milliseconds = 0
)
}
)
internal fun NearDateTimeCalculatorMonths(
times: Array<Byte>
) = CommonNearDateTimeCalculator(
times,
{ it.dayOfMonth.toByte() },
{ dateTime, newOne ->
(if (newOne < dateTime.month0) {
dateTime.plus(1.years)
} else {
dateTime
}).copy(
month = newOne.toInt() + 1, // index1
dayOfMonth = 1, // index1
hour = 0,
minute = 0,
second = 0,
milliseconds = 0
)
}
)
internal fun NearDateTimeCalculatorWeekDays(
times: Array<Byte>
) = CommonNearDateTimeCalculator(
times,
{ it.dayOfWeek.index0.toByte() },
{ dateTime, newOne ->
val currentDayOfWeek = dateTime.dayOfWeek.index0
if (newOne.toInt() == currentDayOfWeek) return@CommonNearDateTimeCalculator dateTime
(if (newOne < currentDayOfWeek) {
dateTime.plus(7.days - (currentDayOfWeek - newOne).days)
} else {
dateTime.plus(newOne.toInt().days - currentDayOfWeek.days)
}).copy(
hour = 0,
minute = 0,
second = 0,
milliseconds = 0
)
}
)
internal fun NearDateTimeCalculatorYears(
times: Array<Int>
) = CommonNearDateTimeCalculator(
times,
{ it.yearInt },
{ dateTime, newOne ->
val currentYear = dateTime.yearInt
if (newOne == currentYear) return@CommonNearDateTimeCalculator dateTime
(if (newOne < currentYear) {
null
} else {
dateTime.plus(newOne.years - currentYear.years)
}) ?.copy(
month = 1, // index1
dayOfMonth = 1, // index1
hour = 0,
minute = 0,
second = 0,
milliseconds = 0
)
}
)

View File

@@ -8,7 +8,7 @@ private fun <T> 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()

View File

@@ -0,0 +1,23 @@
package dev.inmo.krontab.utils
import com.soywiz.klock.DateTime
import com.soywiz.klock.Month
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
)

View File

@@ -2,9 +2,9 @@ package dev.inmo.krontab.utils
import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz
import dev.inmo.krontab.KronScheduler
import dev.inmo.krontab.next
import kotlinx.coroutines.*
import dev.inmo.krontab.*
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
/**
@@ -12,23 +12,41 @@ import kotlinx.coroutines.flow.*
* time zones
*
* @see channelFlow
* @see KronSchedulerTz.doWhile
*/
@FlowPreview
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)
fun KronSchedulerTz.asTzFlow(): Flow<DateTimeTz> = channelFlow {
doInfinity {
send(DateTime.nowLocal())
}
}
/**
* This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s
*
* @see channelFlow
* @see KronScheduler.doWhile
*/
@FlowPreview
fun KronScheduler.asFlow(): Flow<DateTime> = asTzFlow().map { it.local }
fun KronScheduler.asFlow(): Flow<DateTime> = channelFlow {
doInfinity {
send(DateTime.now())
}
}
/**
* This [Flow] will trigger emitting each near time which will be returned from [this] [KronScheduler] with attention to
* time zones
*
* @see channelFlow
* @see KronScheduler.asFlow
* @see KronSchedulerTz.asTzFlow
*/
@FlowPreview
fun KronScheduler.asTzFlow(): Flow<DateTimeTz> = when (this) {
is KronSchedulerTz -> asTzFlow()
else -> asFlow().map { it.local }
}
@Deprecated(
"It is not recommended to use this class in future. This functionality will be removed soon",
@@ -48,4 +66,4 @@ class SchedulerFlow(
collector.emit(nextTime)
}
}
}
}

View File

@@ -67,8 +67,8 @@ class StringParseTest {
val flow = kronScheduler.asFlow()
runTest {
val ranges = rangesEnds.map { it.first .. it.second }.flatten().toMutableList()
val expectedCollects = rangesEnds.sumOf { it.second - it.first + 1 }
val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList()
val expectedCollects = ranges.size
var collected = 0
flow.takeWhile { ranges.isNotEmpty() }.collect {
@@ -80,7 +80,7 @@ class StringParseTest {
}
@Test
fun testThatTimezoneCorrectlyDeserialized() {
val now = DateTimeTz.nowLocal()
val now = DateTime.now().copy(milliseconds = 0).local
runTest {
for (i in 0 .. 1339) {

View File

@@ -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
)
}
}
}
}
}
}