mirror of
https://github.com/InsanusMokrassar/krontab.git
synced 2025-12-16 01:45:48 +00:00
Compare commits
292 Commits
0.3.1
...
2efd7e6157
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 6aa40ad463 | |||
| 3062f839af | |||
| 001756ab9f | |||
| b7ebd0ac1c | |||
| ece767e4d0 | |||
| 222542b38f | |||
| 5798774b2d | |||
| 32bf5c0641 | |||
| 5916f3eebb | |||
| df01a66b5a | |||
| a622168297 | |||
| 9202eabf90 | |||
| bc612604eb | |||
| 8808c00dc2 | |||
| f3db5fd2e4 | |||
| b66284b6e5 | |||
| 2a16f3c588 | |||
| 6d055739fe | |||
| 92ae81d7a7 | |||
| 9e9c09e364 | |||
| a8be548a72 | |||
| 6ec77b4e6f | |||
| 0ecb34709b | |||
| 0f973adb9f | |||
| abc7a63d2e | |||
| fbab0e232f | |||
| 57fa3a30a6 | |||
| f50564abe2 | |||
| 8276ebc7ec | |||
| 5a6084c573 | |||
| 437206e915 | |||
| 0ecc6f0028 | |||
| fec17f949f | |||
| c3ed1d024c | |||
| b4790b892a | |||
| 28f84d4e3a | |||
| 22ef00fe8f | |||
| c509d35b00 | |||
| 3de66a7274 | |||
| 1f0387363d | |||
| e816ad88bc | |||
| 404ea5e188 | |||
| 63e110dbc8 | |||
| 39323f3cd1 | |||
| 15e3430a55 | |||
| fde5d91375 | |||
| ba548969e1 | |||
| c6bfc5c8f8 | |||
| 301afb1ee0 | |||
| 7e58ec0873 | |||
| db1af7818e | |||
| 635a2d6a44 | |||
| da88d18d4d | |||
| 20f12f3f2d | |||
| 86cdda51a1 | |||
| bfa546ad1f | |||
| 0c7ceb03ee | |||
| b0889d0677 | |||
| 3408d58cb7 | |||
| 5c81eede9c | |||
| 5844859369 | |||
| a07027399c | |||
| 93246cada7 | |||
| 5311e651f4 | |||
| 68c49faa1d | |||
| 8ce351ecfe | |||
| b43bbea341 | |||
| fff010b890 | |||
| 3a0870144b | |||
| 40adf80d6f | |||
| 0d1e382182 | |||
| a15b4162fb | |||
| d52435a014 | |||
| 9ce69efad5 | |||
| 092fc6bc9b | |||
| ec85fd77f9 | |||
| 496d4cf100 | |||
| dce0fd87a1 | |||
| fd842b17fa | |||
| 726824fb4f | |||
| e04d14ccb1 | |||
| 3f132da62d | |||
| 3cce44664f | |||
| 0376eb7910 | |||
| bbb4f41723 | |||
| 365adb1016 | |||
| 476239faa6 | |||
| 0a2b5ef6a0 | |||
| aedb49f6f8 | |||
| e8071fa15c | |||
| 1cc8ff5519 | |||
| aebd32cb5e | |||
| d11f106f19 | |||
| 36f316357f | |||
| 5fcbe50e39 | |||
| 86552d427c | |||
| 92d902c027 | |||
| 9d8cb20d10 | |||
| ca248a25ad | |||
| 4995a34c1a | |||
| 3162780447 | |||
| 05af4d1f67 | |||
| 3c0818cabf | |||
| a38c233bbf | |||
| 346672b32b | |||
| 7458e4880d | |||
| 46f227f9a3 | |||
| 35f5531d26 | |||
| 1e7acb2f4b | |||
| 50e6868fba | |||
| e0ece2de33 | |||
| 104e9b1c87 | |||
| c1d912f170 | |||
| f4ace9ac37 | |||
| 6374911902 | |||
| 7b370ef3e9 | |||
| 07f2d7b9cd | |||
| 95244dc18f | |||
| 2a8267f0c9 | |||
| 753dcae747 | |||
| 1431c0cda2 | |||
| 66e75b4315 | |||
| 5a13437c17 | |||
| 6d612ce95d | |||
| 92f1ec03dd | |||
| f23740a6a5 | |||
| 16d8850ca7 | |||
| f278361470 |
21
.github/workflows/kdocs.yml
vendored
Normal file
21
.github/workflows/kdocs.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Publish KDocs
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
publishing:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew dokkaHtml
|
||||||
|
- name: Publish KDocs
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./build/dokka/html
|
||||||
|
publish_branch: kdocs
|
||||||
27
.github/workflows/publishing_packages.yml
vendored
Normal file
27
.github/workflows/publishing_packages.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Publish package to GitHub Packages
|
||||||
|
on: [push]
|
||||||
|
jobs:
|
||||||
|
publishing:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
- name: Update version
|
||||||
|
run: |
|
||||||
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
sed -i -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" gradle.properties
|
||||||
|
- name: prebuild
|
||||||
|
run: ./gradlew clean build
|
||||||
|
- name: Publish to Gitea
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
- name: Publish package
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./gradlew --no-parallel publishAllPublicationsToGithubPackagesRepository
|
||||||
|
env:
|
||||||
|
GITHUBPACKAGES_USER: ${{ github.actor }}
|
||||||
|
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,4 +9,6 @@ settings.xml
|
|||||||
build/
|
build/
|
||||||
out/
|
out/
|
||||||
|
|
||||||
|
kotlin-js-store/
|
||||||
|
|
||||||
local.properties
|
local.properties
|
||||||
|
|||||||
10
.travis.yml
10
.travis.yml
@@ -1,10 +0,0 @@
|
|||||||
language: java
|
|
||||||
install: true
|
|
||||||
|
|
||||||
os: linux
|
|
||||||
dist: trusty
|
|
||||||
jdk: oraclejdk8
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ./gradlew build allTests -s
|
|
||||||
|
|
||||||
363
CHANGELOG.md
363
CHANGELOG.md
@@ -1,5 +1,342 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
* `Kotlin`: `1.5.31`
|
||||||
|
* `Coroutines`: `1.5.2`
|
||||||
|
* `Klock`: `2.4.2`
|
||||||
|
* `Androidx Work`: `2.6.0`
|
||||||
|
|
||||||
|
## 0.6.3
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.5.30`
|
||||||
|
* `Klock`: `2.4.0`
|
||||||
|
|
||||||
|
## 0.6.2
|
||||||
|
|
||||||
|
**LEGACY COMPILER VARIANT FOR JS IS NOT AVAILABLE SINCE THIS UPDATE**
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.5.21`
|
||||||
|
* `Coroutines`: `1.5.1`
|
||||||
|
* `Klock`: `2.3.1`
|
||||||
|
|
||||||
|
## 0.6.1
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Klock`: `2.1.0` -> `2.1.2`
|
||||||
|
* Rewriting of default mechanism of `KronScheduler`s
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.4.32` -> `1.5.10`
|
||||||
|
* `Coroutines`: `1.4.3` -> `1.5.0`
|
||||||
|
* `Klock`: `2.0.7` -> `2.1.0`
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.4.31` -> `1.4.32`
|
||||||
|
* Supporting of weekdays
|
||||||
|
* Supporting of timezones
|
||||||
|
* Any `KronScheduler` now can be used for calling `next` with `DateTimeTz`
|
||||||
|
* New type `KronSchedulerTz`
|
||||||
|
* `SchedulerFlow` has been deprecated
|
||||||
|
* New extension `asTzFlow` and small changes in `asFlow` logic
|
||||||
|
* `merge` extensions now return `CollectionKronScheduler` instead of just `KronScheduler`
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.4.21` -> `1.4.31`
|
||||||
|
* `Coroutines`: `1.4.2` -> `1.4.3`
|
||||||
|
* `Klock`: `2.0.3` -> `2.0.7`
|
||||||
|
* `Androidx Work`: `2.4.0` -> `2.5.0`
|
||||||
|
|
||||||
|
## 0.5.0 Years
|
||||||
|
|
||||||
|
**BREAKING CHANGES**
|
||||||
|
|
||||||
|
* `CronDateTimeScheduler` has been marked as `internal` and no longer accessible outside of internal functions
|
||||||
|
* Old methods `merge` and `plus` related to `CronDateTimeScheduler` has been marked as `deprecated` and changed their
|
||||||
|
parameters types - it is `KronScheduler` now
|
||||||
|
* New methods `merge` has been added
|
||||||
|
* **`KronScheduler#next` method now is nullable. Use `nextOrRelative`/`nextOrNow` to get next time certainly**
|
||||||
|
* **Years was added as optional part of krontab template and opportunity in `SchedulerBuilder`**
|
||||||
|
* New builder `YearsBuilder`
|
||||||
|
* `SchedulerFlow#collectSafely` will be normally (without exceptions) finish when `next` of scheduler will return
|
||||||
|
null
|
||||||
|
* `KronScheduler#doOnce` will run code immediately in case when `next` is returning null value
|
||||||
|
* `KrontabTemplateWrapper` has been added
|
||||||
|
* New extension `KrontabTemplate#toKronScheduler` (works as `toSchedule`)
|
||||||
|
* **Fixed issue related to the fact that `toNearDateTime` of `CronDateTime` incorrectly handled months**
|
||||||
|
* **Android target has been added**
|
||||||
|
|
||||||
|
## 0.4.2
|
||||||
|
|
||||||
|
* Versions
|
||||||
|
* `Kotlin`: `1.4.20` -> `1.4.21`
|
||||||
|
* `Klock`: `2.0.1` -> `2.0.3`
|
||||||
|
* `CronDateTimeScheduler` now is deprecated and will be set up as `internal` in future
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
* `Coroutines`: `1.4.1` -> `1.4.2`
|
||||||
|
* `Klock`: `2.0.0` -> `2.0.1`
|
||||||
|
* `CronDateTimeScheduler` now is public
|
||||||
|
* New functions for `CronDateTimeScheduler`
|
||||||
|
* Add `CollectionKronScheduler`. It will give opportunity to unite several schedulers in one
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
**BREAKING CHANGES**
|
||||||
|
Package of project has been changed. Migration:
|
||||||
|
|
||||||
|
* Replace in your dependencies `com.insanusmokrassar:krontab` by `dev.inmo:krontab`
|
||||||
|
* Replace in your project all imports `com.insanusmokrassar.krontab` by `dev.inmo.krontab`
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
* `Kotlin`: `1.4.10` -> `1.4.20`
|
||||||
|
* `Klock`: `1.12.1` -> `2.0.0`
|
||||||
|
|
||||||
|
## 0.3.3
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
* `Coroutines`: `1.3.9` -> `1.4.1`
|
||||||
|
|
||||||
|
## 0.3.2
|
||||||
|
|
||||||
|
* Function `TimeBuilder#each` was added (works as `at`)
|
||||||
|
* Add opportunity to use `first` shortcuts:
|
||||||
|
* Value property `TimeBuilder#first` for including via functions like `TimeBuilder#at`
|
||||||
|
* Shortcut for kron string format `f` or `F`
|
||||||
|
* Add opportunity to use `last` shortcuts:
|
||||||
|
* Value property `TimeBuilder#last` for including via functions like `TimeBuilder#at`
|
||||||
|
* Shortcut for kron string format `l` or `L`
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
* Versions:
|
||||||
|
* `Kotlin`: `1.4.0` -> `1.4.10`
|
||||||
|
* `Klock`: `1.12.0` -> `1.12.1`
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
* Versions:
|
* Versions:
|
||||||
@@ -9,19 +346,6 @@
|
|||||||
* Typealias `KrontabTemplate` was added
|
* Typealias `KrontabTemplate` was added
|
||||||
* Extension `KrontabTemplate#toSchedule` was added
|
* Extension `KrontabTemplate#toSchedule` was added
|
||||||
|
|
||||||
### 0.3.1
|
|
||||||
|
|
||||||
* Versions:
|
|
||||||
* `Kotlin`: `1.4.0` -> `1.4.10`
|
|
||||||
* `Klock`: `1.12.0` -> `1.12.1`
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
* Updated way of publishing (for more info look at the [git](https://git.insanusmokrassar.com/InsanusMokrassar/krontab))
|
|
||||||
* Updates in libraries:
|
|
||||||
* Coroutines `1.3.2` -> `1.3.3`
|
|
||||||
* Klock `1.7.3` -> `1.8.6`
|
|
||||||
|
|
||||||
### 0.2.4
|
### 0.2.4
|
||||||
|
|
||||||
* Updates in libraries:
|
* Updates in libraries:
|
||||||
@@ -29,7 +353,7 @@
|
|||||||
* Coroutines `1.3.7` -> `1.3.8`
|
* Coroutines `1.3.7` -> `1.3.8`
|
||||||
* Ranges support were included. Now it is possible to correctly use syntax `0-5` in strings schedules
|
* Ranges support were included. Now it is possible to correctly use syntax `0-5` in strings schedules
|
||||||
|
|
||||||
### 0.2.3
|
## 0.2.3
|
||||||
|
|
||||||
* Updates in libraries:
|
* Updates in libraries:
|
||||||
* Kotlin `1.3.70` -> `1.3.72`
|
* Kotlin `1.3.70` -> `1.3.72`
|
||||||
@@ -41,13 +365,20 @@
|
|||||||
* `KronScheduler#doWhile` was rewritten to use `KronScheduler#doOnce` for calculations of `block` result
|
* `KronScheduler#doWhile` was rewritten to use `KronScheduler#doOnce` for calculations of `block` result
|
||||||
* New `buildSchedule(String)` function as a shortcut for `createSimpleScheduler(String)`
|
* New `buildSchedule(String)` function as a shortcut for `createSimpleScheduler(String)`
|
||||||
|
|
||||||
### 0.2.2
|
## 0.2.2
|
||||||
|
|
||||||
* Updates in libraries:
|
* Updates in libraries:
|
||||||
* Kotlin `1.3.61` -> `1.3.70`
|
* Kotlin `1.3.61` -> `1.3.70`
|
||||||
* Coroutines `1.3.3` -> `1.3.5`
|
* Coroutines `1.3.3` -> `1.3.5`
|
||||||
* Klock `1.8.6` -> `1.10.0`
|
* Klock `1.8.6` -> `1.10.0`
|
||||||
|
|
||||||
### 0.2.1
|
## 0.2.1
|
||||||
|
|
||||||
* Added support of flows: now any `KronScheduler` can be convert to `Flow<DateTime>` using `asFlow` extension
|
* Added support of flows: now any `KronScheduler` can be convert to `Flow<DateTime>` using `asFlow` extension
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
* Updated way of publishing (for more info look at the [git](https://git.insanusmokrassar.com/InsanusMokrassar/krontab))
|
||||||
|
* Updates in libraries:
|
||||||
|
* Coroutines `1.3.2` -> `1.3.3`
|
||||||
|
* Klock `1.7.3` -> `1.8.6`
|
||||||
|
|||||||
111
README.md
111
README.md
@@ -1,20 +1,20 @@
|
|||||||
# krontab
|
# krontab
|
||||||
|
|
||||||
[  ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab/_latestVersion)
|
[](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
|
||||||
[](https://maven-badges.herokuapp.com/maven-central/com.insanusmokrassar/krontab)
|
|
||||||
[](https://travis-ci.com/InsanusMokrassar/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
|
Library was created to give oppotunity to launch some things from time to time according to some schedule in
|
||||||
runtime of applications.
|
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
|
## How to use
|
||||||
|
|
||||||
There are several ways to configure and use this library:
|
There are several ways to configure and use this library:
|
||||||
@@ -27,7 +27,7 @@ Anyway, to start some action from time to time you will need to use one of exten
|
|||||||
```kotlin
|
```kotlin
|
||||||
val kronScheduler = /* creating of KronScheduler instance */;
|
val kronScheduler = /* creating of KronScheduler instance */;
|
||||||
|
|
||||||
kronScheuler.doWhile {
|
kronScheduler.doWhile {
|
||||||
// some action
|
// some action
|
||||||
true // true - repeat on next time
|
true // true - repeat on next time
|
||||||
}
|
}
|
||||||
@@ -39,12 +39,12 @@ If you want to include `krontab` in your project, just add next line to your
|
|||||||
dependencies part:
|
dependencies part:
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
implementation "com.insanusmokrassar:krontab:$krontab_version"
|
implementation "dev.inmo:krontab:$krontab_version"
|
||||||
```
|
```
|
||||||
|
|
||||||
Next version is the latest currently for the library:
|
Next version is the latest currently for the library:
|
||||||
|
|
||||||
[  ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab/_latestVersion)
|
[](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
|
||||||
|
|
||||||
For old version of Gradle, instead of `implementation` word developers must use `compile`.
|
For old version of Gradle, instead of `implementation` word developers must use `compile`.
|
||||||
|
|
||||||
@@ -52,19 +52,22 @@ For old version of Gradle, instead of `implementation` word developers must use
|
|||||||
|
|
||||||
Developers can use more simple way to configure repeat times is string. String configuring
|
Developers can use more simple way to configure repeat times is string. String configuring
|
||||||
like a `crontab`, but with a little bit different meanings:
|
like a `crontab`, but with a little bit different meanings:
|
||||||
|
|
||||||
```
|
```
|
||||||
/-------- Seconds
|
/--------------- Seconds
|
||||||
| /------ Minutes
|
| /------------- Minutes
|
||||||
| | /---- Hours
|
| | /----------- Hours
|
||||||
| | | /-- Days of months
|
| | | /--------- Days of months
|
||||||
| | | | / Months
|
| | | | /------- Months
|
||||||
| | | | |
|
| | | | | /----- (optional) Year
|
||||||
* * * * *
|
| | | | | | /--- (optional) Timezone offset
|
||||||
|
| | | | | | | / (optional) Week days
|
||||||
|
* * * * * * 0o *w
|
||||||
```
|
```
|
||||||
|
|
||||||
It is different with original `crontab` syntax for the reason, that expected that in practice developers
|
It is different with original `crontab` syntax for the reason, that expected that in practice developers
|
||||||
will use seconds and minutes with more probability than months (for example). In fact, developers will use something
|
will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use
|
||||||
like:
|
something like:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
doWhile("/5 * * * *") {
|
doWhile("/5 * * * *") {
|
||||||
@@ -73,7 +76,7 @@ doWhile("/5 * * * *") {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Or more version:
|
An other version:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
doInfinity("/5 * * * *") {
|
doInfinity("/5 * * * *") {
|
||||||
@@ -85,7 +88,7 @@ Both of examples will print `Called` message every five seconds.
|
|||||||
|
|
||||||
### Config via builder
|
### Config via builder
|
||||||
|
|
||||||
Also this library currently supports DSL for creating the same goals:
|
Also, this library currently supports DSL for creating the same goals:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val kronScheduler = buildSchedule {
|
val kronScheduler = buildSchedule {
|
||||||
@@ -128,6 +131,38 @@ kronScheduler.doInfinity {
|
|||||||
|
|
||||||
All of these examples will do the same things: print `Called` message every five seconds.
|
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
|
### KronScheduler as a Flow
|
||||||
|
|
||||||
Any `KronScheduler`can e converted to a `Flow<DateTime` using extension `asFlow`:
|
Any `KronScheduler`can e converted to a `Flow<DateTime` using extension `asFlow`:
|
||||||
@@ -152,3 +187,27 @@ flow.takeWhile {
|
|||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Offsets
|
||||||
|
|
||||||
|
Offsets in this library works via passing parameter ending with `o` in any place after `month` config. Currently
|
||||||
|
there is only one format supported for offsets: minutes of offsets. To use time zones you will need to call `next`
|
||||||
|
method with `DateTimeTz` argument or `nextTimeZoned` method with any `KronScheduler` instance, but in case if this
|
||||||
|
scheduler is not instance of `KronSchedulerTz` it will 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:
|
||||||
|
|
||||||
|
* Using `createSimpleScheduler`/`buildSchedule`/`KrontabTemplate#toSchedule`/`KrontabTemplate#toKronScheduler` methods
|
||||||
|
with passing `defaultOffset` parameter
|
||||||
|
* Using `SchedulerBuilder#build`/`createSimpleScheduler`/`buildSchedule`/`KrontabTemplate#toSchedule`/`KrontabTemplate#toKronScheduler`
|
||||||
|
methods with casting to `KronSchedulerTz` in case you are pretty sure that it is timezoned `KronScheduler`
|
||||||
|
* Creating your own implementation of `KronSchedulerTz`
|
||||||
|
|
||||||
|
### Note about week days
|
||||||
|
|
||||||
|
Unlike original CRON, here week days:
|
||||||
|
|
||||||
|
* Works as `AND`: cron date time will search first day which will pass requirement according all parameters including
|
||||||
|
week days
|
||||||
|
* You may use any related to numbers syntax with week days: `0-3w`, `0,1,2,3w`, etc.
|
||||||
|
* Week days (like years and offsets) are optional and can be placed anywhere after `month`
|
||||||
|
|||||||
100
build.gradle
100
build.gradle
@@ -1,43 +1,67 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
// jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
classpath "com.github.breadmoirai:github-release:$github_release_plugin_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:$android_gradle_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
|
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
|
||||||
id "org.jetbrains.dokka" version "$dokka_version"
|
id "org.jetbrains.dokka" version "$dokka_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
project.version = "0.3.1"
|
// temporal crutch until legacy tests will be stabled or legacy target will be removed
|
||||||
project.group = "com.insanusmokrassar"
|
allprojects {
|
||||||
|
if (it != rootProject.findProject("docs")) {
|
||||||
|
tasks.whenTaskAdded { task ->
|
||||||
|
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
|
||||||
|
task.enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: "com.android.library"
|
||||||
|
|
||||||
|
project.version = "$version"
|
||||||
|
project.group = "dev.inmo"
|
||||||
|
|
||||||
apply from: "publish.gradle"
|
apply from: "publish.gradle"
|
||||||
apply from: "github_release.gradle"
|
apply from: "github_release.gradle"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
// jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://kotlin.bintray.com/kotlinx" }
|
// maven { url "https://kotlin.bintray.com/kotlinx" }
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: './dokka.gradle'
|
apply from: './dokka.gradle'
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
js(BOTH) {
|
js(IR) {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
|
android {
|
||||||
|
publishAllLibraryVariants()
|
||||||
|
}
|
||||||
|
linuxX64()
|
||||||
|
mingwX64()
|
||||||
|
linuxArm64()
|
||||||
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -45,18 +69,23 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('stdlib')
|
implementation kotlin('stdlib')
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
|
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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidMain {
|
||||||
|
dependencies {
|
||||||
|
api "androidx.work:work-runtime-ktx:$androidx_work_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
commonTest {
|
commonTest {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-common')
|
implementation kotlin('test-common')
|
||||||
implementation kotlin('test-annotations-common')
|
implementation kotlin('test-annotations-common')
|
||||||
}
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
|
||||||
}
|
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmTest {
|
jvmTest {
|
||||||
@@ -64,10 +93,55 @@ kotlin {
|
|||||||
implementation kotlin('test-junit')
|
implementation kotlin('test-junit')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsMain {
|
jsTest {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation kotlin('test-js')
|
implementation kotlin('test-js')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
androidUnitTest {
|
||||||
|
dependencies {
|
||||||
|
implementation kotlin('test-junit')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk "$android_compileSdkVersion".toInteger()
|
||||||
|
buildToolsVersion = "$android_buildToolsVersion"
|
||||||
|
namespace "${group}.${project.name}"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion "$android_minSdkVersion".toInteger()
|
||||||
|
targetSdkVersion "$android_compileSdkVersion".toInteger()
|
||||||
|
versionCode "${android_code_version}".toInteger()
|
||||||
|
versionName "$version"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
test {
|
||||||
|
java.srcDir file("src/jvmTest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|||||||
68
dokka.gradle
68
dokka.gradle
@@ -1,42 +1,54 @@
|
|||||||
dokka {
|
dokkaHtml {
|
||||||
outputFormat = 'html'
|
String dokkaOutputDirectoryPath = null
|
||||||
|
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case project.hasProperty("DOKKA_PATH"):
|
case project.hasProperty("DOKKA_PATH"):
|
||||||
outputDirectory = project.property("DOKKA_PATH").toString()
|
dokkaOutputDirectoryPath = project.property("DOKKA_PATH").toString()
|
||||||
break
|
break
|
||||||
case System.getenv("DOKKA_PATH") != null:
|
case System.getenv("DOKKA_PATH") != null:
|
||||||
outputDirectory = System.getenv("DOKKA_PATH")
|
dokkaOutputDirectoryPath = System.getenv("DOKKA_PATH")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if (dokkaOutputDirectoryPath != null) {
|
||||||
|
File outputDirectoryFile = new File(dokkaOutputDirectoryPath)
|
||||||
|
outputDirectoryFile.mkdirs()
|
||||||
|
outputDirectory = outputDirectoryFile
|
||||||
|
}
|
||||||
|
|
||||||
multiplatform {
|
moduleName.set("kdocs")
|
||||||
global {
|
|
||||||
perPackageOption {
|
dokkaSourceSets {
|
||||||
prefix = "com.insanusmokrassar"
|
switch (true) {
|
||||||
skipDeprecated = true
|
case project.hasProperty("DOKKA_PATH"):
|
||||||
includeNonPublic = true
|
outputDirectory = new File(project.property("DOKKA_PATH").toString())
|
||||||
reportUndocumented = true
|
break
|
||||||
|
case System.getenv("DOKKA_PATH") != null:
|
||||||
|
outputDirectory = new File(System.getenv("DOKKA_PATH"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
dokkaSourceSets {
|
||||||
|
configureEach {
|
||||||
|
skipDeprecated.set(true)
|
||||||
|
|
||||||
|
sourceLink {
|
||||||
|
localDirectory.set(file("./"))
|
||||||
|
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/krontab/blob/master/"))
|
||||||
|
remoteLineSuffix.set("#L")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceLink {
|
named("commonMain") {
|
||||||
path = "./"
|
sourceRoots.setFrom(findSourcesWithName("commonMain"))
|
||||||
url = "https://github.com/InsanusMokrassar/krontab/blob/master/"
|
|
||||||
lineSuffix = "#L"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
common {
|
//
|
||||||
targets = ["JVM", "JS"]
|
// named("jsMain") {
|
||||||
sourceRoot { path = "src/commonMain" }
|
// sourceRoot { path = "src/jsMain" }
|
||||||
}
|
// }
|
||||||
js {
|
//
|
||||||
targets = ["JS"]
|
// named("jvmMain") {
|
||||||
sourceRoot { path = "src/jsMain" }
|
// sourceRoot { path = "src/jvmMain" }
|
||||||
}
|
// }
|
||||||
jvm {
|
|
||||||
targets = ["JVM"]
|
|
||||||
sourceRoot { path = "src/jvmMain" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
private String getCurrentVersionChangelog() {
|
private String getCurrentVersionChangelog(String version) {
|
||||||
OutputStream changelogDataOS = new ByteArrayOutputStream()
|
OutputStream changelogDataOS = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
standardOutput = changelogDataOS
|
|
||||||
commandLine 'chmod', "+x", './changelog_parser.sh'
|
commandLine 'chmod', "+x", './changelog_parser.sh'
|
||||||
commandLine './changelog_parser.sh', "$library_version", 'CHANGELOG.md'
|
}
|
||||||
|
exec {
|
||||||
|
standardOutput = changelogDataOS
|
||||||
|
commandLine './changelog_parser.sh', "$version", 'CHANGELOG.md'
|
||||||
}
|
}
|
||||||
|
|
||||||
return changelogDataOS.toString().trim()
|
return changelogDataOS.toString().trim()
|
||||||
@@ -16,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
|
|||||||
githubRelease {
|
githubRelease {
|
||||||
token "${project.property('GITHUB_RELEASE_TOKEN')}"
|
token "${project.property('GITHUB_RELEASE_TOKEN')}"
|
||||||
|
|
||||||
owner "InsanusMokrassar"
|
owner = "InsanusMokrassar"
|
||||||
repo "${rootProject.name}"
|
repo = "${rootProject.name}"
|
||||||
|
|
||||||
tagName "${project.version}"
|
tagName = "v${project.version}"
|
||||||
releaseName "${project.version}"
|
releaseName = "${project.version}"
|
||||||
targetCommitish "${project.version}"
|
targetCommitish = "${project.version}"
|
||||||
|
|
||||||
body getCurrentVersionChangelog()
|
body = getCurrentVersionChangelog("${project.version}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,40 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx512m
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin_version=1.4.10
|
org.gradle.parallel=true
|
||||||
kotlin_coroutines_version=1.3.9
|
kotlin.js.generate.externals=true
|
||||||
|
kotlin.incremental=true
|
||||||
dokka_version=0.10.1
|
kotlin.incremental.js=true
|
||||||
|
|
||||||
gradle_bintray_plugin_version=1.8.5
|
|
||||||
|
|
||||||
klockVersion=1.12.1
|
|
||||||
|
|
||||||
github_release_plugin_version=2.2.12
|
|
||||||
|
|
||||||
kotlin.incremental.multiplatform=true
|
kotlin.incremental.multiplatform=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=false
|
||||||
|
|
||||||
|
|
||||||
|
kotlin_version=1.9.23
|
||||||
|
kotlin_coroutines_version=1.8.0
|
||||||
|
kotlin_serialization_version=1.6.3
|
||||||
|
|
||||||
|
dokka_version=1.9.20
|
||||||
|
|
||||||
|
klockVersion=5.4.0
|
||||||
|
|
||||||
|
## Github reease
|
||||||
|
|
||||||
|
github_release_plugin_version=2.5.2
|
||||||
|
|
||||||
|
## Android
|
||||||
|
|
||||||
|
android_gradle_version=8.1.4
|
||||||
|
android_minSdkVersion=21
|
||||||
|
android_compileSdkVersion=34
|
||||||
|
android_buildToolsVersion=34.0.0
|
||||||
|
dexcount_version=4.0.0
|
||||||
|
junit_version=4.12
|
||||||
|
test_ext_junit_version=1.1.3
|
||||||
|
espresso_core=3.4.0
|
||||||
|
|
||||||
|
androidx_work_version=2.9.0
|
||||||
|
|
||||||
|
## Common
|
||||||
|
|
||||||
|
version=2.2.9
|
||||||
|
android_code_version=39
|
||||||
|
|||||||
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
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
apply plugin: 'maven-publish'
|
|
||||||
|
|
||||||
task javadocsJar(type: Jar) {
|
|
||||||
classifier = 'javadoc'
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEvaluate {
|
|
||||||
project.publishing.publications.all {
|
|
||||||
// rename artifacts
|
|
||||||
groupId "${project.group}"
|
|
||||||
if (it.name.contains('kotlinMultiplatform')) {
|
|
||||||
artifactId = "${project.name}"
|
|
||||||
} else {
|
|
||||||
artifactId = "${project.name}-$name"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications.all {
|
|
||||||
artifact javadocsJar
|
|
||||||
|
|
||||||
pom {
|
|
||||||
description = "It is an analog of crontab util for Kotlin Coroutines"
|
|
||||||
name = "Krontab"
|
|
||||||
url = "https://git.insanusmokrassar.com/InsanusMokrassar/krontab"
|
|
||||||
|
|
||||||
scm {
|
|
||||||
developerConnection = "scm:git:[fetch=]https://git.insanusmokrassar.com:8322/InsanusMokrassar/krontab.git[push=]https://git.insanusmokrassar.com:8322/InsanusMokrassar/krontab.git"
|
|
||||||
url = "https://git.insanusmokrassar.com:8322/InsanusMokrassar/krontab.git"
|
|
||||||
}
|
|
||||||
|
|
||||||
developers {
|
|
||||||
|
|
||||||
developer {
|
|
||||||
id = "InsanusMokrassar"
|
|
||||||
name = "Ovsiannikov Aleksei"
|
|
||||||
email = "ovsyannikov.alexey95@gmail.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
licenses {
|
|
||||||
|
|
||||||
license {
|
|
||||||
name = "Apache Software License 2.0"
|
|
||||||
url = "https://git.insanusmokrassar.com/InsanusMokrassar/krontab/src/master/LICENSE"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"bintrayConfig":{"repo":"InsanusMokrassar","packageName":"${project.name}-mpp","packageVcs":"https://git.insanusmokrassar.com/InsanusMokrassar/krontab","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://git.insanusmokrassar.com/InsanusMokrassar/krontab/src/master/LICENSE"}],"mavenConfig":{"name":"Krontab","description":"It is an analog of crontab util for Kotlin Coroutines","url":"https://git.insanusmokrassar.com/InsanusMokrassar/krontab","vcsUrl":"https://git.insanusmokrassar.com:8322/InsanusMokrassar/krontab.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"Multiplatform"}
|
|
||||||
157
publish.gradle
157
publish.gradle
@@ -1,59 +1,118 @@
|
|||||||
apply plugin: 'com.jfrog.bintray'
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
apply from: "maven.publish.gradle"
|
task javadocsJar(type: Jar) {
|
||||||
|
archiveClassifier = 'javadoc'
|
||||||
|
}
|
||||||
|
|
||||||
bintray {
|
publishing {
|
||||||
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
|
publications.all {
|
||||||
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
|
artifact javadocsJar
|
||||||
filesSpec {
|
|
||||||
from "${buildDir}/publications/"
|
pom {
|
||||||
eachFile {
|
description = "It is an analog of crontab util for Kotlin Coroutines"
|
||||||
String directorySubname = it.getFile().parentFile.name
|
name = "Krontab"
|
||||||
if (it.getName() == "module.json") {
|
url = "https://github.com/InsanusMokrassar/krontab"
|
||||||
if (directorySubname == "kotlinMultiplatform") {
|
|
||||||
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
|
scm {
|
||||||
} else {
|
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/krontab.git[push=]https://github.com/InsanusMokrassar/krontab.git"
|
||||||
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
|
url = "https://github.com/InsanusMokrassar/krontab.git"
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
|
developers {
|
||||||
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
|
|
||||||
} else {
|
developer {
|
||||||
it.exclude()
|
id = "InsanusMokrassar"
|
||||||
}
|
name = "Ovsiannikov Aleksei"
|
||||||
|
email = "ovsyannikov.alexey95@gmail.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
|
||||||
|
license {
|
||||||
|
name = "Apache Software License 2.0"
|
||||||
|
url = "https://github.com/InsanusMokrassar/krontab/blob/master/LICENSE"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
into "${project.group}".replace(".", "/")
|
repositories {
|
||||||
}
|
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
|
||||||
|
maven {
|
||||||
publish = true
|
name = "GithubPackages"
|
||||||
override = true
|
url = uri("https://maven.pkg.github.com/InsanusMokrassar/krontab")
|
||||||
|
|
||||||
pkg {
|
credentials {
|
||||||
repo = "InsanusMokrassar"
|
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
|
||||||
name = "${project.name}-mpp"
|
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
|
||||||
vcsUrl = "https://git.insanusmokrassar.com/InsanusMokrassar/krontab"
|
}
|
||||||
licenses = ["Apache-2.0"]
|
|
||||||
version {
|
}
|
||||||
name = "${project.version}"
|
}
|
||||||
released = new Date()
|
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
|
||||||
vcsTag = "${project.version}"
|
maven {
|
||||||
gpg {
|
name = "InmoNexus"
|
||||||
sign = true
|
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
|
||||||
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bintrayUpload.doFirst {
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
publications = publishing.publications.collect {
|
apply plugin: 'signing'
|
||||||
if (it.name.contains('kotlinMultiplatform')) {
|
|
||||||
null
|
signing {
|
||||||
} else {
|
useGpgCmd()
|
||||||
it.name
|
|
||||||
|
sign publishing.publications
|
||||||
|
}
|
||||||
|
|
||||||
|
task signAll {
|
||||||
|
tasks.withType(Sign).forEach {
|
||||||
|
dependsOn(it)
|
||||||
}
|
}
|
||||||
} - null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bintrayUpload.dependsOn publishToMavenLocal
|
// 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
148
src/androidMain/kotlin/KronSchedulerWork.kt
Normal file
148
src/androidMain/kotlin/KronSchedulerWork.kt
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.*
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.millisecondsLong
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will enqueue [OneTimeWorkRequest] with [workName] and [existingWorkPolicy]. Use [setUpRequest] callback
|
||||||
|
* in case you need some additional actions to do before request will be enqueued
|
||||||
|
*/
|
||||||
|
suspend fun <T : KronSchedulerWork> Context.enqueueKronSchedulerWork(
|
||||||
|
workName: String,
|
||||||
|
delayMillis: Long,
|
||||||
|
workClass: Class<T>,
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||||
|
setUpRequest: suspend OneTimeWorkRequest.Builder.() -> Unit = {}
|
||||||
|
) = WorkManager.getInstance(applicationContext).enqueueUniqueWork(
|
||||||
|
workName,
|
||||||
|
existingWorkPolicy,
|
||||||
|
OneTimeWorkRequest.Builder(workClass).apply {
|
||||||
|
setInitialDelay(delayMillis, TimeUnit.MILLISECONDS)
|
||||||
|
setUpRequest()
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* This method is shortcut for [enqueueKronSchedulerWork] with reified [T] parameter
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified T : KronSchedulerWork> Context.enqueueKronSchedulerWork(
|
||||||
|
workName: String,
|
||||||
|
delayMillis: Long,
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||||
|
noinline setUpRequest: suspend OneTimeWorkRequest.Builder.() -> Unit = {}
|
||||||
|
) = enqueueKronSchedulerWork(workName, delayMillis, T::class.java, existingWorkPolicy, setUpRequest)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is shortcut for [enqueueKronSchedulerWork] with [initialScheduler]. It will try to calculate delay by
|
||||||
|
* itself. In case if [KronScheduler.next] of [initialScheduler] will return null, work WILL NOT be enqueued
|
||||||
|
*
|
||||||
|
* @return null in case if [KronScheduler.next] of [initialScheduler] has returned null and work has not been enqueued
|
||||||
|
*/
|
||||||
|
suspend fun <T : KronSchedulerWork> Context.enqueueKronSchedulerWork(
|
||||||
|
workName: String,
|
||||||
|
initialScheduler: KronScheduler,
|
||||||
|
workClass: Class<T>,
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||||
|
setUpRequest: suspend OneTimeWorkRequest.Builder.() -> Unit = {}
|
||||||
|
): Operation? {
|
||||||
|
val now = DateTime.now()
|
||||||
|
val nextTriggerTime = initialScheduler.next(now)
|
||||||
|
val delayMillis = nextTriggerTime ?.minus(now) ?.millisecondsLong ?: return null
|
||||||
|
|
||||||
|
return enqueueKronSchedulerWork(workName, delayMillis, workClass, existingWorkPolicy, setUpRequest)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This method is shortcut for [enqueueKronSchedulerWork] with reified [T]
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified T : KronSchedulerWork> Context.enqueueKronSchedulerWork(
|
||||||
|
workName: String,
|
||||||
|
initialScheduler: KronScheduler,
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||||
|
noinline setUpRequest: suspend OneTimeWorkRequest.Builder.() -> Unit = {}
|
||||||
|
) = enqueueKronSchedulerWork(workName, initialScheduler, T::class.java, existingWorkPolicy, setUpRequest)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this class as a super class in case you wish to implement krontab-based enqueuing of works
|
||||||
|
*
|
||||||
|
* @see enqueueKronSchedulerWork
|
||||||
|
* @see KrontabTemplateSchedulerWork
|
||||||
|
*/
|
||||||
|
abstract class KronSchedulerWork(
|
||||||
|
context: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : CoroutineWorker(
|
||||||
|
context,
|
||||||
|
workerParams
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* This variable will be used to reorder new work after that one is happen
|
||||||
|
*
|
||||||
|
* @see prolongOnException
|
||||||
|
* @see prolongOnFailure
|
||||||
|
* @see prolongOnSuccess
|
||||||
|
*/
|
||||||
|
protected abstract val workName: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set this to false in case when this work must not be enqueued after successful complete
|
||||||
|
*/
|
||||||
|
protected open val prolongOnSuccess: Boolean = true
|
||||||
|
/**
|
||||||
|
* Set this to false in case when this work must not be enqueued after failure complete
|
||||||
|
*/
|
||||||
|
protected open val prolongOnFailure
|
||||||
|
get() = prolongOnSuccess
|
||||||
|
/**
|
||||||
|
* Set this to false in case when this work must not be enqueued after exception happen
|
||||||
|
*/
|
||||||
|
protected open val prolongOnException = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [KronScheduler] of this method will be used to [prolong] this worker
|
||||||
|
*/
|
||||||
|
protected abstract suspend fun kronScheduler(): KronScheduler?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is replacement of [doWork]. It is required to wrap work with [prolong]ing and handling of complete
|
||||||
|
* state
|
||||||
|
*/
|
||||||
|
protected abstract suspend fun onWork(): Result
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this method in case you have some additional settings for future [OneTimeWorkRequest]
|
||||||
|
*/
|
||||||
|
protected open suspend fun OneTimeWorkRequest.Builder.setUpRequest() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will [enqueueKronSchedulerWork] using [workName], [kronScheduler] and default
|
||||||
|
* [ExistingWorkPolicy.REPLACE]. You can call this method in case you want to enqueue work by yourself, but you must
|
||||||
|
* be sure that you set up to false [prolongOnSuccess], [prolongOnFailure] and [prolongOnException]
|
||||||
|
*/
|
||||||
|
protected suspend fun prolong() {
|
||||||
|
applicationContext.enqueueKronSchedulerWork(
|
||||||
|
workName,
|
||||||
|
kronScheduler() ?: return,
|
||||||
|
this::class.java
|
||||||
|
) {
|
||||||
|
setUpRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val result = try {
|
||||||
|
onWork()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
if (prolongOnException) {
|
||||||
|
prolong()
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
when (result) {
|
||||||
|
is Result.Failure -> if (prolongOnFailure) prolong()
|
||||||
|
is Result.Success -> if (prolongOnSuccess) prolong()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
68
src/androidMain/kotlin/KrontabTemplateSchedulerWork.kt
Normal file
68
src/androidMain/kotlin/KrontabTemplateSchedulerWork.kt
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.*
|
||||||
|
|
||||||
|
const val krontabTemplateWorkField = "krontabTemplate"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will [enqueueKronSchedulerWork] with [KronScheduler] from [krontabTemplate] and call [setUpRequest] on setting up
|
||||||
|
* [OneTimeWorkRequest.Builder] with [Data] which will be used to [OneTimeWorkRequest.Builder.setInputData] after
|
||||||
|
* [setUpRequest] completed
|
||||||
|
*/
|
||||||
|
suspend inline fun <reified T : KrontabTemplateSchedulerWork> Context.enqueueKrontabTemplateSchedulerWork(
|
||||||
|
workName: String,
|
||||||
|
krontabTemplate: KrontabTemplate,
|
||||||
|
existingWorkPolicy: ExistingWorkPolicy = ExistingWorkPolicy.REPLACE,
|
||||||
|
noinline setUpRequest: suspend OneTimeWorkRequest.Builder.(
|
||||||
|
data: Data
|
||||||
|
) -> Unit = {}
|
||||||
|
) = enqueueKronSchedulerWork(workName, krontabTemplate.toKronScheduler(), T::class.java, existingWorkPolicy) {
|
||||||
|
val data = workDataOf(
|
||||||
|
krontabTemplateWorkField to krontabTemplate
|
||||||
|
)
|
||||||
|
setUpRequest(data)
|
||||||
|
setInputData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend this class in case you wish to base on [KrontabTemplate]. It will automatically handle request of
|
||||||
|
* [kronScheduler] and put it in [setUpRequest]
|
||||||
|
*/
|
||||||
|
abstract class KrontabTemplateSchedulerWork(
|
||||||
|
context: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : KronSchedulerWork(context, workerParams) {
|
||||||
|
/**
|
||||||
|
* Will try to get [KrontabTemplate] from [getInputData] by key [krontabTemplateWorkField]
|
||||||
|
*
|
||||||
|
* @see setUpRequest
|
||||||
|
*/
|
||||||
|
protected val krontabTemplate: KrontabTemplate?
|
||||||
|
get() = inputData.getString(krontabTemplateWorkField)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this methods instead of old [setUpRequest] in case you wish to set up some work request parameters
|
||||||
|
*
|
||||||
|
* @param data This parameter will be used to put data inside of [OneTimeWorkRequest.Builder] after this method
|
||||||
|
* will be completed
|
||||||
|
*/
|
||||||
|
protected open suspend fun OneTimeWorkRequest.Builder.setUpRequest(data: Data) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will automatically put [krontabTemplate] into work data, call [setUpRequest] with future [Data] object and then
|
||||||
|
* call [OneTimeWorkRequest.Builder.setInputData] with that [Data] object
|
||||||
|
*/
|
||||||
|
override suspend fun OneTimeWorkRequest.Builder.setUpRequest() {
|
||||||
|
val data = workDataOf(
|
||||||
|
krontabTemplateWorkField to krontabTemplate,
|
||||||
|
)
|
||||||
|
setUpRequest(data)
|
||||||
|
setInputData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return [KronScheduler] in case if [krontabTemplate] was not null
|
||||||
|
*/
|
||||||
|
override suspend fun kronScheduler(): KronScheduler? = krontabTemplate ?.toKronScheduler()
|
||||||
|
}
|
||||||
175
src/commonMain/kotlin/Executes.kt
Normal file
175
src/commonMain/kotlin/Executes.kt
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
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!!! If you want to launch it in parallel, you must do this explicitly.
|
||||||
|
*
|
||||||
|
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediatelly
|
||||||
|
*/
|
||||||
|
suspend inline fun <T> KronScheduler.doOnceTz(noinline block: suspend (DateTimeTz) -> T): T {
|
||||||
|
val time = when (this) {
|
||||||
|
is KronSchedulerTz -> nextOrNowWithOffset()
|
||||||
|
else -> nextOrNow().local
|
||||||
|
}
|
||||||
|
delay((time - DateTimeTz.nowLocal()).millisecondsLong)
|
||||||
|
return block(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
do {
|
||||||
|
delay(1L)
|
||||||
|
} while (doOnce(block))
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
do {
|
||||||
|
delay(1L)
|
||||||
|
} while (doOnceTz(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
28
src/commonMain/kotlin/KronScheduler.kt
Normal file
28
src/commonMain/kotlin/KronScheduler.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface was created for abstraction of [next] operation. Currently, there is only
|
||||||
|
* [dev.inmo.krontab.internal.CronDateTimeScheduler] realisation of this interface inside of this library,
|
||||||
|
* but you it is possible to create your own realisation of this interface for scheduling, for example, depending of
|
||||||
|
* users activity or something like this
|
||||||
|
*
|
||||||
|
* @see dev.inmo.krontab.internal.CronDateTimeScheduler
|
||||||
|
*/
|
||||||
|
interface KronScheduler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Next [DateTime] when some action must be triggered according to settings of this instance
|
||||||
|
*
|
||||||
|
* @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)
|
||||||
|
suspend fun KronScheduler.nextOrNow(): DateTime = DateTime.now().let {
|
||||||
|
next(it) ?: getAnyNext(it)
|
||||||
|
}
|
||||||
34
src/commonMain/kotlin/KronSchedulerTz.kt
Normal file
34
src/commonMain/kotlin/KronSchedulerTz.kt
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.DateTimeTz
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface extending [KronScheduler] to use [DateTimeTz] with taking into account offset of incoming time for
|
||||||
|
* [next] operation.
|
||||||
|
*
|
||||||
|
* @see dev.inmo.krontab.internal.CronDateTimeScheduler
|
||||||
|
* @see dev.inmo.krontab.KronScheduler
|
||||||
|
*/
|
||||||
|
interface KronSchedulerTz : KronScheduler {
|
||||||
|
suspend fun next(relatively: DateTimeTz): DateTimeTz?
|
||||||
|
|
||||||
|
override suspend fun next(relatively: DateTime): DateTime? = next(relatively.local) ?.local
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun KronSchedulerTz.nextOrRelative(relatively: DateTimeTz): DateTimeTz = next(relatively) ?: getAnyNext(
|
||||||
|
relatively.local
|
||||||
|
).toOffsetUnadjusted(relatively.offset)
|
||||||
|
suspend fun KronSchedulerTz.nextOrNowWithOffset(): DateTimeTz = DateTimeTz.nowLocal().let {
|
||||||
|
next(it) ?: getAnyNext(
|
||||||
|
it.local
|
||||||
|
).toOffsetUnadjusted(it.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun KronScheduler.next(relatively: DateTimeTz) = if (this is KronSchedulerTz) {
|
||||||
|
this.next(relatively)
|
||||||
|
} else {
|
||||||
|
this.next(relatively.local) ?.toOffsetUnadjusted(relatively.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun KronScheduler.nextTimeZoned() = next(DateTime.now().local)
|
||||||
46
src/commonMain/kotlin/KronSchedulersMerging.kt
Normal file
46
src/commonMain/kotlin/KronSchedulersMerging.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import dev.inmo.krontab.collection.CollectionKronScheduler
|
||||||
|
import dev.inmo.krontab.collection.includeAll
|
||||||
|
import dev.inmo.krontab.internal.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator]
|
||||||
|
*
|
||||||
|
* @see CollectionKronScheduler
|
||||||
|
* @see CollectionKronScheduler.include
|
||||||
|
*/
|
||||||
|
fun Iterator<KronScheduler>.merge(): CollectionKronScheduler {
|
||||||
|
val cronDateTimes = mutableListOf<CronDateTime>()
|
||||||
|
val timezonedCronDateTimes = mutableListOf<CronDateTimeSchedulerTz>()
|
||||||
|
val collectionScheduler = CollectionKronScheduler()
|
||||||
|
forEach {
|
||||||
|
when (it) {
|
||||||
|
is CronDateTimeScheduler -> cronDateTimes.add(it.cronDateTime)
|
||||||
|
is CronDateTimeSchedulerTz -> timezonedCronDateTimes.add(it)
|
||||||
|
else -> collectionScheduler.include(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cronDateTimes.isNotEmpty()) {
|
||||||
|
collectionScheduler.include(CronDateTimeScheduler(cronDateTimes.merge()))
|
||||||
|
}
|
||||||
|
if (timezonedCronDateTimes.isNotEmpty()) {
|
||||||
|
collectionScheduler.includeAll(mergeCronDateTimeSchedulers(timezonedCronDateTimes))
|
||||||
|
}
|
||||||
|
return collectionScheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator]
|
||||||
|
*
|
||||||
|
* @see CollectionKronScheduler
|
||||||
|
* @see CollectionKronScheduler.include
|
||||||
|
*/
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun Iterable<KronScheduler>.merge(): CollectionKronScheduler = iterator().merge()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Vararg shortcut for [merge]
|
||||||
|
*/
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun merge(vararg kronDateTimeSchedulers: KronScheduler): CollectionKronScheduler = kronDateTimeSchedulers.iterator().merge()
|
||||||
165
src/commonMain/kotlin/KrontabConfig.kt
Normal file
165
src/commonMain/kotlin/KrontabConfig.kt
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
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(" ").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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/commonMain/kotlin/KrontabTemplateWrapper.kt
Normal file
26
src/commonMain/kotlin/KrontabTemplateWrapper.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains [template] and can be simply serialized/deserialized. In fact that class will work as
|
||||||
|
* [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()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will create [KrontabTemplateWrapper] from [this] [KrontabTemplate]
|
||||||
|
*
|
||||||
|
* @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
|
||||||
109
src/commonMain/kotlin/StringParser.kt
Normal file
109
src/commonMain/kotlin/StringParser.kt
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package dev.inmo.krontab
|
||||||
|
|
||||||
|
import dev.inmo.krontab.internal.*
|
||||||
|
import dev.inmo.krontab.utils.Minutes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see createSimpleScheduler
|
||||||
|
* @see buildSchedule
|
||||||
|
*/
|
||||||
|
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**
|
||||||
|
* * **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
|
||||||
|
*
|
||||||
|
* @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 = KrontabConfig(incoming).scheduler()
|
||||||
|
|
||||||
|
fun createSimpleScheduler(
|
||||||
|
incoming: KrontabTemplate,
|
||||||
|
defaultOffset: Minutes
|
||||||
|
): KronSchedulerTz = KrontabConfig(incoming).scheduler(defaultOffset)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for [createSimpleScheduler]
|
||||||
|
*/
|
||||||
|
fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming)
|
||||||
|
/**
|
||||||
|
* Shortcut for [createSimpleScheduler]
|
||||||
|
*/
|
||||||
|
fun buildSchedule(incoming: KrontabTemplate, defaultOffset: Minutes): KronSchedulerTz = createSimpleScheduler(incoming, defaultOffset)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for [buildSchedule]
|
||||||
|
*/
|
||||||
|
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
|
||||||
|
/**
|
||||||
|
* Shortcut for [buildSchedule]
|
||||||
|
*/
|
||||||
|
fun KrontabTemplate.toSchedule(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for [buildSchedule]
|
||||||
|
*/
|
||||||
|
fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this)
|
||||||
|
/**
|
||||||
|
* Shortcut for [buildSchedule]
|
||||||
|
*/
|
||||||
|
fun KrontabTemplate.toKronScheduler(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset)
|
||||||
205
src/commonMain/kotlin/builder/SchedulerBuilder.kt
Normal file
205
src/commonMain/kotlin/builder/SchedulerBuilder.kt
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
package dev.inmo.krontab.builder
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will help to create an instance of [KronScheduler]
|
||||||
|
*
|
||||||
|
* @see dev.inmo.krontab.createSimpleScheduler
|
||||||
|
*/
|
||||||
|
inline fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
||||||
|
val builder = SchedulerBuilder()
|
||||||
|
|
||||||
|
builder.settingsBlock()
|
||||||
|
|
||||||
|
return builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will help to create an instance of [KronScheduler]
|
||||||
|
*
|
||||||
|
* @see dev.inmo.krontab.createSimpleScheduler
|
||||||
|
*/
|
||||||
|
inline fun buildSchedule(
|
||||||
|
offset: Minutes,
|
||||||
|
settingsBlock: SchedulerBuilder.() -> Unit
|
||||||
|
): KronSchedulerTz {
|
||||||
|
val builder = SchedulerBuilder(offset = offset)
|
||||||
|
|
||||||
|
builder.settingsBlock()
|
||||||
|
|
||||||
|
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,
|
||||||
|
private var hours: Array<Byte>? = null,
|
||||||
|
private var dayOfMonth: Array<Byte>? = null,
|
||||||
|
private var month: Array<Byte>? = null,
|
||||||
|
private var year: Array<Int>? = null,
|
||||||
|
private var dayOfWeek: Array<Byte>? = null,
|
||||||
|
private val offset: Minutes? = null,
|
||||||
|
private var milliseconds: Array<Short>? = null
|
||||||
|
) {
|
||||||
|
private fun <I, T : TimeBuilder<I>> callAndReturn(
|
||||||
|
initial: Array<I>?,
|
||||||
|
builder: T,
|
||||||
|
block: T.() -> Unit
|
||||||
|
): List<I>? {
|
||||||
|
builder.block()
|
||||||
|
|
||||||
|
val builderValue = builder.build()
|
||||||
|
|
||||||
|
return initial ?.let {
|
||||||
|
builderValue ?.let { _ ->
|
||||||
|
(it + builderValue).distinct()
|
||||||
|
} ?: builderValue
|
||||||
|
} ?: builderValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an milliseconds block
|
||||||
|
*/
|
||||||
|
fun milliseconds(block: MillisecondsBuilder.() -> Unit) {
|
||||||
|
milliseconds = callAndReturn(
|
||||||
|
milliseconds,
|
||||||
|
MillisecondsBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an seconds block
|
||||||
|
*/
|
||||||
|
fun seconds(block: SecondsBuilder.() -> Unit) {
|
||||||
|
seconds = callAndReturn(
|
||||||
|
seconds,
|
||||||
|
SecondsBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an minutes block
|
||||||
|
*/
|
||||||
|
fun minutes(block: MinutesBuilder.() -> Unit) {
|
||||||
|
minutes = callAndReturn(
|
||||||
|
minutes,
|
||||||
|
MinutesBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an hours block
|
||||||
|
*/
|
||||||
|
fun hours(block: HoursBuilder.() -> Unit) {
|
||||||
|
hours = callAndReturn(
|
||||||
|
hours,
|
||||||
|
HoursBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an days of month block
|
||||||
|
*/
|
||||||
|
fun dayOfMonth(block: DaysOfMonthBuilder.() -> Unit) {
|
||||||
|
dayOfMonth = callAndReturn(
|
||||||
|
dayOfMonth,
|
||||||
|
DaysOfMonthBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an hours block
|
||||||
|
*/
|
||||||
|
fun dayOfWeek(block: WeekDaysBuilder.() -> Unit) {
|
||||||
|
dayOfWeek = callAndReturn(
|
||||||
|
dayOfWeek,
|
||||||
|
WeekDaysBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an months block
|
||||||
|
*/
|
||||||
|
fun months(block: MonthsBuilder.() -> Unit) {
|
||||||
|
month = callAndReturn(
|
||||||
|
month,
|
||||||
|
MonthsBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an year block
|
||||||
|
*/
|
||||||
|
fun years(block: YearsBuilder.() -> Unit) {
|
||||||
|
year = callAndReturn(
|
||||||
|
year,
|
||||||
|
YearsBuilder(),
|
||||||
|
block
|
||||||
|
) ?.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Completely built and independent [KronScheduler]
|
||||||
|
*
|
||||||
|
* @see dev.inmo.krontab.createSimpleScheduler
|
||||||
|
* @see dev.inmo.krontab.internal.createKronScheduler
|
||||||
|
*/
|
||||||
|
fun build(): KronScheduler = offset ?.let {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,17 +1,28 @@
|
|||||||
package com.insanusmokrassar.krontab.builder
|
package dev.inmo.krontab.builder
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.internal.*
|
import dev.inmo.krontab.internal.*
|
||||||
import com.insanusmokrassar.krontab.utils.clamp
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class was created for incapsulation of builder work with specified [restrictionsRange]. For example,
|
* This class was created for incapsulation of builder work with specified [restrictionsRange]. For example,
|
||||||
* [include] function of [TimeBuilder] will always [clamp] incoming data using its [restrictionsRange]
|
* [include] function of [TimeBuilder] will always [coerceIn] incoming data using its [restrictionsRange]
|
||||||
*/
|
*/
|
||||||
sealed class TimeBuilder (
|
sealed class TimeBuilder<T : Number> (
|
||||||
private val restrictionsRange: IntRange
|
private val restrictionsRange: IntRange,
|
||||||
|
private val converter: Converter<T>
|
||||||
) {
|
) {
|
||||||
private var result: Set<Int>? = null
|
private var result: Set<Int>? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first possible value of builder
|
||||||
|
*/
|
||||||
|
val first
|
||||||
|
get() = restrictionsRange.first
|
||||||
|
/**
|
||||||
|
* The last possible value of builder. Using of this variable equal to using "L" in strings
|
||||||
|
*/
|
||||||
|
val last
|
||||||
|
get() = restrictionsRange.last
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* After calling of this function this builder will allow any value of current time
|
* After calling of this function this builder will allow any value of current time
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +36,7 @@ sealed class TimeBuilder (
|
|||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
infix fun include(array: Array<Int>) {
|
infix fun include(array: Array<Int>) {
|
||||||
val clamped = array.map { it.clamp(restrictionsRange) } + (result ?: emptySet())
|
val clamped = array.map { it.coerceIn(restrictionsRange) } + (result ?: emptySet())
|
||||||
result = clamped.toSet()
|
result = clamped.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,9 +45,16 @@ sealed class TimeBuilder (
|
|||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
infix fun at(value: Int) {
|
infix fun at(value: Int) {
|
||||||
result = (result ?: emptySet()) + value.clamp(restrictionsRange)
|
result = (result ?: emptySet()) + value.coerceIn(restrictionsRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for [at]. In fact will
|
||||||
|
*/
|
||||||
|
@Suppress("unused", "NOTHING_TO_INLINE")
|
||||||
|
inline infix fun each(value: Int) = at(value)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Just wrapper for more obvious writing something like "[from] 2 [every] 5". For example, for [SecondsBuilder] it
|
* Just wrapper for more obvious writing something like "[from] 2 [every] 5". For example, for [SecondsBuilder] it
|
||||||
* will mean "[from] second second [every] 5 seconds", or "2, 7, 13, ..."
|
* will mean "[from] second second [every] 5 seconds", or "2, 7, 13, ..."
|
||||||
@@ -51,7 +69,7 @@ sealed class TimeBuilder (
|
|||||||
* @see [from]
|
* @see [from]
|
||||||
*/
|
*/
|
||||||
infix fun Int.every(delay: Int): Array<Int> {
|
infix fun Int.every(delay: Int): Array<Int> {
|
||||||
val progression = clamp(restrictionsRange) .. restrictionsRange.last step delay
|
val progression = coerceIn(restrictionsRange) .. restrictionsRange.last step delay
|
||||||
val result = progression.toSet().toTypedArray()
|
val result = progression.toSet().toTypedArray()
|
||||||
|
|
||||||
this@TimeBuilder include result
|
this@TimeBuilder include result
|
||||||
@@ -69,7 +87,7 @@ sealed class TimeBuilder (
|
|||||||
*/
|
*/
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
infix fun Int.upTo(endIncluding: Int): Array<Int> {
|
infix fun Int.upTo(endIncluding: Int): Array<Int> {
|
||||||
val progression = clamp(restrictionsRange) .. endIncluding.clamp(restrictionsRange)
|
val progression = coerceIn(restrictionsRange) .. endIncluding.coerceIn(restrictionsRange)
|
||||||
val result = progression.toSet().toTypedArray()
|
val result = progression.toSet().toTypedArray()
|
||||||
|
|
||||||
this@TimeBuilder include result
|
this@TimeBuilder include result
|
||||||
@@ -92,11 +110,23 @@ sealed class TimeBuilder (
|
|||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
infix operator fun rangeTo(endIncluding: Int) = (this from 0) rangeTo endIncluding
|
infix operator fun rangeTo(endIncluding: Int) = (this from 0) rangeTo endIncluding
|
||||||
|
|
||||||
internal fun build() = result ?.map { it.toByte() } ?.toTypedArray()
|
/**
|
||||||
|
* Will include the last possible value
|
||||||
|
*/
|
||||||
|
fun includeLast() = at(restrictionsRange.last)
|
||||||
|
/**
|
||||||
|
* Will include the first possible value
|
||||||
|
*/
|
||||||
|
fun includeFirst() = at(restrictionsRange.first)
|
||||||
|
|
||||||
|
internal fun build() = result ?.map(converter)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SecondsBuilder : TimeBuilder(secondsRange)
|
class MillisecondsBuilder : TimeBuilder<Short>(millisecondsRange, intToShortConverter)
|
||||||
class MinutesBuilder : TimeBuilder(minutesRange)
|
class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
|
||||||
class HoursBuilder : TimeBuilder(hoursRange)
|
class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
|
||||||
class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange)
|
class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
|
||||||
class MonthsBuilder : TimeBuilder(monthRange)
|
class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
|
||||||
|
class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
|
||||||
|
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)
|
||||||
|
class WeekDaysBuilder : TimeBuilder<Byte>(dayOfWeekRange, intToByteConverter)
|
||||||
25
src/commonMain/kotlin/collection/CollectionFunctions.kt
Normal file
25
src/commonMain/kotlin/collection/CollectionFunctions.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.krontab.collection
|
||||||
|
|
||||||
|
import dev.inmo.krontab.KronScheduler
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun CollectionKronScheduler.includeAll(kronSchedulers: List<KronScheduler>) {
|
||||||
|
kronSchedulers.forEach {
|
||||||
|
include(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
inline fun CollectionKronScheduler.includeAll(vararg kronSchedulers: KronScheduler) {
|
||||||
|
includeAll(kronSchedulers.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun KronScheduler.plus(kronScheduler: KronScheduler): CollectionKronScheduler {
|
||||||
|
return CollectionKronScheduler().apply {
|
||||||
|
includeAll(this, kronScheduler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun CollectionKronScheduler.plusAssign(kronScheduler: KronScheduler) {
|
||||||
|
include(kronScheduler)
|
||||||
|
}
|
||||||
66
src/commonMain/kotlin/collection/CollectionKronScheduler.kt
Normal file
66
src/commonMain/kotlin/collection/CollectionKronScheduler.kt
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package dev.inmo.krontab.collection
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.DateTimeTz
|
||||||
|
import dev.inmo.krontab.*
|
||||||
|
import dev.inmo.krontab.internal.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This scheduler will be useful in case you want to unite several different [KronScheduler]s
|
||||||
|
*/
|
||||||
|
data class CollectionKronScheduler internal constructor(
|
||||||
|
internal val schedulers: MutableList<KronScheduler>
|
||||||
|
) : KronSchedulerTz {
|
||||||
|
internal constructor() : this(mutableListOf())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add [kronScheduler] into its [schedulers] list
|
||||||
|
*
|
||||||
|
* * When [kronScheduler] is [CronDateTimeScheduler] it will merge all [CronDateTimeScheduler]s from [schedulers] list
|
||||||
|
* and this [kronScheduler] using [mergeCronDateTimeSchedulers] function
|
||||||
|
* * When [kronScheduler] is [CollectionKronScheduler] it this instance will include all [kronScheduler]
|
||||||
|
* [schedulers]
|
||||||
|
* * Otherwise [kronScheduler] will be added to [schedulers] list
|
||||||
|
*/
|
||||||
|
fun include(kronScheduler: KronScheduler) {
|
||||||
|
when (kronScheduler) {
|
||||||
|
is CronDateTimeScheduler -> {
|
||||||
|
val resultCronDateTimes = mutableListOf(kronScheduler)
|
||||||
|
schedulers.removeAll {
|
||||||
|
if (it is CronDateTimeScheduler) {
|
||||||
|
resultCronDateTimes.add(it)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schedulers.add(
|
||||||
|
mergeCronDateTimeSchedulers(resultCronDateTimes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is CronDateTimeSchedulerTz -> {
|
||||||
|
val newCronDateTimes = mutableListOf(kronScheduler.cronDateTime)
|
||||||
|
schedulers.removeAll {
|
||||||
|
if (it is CronDateTimeSchedulerTz && it.offset == kronScheduler.offset) {
|
||||||
|
newCronDateTimes.add(it.cronDateTime)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schedulers.add(CronDateTimeSchedulerTz(newCronDateTimes.merge(), kronScheduler.offset))
|
||||||
|
}
|
||||||
|
is CollectionKronScheduler -> kronScheduler.schedulers.forEach {
|
||||||
|
include(it)
|
||||||
|
}
|
||||||
|
else -> schedulers.add(kronScheduler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun next(relatively: DateTime): DateTime {
|
||||||
|
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun next(relatively: DateTimeTz): DateTimeTz {
|
||||||
|
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively.local).toOffsetUnadjusted(relatively.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab
|
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T {
|
|
||||||
delay((next() - 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,21 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab
|
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This interface was created for abstraction of [next] operation. Currently, there is only
|
|
||||||
* [com.insanusmokrassar.krontab.internal.CronDateTimeScheduler] realisation of this interface inside of this library,
|
|
||||||
* but you it is possible to create your own realisation of this interface for scheduling, for example, depending of
|
|
||||||
* users activity or something like this
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.internal.CronDateTimeScheduler
|
|
||||||
*/
|
|
||||||
interface KronScheduler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Next [DateTime] when some action must be triggered according to settings of this instance
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.internal.CronDateTimeScheduler.next
|
|
||||||
*/
|
|
||||||
suspend fun next(relatively: DateTime = DateTime.now()): DateTime
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.builder.buildSchedule
|
|
||||||
import com.insanusmokrassar.krontab.internal.CronDateTime
|
|
||||||
import com.insanusmokrassar.krontab.internal.CronDateTimeScheduler
|
|
||||||
|
|
||||||
internal val anyCronDateTime by lazy {
|
|
||||||
CronDateTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now]
|
|
||||||
*/
|
|
||||||
val AnyTimeScheduler: KronScheduler by lazy {
|
|
||||||
CronDateTimeScheduler(listOf(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 } }
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.internal.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see createSimpleScheduler
|
|
||||||
* @see buildSchedule
|
|
||||||
*/
|
|
||||||
typealias KrontabTemplate = String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse [incoming] string and adapt according to next format: "* * * * *" where order of things:
|
|
||||||
*
|
|
||||||
* * seconds
|
|
||||||
* * minutes
|
|
||||||
* * hours
|
|
||||||
* * dayOfMonth
|
|
||||||
* * month
|
|
||||||
*
|
|
||||||
* And each one have next format:
|
|
||||||
*
|
|
||||||
* `{number}[,{number},...]` or `*`
|
|
||||||
*
|
|
||||||
* and {number} here is one of
|
|
||||||
*
|
|
||||||
* * {int}-{int}
|
|
||||||
* * {int}/{int}
|
|
||||||
* * */{int}
|
|
||||||
* * {int}
|
|
||||||
*
|
|
||||||
* 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]
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* * "0/5 * * * *" for every five seconds triggering
|
|
||||||
* * "0/15 30 * * *" for every 15th seconds in a half of each hour
|
|
||||||
* * "1 2 3 4 5" for triggering in near first second of second minute of third hour of fourth day of may
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.internal.createKronScheduler
|
|
||||||
*/
|
|
||||||
fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler {
|
|
||||||
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ")
|
|
||||||
|
|
||||||
val secondsParsed = parseSeconds(secondsSource)
|
|
||||||
val minutesParsed = parseMinutes(minutesSource)
|
|
||||||
val hoursParsed = parseHours(hoursSource)
|
|
||||||
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
|
|
||||||
val monthParsed = parseMonths(monthSource)
|
|
||||||
|
|
||||||
return createKronScheduler(
|
|
||||||
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for [createSimpleScheduler]
|
|
||||||
*/
|
|
||||||
fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for [buildSchedule]
|
|
||||||
*/
|
|
||||||
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.builder
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.KronScheduler
|
|
||||||
import com.insanusmokrassar.krontab.internal.*
|
|
||||||
import com.insanusmokrassar.krontab.internal.CronDateTime
|
|
||||||
import com.insanusmokrassar.krontab.internal.CronDateTimeScheduler
|
|
||||||
import com.insanusmokrassar.krontab.internal.fillWith
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will help to create an instance of [KronScheduler]
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.createSimpleScheduler
|
|
||||||
*/
|
|
||||||
fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
|
|
||||||
val builder = SchedulerBuilder()
|
|
||||||
|
|
||||||
builder.settingsBlock()
|
|
||||||
|
|
||||||
return builder.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
class SchedulerBuilder(
|
|
||||||
private var seconds: Array<Byte>? = null,
|
|
||||||
private var minutes: Array<Byte>? = null,
|
|
||||||
private var hours: Array<Byte>? = null,
|
|
||||||
private var dayOfMonth: Array<Byte>? = null,
|
|
||||||
private var month: Array<Byte>? = null
|
|
||||||
) {
|
|
||||||
private fun <T : TimeBuilder> callAndReturn(
|
|
||||||
initial: Array<Byte>?,
|
|
||||||
builder: T,
|
|
||||||
block: T.() -> Unit
|
|
||||||
): Array<Byte>? {
|
|
||||||
builder.block()
|
|
||||||
|
|
||||||
val builderValue = builder.build()
|
|
||||||
|
|
||||||
return initial ?.let {
|
|
||||||
builderValue ?.let { _ ->
|
|
||||||
(it + builderValue).distinct().toTypedArray()
|
|
||||||
} ?: builderValue
|
|
||||||
} ?: builderValue
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an seconds block
|
|
||||||
*/
|
|
||||||
fun seconds(block: SecondsBuilder.() -> Unit) {
|
|
||||||
seconds = callAndReturn(
|
|
||||||
seconds,
|
|
||||||
SecondsBuilder(),
|
|
||||||
block
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an minutes block
|
|
||||||
*/
|
|
||||||
fun minutes(block: MinutesBuilder.() -> Unit) {
|
|
||||||
minutes = callAndReturn(
|
|
||||||
minutes,
|
|
||||||
MinutesBuilder(),
|
|
||||||
block
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an hours block
|
|
||||||
*/
|
|
||||||
fun hours(block: HoursBuilder.() -> Unit) {
|
|
||||||
hours = callAndReturn(
|
|
||||||
hours,
|
|
||||||
HoursBuilder(),
|
|
||||||
block
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an days of month block
|
|
||||||
*/
|
|
||||||
fun dayOfMonth(block: DaysOfMonthBuilder.() -> Unit) {
|
|
||||||
dayOfMonth = callAndReturn(
|
|
||||||
dayOfMonth,
|
|
||||||
DaysOfMonthBuilder(),
|
|
||||||
block
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an months block
|
|
||||||
*/
|
|
||||||
fun months(block: MonthsBuilder.() -> Unit) {
|
|
||||||
month = callAndReturn(
|
|
||||||
month,
|
|
||||||
MonthsBuilder(),
|
|
||||||
block
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Completely built and independent [KronScheduler]
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.createSimpleScheduler
|
|
||||||
* @see com.insanusmokrassar.krontab.internal.createKronScheduler
|
|
||||||
*/
|
|
||||||
fun build(): KronScheduler = createKronScheduler(seconds, minutes, hours, dayOfMonth, month)
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.internal
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.KronScheduler
|
|
||||||
import com.insanusmokrassar.krontab.utils.clamp
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
import com.soywiz.klock.DateTimeSpan
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param month 0-11
|
|
||||||
* @param dayOfMonth 0-31
|
|
||||||
* @param hours 0-23
|
|
||||||
* @param minutes 0-59
|
|
||||||
* @param seconds 0-59
|
|
||||||
*/
|
|
||||||
internal data class CronDateTime(
|
|
||||||
val month: Byte? = null,
|
|
||||||
val dayOfMonth: Byte? = null,
|
|
||||||
val hours: Byte? = null,
|
|
||||||
val minutes: Byte? = null,
|
|
||||||
val seconds: Byte? = null
|
|
||||||
) {
|
|
||||||
init {
|
|
||||||
check(month ?.let { it in monthRange } ?: true)
|
|
||||||
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true)
|
|
||||||
check(hours?.let { it in hoursRange } ?: true)
|
|
||||||
check(minutes?.let { it in minutesRange } ?: true)
|
|
||||||
check(seconds?.let { it in secondsRange } ?: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val klockDayOfMonth = dayOfMonth ?.plus(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The near [DateTime] which happens after [relativelyTo] or will be equal to [relativelyTo]
|
|
||||||
*/
|
|
||||||
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime {
|
|
||||||
var current = relativelyTo
|
|
||||||
|
|
||||||
seconds?.let {
|
|
||||||
val left = it - current.seconds
|
|
||||||
current += DateTimeSpan(minutes = if (left <= 0) 1 else 0, seconds = left)
|
|
||||||
}
|
|
||||||
|
|
||||||
minutes?.let {
|
|
||||||
val left = it - current.minutes
|
|
||||||
current += DateTimeSpan(hours = if (left < 0) 1 else 0, minutes = left)
|
|
||||||
}
|
|
||||||
|
|
||||||
hours?.let {
|
|
||||||
val left = it - current.hours
|
|
||||||
current += DateTimeSpan(days = if (left < 0) 1 else 0, hours = left)
|
|
||||||
}
|
|
||||||
|
|
||||||
klockDayOfMonth ?.let {
|
|
||||||
val left = it - current.dayOfMonth
|
|
||||||
current += DateTimeSpan(months = if (left < 0) 1 else 0, days = left)
|
|
||||||
}
|
|
||||||
|
|
||||||
month ?.let {
|
|
||||||
val left = it - current.month0
|
|
||||||
current += DateTimeSpan(months = if (left < 0) 1 else 0, days = left)
|
|
||||||
}
|
|
||||||
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
|
||||||
*/
|
|
||||||
internal fun createKronScheduler(
|
|
||||||
seconds: Array<Byte>? = null,
|
|
||||||
minutes: Array<Byte>? = null,
|
|
||||||
hours: Array<Byte>? = null,
|
|
||||||
dayOfMonth: Array<Byte>? = null,
|
|
||||||
month: Array<Byte>? = null
|
|
||||||
): KronScheduler {
|
|
||||||
val resultCronDateTimes = mutableListOf(CronDateTime())
|
|
||||||
|
|
||||||
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
|
||||||
previousCronDateTime.copy(seconds = currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
minutes ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
|
||||||
previousCronDateTime.copy(minutes = currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
hours ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
|
||||||
previousCronDateTime.copy(hours = currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
dayOfMonth ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
|
||||||
previousCronDateTime.copy(dayOfMonth = currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
|
|
||||||
previousCronDateTime.copy(month = currentTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CronDateTimeScheduler(resultCronDateTimes.toList())
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.internal
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.KronScheduler
|
|
||||||
import com.insanusmokrassar.krontab.anyCronDateTime
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cron-oriented realisation of [KronScheduler]
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.AnyTimeScheduler
|
|
||||||
* @see com.insanusmokrassar.krontab.EverySecondScheduler
|
|
||||||
* @see com.insanusmokrassar.krontab.EveryMinuteScheduler
|
|
||||||
* @see com.insanusmokrassar.krontab.EveryHourScheduler
|
|
||||||
* @see com.insanusmokrassar.krontab.EveryDayOfMonthScheduler
|
|
||||||
* @see com.insanusmokrassar.krontab.EveryMonthScheduler
|
|
||||||
*
|
|
||||||
* @see com.insanusmokrassar.krontab.builder.buildSchedule
|
|
||||||
* @see com.insanusmokrassar.krontab.builder.SchedulerBuilder
|
|
||||||
*/
|
|
||||||
internal data class CronDateTimeScheduler internal constructor(
|
|
||||||
internal val cronDateTimes: List<CronDateTime>
|
|
||||||
) : KronScheduler {
|
|
||||||
/**
|
|
||||||
* @return Near date using [cronDateTimes] list and getting the [Iterable.min] one
|
|
||||||
*
|
|
||||||
* @see toNearDateTime
|
|
||||||
*/
|
|
||||||
override suspend fun next(relatively: DateTime): DateTime {
|
|
||||||
return cronDateTimes.map { it.toNearDateTime(relatively) }.minOrNull() ?: anyCronDateTime.toNearDateTime(relatively)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.internal
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.utils.clamp
|
|
||||||
|
|
||||||
private fun createSimpleScheduler(from: String, dataRange: IntRange): Array<Byte>? {
|
|
||||||
val things = from.split(",")
|
|
||||||
|
|
||||||
val results = things.flatMap {
|
|
||||||
when {
|
|
||||||
it.contains("-") -> {
|
|
||||||
val splitted = it.split("-")
|
|
||||||
(splitted.first().toInt().clamp(dataRange) .. splitted[1].toInt().clamp(dataRange)).toList()
|
|
||||||
}
|
|
||||||
it.contains("/") -> {
|
|
||||||
val (start, step) = it.split("/")
|
|
||||||
val startNum = (if (start.isEmpty() || start == "*") {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
start.toInt()
|
|
||||||
}).clamp(dataRange)
|
|
||||||
val stepNum = step.toInt().clamp(dataRange)
|
|
||||||
(startNum .. dataRange.last step stepNum).map { it }
|
|
||||||
}
|
|
||||||
it == "*" -> return null
|
|
||||||
else -> listOf(it.toInt().clamp(dataRange))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results.map { it.toByte() }.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange)
|
|
||||||
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange)
|
|
||||||
internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange)
|
|
||||||
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange)
|
|
||||||
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange)
|
|
||||||
|
|
||||||
internal fun Array<Byte>.fillWith(
|
|
||||||
whereToPut: MutableList<CronDateTime>,
|
|
||||||
createFactory: (CronDateTime, Byte) -> CronDateTime
|
|
||||||
) {
|
|
||||||
val previousValues = whereToPut.toList()
|
|
||||||
|
|
||||||
whereToPut.clear()
|
|
||||||
|
|
||||||
previousValues.forEach { previousValue ->
|
|
||||||
forEach {
|
|
||||||
whereToPut.add(createFactory(previousValue, it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.utils
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return [min] in case if [this] less than [min]. Otherwise will check that [max] grant than [this] and return [this]
|
|
||||||
* if so or [max] otherwise
|
|
||||||
*/
|
|
||||||
internal fun Int.clamp(min: Int, max: Int): Int = if (this < min) min else if (this > max) max else this
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper function for [clamp] extension
|
|
||||||
*/
|
|
||||||
internal fun Int.clamp(range: IntRange): Int = clamp(range.first, range.last)
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.utils
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.KronScheduler
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
import kotlinx.coroutines.FlowPreview
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
@FlowPreview
|
|
||||||
fun KronScheduler.asFlow(): Flow<DateTime> = SchedulerFlow(this)
|
|
||||||
|
|
||||||
@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)
|
|
||||||
val sleepDelay = (nextTime - now).millisecondsLong
|
|
||||||
delay(sleepDelay)
|
|
||||||
collector.emit(nextTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
136
src/commonMain/kotlin/internal/CronDateTime.kt
Normal file
136
src/commonMain/kotlin/internal/CronDateTime.kt
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.TimezoneOffset
|
||||||
|
import dev.inmo.krontab.KronScheduler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param daysOfWeek 0-6
|
||||||
|
* @param years any int
|
||||||
|
* @param months 0-11
|
||||||
|
* @param daysOfMonth 0-30
|
||||||
|
* @param hours 0-23
|
||||||
|
* @param minutes 0-59
|
||||||
|
* @param seconds 0-59
|
||||||
|
*/
|
||||||
|
internal data class CronDateTime(
|
||||||
|
val daysOfWeek: Array<Byte>? = null,
|
||||||
|
val years: Array<Int>? = null,
|
||||||
|
val months: Array<Byte>? = null,
|
||||||
|
val daysOfMonth: Array<Byte>? = null,
|
||||||
|
val hours: Array<Byte>? = null,
|
||||||
|
val minutes: Array<Byte>? = null,
|
||||||
|
val seconds: Array<Byte>? = null,
|
||||||
|
val milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true)
|
||||||
|
check(years?.all { it in yearRange } ?: true)
|
||||||
|
check(months?.all { it in monthRange } ?: true)
|
||||||
|
check(daysOfMonth ?.all { it in dayOfMonthRange } ?: true)
|
||||||
|
check(hours?.all { it in hoursRange } ?: true)
|
||||||
|
check(minutes?.all { it in minutesRange } ?: true)
|
||||||
|
check(seconds?.all { it in secondsRange } ?: true)
|
||||||
|
check(milliseconds?.all { it in millisecondsRange } ?: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val calculators = listOf(
|
||||||
|
years ?.let { NearDateTimeCalculatorYears(it) },
|
||||||
|
daysOfWeek ?.let { NearDateTimeCalculatorWeekDays(it) },
|
||||||
|
milliseconds ?.let { NearDateTimeCalculatorMillis(it) },
|
||||||
|
seconds ?.let { NearDateTimeCalculatorSeconds(it) },
|
||||||
|
minutes ?.let { NearDateTimeCalculatorMinutes(it) },
|
||||||
|
hours ?.let { NearDateTimeCalculatorHours(it) },
|
||||||
|
daysOfMonth ?.let { NearDateTimeCalculatorDays(it) },
|
||||||
|
months ?.let { NearDateTimeCalculatorMonths(it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? {
|
||||||
|
var current = relativelyTo
|
||||||
|
whileLoop@while (true) {
|
||||||
|
for (calculator in calculators) {
|
||||||
|
val (calculated, requireRecalculation) = (calculator ?: continue).calculateNearTime(current) ?: return null
|
||||||
|
current = calculated
|
||||||
|
if (requireRecalculation) {
|
||||||
|
continue@whileLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun createCronDateTime(
|
||||||
|
seconds: Array<Byte>? = null,
|
||||||
|
minutes: Array<Byte>? = null,
|
||||||
|
hours: Array<Byte>? = null,
|
||||||
|
dayOfMonth: Array<Byte>? = null,
|
||||||
|
month: Array<Byte>? = null,
|
||||||
|
years: Array<Int>? = null,
|
||||||
|
weekDays: Array<Byte>? = null,
|
||||||
|
milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||||
|
): CronDateTime {
|
||||||
|
return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds, milliseconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
||||||
|
*/
|
||||||
|
internal fun createKronScheduler(
|
||||||
|
seconds: Array<Byte>? = null,
|
||||||
|
minutes: Array<Byte>? = null,
|
||||||
|
hours: Array<Byte>? = null,
|
||||||
|
dayOfMonth: Array<Byte>? = null,
|
||||||
|
month: Array<Byte>? = null,
|
||||||
|
years: Array<Int>? = null,
|
||||||
|
weekDays: Array<Byte>? = null,
|
||||||
|
milliseconds: Array<Short>? = millisecondsArrayDefault
|
||||||
|
): KronScheduler = CronDateTimeScheduler(
|
||||||
|
createCronDateTime(
|
||||||
|
seconds,
|
||||||
|
minutes,
|
||||||
|
hours,
|
||||||
|
dayOfMonth,
|
||||||
|
month,
|
||||||
|
years,
|
||||||
|
weekDays,
|
||||||
|
milliseconds
|
||||||
|
)
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* @return [KronScheduler] (in fact [CronDateTimeScheduler]) based on incoming data
|
||||||
|
*/
|
||||||
|
internal fun createKronSchedulerWithOffset(
|
||||||
|
seconds: Array<Byte>? = null,
|
||||||
|
minutes: Array<Byte>? = null,
|
||||||
|
hours: Array<Byte>? = null,
|
||||||
|
dayOfMonth: Array<Byte>? = null,
|
||||||
|
month: Array<Byte>? = null,
|
||||||
|
years: Array<Int>? = null,
|
||||||
|
weekDays: Array<Byte>? = null,
|
||||||
|
offset: TimezoneOffset,
|
||||||
|
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() },
|
||||||
|
flatMap { it.years ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
flatMap { it.months ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
flatMap { it.daysOfMonth ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
flatMap { it.hours ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
flatMap { it.minutes ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
flatMap { it.seconds ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
flatMap { it.milliseconds ?.toList() ?: listOf(0) }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
|
||||||
|
)
|
||||||
38
src/commonMain/kotlin/internal/CronDateTimeScheduler.kt
Normal file
38
src/commonMain/kotlin/internal/CronDateTimeScheduler.kt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import dev.inmo.krontab.KronScheduler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @see dev.inmo.krontab.EveryDayOfMonthScheduler
|
||||||
|
* @see dev.inmo.krontab.EveryMonthScheduler
|
||||||
|
* @see dev.inmo.krontab.EveryYearScheduler
|
||||||
|
*
|
||||||
|
* @see dev.inmo.krontab.builder.buildSchedule
|
||||||
|
* @see dev.inmo.krontab.builder.SchedulerBuilder
|
||||||
|
*/
|
||||||
|
internal data class CronDateTimeScheduler internal constructor(
|
||||||
|
internal val cronDateTime: CronDateTime
|
||||||
|
) : KronScheduler {
|
||||||
|
/**
|
||||||
|
* @return Near date using [cronDateTimes] list and getting the [Iterable.minByOrNull] one
|
||||||
|
*
|
||||||
|
* @see toNearDateTime
|
||||||
|
*/
|
||||||
|
override suspend fun next(relatively: DateTime): DateTime? {
|
||||||
|
return cronDateTime.toNearDateTime(relatively)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun mergeCronDateTimeSchedulers(
|
||||||
|
schedulers: List<CronDateTimeScheduler>
|
||||||
|
): CronDateTimeScheduler = CronDateTimeScheduler(
|
||||||
|
schedulers.map { it.cronDateTime }.merge()
|
||||||
|
)
|
||||||
37
src/commonMain/kotlin/internal/CronDateTimeSchedulerTz.kt
Normal file
37
src/commonMain/kotlin/internal/CronDateTimeSchedulerTz.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.DateTimeTz
|
||||||
|
import korlibs.time.TimezoneOffset
|
||||||
|
import dev.inmo.krontab.KronScheduler
|
||||||
|
import dev.inmo.krontab.KronSchedulerTz
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cron-oriented realisation of [KronScheduler] with taking into account [offset] for list of [cronDateTimes]
|
||||||
|
*
|
||||||
|
* @see CronDateTime
|
||||||
|
*/
|
||||||
|
internal data class CronDateTimeSchedulerTz internal constructor(
|
||||||
|
internal val cronDateTime: CronDateTime,
|
||||||
|
internal val offset: TimezoneOffset
|
||||||
|
) : KronSchedulerTz {
|
||||||
|
override suspend fun next(relatively: DateTimeTz): DateTimeTz? {
|
||||||
|
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(
|
||||||
|
schedulers: List<CronDateTimeSchedulerTz>
|
||||||
|
) = schedulers.groupBy {
|
||||||
|
it.offset
|
||||||
|
}.map { (offset, schedulers) ->
|
||||||
|
CronDateTimeSchedulerTz(
|
||||||
|
schedulers.map { it.cronDateTime }.merge(),
|
||||||
|
offset
|
||||||
|
)
|
||||||
|
}
|
||||||
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)
|
||||||
190
src/commonMain/kotlin/internal/NearDateTimeCalculator.kt
Normal file
190
src/commonMain/kotlin/internal/NearDateTimeCalculator.kt
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
import korlibs.time.*
|
||||||
|
import dev.inmo.krontab.utils.copy
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
fun interface NearDateTimeCalculator {
|
||||||
|
/**
|
||||||
|
* @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be
|
||||||
|
* recalculated
|
||||||
|
*/
|
||||||
|
fun calculateNearTime(
|
||||||
|
relativelyTo: DateTime
|
||||||
|
): Pair<DateTime, Boolean>?
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class CommonNearDateTimeCalculator<T>(
|
||||||
|
private val times: Array<T>,
|
||||||
|
private val partGetter: (DateTime) -> T,
|
||||||
|
private val partSetter: (DateTime, T) -> DateTime?
|
||||||
|
) : NearDateTimeCalculator where T : Comparable<T>, T : Number {
|
||||||
|
/**
|
||||||
|
* @return pair of near [DateTime] for this checker and [Boolean] flag that all previous calculations must be
|
||||||
|
* recalculated
|
||||||
|
*/
|
||||||
|
override fun calculateNearTime(
|
||||||
|
relativelyTo: DateTime
|
||||||
|
): Pair<DateTime, Boolean>? {
|
||||||
|
val currentData = partGetter(relativelyTo)
|
||||||
|
val greaterOrEquals = times.firstOrNull { it >= currentData }
|
||||||
|
val newDateTime = when (greaterOrEquals) {
|
||||||
|
null -> partSetter(relativelyTo, times.first()) ?: return null
|
||||||
|
currentData -> relativelyTo
|
||||||
|
else -> partSetter(relativelyTo, greaterOrEquals) ?: return null
|
||||||
|
}
|
||||||
|
return if (newDateTime == relativelyTo) {
|
||||||
|
relativelyTo to false
|
||||||
|
} else {
|
||||||
|
newDateTime to true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorMillis(
|
||||||
|
times: Array<Short>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.milliseconds.toShort() },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
(if (newOne < dateTime.milliseconds) {
|
||||||
|
dateTime.plus(1.seconds)
|
||||||
|
} else {
|
||||||
|
dateTime
|
||||||
|
}).copy(milliseconds = newOne.toInt())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorSeconds(
|
||||||
|
times: Array<Byte>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.seconds.toByte() },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
(if (newOne < dateTime.seconds) {
|
||||||
|
dateTime.plus(1.minutes)
|
||||||
|
} else {
|
||||||
|
dateTime
|
||||||
|
}).copy(second = newOne.toInt(), milliseconds = 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorMinutes(
|
||||||
|
times: Array<Byte>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.minutes.toByte() },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
(if (newOne < dateTime.minutes) {
|
||||||
|
dateTime.plus(1.hours)
|
||||||
|
} else {
|
||||||
|
dateTime
|
||||||
|
}).copy(minute = newOne.toInt(), second = 0, milliseconds = 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorHours(
|
||||||
|
times: Array<Byte>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.hours.toByte() },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
(if (newOne < dateTime.hours) {
|
||||||
|
dateTime.plus(1.days)
|
||||||
|
} else {
|
||||||
|
dateTime
|
||||||
|
}).copy(hour = newOne.toInt(), minute = 0, second = 0, milliseconds = 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorDays(
|
||||||
|
times: Array<Byte>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ (it.dayOfMonth - 1).toByte() }, // index1, so, decrease
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
val dateTime = (if (newOne < dateTime.dayOfMonth) {
|
||||||
|
dateTime.plus(1.months)
|
||||||
|
} else {
|
||||||
|
dateTime
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorMonths(
|
||||||
|
times: Array<Byte>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.month0.toByte() },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
(if (newOne < dateTime.month0) {
|
||||||
|
dateTime.plus(1.years)
|
||||||
|
} else {
|
||||||
|
dateTime
|
||||||
|
}).copy(
|
||||||
|
month = newOne.toInt() + 1, // index1
|
||||||
|
dayOfMonth = 1, // index1
|
||||||
|
hour = 0,
|
||||||
|
minute = 0,
|
||||||
|
second = 0,
|
||||||
|
milliseconds = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorWeekDays(
|
||||||
|
times: Array<Byte>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.dayOfWeek.index0.toByte() },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
val currentDayOfWeek = dateTime.dayOfWeek.index0
|
||||||
|
if (newOne.toInt() == currentDayOfWeek) return@CommonNearDateTimeCalculator dateTime
|
||||||
|
(if (newOne < currentDayOfWeek) {
|
||||||
|
dateTime.plus(7.days - (currentDayOfWeek - newOne).days)
|
||||||
|
} else {
|
||||||
|
dateTime.plus(newOne.toInt().days - currentDayOfWeek.days)
|
||||||
|
}).copy(
|
||||||
|
hour = 0,
|
||||||
|
minute = 0,
|
||||||
|
second = 0,
|
||||||
|
milliseconds = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun NearDateTimeCalculatorYears(
|
||||||
|
times: Array<Int>
|
||||||
|
) = CommonNearDateTimeCalculator(
|
||||||
|
times,
|
||||||
|
{ it.yearInt },
|
||||||
|
{ dateTime, newOne ->
|
||||||
|
val currentYear = dateTime.yearInt
|
||||||
|
if (newOne == currentYear) return@CommonNearDateTimeCalculator dateTime
|
||||||
|
(if (newOne < currentYear) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
dateTime.plus(newOne.years - currentYear.years)
|
||||||
|
}) ?.copy(
|
||||||
|
month = 1, // index1
|
||||||
|
dayOfMonth = 1, // index1
|
||||||
|
hour = 0,
|
||||||
|
minute = 0,
|
||||||
|
second = 0,
|
||||||
|
milliseconds = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
77
src/commonMain/kotlin/internal/Parser.kt
Normal file
77
src/commonMain/kotlin/internal/Parser.kt
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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(",")
|
||||||
|
|
||||||
|
val results = things.flatMap {
|
||||||
|
val currentToken = it.lowercase().replace(
|
||||||
|
"f", dataRange.first.toString()
|
||||||
|
).replace(
|
||||||
|
"l", dataRange.last.toString()
|
||||||
|
)
|
||||||
|
when {
|
||||||
|
currentToken.contains("-") -> {
|
||||||
|
val splitted = currentToken.split("-")
|
||||||
|
(splitted.first().toInt().coerceIn(dataRange) .. splitted[1].toInt().coerceIn(dataRange)).toList()
|
||||||
|
}
|
||||||
|
currentToken.contains("/") -> {
|
||||||
|
val (start, step) = currentToken.split("/")
|
||||||
|
val startNum = (if (start.isEmpty() || start == "*") {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
start.toInt()
|
||||||
|
}).coerceIn(dataRange)
|
||||||
|
val stepNum = step.toInt().coerceIn(dataRange)
|
||||||
|
(startNum .. dataRange.last step stepNum).map { it }
|
||||||
|
}
|
||||||
|
currentToken == "*" -> return null
|
||||||
|
else -> listOf(currentToken.toInt().coerceIn(dataRange))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.map(dataConverter)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null }
|
||||||
|
internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null }
|
||||||
|
internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }
|
||||||
|
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray()
|
||||||
|
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray()
|
||||||
|
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>,
|
||||||
|
createFactory: (CronDateTime, T) -> CronDateTime
|
||||||
|
) {
|
||||||
|
val previousValues = whereToPut.toList()
|
||||||
|
|
||||||
|
whereToPut.clear()
|
||||||
|
|
||||||
|
previousValues.forEach { previousValue ->
|
||||||
|
forEach {
|
||||||
|
whereToPut.add(createFactory(previousValue, it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun <T> T.fillWith(
|
||||||
|
whereToPut: MutableList<CronDateTime>,
|
||||||
|
createFactory: (CronDateTime, T) -> CronDateTime
|
||||||
|
) {
|
||||||
|
val previousValues = whereToPut.toList()
|
||||||
|
|
||||||
|
whereToPut.clear()
|
||||||
|
|
||||||
|
previousValues.forEach { previousValue ->
|
||||||
|
whereToPut.add(createFactory(previousValue, this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.insanusmokrassar.krontab.internal
|
package dev.inmo.krontab.internal
|
||||||
|
|
||||||
|
internal val dayOfWeekRange = 0 .. 6
|
||||||
|
internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE
|
||||||
internal val monthRange = 0 .. 11
|
internal val monthRange = 0 .. 11
|
||||||
internal val dayOfMonthRange = 0 .. 30
|
internal val dayOfMonthRange = 0 .. 30
|
||||||
internal val hoursRange = 0 .. 23
|
internal val hoursRange = 0 .. 23
|
||||||
internal val minutesRange = 0 .. 59
|
internal val minutesRange = 0 .. 59
|
||||||
internal val secondsRange = minutesRange
|
internal val secondsRange = minutesRange
|
||||||
|
internal val millisecondsRange = 0 .. 999
|
||||||
23
src/commonMain/kotlin/utils/DateTimeCopy.kt
Normal file
23
src/commonMain/kotlin/utils/DateTimeCopy.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.Month
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
fun DateTime.copy(
|
||||||
|
year: Int = yearInt,
|
||||||
|
month: Int = month1,
|
||||||
|
dayOfMonth: Int = this.dayOfMonth,
|
||||||
|
hour: Int = hours,
|
||||||
|
minute: Int = minutes,
|
||||||
|
second: Int = seconds,
|
||||||
|
milliseconds: Int = this.milliseconds
|
||||||
|
) = DateTime(
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
min(Month(month).days(yearInt), dayOfMonth),
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second,
|
||||||
|
milliseconds
|
||||||
|
)
|
||||||
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()
|
||||||
3
src/commonMain/kotlin/utils/Typealiases.kt
Normal file
3
src/commonMain/kotlin/utils/Typealiases.kt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
typealias Minutes = Int
|
||||||
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 com.insanusmokrassar.krontab.utils
|
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 {
|
fun CoroutineScope.createFailJob(forTimeMillis: Long) = launch {
|
||||||
delay(forTimeMillis)
|
delay(forTimeMillis)
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package com.insanusmokrassar.krontab.utils
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.builder.buildSchedule
|
import dev.inmo.krontab.builder.buildSchedule
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ class SchedulerFlowTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val flow = kronScheduler.asFlow()
|
val flow = kronScheduler.asFlowWithoutDelays()
|
||||||
|
|
||||||
runTest {
|
runTest {
|
||||||
val mustBeCollected = 10
|
val mustBeCollected = 10
|
||||||
@@ -41,14 +40,14 @@ class SchedulerFlowTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val flow = kronScheduler.asFlow()
|
val flow = kronScheduler.asFlowWithoutDelays()
|
||||||
|
|
||||||
runTest {
|
runTest {
|
||||||
val testsCount = 10
|
val testsCount = 10
|
||||||
val failJob = it.createFailJob((testsCount * 2) * 1000L)
|
val failJob = createFailJob((testsCount * 2) * 1000L)
|
||||||
val mustBeCollected = 10
|
val mustBeCollected = 10
|
||||||
val answers = (0 until testsCount).map { _ ->
|
val answers = (0 until testsCount).map { _ ->
|
||||||
it.async {
|
async {
|
||||||
var collected = 0
|
var collected = 0
|
||||||
flow.takeWhile {
|
flow.takeWhile {
|
||||||
collected < mustBeCollected
|
collected < mustBeCollected
|
||||||
138
src/commonTest/kotlin/StringParseTest.kt
Normal file
138
src/commonTest/kotlin/StringParseTest.kt
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
import korlibs.time.*
|
||||||
|
import dev.inmo.krontab.KronSchedulerTz
|
||||||
|
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 {
|
||||||
|
@Test
|
||||||
|
fun testThatFlowIsCorrectlyWorkEverySecondBuiltOnString() {
|
||||||
|
val kronScheduler = buildSchedule("*/1 * * * *")
|
||||||
|
|
||||||
|
val flow = kronScheduler.asFlowWithoutDelays()
|
||||||
|
|
||||||
|
runTest {
|
||||||
|
val mustBeCollected = 10
|
||||||
|
var collected = 0
|
||||||
|
flow.takeWhile {
|
||||||
|
collected < mustBeCollected
|
||||||
|
}.collect {
|
||||||
|
collected++
|
||||||
|
}
|
||||||
|
assertEquals(mustBeCollected, collected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
|
||||||
|
val kronScheduler = buildSchedule("*/1 * * * * 500ms")
|
||||||
|
|
||||||
|
val flow = kronScheduler.asFlowWithoutDelays()
|
||||||
|
|
||||||
|
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.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/commonTest/kotlin/TimeZoneTest.kt
Normal file
29
src/commonTest/kotlin/TimeZoneTest.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
class TimeZoneTest {
|
||||||
|
@Test
|
||||||
|
fun testDifferentTimeZonesReturnsDifferentTimes() {
|
||||||
|
val scheduler = buildSchedule { seconds { every(1) } }
|
||||||
|
val additionalMilliseconds = 100.milliseconds
|
||||||
|
val baseDate = DateTime.now().startOfWeek.copy(milliseconds = additionalMilliseconds.millisecondsInt)
|
||||||
|
runTest {
|
||||||
|
for (i in 0 until 7) {
|
||||||
|
val now = baseDate + i.days
|
||||||
|
for (j in 0 .. 24) {
|
||||||
|
val nowTz = now.toOffset(j.hours)
|
||||||
|
val next = scheduler.next(nowTz)!!
|
||||||
|
assertEquals(
|
||||||
|
(nowTz + 1.seconds - additionalMilliseconds).utc.unixMillisLong, next.utc.unixMillisLong
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/commonTest/kotlin/WeekDaysTest.kt
Normal file
39
src/commonTest/kotlin/WeekDaysTest.kt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package dev.inmo.krontab.utils
|
||||||
|
|
||||||
|
import korlibs.time.*
|
||||||
|
import dev.inmo.krontab.builder.buildSchedule
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class WeekDaysTest {
|
||||||
|
@Test
|
||||||
|
fun testThatWeekDaysSchedulingWorks() {
|
||||||
|
val startDateTime = DateTime.now().startOfDay
|
||||||
|
val weekDay = startDateTime.dayOfWeek.index0
|
||||||
|
val testDays = 400
|
||||||
|
val scheduler = buildSchedule {
|
||||||
|
dayOfWeek {
|
||||||
|
at(weekDay)
|
||||||
|
}
|
||||||
|
years {
|
||||||
|
at(startDateTime.yearInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runTest {
|
||||||
|
for (day in 0 until testDays) {
|
||||||
|
val currentDateTime = startDateTime + day.days
|
||||||
|
val next = scheduler.next(currentDateTime)
|
||||||
|
val expected = when {
|
||||||
|
day % 7 == 0 -> currentDateTime
|
||||||
|
else -> startDateTime + ceil(day.toFloat() / 7).weeks
|
||||||
|
}
|
||||||
|
if (expected.yearInt != startDateTime.yearInt) {
|
||||||
|
assertNull(next)
|
||||||
|
} else {
|
||||||
|
assertEquals(expected, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.utils
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Workaround to use suspending functions in unit tests
|
|
||||||
*/
|
|
||||||
expect fun runTest(block: suspend (scope : CoroutineScope) -> Unit)
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.utils
|
|
||||||
|
|
||||||
import com.insanusmokrassar.krontab.buildSchedule
|
|
||||||
import com.soywiz.klock.DateTime
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.takeWhile
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
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().toMutableList()
|
|
||||||
val expectedCollects = rangesEnds.sumBy { it.second - it.first + 1 }
|
|
||||||
var collected = 0
|
|
||||||
|
|
||||||
flow.takeWhile { ranges.isNotEmpty() }.collect {
|
|
||||||
ranges.remove(it.seconds)
|
|
||||||
collected++
|
|
||||||
}
|
|
||||||
assertEquals(expectedCollects, collected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package com.insanusmokrassar.krontab.utils
|
|
||||||
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
|
|
||||||
actual fun runTest(block: suspend (scope : CoroutineScope) -> Unit): dynamic = GlobalScope.promise { block(this) }
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.insanusmokrassar.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)
|
|
||||||
Reference in New Issue
Block a user