mirror of
https://github.com/InsanusMokrassar/krontab.git
synced 2025-12-05 04:25:48 +00:00
Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c76ca1798e | |||
| 58917c6458 | |||
| 5ef6134d9f | |||
| b9237e13c9 | |||
| 17971ed8fb | |||
| 505108d2dd | |||
| 094284562d | |||
| 5ac1bc26e1 | |||
| d82107b8b8 | |||
| 6b6b7a6fe0 | |||
| aff50f0aec | |||
| e0f9db9c60 | |||
| e4b3a5059a | |||
| ca5794726f | |||
| 72ef9317f9 | |||
| 4651a290d0 | |||
| 30df50c192 | |||
| 05c6d74f7b | |||
| 7269833c6f | |||
| 6e97a365e5 | |||
| 0c497f5dcb | |||
| 552ec14d66 | |||
| 3845f6e0c5 | |||
| 3bd4a7a245 | |||
| f90d8d03d9 | |||
| 36671cb379 | |||
| 68be768763 | |||
| 9546eb5aec | |||
| 2efd7e6157 | |||
| 6845537afd | |||
| af28fce6f5 | |||
| 237b42c2d3 | |||
| 49f852f27e | |||
|
|
470dc92f54 | ||
| b5dcdc185f | |||
| 8df9f9c086 | |||
| 484f46223f | |||
| 1ce654fa06 | |||
| 6da04f54da | |||
| e1d716688a | |||
| e247de9e24 | |||
| a153bc7996 | |||
| 9753cda961 | |||
| e8ef6e6935 | |||
| b41bf0ea07 | |||
| 09d811e2c4 | |||
| 3faadefced | |||
| f3883a8678 | |||
| 5ff02a2a9d | |||
| f56215ee10 | |||
| e66acb34de | |||
| 78ac446311 | |||
| 697b4e81b8 | |||
| d1288de032 | |||
| eb867e7366 | |||
| 4aa10e00cf | |||
| 170d443517 | |||
| d1aab7ccd6 | |||
| 58d758f141 | |||
| 71bd3f0eb4 | |||
| bd2a852b16 | |||
| 8e844b5875 | |||
| e0f7dddf42 | |||
| d64acc9806 | |||
| 58375452c7 | |||
| cbfbdc1b33 | |||
| 715d414f51 | |||
| 2ecd8c4292 | |||
| 1be8d947f7 | |||
| f66c1c2477 | |||
| 69cf7afd4e | |||
| 92df91edd3 | |||
| f7a388f438 | |||
| 9a10c7eb87 | |||
| d67be582af | |||
| 171bb687ea | |||
| 7807d10121 | |||
| e5bbea84d9 | |||
| a01a18a6d1 | |||
| 132986d275 | |||
| 04118902e8 | |||
| 96857aa7bc | |||
| f8b677406f | |||
| 53f34f0a27 | |||
| 989780243a | |||
| 7b5417ccc5 | |||
| 2004a7dd05 | |||
| 7c4217bda6 | |||
| 3d6fee7257 | |||
| 6cec25eca0 | |||
| a0972eaff9 | |||
| cc75501b04 | |||
| 89e500ff33 | |||
| a61cd61602 | |||
|
|
5fdb2ea049 | ||
| 68ed562b19 | |||
| 87b5dfe1aa | |||
| 6a6bfe0552 | |||
| ede47ae664 | |||
| 890ab5b15d | |||
| 5a1ed2f933 | |||
| 7da67386cf | |||
| 7027719fe5 | |||
| a81c7c7c3f | |||
|
|
e5658998d4 | ||
| 6b3cb981ab | |||
| f6518d4d25 | |||
| 97a8f39826 | |||
| 469861f7e3 | |||
| 69305506ce | |||
| 63ea3f0f1a | |||
| 13a8bacb52 | |||
| 9c80e29e71 | |||
| cfd2de8fe9 | |||
| e3490d432e | |||
| 1c95636b71 | |||
| b31a6ab86a | |||
| c35a679827 | |||
| 06f56026db | |||
| 54be57d709 | |||
| fae297e4a6 | |||
| bd81298ac9 | |||
| cfac5fcd1c | |||
| 93d38f8945 | |||
| 97dcd77a8a | |||
| afa2521b5d | |||
| 37b0ec9fb7 | |||
| d36888173a | |||
| 30011cd309 | |||
| fd37a5ddc3 | |||
| 3874f6a42c | |||
| 8a98ebd5f9 | |||
| 8d0c55129f | |||
| 54b06bf9e6 | |||
| da436ab432 | |||
| 154d211514 | |||
| 0b843ada4f | |||
| 25f204a488 | |||
| 2e4ccb9253 | |||
| 411a52b85e | |||
| 5a5bde6f20 | |||
| 8f9b84ecc7 | |||
| 62c5a0f98d | |||
| dfd1a5a909 | |||
| 04390f0726 | |||
| d0f02e37ce | |||
| 681043db5a | |||
| f63ed1a873 | |||
| 9ea8194d7d | |||
| 0c37ccc902 | |||
| 2abef58335 | |||
| 8ac6cd3e62 | |||
| 58f30aef15 | |||
| f163a64964 | |||
| 79ce455f5c | |||
| fd54b035fe | |||
| 4fab7a28ff | |||
| 28d5665a14 | |||
| f98fd5ddb7 | |||
| 981327675a | |||
| 7649d20984 | |||
| ab274636b9 | |||
| 7f2f8a4a26 | |||
| f64171c0f0 | |||
| 8382108b01 | |||
| 32499d02fb | |||
| 2b06033c75 | |||
| 84f1063e3f | |||
| 347cf64cb2 | |||
| eb73da67eb | |||
| 4aadf513aa | |||
| 8c011ea1fb | |||
| aa9c2b8ea0 | |||
| adf10301dc | |||
| 7799077e44 | |||
| 83339dc095 | |||
| 3040951f37 | |||
| 0bfe022476 | |||
| 06107f75f2 | |||
| 039fe1cb5f | |||
| ea7fab6130 | |||
| 2f3796733d | |||
| 89a6a305f8 | |||
| 97752e1ac0 | |||
| 276ecaac27 | |||
| d36e406b59 | |||
| 4dc65bf09a | |||
| 539b5fbcd8 | |||
| e4f417877d | |||
| 4f6054c0d9 | |||
| 88dbfee370 | |||
| 5af29c2246 |
2
.github/workflows/kdocs.yml
vendored
2
.github/workflows/kdocs.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
java-version: 17
|
||||
- name: Build
|
||||
run: ./gradlew dokkaHtml
|
||||
- name: Publish KDocs
|
||||
|
||||
7
.github/workflows/publishing_packages.yml
vendored
7
.github/workflows/publishing_packages.yml
vendored
@@ -7,10 +7,7 @@ jobs:
|
||||
- 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
|
||||
java-version: 17
|
||||
- name: Update version
|
||||
run: |
|
||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||
@@ -19,7 +16,7 @@ jobs:
|
||||
run: ./gradlew clean build
|
||||
- name: Publish package
|
||||
continue-on-error: true
|
||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication -x signAndroidReleasePublication -x signAndroidDebugPublication -x signAndroidReleasePublication
|
||||
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
||||
env:
|
||||
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.idea
|
||||
.kotlin
|
||||
out/*
|
||||
*.iml
|
||||
target
|
||||
@@ -9,4 +10,6 @@ settings.xml
|
||||
build/
|
||||
out/
|
||||
|
||||
kotlin-js-store/
|
||||
|
||||
local.properties
|
||||
|
||||
249
CHANGELOG.md
249
CHANGELOG.md
@@ -1,5 +1,254 @@
|
||||
# Changelog
|
||||
|
||||
## 2.6.1
|
||||
|
||||
* Fixes in parsing of string parts
|
||||
|
||||
## 2.6.0
|
||||
|
||||
* Fix of `doOnceTz` behaviour: now it will use local time as utc time to get next waking up time
|
||||
|
||||
## 2.5.1
|
||||
|
||||
* Add cleaning up of incoming template, which must remove all malformed parts from string template (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126))
|
||||
* Add support of insufficient amount of arguments (fix of [#126](https://github.com/InsanusMokrassar/krontab/issues/126))
|
||||
* `Version`:
|
||||
* `Kotlin`: `2.0.21`
|
||||
* `AndroidXWork`: `2.10.0`
|
||||
|
||||
## 2.5.0
|
||||
|
||||
* `Version`:
|
||||
* `Kotlin`: `2.0.20`
|
||||
* `Coroutines`: `1.9.0`
|
||||
* `Serialization`: `1.7.3`
|
||||
|
||||
## 2.4.0
|
||||
|
||||
* `Version`:
|
||||
* `Kotlin`: `2.0.10`
|
||||
* `Coroutines`: `1.8.1`
|
||||
* `Serialization`: `1.7.1`
|
||||
* `Klock`: `5.4.0`
|
||||
* `AndroidXWork`: `2.9.1`
|
||||
|
||||
## 2.3.0
|
||||
|
||||
* `doWhile` now will guarantee that it will not call `doOnce` more than once for time
|
||||
|
||||
## 2.2.9
|
||||
|
||||
* `Version`:
|
||||
* `Kotlin`: `1.9.23`
|
||||
* `Serialization`: `1.6.3`
|
||||
* `Klock`: `5.3.2`
|
||||
* Fixes in build-in schedulers
|
||||
|
||||
## 2.2.8
|
||||
|
||||
* `Version`:
|
||||
* `Coroutines`: `1.8.0`
|
||||
|
||||
## 2.2.7
|
||||
|
||||
* `Version`:
|
||||
* `Klock`: `5.3.1`
|
||||
* `AndroidXWork`: `2.9.0`
|
||||
|
||||
## 2.2.6
|
||||
|
||||
**Since this version klock depdendency replaced with `com.soywiz.korge:korlibs-time`**
|
||||
|
||||
* `Version`:
|
||||
* `Klock`: `5.3.0`
|
||||
|
||||
## 2.2.5
|
||||
|
||||
* `Versions`
|
||||
* `Kotlin`: `1.9.22`
|
||||
* `Serialization`: `1.6.2`
|
||||
|
||||
## 2.2.4
|
||||
|
||||
* `Versions`
|
||||
* `Kotlin`: `1.9.21`
|
||||
* `Serialization`: `1.6.1`
|
||||
|
||||
## 2.2.3
|
||||
|
||||
**THIS UPDATE CONTAINS UPGRADE OF USED JDK UP TO 17**
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.9.20`
|
||||
|
||||
## 2.2.2
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.9.20-RC`
|
||||
* `Android` (min/target): `34`
|
||||
|
||||
## 2.2.1
|
||||
|
||||
* Potential fix of [#65](https://github.com/InsanusMokrassar/krontab/issues/65)
|
||||
* Versions
|
||||
* `Kotlin`: `1.9.20-Beta`
|
||||
* `Klock`: `4.0.10`
|
||||
* `Serialization`: `1.6.0`
|
||||
|
||||
## 2.2.0
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.9.0`
|
||||
* `Coroutines`: `1.7.3`
|
||||
* `Klock`: `4.0.9`
|
||||
|
||||
## 2.1.2
|
||||
|
||||
* Versions
|
||||
* `Coroutines`: `1.7.2`
|
||||
* All callbacks for standard extensions like `doInfinity` now fully inline
|
||||
|
||||
## 2.1.1
|
||||
|
||||
* `asFlowWithoutDelays` and `asTzFlowWithoutDelays` will have nullable `since` parameters with default to `null`
|
||||
to avoid any inconsistency of `Flow` idiom.
|
||||
<details>
|
||||
<summary>About the reason of changes</summary>
|
||||
Cold flows should not contain some state by default. So, it was not right to save some `DateTime`/`DateTimeTz`
|
||||
by default. Now it will not use some external state unless developers will set it manually
|
||||
</details>
|
||||
|
||||
## 2.1.0
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.8.22`
|
||||
* `Klock`: `4.0.3`
|
||||
* New value class `KrontabConfig`. It is preferable way to create `KronScheduler` instead of
|
||||
`KrontabTemplate` since this update
|
||||
* You may configure krontab with builders using simple `KronScheduler` invoke extension
|
||||
* New useful extensions like `KronScheduler.daily`
|
||||
* `KrontabTemplateWrapper` is obsolete in favor to `KrontabConfig`
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.8.21`
|
||||
* `Klock`: `4.0.1`
|
||||
* Support of `mingwx64` platform
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.8.20`
|
||||
* `AndroidXWork`: `2.8.1`
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* New extensions for `KronScheduler`:
|
||||
* `asTzFlowWithoutDelays`/`asFlowWithoutDelays`
|
||||
* `asTzFlowWithDelays`/`asFlowWithDelays`
|
||||
* Old `KronScheduler.asFlow` and `KronScheduler.asTzFlow` temporarily marked as deprecated: after several versions their
|
||||
behaviour will be changed to undelayed one
|
||||
* All the flow extensions now use `cold` non-channel flows. Potentially it should increase performance and decrease memory usage
|
||||
|
||||
## 0.9.0
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.8.10`
|
||||
* `AndroidXWork`: `2.8.0`
|
||||
|
||||
## 0.8.5
|
||||
|
||||
* Project is now available in owner `Gitea`: https://git.inmo.dev/InsanusMokrassar/-/packages/maven/dev.inmo-krontab
|
||||
* `KronSchedulerTz#next` with incoming `DateTime` now will use adjusted local time instead of unadjusted one
|
||||
* `CronDateTimeSchedulerTz#next` with incoming `DateTime` will convert that parameter to internal offset directly
|
||||
|
||||
## 0.8.4
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.7.22`
|
||||
|
||||
## 0.8.3
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.7.21`
|
||||
* `Klock`: `3.4.0`
|
||||
|
||||
## 0.8.2
|
||||
|
||||
**THIS VERSION HAS CHANGED COMPILE ANDROID SDK FROM 32 -> 33**
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.7.20`
|
||||
* `Klock`: `3.2.0`
|
||||
|
||||
## 0.8.1
|
||||
|
||||
* Versions
|
||||
* `Klock`: `3.1.0`
|
||||
|
||||
## 0.8.0
|
||||
|
||||
**THIS VERSION HAS CHANGED MIN ANDROID SDK FROM 19 -> 21**
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.7.10`
|
||||
* `Klock`: `3.0.0`
|
||||
|
||||
## 0.7.5
|
||||
|
||||
* Versions
|
||||
* `Coroutines`: `1.6.4`
|
||||
|
||||
## 0.7.4
|
||||
|
||||
* Versions
|
||||
* `Coroutines`: `1.6.3`
|
||||
|
||||
## 0.7.3
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.6.21`
|
||||
* Several bug fixes ([#27](https://github.com/InsanusMokrassar/krontab/issues/27), [#34](https://github.com/InsanusMokrassar/krontab/issues/34))
|
||||
* New factories for `KronScheduler` and `KronSchedulerTz` (fix of [#35](https://github.com/InsanusMokrassar/krontab/issues/35))
|
||||
* Fill extensions for flow with time zones (fix of [#33](https://github.com/InsanusMokrassar/krontab/issues/33))
|
||||
|
||||
## 0.7.2
|
||||
|
||||
* Versions
|
||||
* `Coroutines`: `1.6.1`
|
||||
* `Klock`: `2.7.0`
|
||||
* Deprecate `do*Local` due to their redundancy (use `do*` instead)
|
||||
* Add support of milliseconds as optional parameter after month (fix of [#31](https://github.com/InsanusMokrassar/krontab/issues/31))
|
||||
* Add support of filters for `Flow`s with `DateTime` (fix of [#30](https://github.com/InsanusMokrassar/krontab/issues/30))
|
||||
|
||||
## 0.7.1
|
||||
|
||||
* Versions
|
||||
* `Klock`: `2.5.2`
|
||||
|
||||
## 0.7.0
|
||||
|
||||
**ALL DEPRECATIONS HAVE BEEN REMOVED**
|
||||
|
||||
* Versions
|
||||
* `Kotlin`: `1.6.10`
|
||||
* `Klock`: `2.4.10`
|
||||
* `Coroutines`: `1.6.0`
|
||||
* `Androidx Work`: `2.7.1`
|
||||
|
||||
## 0.6.6
|
||||
|
||||
* Versions
|
||||
* `Klock`: `2.4.8`
|
||||
* `Androidx Work`: `2.7.0`
|
||||
|
||||
## 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
|
||||
|
||||
62
README.md
62
README.md
@@ -1,22 +1,20 @@
|
||||
# krontab
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
|
||||
[](https://github.com/InsanusMokrassar/krontab/actions/workflows/regular_build.yml)
|
||||
[](https://krontab.inmo.dev/index.html)
|
||||
|
||||
[](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
|
||||
|
||||
[](https://inmodev_chat.t.me)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
[](https://insanusmokrassar.github.io/krontab/)
|
||||
[](https://docs.inmo.dev/krontab/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:
|
||||
@@ -29,7 +27,7 @@ Anyway, to start some action from time to time you will need to use one of exten
|
||||
```kotlin
|
||||
val kronScheduler = /* creating of KronScheduler instance */;
|
||||
|
||||
kronScheuler.doWhile {
|
||||
kronScheduler.doWhile {
|
||||
// some action
|
||||
true // true - repeat on next time
|
||||
}
|
||||
@@ -133,6 +131,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 +193,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:
|
||||
|
||||
|
||||
27
build.gradle
27
build.gradle
@@ -8,14 +8,16 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$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.2.2'
|
||||
classpath "com.android.tools.build:gradle:$android_gradle_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
|
||||
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
|
||||
id "org.jetbrains.dokka" version "$dokka_version"
|
||||
}
|
||||
|
||||
@@ -57,6 +59,9 @@ kotlin {
|
||||
android {
|
||||
publishAllLibraryVariants()
|
||||
}
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
linuxArm64()
|
||||
|
||||
|
||||
sourceSets {
|
||||
@@ -64,8 +69,9 @@ kotlin {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
||||
api "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization_version"
|
||||
|
||||
api "com.soywiz.korlibs.klock:klock:$klockVersion"
|
||||
api "com.soywiz.korge:korlibs-time:$klockVersion"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +85,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation kotlin('test-common')
|
||||
implementation kotlin('test-annotations-common')
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
|
||||
}
|
||||
}
|
||||
jvmTest {
|
||||
@@ -91,7 +98,7 @@ kotlin {
|
||||
implementation kotlin('test-js')
|
||||
}
|
||||
}
|
||||
androidTest {
|
||||
androidUnitTest {
|
||||
dependencies {
|
||||
implementation kotlin('test-junit')
|
||||
}
|
||||
@@ -102,8 +109,9 @@ kotlin {
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
android {
|
||||
compileSdkVersion "$android_compileSdkVersion".toInteger()
|
||||
buildToolsVersion "$android_buildToolsVersion"
|
||||
compileSdk "$android_compileSdkVersion".toInteger()
|
||||
buildToolsVersion = "$android_buildToolsVersion"
|
||||
namespace "${group}.${project.name}"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion "$android_minSdkVersion".toInteger()
|
||||
@@ -121,8 +129,8 @@ android {
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -132,3 +140,8 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
|
||||
githubRelease {
|
||||
token "${project.property('GITHUB_RELEASE_TOKEN')}"
|
||||
|
||||
owner "InsanusMokrassar"
|
||||
repo "${rootProject.name}"
|
||||
owner = "InsanusMokrassar"
|
||||
repo = "${rootProject.name}"
|
||||
|
||||
tagName "${project.version}"
|
||||
releaseName "${project.version}"
|
||||
targetCommitish "${project.version}"
|
||||
tagName = "v${project.version}"
|
||||
releaseName = "${project.version}"
|
||||
targetCommitish = "${project.version}"
|
||||
|
||||
body getCurrentVersionChangelog("${project.version}")
|
||||
body = getCurrentVersionChangelog("${project.version}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx512m
|
||||
kotlin.code.style=official
|
||||
org.gradle.parallel=true
|
||||
kotlin.js.generate.externals=true
|
||||
@@ -8,30 +9,32 @@ android.useAndroidX=true
|
||||
android.enableJetifier=false
|
||||
|
||||
|
||||
kotlin_version=1.5.31
|
||||
kotlin_coroutines_version=1.5.2
|
||||
kotlin_version=2.0.21
|
||||
kotlin_coroutines_version=1.9.0
|
||||
kotlin_serialization_version=1.7.3
|
||||
|
||||
dokka_version=1.5.30
|
||||
dokka_version=1.9.20
|
||||
|
||||
klockVersion=2.4.2
|
||||
klockVersion=5.4.0
|
||||
|
||||
## Github reease
|
||||
|
||||
github_release_plugin_version=2.2.12
|
||||
github_release_plugin_version=2.5.2
|
||||
|
||||
## Android
|
||||
|
||||
android_minSdkVersion=19
|
||||
android_compileSdkVersion=31
|
||||
android_buildToolsVersion=31.0.0
|
||||
dexcount_version=3.0.0
|
||||
android_gradle_version=8.2.2
|
||||
android_minSdkVersion=21
|
||||
android_compileSdkVersion=35
|
||||
android_buildToolsVersion=35.0.0
|
||||
dexcount_version=4.0.0
|
||||
junit_version=4.12
|
||||
test_ext_junit_version=1.1.2
|
||||
espresso_core=3.3.0
|
||||
test_ext_junit_version=1.1.3
|
||||
espresso_core=3.4.0
|
||||
|
||||
androidx_work_version=2.6.0
|
||||
androidx_work_version=2.10.0
|
||||
|
||||
## Common
|
||||
|
||||
version=0.6.4
|
||||
android_code_version=8
|
||||
version=2.6.1
|
||||
android_code_version=45
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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-7.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/krontab/blob/master/LICENSE"}],"mavenConfig":{"name":"Krontab","description":"It is an analog of crontab util for Kotlin Coroutines","url":"https://github.com/InsanusMokrassar/krontab","vcsUrl":"https://github.com/InsanusMokrassar/krontab.git","includeGpgSigning":true,"developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/krontab"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}]}}
|
||||
@@ -1,8 +1,7 @@
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'signing'
|
||||
|
||||
task javadocsJar(type: Jar) {
|
||||
classifier = 'javadoc'
|
||||
archiveClassifier = 'javadoc'
|
||||
}
|
||||
|
||||
publishing {
|
||||
@@ -43,27 +42,77 @@ publishing {
|
||||
maven {
|
||||
name = "GithubPackages"
|
||||
url = uri("https://maven.pkg.github.com/InsanusMokrassar/krontab")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
|
||||
maven {
|
||||
name = "InmoNexus"
|
||||
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
|
||||
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||
maven {
|
||||
name = "sonatype"
|
||||
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
|
||||
credentials {
|
||||
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||
apply plugin: 'signing'
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
|
||||
sign publishing.publications
|
||||
}
|
||||
|
||||
task signAll {
|
||||
tasks.withType(Sign).forEach {
|
||||
dependsOn(it)
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign publishing.publications
|
||||
// Workaround to make android sign operations depend on signing tasks
|
||||
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
|
||||
def signingTasks = project.getTasks().withType(Sign.class)
|
||||
mustRunAfter(signingTasks)
|
||||
}
|
||||
// Workaround to make test tasks use sign
|
||||
project.getTasks().withType(Sign.class).configureEach { signTask ->
|
||||
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
|
||||
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
|
||||
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
|
||||
|
||||
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
|
||||
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
|
||||
if (debugTestTask != null) {
|
||||
signTask.mustRunAfter(debugTestTask)
|
||||
}
|
||||
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
|
||||
def testTask = tasks.findByName("compileTestKotlin$pubName")
|
||||
if (testTask != null) {
|
||||
signTask.mustRunAfter(testTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
publish.kpsb
Normal file
1
publish.kpsb
Normal file
@@ -0,0 +1 @@
|
||||
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/krontab/blob/master/LICENSE"}],"mavenConfig":{"name":"Krontab","description":"It is an analog of crontab util for Kotlin Coroutines","url":"https://github.com/InsanusMokrassar/krontab","vcsUrl":"https://github.com/InsanusMokrassar/krontab.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/krontab"},{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
||||
6
renovate.json
Normal file
6
renovate.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package dev.inmo.krontab
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
import com.soywiz.klock.DateTime
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.millisecondsLong
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
@@ -138,10 +139,14 @@ abstract class KronSchedulerWork(
|
||||
}
|
||||
throw e
|
||||
}
|
||||
when (result) {
|
||||
is Result.Failure -> if (prolongOnFailure) prolong()
|
||||
is Result.Success -> if (prolongOnSuccess) prolong()
|
||||
}
|
||||
result.checkResults(
|
||||
onFailure = {
|
||||
if (prolongOnFailure) prolong()
|
||||
},
|
||||
onSuccess = {
|
||||
if (prolongOnSuccess) prolong()
|
||||
}
|
||||
)
|
||||
return result
|
||||
}
|
||||
}
|
||||
15
src/androidMain/kotlin/ResultChecker.kt
Normal file
15
src/androidMain/kotlin/ResultChecker.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.work.ListenableWorker
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
internal inline fun ListenableWorker.Result.checkResults(
|
||||
onFailure: () -> Unit,
|
||||
onSuccess: () -> Unit
|
||||
) {
|
||||
when (this) {
|
||||
is ListenableWorker.Result.Failure -> onFailure()
|
||||
is ListenableWorker.Result.Success -> onSuccess()
|
||||
}
|
||||
}
|
||||
195
src/commonMain/kotlin/Executes.kt
Normal file
195
src/commonMain/kotlin/Executes.kt
Normal file
@@ -0,0 +1,195 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.DateTimeTz
|
||||
import korlibs.time.millisecondsLong
|
||||
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.
|
||||
*
|
||||
* 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(block: (DateTime) -> T): T {
|
||||
val time = nextOrNow().also {
|
||||
delay((it - DateTime.now()).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
|
||||
*/
|
||||
@Deprecated("Replaceable", ReplaceWith("doOnce", "dev.inmo.krontab.doOnce"))
|
||||
suspend inline fun <T> KronScheduler.doOnceLocal(block: (DateTime) -> T): T = doOnce(block)
|
||||
|
||||
/**
|
||||
* Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
|
||||
*
|
||||
* WARNING!!! This method will use local time instead of utc
|
||||
*
|
||||
* 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 -> nextTimeZoned() ?: DateTimeTz.nowLocal()
|
||||
}
|
||||
delay((time - DateTimeTz.nowLocal()).millisecondsLong)
|
||||
return block(time)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun <T> doOnce(
|
||||
scheduleConfig: String,
|
||||
block: (DateTime) -> T
|
||||
) = buildSchedule(scheduleConfig).doOnce(block)
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun <T> doOnceTz(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend (DateTimeTz) -> T
|
||||
) = buildSchedule(scheduleConfig).doOnceTz(block)
|
||||
|
||||
|
||||
/**
|
||||
* Will execute [block] while it will return true as a result of its calculation
|
||||
*/
|
||||
suspend inline fun KronScheduler.doWhile(block: (DateTime) -> Boolean) {
|
||||
var latest: DateTime? = null
|
||||
do {
|
||||
delay(1L)
|
||||
val result = doOnce {
|
||||
if (latest != it) {
|
||||
latest = it
|
||||
block(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} while (result == null || result)
|
||||
}
|
||||
/**
|
||||
* Will execute [block] while it will return true as a result of its calculation
|
||||
*/
|
||||
@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile"))
|
||||
suspend inline fun KronScheduler.doWhileLocal(block: (DateTime) -> Boolean) = doWhile(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) {
|
||||
var latest: DateTimeTz? = null
|
||||
do {
|
||||
delay(1L)
|
||||
val result = doOnceTz {
|
||||
if (latest != it) {
|
||||
latest = it
|
||||
block(it)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} while (result == null || result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
|
||||
*
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun doWhile(
|
||||
scheduleConfig: String,
|
||||
block: (DateTime) -> Boolean
|
||||
) = buildSchedule(scheduleConfig).doWhile(block)
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
|
||||
*
|
||||
* @see buildSchedule
|
||||
*/
|
||||
@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile"))
|
||||
suspend inline fun doWhileLocal(
|
||||
scheduleConfig: String,
|
||||
block: (DateTime) -> Boolean
|
||||
) = doWhile(scheduleConfig, 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 execute [block] without any checking of result
|
||||
*/
|
||||
suspend inline fun KronScheduler.doInfinity(block: (DateTime) -> Unit) = doWhile {
|
||||
block(it)
|
||||
coroutineContext.isActive
|
||||
}
|
||||
/**
|
||||
* Will execute [block] without any checking of result
|
||||
*/
|
||||
@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity"))
|
||||
suspend inline fun KronScheduler.doInfinityLocal(block: (DateTime) -> Unit) = doInfinity(block)
|
||||
|
||||
/**
|
||||
* Will execute [block] without any checking of result
|
||||
*/
|
||||
suspend inline fun KronScheduler.doInfinityTz(noinline block: suspend (DateTimeTz) -> Unit) = doWhileTz {
|
||||
block(it)
|
||||
coroutineContext.isActive
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
|
||||
*
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun doInfinity(
|
||||
scheduleConfig: String,
|
||||
block: (DateTime) -> Unit
|
||||
) = buildSchedule(scheduleConfig).doInfinity(block)
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
|
||||
*
|
||||
* @see buildSchedule
|
||||
*/
|
||||
@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity"))
|
||||
suspend inline fun doInfinityLocal(
|
||||
scheduleConfig: String,
|
||||
block: (DateTime) -> Unit
|
||||
) = doInfinity(scheduleConfig, 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)
|
||||
@@ -1,6 +1,6 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import korlibs.time.DateTime
|
||||
|
||||
/**
|
||||
* This interface was created for abstraction of [next] operation. Currently, there is only
|
||||
@@ -18,6 +18,8 @@ interface KronScheduler {
|
||||
* @see dev.inmo.krontab.internal.CronDateTimeScheduler.next
|
||||
*/
|
||||
suspend fun next(relatively: DateTime = DateTime.now()): DateTime?
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
suspend fun KronScheduler.nextOrRelative(relatively: DateTime = DateTime.now()): DateTime = next(relatively) ?: getAnyNext(relatively)
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import com.soywiz.klock.DateTimeTz
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.DateTimeTz
|
||||
|
||||
/**
|
||||
* This interface extending [KronScheduler] to use [DateTimeTz] with taking into account offset of incoming time for
|
||||
@@ -13,7 +13,7 @@ import com.soywiz.klock.DateTimeTz
|
||||
interface KronSchedulerTz : KronScheduler {
|
||||
suspend fun next(relatively: DateTimeTz): DateTimeTz?
|
||||
|
||||
override suspend fun next(relatively: DateTime): DateTime? = next(relatively.localUnadjusted) ?.local
|
||||
override suspend fun next(relatively: DateTime): DateTime? = next(relatively.local) ?.local
|
||||
}
|
||||
|
||||
suspend fun KronSchedulerTz.nextOrRelative(relatively: DateTimeTz): DateTimeTz = next(relatively) ?: getAnyNext(
|
||||
@@ -31,4 +31,4 @@ suspend fun KronScheduler.next(relatively: DateTimeTz) = if (this is KronSchedul
|
||||
this.next(relatively.local) ?.toOffsetUnadjusted(relatively.offset)
|
||||
}
|
||||
|
||||
suspend fun KronScheduler.nextTimeZoned() = next(DateTime.now().local)
|
||||
suspend fun KronScheduler.nextTimeZoned() = next(DateTime.now().local)
|
||||
176
src/commonMain/kotlin/KrontabConfig.kt
Normal file
176
src/commonMain/kotlin/KrontabConfig.kt
Normal file
@@ -0,0 +1,176 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import dev.inmo.krontab.internal.*
|
||||
import dev.inmo.krontab.internal.CronDateTimeScheduler
|
||||
import dev.inmo.krontab.internal.CronDateTimeSchedulerTz
|
||||
import dev.inmo.krontab.internal.createKronScheduler
|
||||
import dev.inmo.krontab.internal.createKronSchedulerWithOffset
|
||||
import dev.inmo.krontab.internal.millisecondsArrayDefault
|
||||
import dev.inmo.krontab.internal.parseDaysOfMonth
|
||||
import dev.inmo.krontab.internal.parseHours
|
||||
import dev.inmo.krontab.internal.parseMilliseconds
|
||||
import dev.inmo.krontab.internal.parseMinutes
|
||||
import dev.inmo.krontab.internal.parseMonths
|
||||
import dev.inmo.krontab.internal.parseOffset
|
||||
import dev.inmo.krontab.internal.parseSeconds
|
||||
import dev.inmo.krontab.internal.parseWeekDay
|
||||
import dev.inmo.krontab.internal.parseYears
|
||||
import dev.inmo.krontab.utils.Minutes
|
||||
import korlibs.time.TimezoneOffset
|
||||
import korlibs.time.minutes
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
/**
|
||||
* This value class contains [KrontabTemplate]
|
||||
*
|
||||
* * **seconds**
|
||||
* * **minutes**
|
||||
* * **hours**
|
||||
* * **dayOfMonth**
|
||||
* * **month**
|
||||
* * **year** (optional)
|
||||
* * **offset** (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00)
|
||||
* * **dayOfWeek** (optional) (can be placed anywhere after month)
|
||||
* * **milliseconds** (optional) (can be placed anywhere after month) (must be marked with `ms` at the end, for example: 500ms; 100-200ms)
|
||||
*
|
||||
* And each one (except of offsets) have next format:
|
||||
*
|
||||
* `{number}[,{number},...]` or `*`
|
||||
*
|
||||
* and {number} here is one of
|
||||
*
|
||||
* * {int}-{int}
|
||||
* * {int}/{int}
|
||||
* * */{int}
|
||||
* * {int}
|
||||
* * 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]
|
||||
* * Minutes ranges can be found in [minutesRange]
|
||||
* * Hours ranges can be found in [hoursRange]
|
||||
* * 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])
|
||||
* * WeekDay (timezone) ranges can be found in [dayOfWeekRange]
|
||||
* * Milliseconds ranges can be found in [millisecondsRange]
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* * "0/5 * * * *" for every five seconds triggering
|
||||
* * "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 * * * 500ms" for every 15th seconds in a half of each hour when milliseconds equal to 500
|
||||
* * "1 2 3 F,4,L 5" for triggering in near first second of second minute of third hour of first, fifth and last days of may
|
||||
* * "1 2 3 F,4,L 5 60o" for triggering in near first second of second minute of third hour of first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00
|
||||
* * "1 2 3 F,4,L 5 2021 60o 0-2w 500ms" for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500
|
||||
*
|
||||
* @see dev.inmo.krontab.internal.createKronScheduler
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class KrontabConfig(
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val template: KrontabTemplate
|
||||
) {
|
||||
/**
|
||||
* Creates __new__ [KronScheduler] based on a [template]
|
||||
*
|
||||
* @return In case when offset parameter is absent in [template] will be used [createSimpleScheduler] method and
|
||||
* returned [CronDateTimeScheduler]. In case when offset parameter there is in [template] [KrontabTemplate] will be used
|
||||
* [createKronSchedulerWithOffset] and returned [CronDateTimeSchedulerTz]
|
||||
*/
|
||||
fun scheduler(): KronScheduler {
|
||||
var offsetParsed: Int? = null
|
||||
var dayOfWeekParsed: Array<Byte>? = null
|
||||
var yearParsed: Array<Int>? = null
|
||||
var millisecondsParsed: Array<Short>? = null
|
||||
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = template
|
||||
.split(" ")
|
||||
.filter { checkIncomingPart(it) } // filter garbage from string
|
||||
.let {
|
||||
if (it.size < 5) { // reconstruction in case of insufficient arguments; 5 is amount of required arguments out of latest also code
|
||||
it + (it.size until 5).map { "*" }
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
.also {
|
||||
listOfNotNull(
|
||||
it.getOrNull(5),
|
||||
it.getOrNull(6),
|
||||
it.getOrNull(7),
|
||||
it.getOrNull(8)
|
||||
).forEach {
|
||||
val offsetFromString = parseOffset(it)
|
||||
val dayOfWeekFromString = parseWeekDay(it)
|
||||
val millisecondsFromString = parseMilliseconds(it)
|
||||
offsetParsed = offsetParsed ?: offsetFromString
|
||||
dayOfWeekParsed = dayOfWeekParsed ?: dayOfWeekFromString
|
||||
millisecondsParsed = millisecondsParsed ?: millisecondsFromString
|
||||
when {
|
||||
dayOfWeekFromString != null || offsetFromString != null || millisecondsFromString != null -> return@forEach
|
||||
yearParsed == null -> {
|
||||
yearParsed = parseYears(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val secondsParsed = parseSeconds(secondsSource)
|
||||
val minutesParsed = parseMinutes(minutesSource)
|
||||
val hoursParsed = parseHours(hoursSource)
|
||||
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
||||
val monthParsed = parseMonths(monthSource)
|
||||
|
||||
return offsetParsed ?.let { offset ->
|
||||
createKronSchedulerWithOffset(
|
||||
secondsParsed,
|
||||
minutesParsed,
|
||||
hoursParsed,
|
||||
dayOfMonthParsed,
|
||||
monthParsed,
|
||||
yearParsed,
|
||||
dayOfWeekParsed,
|
||||
TimezoneOffset(offset.minutes),
|
||||
millisecondsParsed ?: millisecondsArrayDefault
|
||||
)
|
||||
} ?: createKronScheduler(
|
||||
secondsParsed,
|
||||
minutesParsed,
|
||||
hoursParsed,
|
||||
dayOfMonthParsed,
|
||||
monthParsed,
|
||||
yearParsed,
|
||||
dayOfWeekParsed,
|
||||
millisecondsParsed ?: millisecondsArrayDefault
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates base [KronScheduler] using [scheduler] function. In case when returned [KronScheduler] is [KronSchedulerTz],
|
||||
* it will be returned as is. Otherwise, will be created new [CronDateTimeSchedulerTz] with [defaultOffset] as
|
||||
* offset
|
||||
*/
|
||||
fun scheduler(defaultOffset: Minutes): KronSchedulerTz {
|
||||
val scheduler = scheduler()
|
||||
return if (scheduler is KronSchedulerTz) {
|
||||
scheduler
|
||||
} else {
|
||||
CronDateTimeSchedulerTz(
|
||||
(scheduler as CronDateTimeScheduler).cronDateTime,
|
||||
TimezoneOffset(defaultOffset.minutes)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ package dev.inmo.krontab
|
||||
* [dev.inmo.krontab.internal.CronDateTimeScheduler] due to the fact that [toKronScheduler] will return it under the
|
||||
* hood
|
||||
*/
|
||||
@Deprecated(
|
||||
"It is useless wrapper for KrontabTemplate. Use KrontabConfig instead",
|
||||
ReplaceWith("KrontabConfig(template)", "dev.inmo.krontab.KrontabConfig")
|
||||
)
|
||||
data class KrontabTemplateWrapper(
|
||||
val template: KrontabTemplate
|
||||
) : KronScheduler by template.toKronScheduler()
|
||||
@@ -15,4 +19,8 @@ data class KrontabTemplateWrapper(
|
||||
* @see [toKronScheduler]
|
||||
* @see [KrontabTemplateWrapper]
|
||||
*/
|
||||
@Deprecated(
|
||||
"Will be removed in near major update with KrontabTemplateWrapper",
|
||||
ReplaceWith("this.krontabConfig", "dev.inmo.krontab.krontabConfig")
|
||||
)
|
||||
fun KrontabTemplate.wrapAsKronScheduler() = KrontabTemplateWrapper(this)
|
||||
13
src/commonMain/kotlin/LambdaKronScheduler.kt
Normal file
13
src/commonMain/kotlin/LambdaKronScheduler.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import korlibs.time.DateTime
|
||||
|
||||
class LambdaKronScheduler(
|
||||
private val onNext: suspend (DateTime) -> DateTime?
|
||||
) : KronScheduler {
|
||||
override suspend fun next(relatively: DateTime): DateTime? = onNext(relatively)
|
||||
}
|
||||
|
||||
fun KronScheduler(
|
||||
onNext: suspend (DateTime) -> DateTime?
|
||||
) = LambdaKronScheduler(onNext)
|
||||
13
src/commonMain/kotlin/LambdaKronSchedulerTz.kt
Normal file
13
src/commonMain/kotlin/LambdaKronSchedulerTz.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import korlibs.time.DateTimeTz
|
||||
|
||||
class LambdaKronSchedulerTz(
|
||||
private val onNext: suspend (DateTimeTz) -> DateTimeTz?
|
||||
) : KronSchedulerTz {
|
||||
override suspend fun next(relatively: DateTimeTz): DateTimeTz? = onNext(relatively)
|
||||
}
|
||||
|
||||
fun KronSchedulerTz(
|
||||
onNext: suspend (DateTimeTz) -> DateTimeTz?
|
||||
) = LambdaKronSchedulerTz(onNext)
|
||||
133
src/commonMain/kotlin/SchedulerShortcuts.kt
Normal file
133
src/commonMain/kotlin/SchedulerShortcuts.kt
Normal file
@@ -0,0 +1,133 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import korlibs.time.DateTime
|
||||
import dev.inmo.krontab.builder.buildSchedule
|
||||
import dev.inmo.krontab.internal.*
|
||||
|
||||
internal val anyCronDateTime by lazy {
|
||||
CronDateTime()
|
||||
}
|
||||
internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(relatively)!!
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now]
|
||||
*/
|
||||
val AnyTimeScheduler: KronScheduler by lazy {
|
||||
CronDateTimeScheduler(anyCronDateTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one millisecond
|
||||
*/
|
||||
val EveryMillisecondScheduler: KronScheduler by lazy {
|
||||
buildSchedule { milliseconds { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one second
|
||||
*/
|
||||
val EverySecondScheduler: KronScheduler by lazy {
|
||||
buildSchedule { seconds { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one minute
|
||||
*/
|
||||
val EveryMinuteScheduler: KronScheduler by lazy {
|
||||
buildSchedule {
|
||||
seconds { at(0) }
|
||||
minutes { 0 every 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one hour
|
||||
*/
|
||||
val EveryHourScheduler: KronScheduler by lazy {
|
||||
buildSchedule {
|
||||
seconds { at(0) }
|
||||
minutes { at(0) }
|
||||
hours { 0 every 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one day
|
||||
*/
|
||||
val EveryDayOfMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule {
|
||||
seconds { at(0) }
|
||||
minutes { at(0) }
|
||||
hours { at(0) }
|
||||
dayOfMonth { 0 every 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one month
|
||||
*/
|
||||
val EveryMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule {
|
||||
seconds { at(0) }
|
||||
minutes { at(0) }
|
||||
hours { at(0) }
|
||||
dayOfMonth { at(0) }
|
||||
months { 0 every 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [korlibs.time.DateTime.now] + one year
|
||||
*/
|
||||
val EveryYearScheduler: KronScheduler by lazy {
|
||||
buildSchedule {
|
||||
seconds { at(0) }
|
||||
minutes { at(0) }
|
||||
hours { at(0) }
|
||||
dayOfMonth { at(0) }
|
||||
months { at(0) }
|
||||
years { 0 every 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut for [EveryMillisecondScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.everyMillisecond
|
||||
get() = EveryMillisecondScheduler
|
||||
|
||||
/**
|
||||
* Shortcut for [EverySecondScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.everySecond
|
||||
get() = EverySecondScheduler
|
||||
|
||||
/**
|
||||
* Shortcut for [EveryMinuteScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.everyMinute
|
||||
get() = EveryMinuteScheduler
|
||||
|
||||
/**
|
||||
* Shortcut for [EveryHourScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.hourly
|
||||
get() = EveryHourScheduler
|
||||
|
||||
/**
|
||||
* Shortcut for [EveryDayOfMonthScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.daily
|
||||
get() = EveryDayOfMonthScheduler
|
||||
|
||||
/**
|
||||
* Shortcut for [EveryMonthScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.monthly
|
||||
get() = EveryMonthScheduler
|
||||
|
||||
/**
|
||||
* Shortcut for [EveryYearScheduler]
|
||||
*/
|
||||
inline val KronScheduler.Companion.annually
|
||||
get() = EveryYearScheduler
|
||||
@@ -1,7 +1,5 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.TimezoneOffset
|
||||
import com.soywiz.klock.minutes
|
||||
import dev.inmo.krontab.internal.*
|
||||
import dev.inmo.krontab.utils.Minutes
|
||||
|
||||
@@ -11,17 +9,20 @@ import dev.inmo.krontab.utils.Minutes
|
||||
*/
|
||||
typealias KrontabTemplate = String
|
||||
|
||||
inline fun KrontabTemplate.krontabConfig() = KrontabConfig(this)
|
||||
|
||||
/**
|
||||
* Parse [incoming] string and adapt according to next format: "* * * * *" where order of things:
|
||||
*
|
||||
* * seconds
|
||||
* * minutes
|
||||
* * hours
|
||||
* * dayOfMonth
|
||||
* * month
|
||||
* * (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
|
||||
* * **seconds**
|
||||
* * **minutes**
|
||||
* * **hours**
|
||||
* * **dayOfMonth**
|
||||
* * **month**
|
||||
* * **year** (optional)
|
||||
* * **offset** (optional) (can be placed anywhere after month) (must be marked with `o` at the end, for example: 60o == +01:00)
|
||||
* * **dayOfWeek** (optional) (can be placed anywhere after month)
|
||||
* * **milliseconds** (optional) (can be placed anywhere after month) (must be marked with `ms` at the end, for example: 500ms; 100-200ms)
|
||||
*
|
||||
* And each one (except of offsets) have next format:
|
||||
*
|
||||
@@ -48,79 +49,37 @@ typealias KrontabTemplate = String
|
||||
* * Months ranges can be found in [monthRange]
|
||||
* * Years ranges can be found in [yearRange] (in fact - any [Int])
|
||||
* * WeekDay (timezone) ranges can be found in [dayOfWeekRange]
|
||||
* * Milliseconds ranges can be found in [millisecondsRange]
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* * "0/5 * * * *" for every five seconds triggering
|
||||
* * "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
|
||||
* * "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
|
||||
* * "0/15 30 * * * 500ms" for every 15th seconds in a half of each hour when milliseconds equal to 500
|
||||
* * "1 2 3 F,4,L 5" for triggering in near first second of second minute of third hour of first, fifth and last days of may
|
||||
* * "1 2 3 F,4,L 5 60o" for triggering in near first second of second minute of third hour of first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days 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 first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00
|
||||
* * "1 2 3 F,4,L 5 2021 60o 0-2w 500ms" for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500
|
||||
*
|
||||
* @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 KrontabConfig.scheduler
|
||||
*/
|
||||
fun createSimpleScheduler(
|
||||
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 {
|
||||
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 minutesParsed = parseMinutes(minutesSource)
|
||||
val hoursParsed = parseHours(hoursSource)
|
||||
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
||||
val monthParsed = parseMonths(monthSource)
|
||||
|
||||
return offsetParsed ?.let { offset ->
|
||||
createKronSchedulerWithOffset(
|
||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed, TimezoneOffset(offset.minutes)
|
||||
)
|
||||
} ?: createKronScheduler(
|
||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed, dayOfWeekParsed
|
||||
)
|
||||
}
|
||||
): KronScheduler = KrontabConfig(incoming).scheduler()
|
||||
|
||||
fun createSimpleScheduler(
|
||||
incoming: KrontabTemplate,
|
||||
defaultOffset: Minutes
|
||||
): KronSchedulerTz {
|
||||
val scheduler = createSimpleScheduler(incoming)
|
||||
return if (scheduler is KronSchedulerTz) {
|
||||
scheduler
|
||||
} else {
|
||||
CronDateTimeSchedulerTz(
|
||||
(scheduler as CronDateTimeScheduler).cronDateTime,
|
||||
TimezoneOffset(defaultOffset.minutes)
|
||||
)
|
||||
}
|
||||
}
|
||||
): KronSchedulerTz = KrontabConfig(incoming).scheduler(defaultOffset)
|
||||
|
||||
/**
|
||||
* Shortcut for [createSimpleScheduler]
|
||||
@@ -1,9 +1,10 @@
|
||||
package dev.inmo.krontab.builder
|
||||
|
||||
import com.soywiz.klock.TimezoneOffset
|
||||
import com.soywiz.klock.minutes
|
||||
import korlibs.time.TimezoneOffset
|
||||
import korlibs.time.minutes
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.KronSchedulerTz
|
||||
import dev.inmo.krontab.internal.*
|
||||
import dev.inmo.krontab.internal.createKronScheduler
|
||||
import dev.inmo.krontab.internal.createKronSchedulerWithOffset
|
||||
import dev.inmo.krontab.utils.Minutes
|
||||
@@ -13,7 +14,7 @@ import dev.inmo.krontab.utils.Minutes
|
||||
*
|
||||
* @see dev.inmo.krontab.createSimpleScheduler
|
||||
*/
|
||||
fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
||||
inline fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
||||
val builder = SchedulerBuilder()
|
||||
|
||||
builder.settingsBlock()
|
||||
@@ -26,7 +27,7 @@ fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
||||
*
|
||||
* @see dev.inmo.krontab.createSimpleScheduler
|
||||
*/
|
||||
fun buildSchedule(
|
||||
inline fun buildSchedule(
|
||||
offset: Minutes,
|
||||
settingsBlock: SchedulerBuilder.() -> Unit
|
||||
): KronSchedulerTz {
|
||||
@@ -37,6 +38,27 @@ fun buildSchedule(
|
||||
return builder.build() as KronSchedulerTz
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new [KronScheduler] with [settingsBlock]
|
||||
*
|
||||
* Since it is inline function, you may break execution of [settingsBlock]
|
||||
* at any time
|
||||
*/
|
||||
inline operator fun KronScheduler.Companion.invoke(
|
||||
offset: Minutes,
|
||||
settingsBlock: SchedulerBuilder.() -> Unit
|
||||
): KronSchedulerTz = buildSchedule(offset, settingsBlock)
|
||||
|
||||
/**
|
||||
* Creates new [KronScheduler] with [settingsBlock]
|
||||
*
|
||||
* Since it is inline function, you may break execution of [settingsBlock]
|
||||
* at any time
|
||||
*/
|
||||
inline operator fun KronScheduler.Companion.invoke(
|
||||
settingsBlock: SchedulerBuilder.() -> Unit
|
||||
): KronScheduler = buildSchedule(settingsBlock)
|
||||
|
||||
class SchedulerBuilder(
|
||||
private var seconds: Array<Byte>? = null,
|
||||
private var minutes: Array<Byte>? = null,
|
||||
@@ -45,7 +67,8 @@ class SchedulerBuilder(
|
||||
private var month: Array<Byte>? = null,
|
||||
private var year: Array<Int>? = null,
|
||||
private var dayOfWeek: Array<Byte>? = null,
|
||||
private val offset: Minutes? = null
|
||||
private val offset: Minutes? = null,
|
||||
private var milliseconds: Array<Short>? = null
|
||||
) {
|
||||
private fun <I, T : TimeBuilder<I>> callAndReturn(
|
||||
initial: Array<I>?,
|
||||
@@ -63,6 +86,17 @@ class SchedulerBuilder(
|
||||
} ?: builderValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an milliseconds block
|
||||
*/
|
||||
fun milliseconds(block: MillisecondsBuilder.() -> Unit) {
|
||||
milliseconds = callAndReturn(
|
||||
milliseconds,
|
||||
MillisecondsBuilder(),
|
||||
block
|
||||
) ?.toTypedArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts an seconds block
|
||||
*/
|
||||
@@ -147,6 +181,25 @@ class SchedulerBuilder(
|
||||
* @see dev.inmo.krontab.internal.createKronScheduler
|
||||
*/
|
||||
fun build(): KronScheduler = offset ?.let {
|
||||
createKronSchedulerWithOffset(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, TimezoneOffset(it.minutes))
|
||||
} ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek)
|
||||
createKronSchedulerWithOffset(
|
||||
seconds = seconds,
|
||||
minutes = minutes,
|
||||
hours = hours,
|
||||
dayOfMonth = dayOfMonth,
|
||||
month = month,
|
||||
years = year,
|
||||
weekDays = dayOfWeek,
|
||||
offset = TimezoneOffset(it.minutes),
|
||||
milliseconds = milliseconds ?: millisecondsArrayDefault
|
||||
)
|
||||
} ?: createKronScheduler(
|
||||
seconds = seconds,
|
||||
minutes = minutes,
|
||||
hours = hours,
|
||||
dayOfMonth = dayOfMonth,
|
||||
month = month,
|
||||
years = year,
|
||||
weekDays = dayOfWeek,
|
||||
milliseconds = milliseconds ?: millisecondsArrayDefault
|
||||
)
|
||||
}
|
||||
@@ -122,6 +122,7 @@ sealed class TimeBuilder<T : Number> (
|
||||
internal fun build() = result ?.map(converter)
|
||||
}
|
||||
|
||||
class MillisecondsBuilder : TimeBuilder<Short>(millisecondsRange, intToShortConverter)
|
||||
class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
|
||||
class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
|
||||
class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.inmo.krontab.collection
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import com.soywiz.klock.DateTimeTz
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.DateTimeTz
|
||||
import dev.inmo.krontab.*
|
||||
import dev.inmo.krontab.internal.*
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
next() ?.let {
|
||||
delay((it - DateTime.now()).millisecondsLong)
|
||||
}
|
||||
return block()
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doOnce] on it
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun <T> doOnce(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> T
|
||||
) = buildSchedule(scheduleConfig).doOnce(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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
|
||||
*
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun doWhile(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> Boolean
|
||||
) = buildSchedule(scheduleConfig).doWhile(block)
|
||||
|
||||
/**
|
||||
* Will execute [block] without any checking of result
|
||||
*/
|
||||
suspend inline fun KronScheduler.doInfinity(noinline block: suspend () -> Unit) = doWhile {
|
||||
block()
|
||||
true
|
||||
}
|
||||
/**
|
||||
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
|
||||
*
|
||||
* @see buildSchedule
|
||||
*/
|
||||
suspend inline fun doInfinity(
|
||||
scheduleConfig: String,
|
||||
noinline block: suspend () -> Unit
|
||||
) = buildSchedule(scheduleConfig).doInfinity(block)
|
||||
@@ -1,59 +0,0 @@
|
||||
package dev.inmo.krontab
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.krontab.builder.buildSchedule
|
||||
import dev.inmo.krontab.internal.*
|
||||
|
||||
internal val anyCronDateTime by lazy {
|
||||
CronDateTime()
|
||||
}
|
||||
internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(relatively)!!
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now]
|
||||
*/
|
||||
val AnyTimeScheduler: KronScheduler by lazy {
|
||||
CronDateTimeScheduler(anyCronDateTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one second
|
||||
*/
|
||||
val EverySecondScheduler: KronScheduler by lazy {
|
||||
buildSchedule { seconds { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one minute
|
||||
*/
|
||||
val EveryMinuteScheduler: KronScheduler by lazy {
|
||||
buildSchedule { minutes { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one hour
|
||||
*/
|
||||
val EveryHourScheduler: KronScheduler by lazy {
|
||||
buildSchedule { hours { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one day
|
||||
*/
|
||||
val EveryDayOfMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule { dayOfMonth { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one month
|
||||
*/
|
||||
val EveryMonthScheduler: KronScheduler by lazy {
|
||||
buildSchedule { months { 0 every 1 } }
|
||||
}
|
||||
|
||||
/**
|
||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one year
|
||||
*/
|
||||
val EveryYearScheduler: KronScheduler by lazy {
|
||||
buildSchedule { years { 0 every 1 } }
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
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 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
|
||||
fun KronScheduler.asTzFlow(): Flow<DateTimeTz> = channelFlow {
|
||||
var previousTime = DateTime.nowLocal()
|
||||
while (isActive) {
|
||||
val now = DateTime.nowLocal()
|
||||
val nextTime = next(now) ?: break
|
||||
if (previousTime == nextTime) {
|
||||
delay(1L) // skip 1ms
|
||||
continue
|
||||
} else {
|
||||
previousTime = nextTime
|
||||
}
|
||||
val sleepDelay = (nextTime - DateTime.now().local).millisecondsLong
|
||||
delay(sleepDelay)
|
||||
send(nextTime)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
class SchedulerFlow(
|
||||
private val scheduler: KronScheduler
|
||||
) : AbstractFlow<DateTime>() {
|
||||
@FlowPreview
|
||||
override suspend fun collectSafely(collector: FlowCollector<DateTime>) {
|
||||
while (true) {
|
||||
val now = DateTime.now()
|
||||
val nextTime = scheduler.next(now) ?: break
|
||||
val sleepDelay = (nextTime - now).millisecondsLong
|
||||
delay(sleepDelay)
|
||||
collector.emit(nextTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.TimezoneOffset
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.utils.copy
|
||||
|
||||
/**
|
||||
* @param daysOfWeek 0-6
|
||||
* @param years any int
|
||||
* @param months 0-11
|
||||
* @param daysOfMonth 0-31
|
||||
* @param daysOfMonth 0-30
|
||||
* @param hours 0-23
|
||||
* @param minutes 0-59
|
||||
* @param seconds 0-59
|
||||
@@ -20,7 +20,8 @@ internal data class CronDateTime(
|
||||
val daysOfMonth: Array<Byte>? = null,
|
||||
val hours: Array<Byte>? = null,
|
||||
val minutes: Array<Byte>? = null,
|
||||
val seconds: Array<Byte>? = null
|
||||
val seconds: Array<Byte>? = null,
|
||||
val milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||
) {
|
||||
init {
|
||||
check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true)
|
||||
@@ -30,12 +31,13 @@ internal data class CronDateTime(
|
||||
check(hours?.all { it in hoursRange } ?: true)
|
||||
check(minutes?.all { it in minutesRange } ?: true)
|
||||
check(seconds?.all { it in secondsRange } ?: true)
|
||||
check(milliseconds?.all { it in millisecondsRange } ?: true)
|
||||
}
|
||||
|
||||
internal val calculators = listOf(
|
||||
years ?.let { NearDateTimeCalculatorYears(it) },
|
||||
daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) },
|
||||
NearDateTimeCalculatorMillis(arrayOf(0)),
|
||||
milliseconds ?.let { NearDateTimeCalculatorMillis(it) },
|
||||
seconds ?.let { NearDateTimeCalculatorSeconds(it) },
|
||||
minutes ?.let { NearDateTimeCalculatorMinutes(it) },
|
||||
hours ?.let { NearDateTimeCalculatorHours(it) },
|
||||
@@ -65,9 +67,10 @@ internal fun createCronDateTime(
|
||||
dayOfMonth: Array<Byte>? = null,
|
||||
month: Array<Byte>? = null,
|
||||
years: Array<Int>? = null,
|
||||
weekDays: Array<Byte>? = null
|
||||
weekDays: Array<Byte>? = null,
|
||||
milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||
): CronDateTime {
|
||||
return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds)
|
||||
return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds, milliseconds)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +83,20 @@ internal fun createKronScheduler(
|
||||
dayOfMonth: Array<Byte>? = null,
|
||||
month: Array<Byte>? = null,
|
||||
years: Array<Int>? = null,
|
||||
weekDays: Array<Byte>? = null
|
||||
): KronScheduler = CronDateTimeScheduler(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays))
|
||||
weekDays: Array<Byte>? = null,
|
||||
milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||
): KronScheduler = CronDateTimeScheduler(
|
||||
createCronDateTime(
|
||||
seconds,
|
||||
minutes,
|
||||
hours,
|
||||
dayOfMonth,
|
||||
month,
|
||||
years,
|
||||
weekDays,
|
||||
milliseconds
|
||||
)
|
||||
)
|
||||
/**
|
||||
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
||||
*/
|
||||
@@ -93,8 +108,21 @@ internal fun createKronSchedulerWithOffset(
|
||||
month: Array<Byte>? = null,
|
||||
years: Array<Int>? = null,
|
||||
weekDays: Array<Byte>? = null,
|
||||
offset: TimezoneOffset
|
||||
): KronScheduler = CronDateTimeSchedulerTz(createCronDateTime(seconds, minutes, hours, dayOfMonth, month, years, weekDays), offset)
|
||||
offset: TimezoneOffset,
|
||||
milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||
): KronScheduler = CronDateTimeSchedulerTz(
|
||||
createCronDateTime(
|
||||
seconds,
|
||||
minutes,
|
||||
hours,
|
||||
dayOfMonth,
|
||||
month,
|
||||
years,
|
||||
weekDays,
|
||||
milliseconds
|
||||
),
|
||||
offset
|
||||
)
|
||||
|
||||
internal fun List<CronDateTime>.merge() = CronDateTime(
|
||||
flatMap { it.daysOfWeek ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||
@@ -104,4 +132,5 @@ internal fun List<CronDateTime>.merge() = CronDateTime(
|
||||
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() },
|
||||
flatMap { it.milliseconds ?.toList() ?: listOf(0) }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||
)
|
||||
@@ -1,13 +1,13 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import korlibs.time.DateTime
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.collection.plus
|
||||
|
||||
/**
|
||||
* Cron-oriented realisation of [KronScheduler]
|
||||
*
|
||||
* @see dev.inmo.krontab.AnyTimeScheduler
|
||||
* @see dev.inmo.krontab.EveryMillisecondScheduler
|
||||
* @see dev.inmo.krontab.EverySecondScheduler
|
||||
* @see dev.inmo.krontab.EveryMinuteScheduler
|
||||
* @see dev.inmo.krontab.EveryHourScheduler
|
||||
@@ -36,23 +36,3 @@ internal fun mergeCronDateTimeSchedulers(
|
||||
): CronDateTimeScheduler = CronDateTimeScheduler(
|
||||
schedulers.map { it.cronDateTime }.merge()
|
||||
)
|
||||
|
||||
/**
|
||||
* @return New instance of [CronDateTimeScheduler] with all unique [CronDateTimeScheduler.cronDateTimes] of
|
||||
* [kronSchedulers] included
|
||||
*/
|
||||
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
|
||||
fun merge(kronSchedulers: List<KronScheduler>) = kronSchedulers.apply { dev.inmo.krontab.merge() }
|
||||
|
||||
/**
|
||||
* @return Vararg shortcut for [dev.inmo.krontab.merge]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
|
||||
inline fun merge(vararg kronDateTimeSchedulers: KronScheduler) = kronDateTimeSchedulers.apply { dev.inmo.krontab.merge() }
|
||||
/**
|
||||
* @return Vararg shortcut for [dev.inmo.krontab.merge]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
|
||||
inline fun KronScheduler.plus(other: KronScheduler) = this + other
|
||||
@@ -1,7 +1,8 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
import com.soywiz.klock.DateTimeTz
|
||||
import com.soywiz.klock.TimezoneOffset
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.DateTimeTz
|
||||
import korlibs.time.TimezoneOffset
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.KronSchedulerTz
|
||||
|
||||
@@ -18,6 +19,10 @@ internal data class CronDateTimeSchedulerTz internal constructor(
|
||||
val dateTimeWithActualOffset = relatively.toOffset(offset).local
|
||||
return cronDateTime.toNearDateTime(dateTimeWithActualOffset) ?.toOffsetUnadjusted(offset) ?.toOffset(relatively.offset)
|
||||
}
|
||||
|
||||
override suspend fun next(relatively: DateTime): DateTime? {
|
||||
return next(relatively.toOffset(offset)) ?.utc
|
||||
}
|
||||
}
|
||||
|
||||
internal fun mergeCronDateTimeSchedulers(
|
||||
4
src/commonMain/kotlin/internal/Defaults.kt
Normal file
4
src/commonMain/kotlin/internal/Defaults.kt
Normal file
@@ -0,0 +1,4 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
internal const val millisecondsDefault: Short = 0
|
||||
internal val millisecondsArrayDefault: Array<Short> = arrayOf(millisecondsDefault)
|
||||
@@ -1,6 +1,6 @@
|
||||
package dev.inmo.krontab.internal
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import korlibs.time.*
|
||||
import dev.inmo.krontab.utils.copy
|
||||
import kotlin.math.min
|
||||
|
||||
@@ -101,19 +101,26 @@ internal fun NearDateTimeCalculatorDays(
|
||||
times: Array<Byte>
|
||||
) = CommonNearDateTimeCalculator(
|
||||
times,
|
||||
{ it.dayOfMonth.toByte() },
|
||||
{ (it.dayOfMonth - 1).toByte() }, // index1, so, decrease
|
||||
{ dateTime, newOne ->
|
||||
(if (newOne < dateTime.dayOfMonth) {
|
||||
val dateTime = (if (newOne < dateTime.dayOfMonth) {
|
||||
dateTime.plus(1.months)
|
||||
} else {
|
||||
dateTime
|
||||
}).copy(
|
||||
})
|
||||
val newDateTime = dateTime.copy(
|
||||
dayOfMonth = min(dateTime.month.days(dateTime.year), newOne.toInt() + 1), // index1
|
||||
hour = 0,
|
||||
minute = 0,
|
||||
second = 0,
|
||||
milliseconds = 0
|
||||
)
|
||||
// If day of month has not been changed, use old dateTime due to no changes required
|
||||
if (newDateTime.dayOfMonth == dateTime.dayOfMonth) {
|
||||
dateTime
|
||||
} else {
|
||||
newDateTime
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -121,7 +128,7 @@ internal fun NearDateTimeCalculatorMonths(
|
||||
times: Array<Byte>
|
||||
) = CommonNearDateTimeCalculator(
|
||||
times,
|
||||
{ it.dayOfMonth.toByte() },
|
||||
{ it.month0.toByte() },
|
||||
{ dateTime, newOne ->
|
||||
(if (newOne < dateTime.month0) {
|
||||
dateTime.plus(1.years)
|
||||
@@ -3,6 +3,7 @@ package dev.inmo.krontab.internal
|
||||
typealias Converter<T> = (Int) -> T
|
||||
|
||||
internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() }
|
||||
internal val intToShortConverter: Converter<Short> = { it: Int -> it.toShort() }
|
||||
internal val intToIntConverter: Converter<Int> = { it: Int -> it }
|
||||
private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter<T>): List<T>? {
|
||||
val things = from.split(",")
|
||||
@@ -36,6 +37,103 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
|
||||
return results.map(dataConverter)
|
||||
}
|
||||
|
||||
/**
|
||||
* FSM for parsing of incoming data. If at the end of parsing it have non-null state and string is not empty, data passed check
|
||||
*
|
||||
* 1.
|
||||
* * "\\d" -> 2
|
||||
* * "\\*" -> 4
|
||||
* * "f" -> 7
|
||||
* * "l" -> 7
|
||||
* * "/" -> 6
|
||||
* 2.
|
||||
* * "\\d" -> 2
|
||||
* * "/" -> 6
|
||||
* * "," -> 1
|
||||
* * "-" -> 3
|
||||
* * "m" -> 9
|
||||
* * "o" -> 10
|
||||
* * "w" -> 10
|
||||
* 3.
|
||||
* * "l" -> 7
|
||||
* * "\\d" -> 8
|
||||
* 4.
|
||||
* * "/" -> 6
|
||||
* * "," -> 1
|
||||
* 5.
|
||||
* * "/" -> 6
|
||||
* 6.
|
||||
* * "\\d" -> 8
|
||||
* * "\\*" -> 7
|
||||
* 7.
|
||||
* * "," -> 1
|
||||
* 8.
|
||||
* * "\\d" -> 8
|
||||
* * "," -> 1
|
||||
* 9.
|
||||
* * "s" -> 10 // end of ms
|
||||
* 10. Empty, end of parse
|
||||
*/
|
||||
private val checkIncomingPartTransitionsMap = listOf(
|
||||
listOf( // 0
|
||||
Regex("\\d") to 1,
|
||||
Regex("\\*") to 3,
|
||||
Regex("f") to 6,
|
||||
Regex("l") to 6,
|
||||
Regex("/") to 5,
|
||||
),
|
||||
listOf( // 1
|
||||
Regex("\\d") to 1,
|
||||
Regex("/") to 5,
|
||||
Regex(",") to 0,
|
||||
Regex("-") to 2,
|
||||
Regex("m") to 8,
|
||||
Regex("o") to 9,
|
||||
Regex("w") to 9,
|
||||
),
|
||||
listOf( // 2
|
||||
Regex("l") to 6,
|
||||
Regex("\\d") to 7,
|
||||
),
|
||||
listOf( // 3
|
||||
Regex("/") to 5,
|
||||
Regex(",") to 0,
|
||||
),
|
||||
listOf( // 4
|
||||
Regex("/") to 5,
|
||||
),
|
||||
listOf( // 5
|
||||
Regex("\\d") to 7,
|
||||
Regex("\\*") to 6,
|
||||
),
|
||||
listOf( // 6
|
||||
Regex(",") to 0,
|
||||
),
|
||||
listOf( // 7
|
||||
Regex("\\d") to 7,
|
||||
Regex(",") to 0,
|
||||
),
|
||||
listOf( // 8
|
||||
Regex("s") to 9, // end of ms
|
||||
),
|
||||
listOf(), // empty state, end of parsing
|
||||
)
|
||||
internal fun checkIncomingPart(part: String): Boolean {
|
||||
var i = 0
|
||||
var state = checkIncomingPartTransitionsMap[0]
|
||||
while (i < part.length) {
|
||||
val char = part[i]
|
||||
val nextState = state.firstNotNullOfOrNull {
|
||||
it.second.takeIf { _ -> it.first.matches("$char") }
|
||||
}
|
||||
if (nextState == null) return false
|
||||
state = checkIncomingPartTransitionsMap[nextState]
|
||||
i++
|
||||
}
|
||||
|
||||
return part.isNotEmpty()
|
||||
}
|
||||
|
||||
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() }
|
||||
@@ -44,6 +142,7 @@ internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfM
|
||||
internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange, intToByteConverter) ?.toTypedArray()
|
||||
internal fun parseMilliseconds(from: String?) = from ?.let { if (it.endsWith("ms")) createSimpleScheduler(from.removeSuffix("ms"), millisecondsRange, intToShortConverter) ?.toTypedArray() else null }
|
||||
|
||||
internal fun <T> Array<T>.fillWith(
|
||||
whereToPut: MutableList<CronDateTime>,
|
||||
@@ -7,3 +7,4 @@ internal val dayOfMonthRange = 0 .. 30
|
||||
internal val hoursRange = 0 .. 23
|
||||
internal val minutesRange = 0 .. 59
|
||||
internal val secondsRange = minutesRange
|
||||
internal val millisecondsRange = 0 .. 999
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.Month
|
||||
import kotlin.math.min
|
||||
|
||||
fun DateTime.copy(
|
||||
94
src/commonMain/kotlin/utils/SchedulerFlow.kt
Normal file
94
src/commonMain/kotlin/utils/SchedulerFlow.kt
Normal file
@@ -0,0 +1,94 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.DateTimeTz
|
||||
import korlibs.time.milliseconds
|
||||
import dev.inmo.krontab.KronScheduler
|
||||
import dev.inmo.krontab.next
|
||||
import korlibs.time.millisecondsLong
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
|
||||
/**
|
||||
* **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
|
||||
*
|
||||
* Will emit all the [KronScheduler.next] as soon as possible. In case [KronScheduler.next] return null, flow will
|
||||
* be completed
|
||||
*
|
||||
* @param since Will be used as the first parameter for [KronScheduler.next] fun. If passed null, `flow`
|
||||
* will always start since the moment of collecting start
|
||||
*/
|
||||
fun KronScheduler.asTzFlowWithoutDelays(since: DateTimeTz? = null): Flow<DateTimeTz> = flow {
|
||||
var previous = since ?: DateTime.nowLocal()
|
||||
while (currentCoroutineContext().isActive) {
|
||||
val next = next(previous) ?: break
|
||||
emit(next)
|
||||
previous = next + 1.milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
|
||||
*
|
||||
* This [Flow] will use [asTzFlowWithoutDelays], but stop on each time until this time will happen
|
||||
*/
|
||||
fun KronScheduler.asTzFlowWithDelays(): Flow<DateTimeTz> = asTzFlowWithoutDelays().onEach { futureHappenTime ->
|
||||
val now = DateTime.nowLocal()
|
||||
|
||||
delay((futureHappenTime - now).millisecondsLong)
|
||||
}
|
||||
|
||||
/**
|
||||
* **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
|
||||
*
|
||||
* This [Flow] will use [asTzFlowWithoutDelays], but stop on each time until this time will happen
|
||||
*/
|
||||
@Deprecated(
|
||||
"Behaviour will be changed. In some of near versions this flow will not delay executions",
|
||||
ReplaceWith("this.asTzFlowWithDelays()", "dev.inmo.krontab.utils.asTzFlowWithDelays")
|
||||
)
|
||||
fun KronScheduler.asTzFlow(): Flow<DateTimeTz> = asTzFlowWithDelays()
|
||||
|
||||
/**
|
||||
* **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
|
||||
*
|
||||
* Will emit all the [KronScheduler.next] as soon as possible. In case [KronScheduler.next] return null, flow will
|
||||
* be completed
|
||||
*
|
||||
* @param since Will be used as the first parameter for [KronScheduler.next] fun. If passed null, `flow`
|
||||
* will always start since the moment of collecting start
|
||||
*/
|
||||
fun KronScheduler.asFlowWithoutDelays(since: DateTime? = null): Flow<DateTime> = flow {
|
||||
var previous = since ?: DateTime.now()
|
||||
while (currentCoroutineContext().isActive) {
|
||||
val next = next(previous) ?: break
|
||||
emit(next)
|
||||
previous = next + 1.milliseconds
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
|
||||
*
|
||||
* This [Flow] will use [asFlowWithoutDelays], but stop on each time until this time will happen
|
||||
*/
|
||||
fun KronScheduler.asFlowWithDelays(): Flow<DateTime> = asFlowWithoutDelays().onEach { futureHappenTime ->
|
||||
val now = DateTime.now()
|
||||
|
||||
delay((futureHappenTime - now).millisecondsLong)
|
||||
}
|
||||
|
||||
/**
|
||||
* **This flow is [cold](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/)**
|
||||
*
|
||||
* This [Flow] will use [asFlowWithDelays], but stop on each time until this time will happen
|
||||
*/
|
||||
@Deprecated(
|
||||
"Behaviour will be changed. In some of near versions this flow will not delay executions",
|
||||
ReplaceWith("this.asFlowWithDelays()", "dev.inmo.krontab.utils.asFlowWithDelays")
|
||||
)
|
||||
fun KronScheduler.asFlow(): Flow<DateTime> = asFlowWithDelays()
|
||||
34
src/commonMain/kotlin/utils/flows/DateTimeFilters.kt
Normal file
34
src/commonMain/kotlin/utils/flows/DateTimeFilters.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package dev.inmo.krontab.utils.flows
|
||||
|
||||
import korlibs.time.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterSeconds(vararg seconds: Int) = filter { it.seconds in seconds }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterMinutes(vararg minutes: Int) = filter { it.minutes in minutes }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterHours(vararg hours: Int) = filter { it.hours in hours }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterDaysOfMonths(vararg daysOfMonths: Int) = filter { it.dayOfMonth in daysOfMonths }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterMonths(vararg months: Month) = filter { it.month in months }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterMonths0(vararg months: Int) = filter { it.month0 in months }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterMonths1(vararg months: Int) = filter { it.month1 in months }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterYears(year: Year) = filter { it.year == year }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterYears(vararg years: Int) = filter { it.yearInt in years }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterWeekDays(vararg weekDays: DayOfWeek) = filter { it.dayOfWeek in weekDays }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.filterWeekDays(vararg weekDays: Int) = filter { it.dayOfWeekInt in weekDays }
|
||||
34
src/commonMain/kotlin/utils/flows/DateTimeTzFilters.kt
Normal file
34
src/commonMain/kotlin/utils/flows/DateTimeTzFilters.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package dev.inmo.krontab.utils.flows
|
||||
|
||||
import korlibs.time.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterSeconds(vararg seconds: Int) = filter { it.seconds in seconds }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterMinutes(vararg minutes: Int) = filter { it.minutes in minutes }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterHours(vararg hours: Int) = filter { it.hours in hours }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterDaysOfMonths(vararg daysOfMonths: Int) = filter { it.dayOfMonth in daysOfMonths }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterMonths(vararg months: Month) = filter { it.month in months }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterMonths0(vararg months: Int) = filter { it.month0 in months }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterMonths1(vararg months: Int) = filter { it.month1 in months }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterYears(year: Year) = filter { it.year == year }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterYears(vararg years: Int) = filter { it.yearInt in years }
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterWeekDays(vararg weekDays: DayOfWeek) = filter { it.dayOfWeek in weekDays }
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.filterWeekDays(vararg weekDays: Int) = filter { it.dayOfWeekInt in weekDays }
|
||||
25
src/commonMain/kotlin/utils/flows/DateTimeTzWhen.kt
Normal file
25
src/commonMain/kotlin/utils/flows/DateTimeTzWhen.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package dev.inmo.krontab.utils.flows
|
||||
|
||||
import korlibs.time.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfMinutes() = filterSeconds(0)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfHours() = filterMinutes(0).onlyStartsOfMinutes()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfDays() = filterHours(0).onlyStartsOfHours()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfMondays() = filterWeekDays(DayOfWeek.Monday).onlyStartsOfDays()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfSundays() = filterWeekDays(DayOfWeek.Sunday).onlyStartsOfDays()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfMonths() = filterDaysOfMonths(1).onlyStartsOfDays()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTime>.onlyStartsOfYears() = filterMonths(Month.January).onlyStartsOfMonths()
|
||||
25
src/commonMain/kotlin/utils/flows/DateTimeWhen.kt
Normal file
25
src/commonMain/kotlin/utils/flows/DateTimeWhen.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package dev.inmo.krontab.utils.flows
|
||||
|
||||
import korlibs.time.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfMinutes() = filterSeconds(0)
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfHours() = filterMinutes(0).onlyStartsOfMinutes()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfDays() = filterHours(0).onlyStartsOfHours()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfMondays() = filterWeekDays(DayOfWeek.Monday).onlyStartsOfDays()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfSundays() = filterWeekDays(DayOfWeek.Sunday).onlyStartsOfDays()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfMonths() = filterDaysOfMonths(1).onlyStartsOfDays()
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Flow<DateTimeTz>.onlyStartsOfYears() = filterMonths(Month.January).onlyStartsOfMonths()
|
||||
38
src/commonTest/kotlin/BuildersTest.kt
Normal file
38
src/commonTest/kotlin/BuildersTest.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import dev.inmo.krontab.*
|
||||
import korlibs.time.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class BuildersTest {
|
||||
@Test
|
||||
fun presetsWorksCorrectly() {
|
||||
val data = mapOf(
|
||||
EverySecondScheduler to { it: DateTime -> if (it.milliseconds > 0 ) it + 1.seconds - it.milliseconds.milliseconds else it },
|
||||
EveryMinuteScheduler to { it: DateTime -> if (it.seconds > 0 || it.milliseconds > 0 ) it + 1.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
|
||||
EveryHourScheduler to { it: DateTime -> if (it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) it + 1.hours - it.minutes.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
|
||||
EveryDayOfMonthScheduler to { it: DateTime -> if (it.hours > 0 || it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) it + 1.days - it.hours.hours - it.minutes.minutes - it.seconds.seconds - it.milliseconds.milliseconds else it },
|
||||
EveryMonthScheduler to { it: DateTime -> if (it.dayOfMonth > 1 || it.hours > 0 || it.minutes > 0 || it.seconds > 0 || it.milliseconds > 0 ) (it + 1.months).copy(dayOfMonth = 1, hour = 0, minute = 0, second = 0, milliseconds = 0) else it },
|
||||
)
|
||||
val samples = 10000
|
||||
|
||||
runTest {
|
||||
var now = DateTime.now()
|
||||
for (i in 0 until samples) {
|
||||
data.forEach { (scheduler, expectCalculator) ->
|
||||
val expectValue = expectCalculator(now)
|
||||
val newNow = scheduler.nextOrRelative(now)
|
||||
|
||||
assertEquals(expectValue, newNow, "For time ${now.toStringDefault()} calculated wrong value: ${newNow.toStringDefault()} is not equal to ${expectValue.toStringDefault()}")
|
||||
|
||||
now = newNow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/commonTest/kotlin/CheckMonthsAndDaysCorrectWork.kt
Normal file
23
src/commonTest/kotlin/CheckMonthsAndDaysCorrectWork.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import korlibs.time.DateTime
|
||||
import korlibs.time.days
|
||||
import dev.inmo.krontab.buildSchedule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.*
|
||||
|
||||
class CheckMonthsAndDaysCorrectWork {
|
||||
@Test
|
||||
fun checkMonthsAndDaysCorrectWork() {
|
||||
val now = DateTime.now().startOfYear.startOfDay
|
||||
for (i in 0 until now.year.days) {
|
||||
val scheduleDateTime = (now + i.days)
|
||||
runTest {
|
||||
assertEquals(
|
||||
scheduleDateTime,
|
||||
buildSchedule("0 0 0 ${scheduleDateTime.dayOfMonth - 1} ${scheduleDateTime.month0}").next(now)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun CoroutineScope.createFailJob(forTimeMillis: Long) = launch {
|
||||
delay(forTimeMillis)
|
||||
@@ -2,8 +2,8 @@ package dev.inmo.krontab.utils
|
||||
|
||||
import dev.inmo.krontab.builder.buildSchedule
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@@ -18,7 +18,7 @@ class SchedulerFlowTests {
|
||||
}
|
||||
}
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
val flow = kronScheduler.asFlowWithoutDelays()
|
||||
|
||||
runTest {
|
||||
val mustBeCollected = 10
|
||||
@@ -40,14 +40,14 @@ class SchedulerFlowTests {
|
||||
}
|
||||
}
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
val flow = kronScheduler.asFlowWithoutDelays()
|
||||
|
||||
runTest {
|
||||
val testsCount = 10
|
||||
val failJob = it.createFailJob((testsCount * 2) * 1000L)
|
||||
val failJob = createFailJob((testsCount * 2) * 1000L)
|
||||
val mustBeCollected = 10
|
||||
val answers = (0 until testsCount).map { _ ->
|
||||
it.async {
|
||||
async {
|
||||
var collected = 0
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
173
src/commonTest/kotlin/StringParseTest.kt
Normal file
173
src/commonTest/kotlin/StringParseTest.kt
Normal file
@@ -0,0 +1,173 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import korlibs.time.*
|
||||
import dev.inmo.krontab.KronSchedulerTz
|
||||
import dev.inmo.krontab.KrontabTemplate
|
||||
import dev.inmo.krontab.buildSchedule
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.*
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@FlowPreview
|
||||
class StringParseTest {
|
||||
private fun makeSimpleEverySecondTest(template: KrontabTemplate) {
|
||||
val kronScheduler = buildSchedule(template)
|
||||
|
||||
val flow = kronScheduler.asFlowWithoutDelays()
|
||||
|
||||
runTest {
|
||||
val mustBeCollected = 10
|
||||
var collected = 0
|
||||
var previousTime: DateTime? = null
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
previousTime ?.let { previousTime ->
|
||||
assertEquals(previousTime + 1.seconds, it)
|
||||
}
|
||||
previousTime = it
|
||||
collected++
|
||||
}
|
||||
assertEquals(mustBeCollected, collected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
|
||||
makeSimpleEverySecondTest("*/1 * * * *")
|
||||
}
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithWrongAmountOfSpaces() {
|
||||
val templatesFirstReplacers = listOf(
|
||||
"*/1",
|
||||
"*",
|
||||
"/1",
|
||||
"f,/1",
|
||||
)
|
||||
templatesFirstReplacers.forEach { replacer ->
|
||||
makeSimpleEverySecondTest("$replacer * * * * ")
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithGarbageInTemplate() {
|
||||
makeSimpleEverySecondTest(" sdf */1 * * * oo * ")
|
||||
}
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnStringWithInsufficientArgsInTemplate() {
|
||||
makeSimpleEverySecondTest(" sdf */1 ")
|
||||
}
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
|
||||
val kronScheduler = buildSchedule("*/1 * * * * 500ms")
|
||||
|
||||
val flow = kronScheduler.asFlowWithoutDelays()
|
||||
|
||||
runTest {
|
||||
val mustBeCollected = 10
|
||||
var collected = 0
|
||||
var previousTime: DateTime? = null
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
previousTime ?.let { previousTime ->
|
||||
assertEquals(previousTime.copy(milliseconds = 500) + 1.seconds, it)
|
||||
}
|
||||
previousTime = it
|
||||
collected++
|
||||
}
|
||||
assertEquals(mustBeCollected, collected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
|
||||
val kronScheduler = buildSchedule("*/1 * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlowWithoutDelays()
|
||||
|
||||
runTest {
|
||||
val testsCount = 10
|
||||
val failJob = createFailJob((testsCount * 2) * 1000L)
|
||||
val mustBeCollected = 10
|
||||
val answers = (0 until testsCount).map { _ ->
|
||||
async {
|
||||
var collected = 0
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
collected++
|
||||
}
|
||||
collected
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
failJob.cancel()
|
||||
|
||||
answers.forEach {
|
||||
assertEquals(mustBeCollected, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySeveralSecondsRangeBuiltOnString() {
|
||||
val rangesEnds = listOf(0 to 5, 30 to 35)
|
||||
val kronScheduler = buildSchedule("${rangesEnds.joinToString(",") { "${it.first}-${it.second}" }} * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlowWithoutDelays()
|
||||
|
||||
runTest {
|
||||
val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList()
|
||||
val expectedCollects = ranges.size
|
||||
var collected = 0
|
||||
|
||||
flow.takeWhile { ranges.isNotEmpty() }.collect {
|
||||
ranges.remove(it.seconds)
|
||||
collected++
|
||||
assertTrue(
|
||||
collected <= expectedCollects,
|
||||
"Expected value should be less than $expectedCollects, but was $collected. Ranges state: $ranges"
|
||||
)
|
||||
}
|
||||
assertEquals(expectedCollects, collected)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testNextIsCorrectlyWorkEverySeveralMillisecondsRangeBuiltOnString() {
|
||||
val rangesEnds = listOf(0, 200, 500, 750)
|
||||
val kronScheduler = buildSchedule("* * * * * ${rangesEnds.joinToString(",") { "$it" }}ms")
|
||||
|
||||
runTest {
|
||||
val ranges = rangesEnds.toMutableList()
|
||||
val expectedCollects = ranges.size
|
||||
var collected = 0
|
||||
|
||||
var currentTime = DateTime.now()
|
||||
while (ranges.isNotEmpty()) {
|
||||
val nextTrigger = kronScheduler.next(currentTime) ?: error("Strangely unable to get next time")
|
||||
|
||||
ranges.remove(nextTrigger.milliseconds)
|
||||
collected++
|
||||
|
||||
currentTime = nextTrigger + 1.milliseconds
|
||||
}
|
||||
assertEquals(expectedCollects, collected)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testThatTimezoneCorrectlyDeserialized() {
|
||||
val now = DateTime.now().copy(milliseconds = 0).local
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import korlibs.time.*
|
||||
import dev.inmo.krontab.builder.buildSchedule
|
||||
import dev.inmo.krontab.next
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import korlibs.time.*
|
||||
import dev.inmo.krontab.builder.buildSchedule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlin.math.ceil
|
||||
import kotlin.test.*
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
/**
|
||||
* Workaround to use suspending functions in unit tests
|
||||
*/
|
||||
expect fun runTest(block: suspend (scope : CoroutineScope) -> Unit)
|
||||
@@ -1,96 +0,0 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import com.soywiz.klock.*
|
||||
import dev.inmo.krontab.KronSchedulerTz
|
||||
import dev.inmo.krontab.buildSchedule
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@FlowPreview
|
||||
class StringParseTest {
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
|
||||
val kronScheduler = buildSchedule("*/1 * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
|
||||
runTest {
|
||||
val mustBeCollected = 10
|
||||
var collected = 0
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
collected++
|
||||
}
|
||||
assertEquals(mustBeCollected, collected)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
|
||||
val kronScheduler = buildSchedule("*/1 * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
|
||||
runTest {
|
||||
val testsCount = 10
|
||||
val failJob = it.createFailJob((testsCount * 2) * 1000L)
|
||||
val mustBeCollected = 10
|
||||
val answers = (0 until testsCount).map { _ ->
|
||||
it.async {
|
||||
var collected = 0
|
||||
flow.takeWhile {
|
||||
collected < mustBeCollected
|
||||
}.collect {
|
||||
collected++
|
||||
}
|
||||
collected
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
failJob.cancel()
|
||||
|
||||
answers.forEach {
|
||||
assertEquals(mustBeCollected, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testThatFlowIsCorrectlyWorkEverySeveralSecondsRangeBuiltOnString() {
|
||||
val rangesEnds = listOf(0 to 5, 30 to 35)
|
||||
val kronScheduler = buildSchedule("${rangesEnds.joinToString(",") { "${it.first}-${it.second}" }} * * * *")
|
||||
|
||||
val flow = kronScheduler.asFlow()
|
||||
|
||||
runTest {
|
||||
val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList()
|
||||
val expectedCollects = ranges.size
|
||||
var collected = 0
|
||||
|
||||
flow.takeWhile { ranges.isNotEmpty() }.collect {
|
||||
ranges.remove(it.seconds)
|
||||
collected++
|
||||
}
|
||||
assertEquals(expectedCollects, collected)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
fun testThatTimezoneCorrectlyDeserialized() {
|
||||
val now = DateTime.now().copy(milliseconds = 0).local
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
actual fun runTest(block: suspend (scope : CoroutineScope) -> Unit): dynamic = GlobalScope.promise { block(this) }
|
||||
@@ -1,9 +0,0 @@
|
||||
package dev.inmo.krontab.utils
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Workaround to use suspending functions in unit tests
|
||||
*/
|
||||
actual fun runTest(block: suspend (scope: CoroutineScope) -> Unit) = runBlocking(block = block)
|
||||
@@ -1 +0,0 @@
|
||||
<manifest package="dev.inmo.krontab"/>
|
||||
Reference in New Issue
Block a user