Compare commits

...

106 Commits

Author SHA1 Message Date
79ce455f5c Merge pull request #32 from InsanusMokrassar/0.7.2
0.7.2
2022-04-29 22:26:14 +06:00
fd54b035fe update yarn.lock 2022-04-29 22:18:13 +06:00
4fab7a28ff fix of #30 2022-04-29 22:14:42 +06:00
28d5665a14 fix of #31 and several refactorings 2022-04-29 21:57:10 +06:00
f98fd5ddb7 update dependencies 2022-04-29 20:44:51 +06:00
981327675a start 0.7.2 2022-04-29 20:43:33 +06:00
7649d20984 Merge pull request #28 from InsanusMokrassar/0.7.1
0.7.1
2022-02-27 22:08:03 +06:00
ab274636b9 workflows fix 2022-02-27 20:47:10 +06:00
7f2f8a4a26 revert android sdk version 2022-02-27 20:37:48 +06:00
f64171c0f0 update android compile sdk up to 33.0.0 2022-02-27 19:05:07 +06:00
8382108b01 start 0.7.1 2022-02-27 18:20:49 +06:00
32499d02fb Update kdocs.yml 2021-12-25 14:05:13 +06:00
2b06033c75 Merge pull request #25 from InsanusMokrassar/0.7.0
0.7.0
2021-12-25 00:25:16 +06:00
84f1063e3f Update gradle-wrapper.properties 2021-12-24 17:59:39 +06:00
347cf64cb2 Update CHANGELOG.md 2021-12-24 17:59:11 +06:00
eb73da67eb Update gradle.properties 2021-12-23 13:03:16 +06:00
4aadf513aa Update gradle.properties 2021-12-23 13:02:47 +06:00
8c011ea1fb Update publishing_packages.yml 2021-12-22 12:55:22 +06:00
aa9c2b8ea0 Update gradle.properties 2021-12-22 12:52:08 +06:00
adf10301dc Update gradle.properties 2021-12-12 13:37:30 +06:00
7799077e44 Update gradle-wrapper.properties 2021-12-05 10:57:26 +06:00
83339dc095 Update gradle.properties 2021-12-05 10:51:49 +06:00
3040951f37 optimize imports 2021-11-22 19:09:33 +06:00
0bfe022476 update dependencies and remove deprecations 2021-11-22 19:08:24 +06:00
06107f75f2 start 0.7.0 2021-11-22 18:59:44 +06:00
039fe1cb5f Merge pull request #24 from InsanusMokrassar/0.6.6
0.6.6
2021-11-12 16:14:27 +06:00
ea7fab6130 0.6.6 2021-11-12 16:06:08 +06:00
2f3796733d Hotfix for 0.6.5 flows 2021-09-26 14:17:01 +06:00
89a6a305f8 Merge pull request #23 from InsanusMokrassar/0.6.5
0.6.5
2021-09-26 14:08:01 +06:00
97752e1ac0 optimize imports 2021-09-26 13:06:22 +06:00
276ecaac27 overwrite do* extensions 2021-09-26 13:05:59 +06:00
d36e406b59 now flows are using doInfinity 2021-09-26 11:49:01 +06:00
4dc65bf09a delay in doWhile and flows with doWhile instead their own logic 2021-09-25 19:28:49 +06:00
539b5fbcd8 0.6.5 2021-09-25 18:51:51 +06:00
e4f417877d Update kdocs.yml 2021-09-23 13:22:27 +06:00
4f6054c0d9 Update build.gradle 2021-09-23 13:18:40 +06:00
88dbfee370 Update README.md 2021-09-22 20:57:21 +06:00
5af29c2246 Merge pull request #21 from InsanusMokrassar/0.6.4
0.6.4
2021-09-22 20:52:29 +06:00
6aa40ad463 fix publication workflow script 2021-09-22 20:26:12 +06:00
3062f839af fix for publishing packages 2021-09-22 20:20:13 +06:00
001756ab9f add fix for android build 2021-09-22 20:12:50 +06:00
b7ebd0ac1c update workflows 2021-09-22 20:08:06 +06:00
ece767e4d0 update android compile sdk version and build tools 2021-09-22 20:04:08 +06:00
222542b38f update version of build gradle tool 2021-09-22 19:46:35 +06:00
5798774b2d update androidx work dependency 2021-09-22 19:39:18 +06:00
32bf5c0641 update dependencies 2021-09-22 19:34:04 +06:00
5916f3eebb start 0.6.4 2021-09-22 19:29:54 +06:00
df01a66b5a Merge pull request #20 from InsanusMokrassar/0.6.3
0.6.3
2021-08-30 20:26:10 +06:00
a622168297 update dependencies 2021-08-30 18:51:17 +06:00
9202eabf90 start 0.6.3 2021-08-30 18:28:12 +06:00
bc612604eb Update README.md 2021-08-07 17:11:10 +06:00
8808c00dc2 Update README.md 2021-08-07 16:40:24 +06:00
f3db5fd2e4 update kdocs yaml 2021-08-07 16:26:06 +06:00
b66284b6e5 update kdocs script 2021-08-07 16:25:35 +06:00
2a16f3c588 update kdocs pdenendency 2021-08-07 16:20:43 +06:00
6d055739fe Update dokka.gradle 2021-08-07 16:10:59 +06:00
92ae81d7a7 Create kdocs.yml 2021-08-07 16:07:40 +06:00
9e9c09e364 Merge pull request #19 from InsanusMokrassar/0.6.2
0.6.2
2021-07-30 11:02:45 +06:00
a8be548a72 Update build.gradle 2021-07-29 19:35:07 +06:00
6ec77b4e6f Remove bintray from repos 2021-07-29 19:33:35 +06:00
0ecb34709b update workflows 2021-07-28 14:41:27 +06:00
0f973adb9f fill changelog, remove legacy compiler for js 2021-07-28 14:31:30 +06:00
abc7a63d2e Update gradle.properties 2021-07-28 11:37:43 +06:00
fbab0e232f Update README.md 2021-07-28 11:29:31 +06:00
57fa3a30a6 Update dependencies 2021-07-28 11:27:17 +06:00
f50564abe2 start 0.6.2 2021-07-28 11:24:38 +06:00
8276ebc7ec Update README.md 2021-07-28 11:23:55 +06:00
5a6084c573 Merge pull request #18 from InsanusMokrassar/0.6.1
0.6.1
2021-06-03 13:40:06 +06:00
437206e915 remove old comments 2021-06-03 12:55:16 +06:00
0ecc6f0028 upfill of changelog 2021-06-03 12:52:39 +06:00
fec17f949f fixes in tests 2021-06-03 12:37:13 +06:00
c3ed1d024c fix tests 2021-06-03 12:16:39 +06:00
b4790b892a partial fix 2021-05-30 18:53:54 +06:00
28f84d4e3a rewrite mechanism of calculating of near time 2021-05-30 18:53:54 +06:00
22ef00fe8f Update publishing_packages.yml 2021-05-30 09:00:57 +06:00
c509d35b00 update klock 2021-05-29 17:30:28 +06:00
3de66a7274 start 0.6.1 2021-05-29 17:25:40 +06:00
1f0387363d Merge pull request #17 from InsanusMokrassar/0.6.0
0.6.0
2021-05-25 15:48:02 +06:00
e816ad88bc Update CHANGELOG.md 2021-05-25 15:47:47 +06:00
404ea5e188 Update gradle.properties 2021-05-25 15:21:45 +06:00
63e110dbc8 Update CHANGELOG.md 2021-05-15 01:35:10 +06:00
39323f3cd1 Update gradle.properties 2021-05-15 01:34:46 +06:00
15e3430a55 update dependencies 2021-05-07 13:33:35 +06:00
fde5d91375 start 0.6.0 2021-05-07 12:59:13 +06:00
ba548969e1 Merge pull request #16 from InsanusMokrassar/0.5.2
0.5.2
2021-04-24 18:46:21 +06:00
c6bfc5c8f8 optimize imports before merge 2021-04-24 18:33:59 +06:00
301afb1ee0 updates and fixes 2021-04-24 18:33:37 +06:00
7e58ec0873 fixes in flow according to time zones 2021-04-24 16:58:25 +06:00
db1af7818e Update CHANGELOG.md 2021-04-24 01:53:57 +06:00
635a2d6a44 Update CHANGELOG.md 2021-04-24 01:47:29 +06:00
da88d18d4d add note about offsets 2021-04-22 12:44:22 +06:00
20f12f3f2d update readme to include week days info 2021-04-22 12:32:45 +06:00
86cdda51a1 tests and fixes in week work 2021-04-22 12:24:26 +06:00
bfa546ad1f add support of week days 2021-04-22 11:58:19 +06:00
0c7ceb03ee temporarily add nodejs crutch 2021-04-20 17:24:33 +06:00
b0889d0677 remove redundant changes 2021-04-10 15:38:55 +06:00
3408d58cb7 fixes for Tz 2021-04-10 15:38:55 +06:00
5c81eede9c add CronDateTimeSchedulerTz variant in CollectionKronScheduler 2021-04-10 00:33:05 +06:00
5844859369 rewrite system with offsets 2021-04-10 00:28:08 +06:00
a07027399c add offset parameter in strings 2021-03-30 14:07:39 +06:00
93246cada7 fix of next with DateTimeTz 2021-03-29 22:29:10 +06:00
5311e651f4 add work with time zones 2021-03-29 22:14:49 +06:00
68c49faa1d update kotlin 2021-03-29 21:32:39 +06:00
8ce351ecfe start 0.5.2 2021-03-29 21:30:24 +06:00
b43bbea341 Delete .travis.yml 2021-03-29 21:07:52 +06:00
fff010b890 Merge pull request #13 from InsanusMokrassar/0.5.1
0.5.1
2021-03-13 22:19:34 +06:00
35 changed files with 3211 additions and 278 deletions

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

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

View File

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

View File

@@ -1,10 +0,0 @@
language: java
install: true
os: linux
dist: trusty
jdk: oraclejdk8
script:
- ./gradlew build allTests -s

View File

@@ -1,5 +1,89 @@
# Changelog # Changelog
## 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 ## 0.5.1
* Versions * Versions

View File

@@ -1,20 +1,12 @@
# krontab # krontab
[![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab-mpp/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab-mpp/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
[![Build Status](https://travis-ci.com/InsanusMokrassar/krontab.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/krontab) [![Build Status](https://github.com/InsanusMokrassar/krontab/actions/workflows/publishing_packages.yml/badge.svg)](https://github.com/InsanusMokrassar/krontab/actions/workflows/publishing_packages.yml)
[![KDocs](https://raw.githubusercontent.com/InsanusMokrassar/badges/master/kdocs.svg)](https://krontab.inmo.dev/index.html)
Library was created to give oppotunity to launch some things from time to time according to some schedule in 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:
@@ -44,7 +36,7 @@ implementation "dev.inmo:krontab:$krontab_version"
Next version is the latest currently for the library: Next version is the latest currently for the library:
[ ![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab-mpp/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab-mpp/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/krontab)
For old version of Gradle, instead of `implementation` word developers must use `compile`. For old version of Gradle, instead of `implementation` word developers must use `compile`.
@@ -52,14 +44,17 @@ For old version of Gradle, instead of `implementation` word developers must use
Developers can use more simple way to configure repeat times is string. String configuring Developers can use more simple way to configure repeat times is string. String configuring
like a `crontab`, but with a little bit different meanings: like a `crontab`, but with a little bit different meanings:
``` ```
/---------- Seconds /--------------- Seconds
| /-------- Minutes | /------------- Minutes
| | /------ Hours | | /----------- Hours
| | | /---- Days of months | | | /--------- Days of months
| | | | /-- Months | | | | /------- Months
| | | | | / (optional) Year | | | | | /----- (optional) Year
* * * * * * | | | | | | /--- (optional) Timezone offset
| | | | | | | / (optional) Week days
* * * * * * 0o *w
``` ```
It is different with original `crontab` syntax for the reason, that expected that in practice developers It is different with original `crontab` syntax for the reason, that expected that in practice developers
@@ -128,6 +123,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 +179,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`

View File

@@ -1,7 +1,7 @@
buildscript { buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
jcenter() // jcenter()
mavenCentral() mavenCentral()
google() google()
} }
@@ -10,7 +10,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$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.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath 'com.android.tools.build:gradle:4.0.2' classpath 'com.android.tools.build:gradle:7.0.4'
} }
} }
@@ -18,6 +18,18 @@ plugins {
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version" id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
id "org.jetbrains.dokka" version "$dokka_version" id "org.jetbrains.dokka" version "$dokka_version"
} }
// temporal crutch until legacy tests will be stabled or legacy target will be removed
allprojects {
if (it != rootProject.findProject("docs")) {
tasks.whenTaskAdded { task ->
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
task.enabled = false
}
}
}
}
apply plugin: "com.android.library" apply plugin: "com.android.library"
project.version = "$version" project.version = "$version"
@@ -28,9 +40,9 @@ 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() google()
} }
@@ -38,7 +50,7 @@ apply from: './dokka.gradle'
kotlin { kotlin {
jvm() jvm()
js(BOTH) { js(IR) {
browser() browser()
nodejs() nodejs()
} }
@@ -113,10 +125,6 @@ android {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets { sourceSets {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
test { test {
@@ -124,3 +132,8 @@ android {
} }
} }
} }
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

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

View File

@@ -8,31 +8,30 @@ android.useAndroidX=true
android.enableJetifier=false android.enableJetifier=false
kotlin_version=1.4.31 kotlin_version=1.6.10
kotlin_coroutines_version=1.4.3 kotlin_coroutines_version=1.6.1
dokka_version=1.4.20 dokka_version=1.6.10
klockVersion=2.0.7 klockVersion=2.7.0
## Github reease ## Github reease
github_release_plugin_version=2.2.12 github_release_plugin_version=2.3.7
## Android ## Android
android_minSdkVersion=19 android_minSdkVersion=19
android_compileSdkVersion=30 android_compileSdkVersion=32
android_buildToolsVersion=30.0.2 android_buildToolsVersion=32.0.0
dexcount_version=2.0.0 dexcount_version=3.0.1
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.2 test_ext_junit_version=1.1.2
espresso_core=3.3.0 espresso_core=3.3.0
androidx_work_version=2.5.0 androidx_work_version=2.7.1
## Common ## Common
version=0.5.1 version=0.7.2
android_code_version=2 android_code_version=13

View File

@@ -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.8.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip

1998
kotlin-js-store/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,11 @@
package dev.inmo.krontab package dev.inmo.krontab
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz
import kotlinx.coroutines.delay 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. * Execute [block] once at the [KronScheduler.next] time and return result of [block] calculation.
@@ -10,11 +14,37 @@ import kotlinx.coroutines.delay
* *
* WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately * WARNING!!! In case if [KronScheduler.next] of [this] instance will return null, [block] will be called immediately
*/ */
suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T { suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend (DateTime) -> T): T {
next() ?.let { val time = nextOrNow().also {
delay((it - DateTime.now()).millisecondsLong) delay((it - DateTime.now()).millisecondsLong)
} }
return block() 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(noinline block: suspend (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)
} }
/** /**
@@ -23,14 +53,40 @@ suspend inline fun <T> KronScheduler.doOnce(noinline block: suspend () -> T): T
*/ */
suspend inline fun <T> doOnce( suspend inline fun <T> doOnce(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend () -> T noinline block: suspend (DateTime) -> T
) = buildSchedule(scheduleConfig).doOnce(block) ) = 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 * Will execute [block] while it will return true as a result of its calculation
*/ */
suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean) { suspend inline fun KronScheduler.doWhile(noinline block: suspend (DateTime) -> Boolean) {
do { val doNext = doOnce(block) } while (doNext) 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(noinline block: suspend (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))
} }
/** /**
@@ -40,16 +96,52 @@ suspend inline fun KronScheduler.doWhile(noinline block: suspend () -> Boolean)
*/ */
suspend inline fun doWhile( suspend inline fun doWhile(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend () -> Boolean noinline block: suspend (DateTime) -> Boolean
) = buildSchedule(scheduleConfig).doWhile(block) ) = 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,
noinline block: suspend (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 * Will execute [block] without any checking of result
*/ */
suspend inline fun KronScheduler.doInfinity(noinline block: suspend () -> Unit) = doWhile { suspend inline fun KronScheduler.doInfinity(noinline block: suspend (DateTime) -> Unit) = doWhile {
block() block(it)
true coroutineContext.isActive
} }
/**
* Will execute [block] without any checking of result
*/
@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity"))
suspend inline fun KronScheduler.doInfinityLocal(noinline block: suspend (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] * Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
* *
@@ -57,5 +149,26 @@ suspend inline fun KronScheduler.doInfinity(noinline block: suspend () -> Unit)
*/ */
suspend inline fun doInfinity( suspend inline fun doInfinity(
scheduleConfig: String, scheduleConfig: String,
noinline block: suspend () -> Unit noinline block: suspend (DateTime) -> Unit
) = buildSchedule(scheduleConfig).doInfinity(block) ) = 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,
noinline block: suspend (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)

View File

@@ -1,7 +1,6 @@
package dev.inmo.krontab package dev.inmo.krontab
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.internal.toNearDateTime
/** /**
* This interface was created for abstraction of [next] operation. Currently, there is only * This interface was created for abstraction of [next] operation. Currently, there is only

View File

@@ -0,0 +1,34 @@
package dev.inmo.krontab
import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz
/**
* This interface extending [KronScheduler] to use [DateTimeTz] with taking into account offset of incoming time for
* [next] operation.
*
* @see dev.inmo.krontab.internal.CronDateTimeScheduler
* @see dev.inmo.krontab.KronScheduler
*/
interface KronSchedulerTz : KronScheduler {
suspend fun next(relatively: DateTimeTz): DateTimeTz?
override suspend fun next(relatively: DateTime): DateTime? = next(relatively.localUnadjusted) ?.local
}
suspend fun KronSchedulerTz.nextOrRelative(relatively: DateTimeTz): DateTimeTz = next(relatively) ?: getAnyNext(
relatively.local
).toOffsetUnadjusted(relatively.offset)
suspend fun KronSchedulerTz.nextOrNowWithOffset(): DateTimeTz = DateTimeTz.nowLocal().let {
next(it) ?: getAnyNext(
it.local
).toOffsetUnadjusted(it.offset)
}
suspend fun KronScheduler.next(relatively: DateTimeTz) = if (this is KronSchedulerTz) {
this.next(relatively)
} else {
this.next(relatively.local) ?.toOffsetUnadjusted(relatively.offset)
}
suspend fun KronScheduler.nextTimeZoned() = next(DateTime.now().local)

View File

@@ -1,8 +1,8 @@
package dev.inmo.krontab package dev.inmo.krontab
import dev.inmo.krontab.collection.CollectionKronScheduler import dev.inmo.krontab.collection.CollectionKronScheduler
import dev.inmo.krontab.internal.CronDateTime import dev.inmo.krontab.collection.includeAll
import dev.inmo.krontab.internal.CronDateTimeScheduler import dev.inmo.krontab.internal.*
/** /**
* Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator] * Create new one [CollectionKronScheduler] to include all [KronScheduler]s of [this] [Iterator]
@@ -10,17 +10,22 @@ import dev.inmo.krontab.internal.CronDateTimeScheduler
* @see CollectionKronScheduler * @see CollectionKronScheduler
* @see CollectionKronScheduler.include * @see CollectionKronScheduler.include
*/ */
fun Iterator<KronScheduler>.merge(): KronScheduler { fun Iterator<KronScheduler>.merge(): CollectionKronScheduler {
val cronDateTimes = mutableListOf<CronDateTime>() val cronDateTimes = mutableListOf<CronDateTime>()
val timezonedCronDateTimes = mutableListOf<CronDateTimeSchedulerTz>()
val collectionScheduler = CollectionKronScheduler() val collectionScheduler = CollectionKronScheduler()
forEach { forEach {
when (it) { when (it) {
is CronDateTimeScheduler -> cronDateTimes.addAll(it.cronDateTimes) is CronDateTimeScheduler -> cronDateTimes.add(it.cronDateTime)
is CronDateTimeSchedulerTz -> timezonedCronDateTimes.add(it)
else -> collectionScheduler.include(it) else -> collectionScheduler.include(it)
} }
} }
if (cronDateTimes.isNotEmpty()) { if (cronDateTimes.isNotEmpty()) {
collectionScheduler.include(CronDateTimeScheduler(cronDateTimes)) collectionScheduler.include(CronDateTimeScheduler(cronDateTimes.merge()))
}
if (timezonedCronDateTimes.isNotEmpty()) {
collectionScheduler.includeAll(mergeCronDateTimeSchedulers(timezonedCronDateTimes))
} }
return collectionScheduler return collectionScheduler
} }
@@ -32,10 +37,10 @@ fun Iterator<KronScheduler>.merge(): KronScheduler {
* @see CollectionKronScheduler.include * @see CollectionKronScheduler.include
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun Iterable<KronScheduler>.merge(): KronScheduler = iterator().merge() inline fun Iterable<KronScheduler>.merge(): CollectionKronScheduler = iterator().merge()
/** /**
* @return Vararg shortcut for [merge] * @return Vararg shortcut for [merge]
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun merge(vararg kronDateTimeSchedulers: KronScheduler) = kronDateTimeSchedulers.iterator().merge() inline fun merge(vararg kronDateTimeSchedulers: KronScheduler): CollectionKronScheduler = kronDateTimeSchedulers.iterator().merge()

View File

@@ -3,8 +3,6 @@ package dev.inmo.krontab
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.builder.buildSchedule import dev.inmo.krontab.builder.buildSchedule
import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.CronDateTime
import dev.inmo.krontab.internal.CronDateTimeScheduler
internal val anyCronDateTime by lazy { internal val anyCronDateTime by lazy {
CronDateTime() CronDateTime()
@@ -15,7 +13,14 @@ internal fun getAnyNext(relatively: DateTime) = anyCronDateTime.toNearDateTime(r
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] * [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now]
*/ */
val AnyTimeScheduler: KronScheduler by lazy { val AnyTimeScheduler: KronScheduler by lazy {
CronDateTimeScheduler(listOf(anyCronDateTime)) CronDateTimeScheduler(anyCronDateTime)
}
/**
* [KronScheduler.next] will always return [com.soywiz.klock.DateTime.now] + one millisecond
*/
val EveryMillisecondScheduler: KronScheduler by lazy {
buildSchedule { milliseconds { 0 every 1 } }
} }
/** /**
@@ -58,4 +63,4 @@ val EveryMonthScheduler: KronScheduler by lazy {
*/ */
val EveryYearScheduler: KronScheduler by lazy { val EveryYearScheduler: KronScheduler by lazy {
buildSchedule { years { 0 every 1 } } buildSchedule { years { 0 every 1 } }
} }

View File

@@ -1,6 +1,9 @@
package dev.inmo.krontab package dev.inmo.krontab
import com.soywiz.klock.TimezoneOffset
import com.soywiz.klock.minutes
import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.*
import dev.inmo.krontab.utils.Minutes
/** /**
* @see createSimpleScheduler * @see createSimpleScheduler
@@ -11,14 +14,17 @@ typealias KrontabTemplate = String
/** /**
* Parse [incoming] string and adapt according to next format: "* * * * *" where order of things: * Parse [incoming] string and adapt according to next format: "* * * * *" where order of things:
* *
* * seconds * * **seconds**
* * minutes * * **minutes**
* * hours * * **hours**
* * dayOfMonth * * **dayOfMonth**
* * month * * **month**
* * (optional) year * * **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 have next format: * And each one (except of offsets) have next format:
* *
* `{number}[,{number},...]` or `*` * `{number}[,{number},...]` or `*`
* *
@@ -31,6 +37,9 @@ typealias KrontabTemplate = String
* * F * * F
* * L * * L
* *
* Week days must be marked with `w` at the end, and starts with 0 which means Sunday. For example, 0w == Sunday. With
* weeks you can use syntax like with any number like seconds, for example: 0-2w means Sunday-Tuesday
*
* Additional info about ranges can be found in follow accordance: * Additional info about ranges can be found in follow accordance:
* *
* * Seconds ranges can be found in [secondsRange] * * Seconds ranges can be found in [secondsRange]
@@ -39,21 +48,56 @@ typealias KrontabTemplate = String
* * Days of month ranges can be found in [dayOfMonthRange] * * Days of month ranges can be found in [dayOfMonthRange]
* * Months ranges can be found in [monthRange] * * Months ranges can be found in [monthRange]
* * Years ranges can be found in [yearRange] (in fact - any [Int]) * * Years ranges can be found in [yearRange] (in fact - any [Int])
* * WeekDay (timezone) ranges can be found in [dayOfWeekRange]
* * Milliseconds ranges can be found in [millisecondsRange]
* *
* Examples: * Examples:
* *
* * "0/5 * * * *" for every five seconds triggering * * "0/5 * * * *" for every five seconds triggering
* * "0/5,L * * * *" for every five seconds triggering and on 59 second * * "0/5,L * * * *" for every five seconds triggering and on 59 second
* * "0/15 30 * * *" for every 15th seconds in a half of each hour * * "0/15 30 * * *" for every 15th seconds in a half of each hour
* * "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 fourth day of may * * "1 2 3 F,4,L 5" for triggering in near first second of second minute of third hour of fourth day of may
* * "1 2 3 F,4,L 5 60o" for triggering in near first second of second minute of third hour of fourth day of may with timezone UTC+01:00
* * "1 2 3 F,4,L 5 60o 0-2w" for triggering in near first second of second minute of third hour of fourth day of may in case if it will be in Sunday-Tuesday week days with timezone UTC+01:00
* * "1 2 3 F,4,L 5 2021" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year * * "1 2 3 F,4,L 5 2021" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year
* * "1 2 3 F,4,L 5 2021 60o" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year with timezone UTC+01:00
* * "1 2 3 F,4,L 5 2021 60o 0-2w" for triggering in near first second of second minute of third hour of fourth day of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00
* * "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 fourth day 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 dev.inmo.krontab.internal.createKronScheduler
*/ */
fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler { fun createSimpleScheduler(
val yearSource: String? incoming: KrontabTemplate
): KronScheduler {
var offsetParsed: Int? = null
var dayOfWeekParsed: Array<Byte>? = null
var yearParsed: Array<Int>? = null
var millisecondsParsed: Array<Short>? = null
val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also { val (secondsSource, minutesSource, hoursSource, dayOfMonthSource, monthSource) = incoming.split(" ").also {
yearSource = it.getOrNull(5) listOfNotNull(
it.getOrNull(5),
it.getOrNull(6),
it.getOrNull(7),
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 secondsParsed = parseSeconds(secondsSource)
@@ -61,24 +105,69 @@ fun createSimpleScheduler(incoming: KrontabTemplate): KronScheduler {
val hoursParsed = parseHours(hoursSource) val hoursParsed = parseHours(hoursSource)
val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource) val dayOfMonthParsed = parseDaysOfMonth(dayOfMonthSource)
val monthParsed = parseMonths(monthSource) val monthParsed = parseMonths(monthSource)
val yearParsed = parseYears(yearSource)
return createKronScheduler( return offsetParsed ?.let { offset ->
secondsParsed, minutesParsed, hoursParsed, dayOfMonthParsed, monthParsed, yearParsed createKronSchedulerWithOffset(
secondsParsed,
minutesParsed,
hoursParsed,
dayOfMonthParsed,
monthParsed,
yearParsed,
dayOfWeekParsed,
TimezoneOffset(offset.minutes),
millisecondsParsed ?: millisecondsArrayDefault
)
} ?: createKronScheduler(
secondsParsed,
minutesParsed,
hoursParsed,
dayOfMonthParsed,
monthParsed,
yearParsed,
dayOfWeekParsed,
millisecondsParsed ?: millisecondsArrayDefault
) )
} }
fun createSimpleScheduler(
incoming: KrontabTemplate,
defaultOffset: Minutes
): KronSchedulerTz {
val scheduler = createSimpleScheduler(incoming)
return if (scheduler is KronSchedulerTz) {
scheduler
} else {
CronDateTimeSchedulerTz(
(scheduler as CronDateTimeScheduler).cronDateTime,
TimezoneOffset(defaultOffset.minutes)
)
}
}
/** /**
* Shortcut for [createSimpleScheduler] * Shortcut for [createSimpleScheduler]
*/ */
fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming) fun buildSchedule(incoming: KrontabTemplate): KronScheduler = createSimpleScheduler(incoming)
/**
* Shortcut for [createSimpleScheduler]
*/
fun buildSchedule(incoming: KrontabTemplate, defaultOffset: Minutes): KronSchedulerTz = createSimpleScheduler(incoming, defaultOffset)
/** /**
* Shortcut for [buildSchedule] * Shortcut for [buildSchedule]
*/ */
fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this) fun KrontabTemplate.toSchedule(): KronScheduler = buildSchedule(this)
/**
* Shortcut for [buildSchedule]
*/
fun KrontabTemplate.toSchedule(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset)
/** /**
* Shortcut for [buildSchedule] * Shortcut for [buildSchedule]
*/ */
fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this) fun KrontabTemplate.toKronScheduler(): KronScheduler = buildSchedule(this)
/**
* Shortcut for [buildSchedule]
*/
fun KrontabTemplate.toKronScheduler(defaultOffset: Minutes): KronSchedulerTz = buildSchedule(this, defaultOffset)

View File

@@ -1,7 +1,13 @@
package dev.inmo.krontab.builder package dev.inmo.krontab.builder
import com.soywiz.klock.TimezoneOffset
import com.soywiz.klock.minutes
import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.KronScheduler
import dev.inmo.krontab.KronSchedulerTz
import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.createKronScheduler import dev.inmo.krontab.internal.createKronScheduler
import dev.inmo.krontab.internal.createKronSchedulerWithOffset
import dev.inmo.krontab.utils.Minutes
/** /**
* Will help to create an instance of [KronScheduler] * Will help to create an instance of [KronScheduler]
@@ -16,13 +22,32 @@ fun buildSchedule(settingsBlock: SchedulerBuilder.() -> Unit): KronScheduler {
return builder.build() return builder.build()
} }
/**
* Will help to create an instance of [KronScheduler]
*
* @see dev.inmo.krontab.createSimpleScheduler
*/
fun buildSchedule(
offset: Minutes,
settingsBlock: SchedulerBuilder.() -> Unit
): KronSchedulerTz {
val builder = SchedulerBuilder(offset = offset)
builder.settingsBlock()
return builder.build() as KronSchedulerTz
}
class SchedulerBuilder( class SchedulerBuilder(
private var seconds: Array<Byte>? = null, private var seconds: Array<Byte>? = null,
private var minutes: Array<Byte>? = null, private var minutes: Array<Byte>? = null,
private var hours: Array<Byte>? = null, private var hours: Array<Byte>? = null,
private var dayOfMonth: Array<Byte>? = null, private var dayOfMonth: Array<Byte>? = null,
private var month: Array<Byte>? = null, private var month: Array<Byte>? = null,
private var year: Array<Int>? = null private var year: Array<Int>? = null,
private var dayOfWeek: Array<Byte>? = null,
private val offset: Minutes? = null,
private var milliseconds: Array<Short>? = null
) { ) {
private fun <I, T : TimeBuilder<I>> callAndReturn( private fun <I, T : TimeBuilder<I>> callAndReturn(
initial: Array<I>?, initial: Array<I>?,
@@ -40,6 +65,17 @@ class SchedulerBuilder(
} ?: builderValue } ?: builderValue
} }
/**
* Starts an milliseconds block
*/
fun milliseconds(block: MillisecondsBuilder.() -> Unit) {
milliseconds = callAndReturn(
milliseconds,
MillisecondsBuilder(),
block
) ?.toTypedArray()
}
/** /**
* Starts an seconds block * Starts an seconds block
*/ */
@@ -84,6 +120,17 @@ class SchedulerBuilder(
) ?.toTypedArray() ) ?.toTypedArray()
} }
/**
* Starts an hours block
*/
fun dayOfWeek(block: WeekDaysBuilder.() -> Unit) {
dayOfWeek = callAndReturn(
dayOfWeek,
WeekDaysBuilder(),
block
) ?.toTypedArray()
}
/** /**
* Starts an months block * Starts an months block
*/ */
@@ -112,5 +159,17 @@ class SchedulerBuilder(
* @see dev.inmo.krontab.createSimpleScheduler * @see dev.inmo.krontab.createSimpleScheduler
* @see dev.inmo.krontab.internal.createKronScheduler * @see dev.inmo.krontab.internal.createKronScheduler
*/ */
fun build(): KronScheduler = createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year) fun build(): KronScheduler = offset ?.let {
createKronSchedulerWithOffset(
seconds,
minutes,
hours,
dayOfMonth,
month,
year,
dayOfWeek,
TimezoneOffset(it.minutes),
milliseconds ?: millisecondsArrayDefault
)
} ?: createKronScheduler(seconds, minutes, hours, dayOfMonth, month, year, dayOfWeek, milliseconds ?: millisecondsArrayDefault)
} }

View File

@@ -1,11 +1,10 @@
package dev.inmo.krontab.builder package dev.inmo.krontab.builder
import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.*
import dev.inmo.krontab.utils.clamp
/** /**
* This class was created for incapsulation of builder work with specified [restrictionsRange]. For example, * This class was created for incapsulation of builder work with specified [restrictionsRange]. For example,
* [include] function of [TimeBuilder] will always [clamp] incoming data using its [restrictionsRange] * [include] function of [TimeBuilder] will always [coerceIn] incoming data using its [restrictionsRange]
*/ */
sealed class TimeBuilder<T : Number> ( sealed class TimeBuilder<T : Number> (
private val restrictionsRange: IntRange, private val restrictionsRange: IntRange,
@@ -37,7 +36,7 @@ sealed class TimeBuilder<T : Number> (
*/ */
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
infix fun include(array: Array<Int>) { infix fun include(array: Array<Int>) {
val clamped = array.map { it.clamp(restrictionsRange) } + (result ?: emptySet()) val clamped = array.map { it.coerceIn(restrictionsRange) } + (result ?: emptySet())
result = clamped.toSet() result = clamped.toSet()
} }
@@ -46,7 +45,7 @@ sealed class TimeBuilder<T : Number> (
*/ */
@Suppress("unused") @Suppress("unused")
infix fun at(value: Int) { infix fun at(value: Int) {
result = (result ?: emptySet()) + value.clamp(restrictionsRange) result = (result ?: emptySet()) + value.coerceIn(restrictionsRange)
} }
@@ -70,7 +69,7 @@ sealed class TimeBuilder<T : Number> (
* @see [from] * @see [from]
*/ */
infix fun Int.every(delay: Int): Array<Int> { infix fun Int.every(delay: Int): Array<Int> {
val progression = clamp(restrictionsRange) .. restrictionsRange.last step delay val progression = coerceIn(restrictionsRange) .. restrictionsRange.last step delay
val result = progression.toSet().toTypedArray() val result = progression.toSet().toTypedArray()
this@TimeBuilder include result this@TimeBuilder include result
@@ -88,7 +87,7 @@ sealed class TimeBuilder<T : Number> (
*/ */
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
infix fun Int.upTo(endIncluding: Int): Array<Int> { infix fun Int.upTo(endIncluding: Int): Array<Int> {
val progression = clamp(restrictionsRange) .. endIncluding.clamp(restrictionsRange) val progression = coerceIn(restrictionsRange) .. endIncluding.coerceIn(restrictionsRange)
val result = progression.toSet().toTypedArray() val result = progression.toSet().toTypedArray()
this@TimeBuilder include result this@TimeBuilder include result
@@ -123,9 +122,11 @@ sealed class TimeBuilder<T : Number> (
internal fun build() = result ?.map(converter) internal fun build() = result ?.map(converter)
} }
class MillisecondsBuilder : TimeBuilder<Short>(millisecondsRange, intToShortConverter)
class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter) class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter) class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter) class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter) class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter) class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter) class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)
class WeekDaysBuilder : TimeBuilder<Byte>(dayOfWeekRange, intToByteConverter)

View File

@@ -1,17 +1,16 @@
package dev.inmo.krontab.collection package dev.inmo.krontab.collection
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeTz
import dev.inmo.krontab.* import dev.inmo.krontab.*
import dev.inmo.krontab.internal.* import dev.inmo.krontab.internal.*
import dev.inmo.krontab.internal.CronDateTimeScheduler
import dev.inmo.krontab.internal.toNearDateTime
/** /**
* This scheduler will be useful in case you want to unite several different [KronScheduler]s * This scheduler will be useful in case you want to unite several different [KronScheduler]s
*/ */
data class CollectionKronScheduler internal constructor( data class CollectionKronScheduler internal constructor(
internal val schedulers: MutableList<KronScheduler> internal val schedulers: MutableList<KronScheduler>
) : KronScheduler { ) : KronSchedulerTz {
internal constructor() : this(mutableListOf()) internal constructor() : this(mutableListOf())
/** /**
@@ -38,6 +37,18 @@ data class CollectionKronScheduler internal constructor(
mergeCronDateTimeSchedulers(resultCronDateTimes) mergeCronDateTimeSchedulers(resultCronDateTimes)
) )
} }
is CronDateTimeSchedulerTz -> {
val newCronDateTimes = 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 { is CollectionKronScheduler -> kronScheduler.schedulers.forEach {
include(it) include(it)
} }
@@ -48,4 +59,8 @@ data class CollectionKronScheduler internal constructor(
override suspend fun next(relatively: DateTime): DateTime { override suspend fun next(relatively: DateTime): DateTime {
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively) return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively)
} }
override suspend fun next(relatively: DateTimeTz): DateTimeTz {
return schedulers.mapNotNull { it.next(relatively) }.minOrNull() ?: getAnyNext(relatively.local).toOffsetUnadjusted(relatively.offset)
}
} }

View File

@@ -1,80 +1,76 @@
package dev.inmo.krontab.internal package dev.inmo.krontab.internal
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import com.soywiz.klock.DateTimeSpan import com.soywiz.klock.TimezoneOffset
import dev.inmo.krontab.KronScheduler import dev.inmo.krontab.KronScheduler
/** /**
* @param month 0-11 * @param daysOfWeek 0-6
* @param dayOfMonth 0-31 * @param years any int
* @param months 0-11
* @param daysOfMonth 0-31
* @param hours 0-23 * @param hours 0-23
* @param minutes 0-59 * @param minutes 0-59
* @param seconds 0-59 * @param seconds 0-59
*/ */
internal data class CronDateTime( internal data class CronDateTime(
val year: Int? = null, val daysOfWeek: Array<Byte>? = null,
val month: Byte? = null, val years: Array<Int>? = null,
val dayOfMonth: Byte? = null, val months: Array<Byte>? = null,
val hours: Byte? = null, val daysOfMonth: Array<Byte>? = null,
val minutes: Byte? = null, val hours: Array<Byte>? = null,
val seconds: Byte? = null val minutes: Array<Byte>? = null,
val seconds: Array<Byte>? = null,
val milliseconds: Array<Short>? = millisecondsArrayDefault
) { ) {
init { init {
check(year ?.let { it in yearRange } ?: true) check(daysOfWeek ?.all { it in dayOfWeekRange } ?: true)
check(month ?.let { it in monthRange } ?: true) check(years?.all { it in yearRange } ?: true)
check(dayOfMonth ?.let { it in dayOfMonthRange } ?: true) check(months?.all { it in monthRange } ?: true)
check(hours?.let { it in hoursRange } ?: true) check(daysOfMonth ?.all { it in dayOfMonthRange } ?: true)
check(minutes?.let { it in minutesRange } ?: true) check(hours?.all { it in hoursRange } ?: true)
check(seconds?.let { it in secondsRange } ?: true) check(minutes?.all { it in minutesRange } ?: true)
check(seconds?.all { it in secondsRange } ?: true)
check(milliseconds?.all { it in millisecondsRange } ?: true)
} }
internal val klockDayOfMonth = dayOfMonth ?.plus(1) 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(
* @return The near [DateTime] which happens after [relativelyTo] or will be equal to [relativelyTo] seconds: Array<Byte>? = null,
*/ minutes: Array<Byte>? = null,
internal fun CronDateTime.toNearDateTime(relativelyTo: DateTime = DateTime.now()): DateTime? { hours: Array<Byte>? = null,
var current = relativelyTo dayOfMonth: Array<Byte>? = null,
month: Array<Byte>? = null,
seconds?.let { years: Array<Int>? = null,
val left = it - current.seconds weekDays: Array<Byte>? = null,
current += DateTimeSpan(minutes = if (left <= 0) 1 else 0, seconds = left) milliseconds: Array<Short>? = millisecondsArrayDefault
} ): CronDateTime {
return CronDateTime(weekDays, years, month, dayOfMonth, hours, minutes, seconds, milliseconds)
minutes?.let {
val left = it - current.minutes
current += DateTimeSpan(hours = if (left < 0) 1 else 0, minutes = left)
}
hours?.let {
val left = it - current.hours
current += DateTimeSpan(days = if (left < 0) 1 else 0, hours = left)
}
klockDayOfMonth ?.let {
val left = (it - current.dayOfMonth).let { diff ->
if (diff > 0 && current.endOfMonth.run { it > dayOfMonth && current.dayOfMonth == dayOfMonth }) {
0
} else {
diff
}
}
current += DateTimeSpan(months = if (left < 0) 1 else 0, days = left)
}
month ?.let {
val left = it - current.month0
current += DateTimeSpan(years = if (left < 0) 1 else 0, months = left)
}
year ?.let {
if (current.yearInt != it) {
return null
}
}
return current
} }
/** /**
@@ -86,33 +82,55 @@ internal fun createKronScheduler(
hours: Array<Byte>? = null, hours: Array<Byte>? = null,
dayOfMonth: Array<Byte>? = null, dayOfMonth: Array<Byte>? = null,
month: Array<Byte>? = null, month: Array<Byte>? = null,
years: Array<Int>? = null years: Array<Int>? = null,
): KronScheduler { weekDays: Array<Byte>? = null,
val resultCronDateTimes = mutableListOf(CronDateTime()) 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
)
seconds ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> internal fun List<CronDateTime>.merge() = CronDateTime(
previousCronDateTime.copy(seconds = currentTime) 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() },
minutes ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> flatMap { it.daysOfMonth ?.toList() ?: emptyList() }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
previousCronDateTime.copy(minutes = currentTime) 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() },
hours ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte -> flatMap { it.milliseconds ?.toList() ?: listOf(0) }.distinct().toTypedArray().takeIf { it.isNotEmpty() },
previousCronDateTime.copy(hours = currentTime) )
}
dayOfMonth ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(dayOfMonth = currentTime)
}
month ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Byte ->
previousCronDateTime.copy(month = currentTime)
}
years ?.fillWith(resultCronDateTimes) { previousCronDateTime: CronDateTime, currentTime: Int ->
previousCronDateTime.copy(year = currentTime)
}
return CronDateTimeScheduler(resultCronDateTimes.toList())
}

View File

@@ -1,13 +1,13 @@
package dev.inmo.krontab.internal package dev.inmo.krontab.internal
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.* import dev.inmo.krontab.KronScheduler
import dev.inmo.krontab.collection.plus
/** /**
* Cron-oriented realisation of [KronScheduler] * Cron-oriented realisation of [KronScheduler]
* *
* @see dev.inmo.krontab.AnyTimeScheduler * @see dev.inmo.krontab.AnyTimeScheduler
* @see dev.inmo.krontab.EveryMillisecondScheduler
* @see dev.inmo.krontab.EverySecondScheduler * @see dev.inmo.krontab.EverySecondScheduler
* @see dev.inmo.krontab.EveryMinuteScheduler * @see dev.inmo.krontab.EveryMinuteScheduler
* @see dev.inmo.krontab.EveryHourScheduler * @see dev.inmo.krontab.EveryHourScheduler
@@ -19,38 +19,20 @@ import dev.inmo.krontab.collection.plus
* @see dev.inmo.krontab.builder.SchedulerBuilder * @see dev.inmo.krontab.builder.SchedulerBuilder
*/ */
internal data class CronDateTimeScheduler internal constructor( internal data class CronDateTimeScheduler internal constructor(
internal val cronDateTimes: List<CronDateTime> internal val cronDateTime: CronDateTime
) : KronScheduler { ) : KronScheduler {
/** /**
* @return Near date using [cronDateTimes] list and getting the [Iterable.minByOrNull] one * @return Near date using [cronDateTimes] list and getting the [Iterable.minByOrNull] one
* *
* @see toNearDateTime * @see toNearDateTime
*/ */
override suspend fun next(relatively: DateTime): DateTime { override suspend fun next(relatively: DateTime): DateTime? {
return cronDateTimes.mapNotNull { it.toNearDateTime(relatively) }.minOrNull() ?: getAnyNext(relatively) return cronDateTime.toNearDateTime(relatively)
} }
} }
internal fun mergeCronDateTimeSchedulers(schedulers: List<CronDateTimeScheduler>) = CronDateTimeScheduler( internal fun mergeCronDateTimeSchedulers(
schedulers.flatMap { it.cronDateTimes } schedulers: List<CronDateTimeScheduler>
): CronDateTimeScheduler = CronDateTimeScheduler(
schedulers.map { it.cronDateTime }.merge()
) )
/**
* @return New instance of [CronDateTimeScheduler] with all unique [CronDateTimeScheduler.cronDateTimes] of
* [kronSchedulers] included
*/
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
fun merge(kronSchedulers: List<KronScheduler>) = kronSchedulers.apply { dev.inmo.krontab.merge() }
/**
* @return Vararg shortcut for [dev.inmo.krontab.merge]
*/
@Suppress("NOTHING_TO_INLINE")
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
inline fun merge(vararg kronDateTimeSchedulers: KronScheduler) = kronDateTimeSchedulers.apply { dev.inmo.krontab.merge() }
/**
* @return Vararg shortcut for [dev.inmo.krontab.merge]
*/
@Suppress("NOTHING_TO_INLINE")
@Deprecated("Will be removed in next major release", ReplaceWith("merge", "dev.inmo.krontab"))
inline fun KronScheduler.plus(other: KronScheduler) = this + other

View File

@@ -0,0 +1,32 @@
package dev.inmo.krontab.internal
import com.soywiz.klock.DateTimeTz
import com.soywiz.klock.TimezoneOffset
import dev.inmo.krontab.KronScheduler
import dev.inmo.krontab.KronSchedulerTz
/**
* Cron-oriented realisation of [KronScheduler] with taking into account [offset] for list of [cronDateTimes]
*
* @see CronDateTime
*/
internal data class CronDateTimeSchedulerTz internal constructor(
internal val 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)
}
}
internal fun mergeCronDateTimeSchedulers(
schedulers: List<CronDateTimeSchedulerTz>
) = schedulers.groupBy {
it.offset
}.map { (offset, schedulers) ->
CronDateTimeSchedulerTz(
schedulers.map { it.cronDateTime }.merge(),
offset
)
}

View File

@@ -0,0 +1,4 @@
package dev.inmo.krontab.internal
internal const val millisecondsDefault: Short = 0
internal val millisecondsArrayDefault: Array<Short> = arrayOf(millisecondsDefault)

View File

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

View File

@@ -1,16 +1,15 @@
package dev.inmo.krontab.internal package dev.inmo.krontab.internal
import dev.inmo.krontab.utils.clamp
typealias Converter<T> = (Int) -> T typealias Converter<T> = (Int) -> T
internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() } internal val intToByteConverter: Converter<Byte> = { it: Int -> it.toByte() }
internal val intToShortConverter: Converter<Short> = { it: Int -> it.toShort() }
internal val intToIntConverter: Converter<Int> = { it: Int -> it } internal val intToIntConverter: Converter<Int> = { it: Int -> it }
private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter<T>): List<T>? { private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataConverter: Converter<T>): List<T>? {
val things = from.split(",") val things = from.split(",")
val results = things.flatMap { val results = things.flatMap {
val currentToken = it.toLowerCase().replace( val currentToken = it.lowercase().replace(
"f", dataRange.first.toString() "f", dataRange.first.toString()
).replace( ).replace(
"l", dataRange.last.toString() "l", dataRange.last.toString()
@@ -18,7 +17,7 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
when { when {
currentToken.contains("-") -> { currentToken.contains("-") -> {
val splitted = currentToken.split("-") val splitted = currentToken.split("-")
(splitted.first().toInt().clamp(dataRange) .. splitted[1].toInt().clamp(dataRange)).toList() (splitted.first().toInt().coerceIn(dataRange) .. splitted[1].toInt().coerceIn(dataRange)).toList()
} }
currentToken.contains("/") -> { currentToken.contains("/") -> {
val (start, step) = currentToken.split("/") val (start, step) = currentToken.split("/")
@@ -26,24 +25,27 @@ private fun <T> createSimpleScheduler(from: String, dataRange: IntRange, dataCon
0 0
} else { } else {
start.toInt() start.toInt()
}).clamp(dataRange) }).coerceIn(dataRange)
val stepNum = step.toInt().clamp(dataRange) val stepNum = step.toInt().coerceIn(dataRange)
(startNum .. dataRange.last step stepNum).map { it } (startNum .. dataRange.last step stepNum).map { it }
} }
currentToken == "*" -> return null currentToken == "*" -> return null
else -> listOf(currentToken.toInt().clamp(dataRange)) else -> listOf(currentToken.toInt().coerceIn(dataRange))
} }
} }
return results.map(dataConverter) return results.map(dataConverter)
} }
internal fun parseWeekDay(from: String?) = from ?.let { if (it.endsWith("w")) createSimpleScheduler(it.removeSuffix("w"), dayOfWeekRange, intToByteConverter) ?.toTypedArray() else null }
internal fun parseOffset(from: String?) = from ?.let { if (it.endsWith("o")) it.removeSuffix("o").toIntOrNull() else null }
internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() } internal fun parseYears(from: String?) = from ?.let { createSimpleScheduler(from, yearRange, intToIntConverter) ?.toTypedArray() }
internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray() internal fun parseMonths(from: String) = createSimpleScheduler(from, monthRange, intToByteConverter) ?.toTypedArray()
internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray() internal fun parseDaysOfMonth(from: String) = createSimpleScheduler(from, dayOfMonthRange, intToByteConverter) ?.toTypedArray()
internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange, intToByteConverter) ?.toTypedArray() internal fun parseHours(from: String) = createSimpleScheduler(from, hoursRange, intToByteConverter) ?.toTypedArray()
internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange, intToByteConverter) ?.toTypedArray() internal fun parseMinutes(from: String) = createSimpleScheduler(from, minutesRange, intToByteConverter) ?.toTypedArray()
internal fun parseSeconds(from: String) = createSimpleScheduler(from, secondsRange, 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( internal fun <T> Array<T>.fillWith(
whereToPut: MutableList<CronDateTime>, whereToPut: MutableList<CronDateTime>,
@@ -60,3 +62,16 @@ internal fun <T> Array<T>.fillWith(
} }
} }
internal fun <T> T.fillWith(
whereToPut: MutableList<CronDateTime>,
createFactory: (CronDateTime, T) -> CronDateTime
) {
val previousValues = whereToPut.toList()
whereToPut.clear()
previousValues.forEach { previousValue ->
whereToPut.add(createFactory(previousValue, this))
}
}

View File

@@ -1,8 +1,10 @@
package dev.inmo.krontab.internal package dev.inmo.krontab.internal
internal val dayOfWeekRange = 0 .. 6
internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE internal val yearRange = Int.MIN_VALUE .. Int.MAX_VALUE
internal val monthRange = 0 .. 11 internal val monthRange = 0 .. 11
internal val dayOfMonthRange = 0 .. 30 internal val dayOfMonthRange = 0 .. 30
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

View File

@@ -1,12 +0,0 @@
package dev.inmo.krontab.utils
/**
* @return [min] in case if [this] less than [min]. Otherwise will check that [max] grant than [this] and return [this]
* if so or [max] otherwise
*/
internal fun Int.clamp(min: Int, max: Int): Int = if (this < min) min else if (this > max) max else this
/**
* Wrapper function for [clamp] extension
*/
internal fun Int.clamp(range: IntRange): Int = clamp(range.first, range.last)

View File

@@ -0,0 +1,23 @@
package dev.inmo.krontab.utils
import com.soywiz.klock.DateTime
import com.soywiz.klock.Month
import kotlin.math.min
fun DateTime.copy(
year: Int = yearInt,
month: Int = month1,
dayOfMonth: Int = this.dayOfMonth,
hour: Int = hours,
minute: Int = minutes,
second: Int = seconds,
milliseconds: Int = this.milliseconds
) = DateTime(
year,
month,
min(Month(month).days(yearInt), dayOfMonth),
hour,
minute,
second,
milliseconds
)

View File

@@ -1,26 +1,36 @@
package dev.inmo.krontab.utils package dev.inmo.krontab.utils
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.krontab.KronScheduler import com.soywiz.klock.DateTimeTz
import dev.inmo.krontab.*
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.isActive
/**
* This [Flow] will trigger emitting each near time which will be returned from [this] [KronScheduler] with attention to
* time zones
*
* @see channelFlow
* @see KronSchedulerTz.doInfinityTz
*/
@FlowPreview @FlowPreview
fun KronScheduler.asFlow(): Flow<DateTime> = SchedulerFlow(this) fun KronScheduler.asTzFlow(): Flow<DateTimeTz> = channelFlow {
doInfinityTz {
@FlowPreview send(it)
class SchedulerFlow(
private val scheduler: KronScheduler
) : AbstractFlow<DateTime>() {
@FlowPreview
override suspend fun collectSafely(collector: FlowCollector<DateTime>) {
while (true) {
val now = DateTime.now()
val nextTime = scheduler.next(now) ?: break
val sleepDelay = (nextTime - now).millisecondsLong
delay(sleepDelay)
collector.emit(nextTime)
}
} }
} }
/**
* This method is a map for [asTzFlow] and will works the same but return flow with [DateTime]s
*
* @see channelFlow
* @see KronScheduler.doInfinity
*/
@FlowPreview
fun KronScheduler.asFlow(): Flow<DateTime> = channelFlow {
doInfinity {
send(it)
}
}

View File

@@ -0,0 +1,3 @@
package dev.inmo.krontab.utils
typealias Minutes = Int

View File

@@ -0,0 +1,34 @@
package dev.inmo.krontab.utils.flows
import com.soywiz.klock.*
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 }

View File

@@ -0,0 +1,25 @@
package dev.inmo.krontab.utils.flows
import com.soywiz.klock.*
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()

View File

@@ -1,11 +1,13 @@
package dev.inmo.krontab.utils package dev.inmo.krontab.utils
import com.soywiz.klock.*
import dev.inmo.krontab.KronSchedulerTz
import dev.inmo.krontab.buildSchedule import dev.inmo.krontab.buildSchedule
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.takeWhile
import kotlin.test.Test import kotlin.math.floor
import kotlin.test.assertEquals import kotlin.test.*
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@FlowPreview @FlowPreview
@@ -27,6 +29,23 @@ class StringParseTest {
assertEquals(mustBeCollected, collected) assertEquals(mustBeCollected, collected)
} }
} }
@Test
fun testThatFlowIsCorrectlyWorkEverySecondWhenMillisIsHalfOfSecondBuiltOnString() {
val kronScheduler = buildSchedule("*/1 * * * * 500ms")
val flow = kronScheduler.asFlow()
runTest {
val mustBeCollected = 10
var collected = 0
flow.takeWhile {
collected < mustBeCollected
}.collect {
collected++
}
assertEquals(mustBeCollected, collected)
}
}
@Test @Test
fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() { fun testThatFlowIsCorrectlyWorkEverySecondWithMuchOfEmittersBuiltOnString() {
@@ -65,15 +84,53 @@ class StringParseTest {
val flow = kronScheduler.asFlow() val flow = kronScheduler.asFlow()
runTest { runTest {
val ranges = rangesEnds.map { it.first .. it.second }.flatten().toMutableList() val ranges = rangesEnds.map { it.first .. it.second }.flatten().distinct().toMutableList()
val expectedCollects = rangesEnds.sumBy { it.second - it.first + 1 } val expectedCollects = ranges.size
var collected = 0 var collected = 0
flow.takeWhile { ranges.isNotEmpty() }.collect { flow.takeWhile { ranges.isNotEmpty() }.collect {
ranges.remove(it.seconds) ranges.remove(it.seconds)
collected++ collected++
assertTrue(collected <= expectedCollects)
} }
assertEquals(expectedCollects, collected) 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)
}
}
}
} }

View File

@@ -0,0 +1,28 @@
package dev.inmo.krontab.utils
import com.soywiz.klock.*
import dev.inmo.krontab.builder.buildSchedule
import dev.inmo.krontab.next
import kotlin.test.Test
import kotlin.test.assertEquals
class TimeZoneTest {
@Test
fun testDifferentTimeZonesReturnsDifferentTimes() {
val scheduler = buildSchedule { seconds { every(1) } }
val 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
)
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
package dev.inmo.krontab.utils
import com.soywiz.klock.*
import dev.inmo.krontab.builder.buildSchedule
import kotlin.math.ceil
import kotlin.test.*
class WeekDaysTest {
@Test
fun testThatWeekDaysSchedulingWorks() {
val startDateTime = DateTime.now().startOfDay
val weekDay = startDateTime.dayOfWeek.index0
val testDays = 400
val scheduler = buildSchedule {
dayOfWeek {
at(weekDay)
}
years {
at(startDateTime.yearInt)
}
}
runTest {
for (day in 0 until testDays) {
val currentDateTime = startDateTime + day.days
val next = scheduler.next(currentDateTime)
val expected = when {
day % 7 == 0 -> currentDateTime
else -> startDateTime + ceil(day.toFloat() / 7).weeks
}
if (expected.yearInt != startDateTime.yearInt) {
assertNull(next)
} else {
assertEquals(expected, next)
}
}
}
}
}