Compare commits

...

282 Commits

Author SHA1 Message Date
f90d8d03d9
Merge pull request #108 from InsanusMokrassar/2.3.0
2.3.0
2024-04-25 18:44:04 +06:00
36671cb379 potential fix of #107 2024-03-21 17:34:22 +06:00
68be768763 start 2.3.0 2024-03-21 17:14:59 +06:00
9546eb5aec
Merge pull request #103 from InsanusMokrassar/2.2.9
2.2.9
2024-03-16 16:06:32 +06:00
2efd7e6157 fixes in built-in schedulers 2024-03-16 15:56:45 +06:00
6845537afd remove redundant change of KronSchedulerWork.kt 2024-03-16 10:31:12 +06:00
af28fce6f5 update dependencies 2024-03-06 20:01:41 +06:00
237b42c2d3 start 2.2.9 2024-03-06 19:36:42 +06:00
49f852f27e
Merge pull request #101 from InsanusMokrassar/renovate/dokka_version
Update plugin org.jetbrains.dokka to v1.9.20
2024-03-05 13:56:26 +06:00
renovate[bot]
470dc92f54
Update plugin org.jetbrains.dokka to v1.9.20 2024-03-05 02:59:24 +00:00
b5dcdc185f
Merge pull request #99 from InsanusMokrassar/2.2.8
2.2.8
2024-02-18 22:24:34 +06:00
8df9f9c086 update dependencies 2024-02-18 22:14:01 +06:00
484f46223f start 2.2.8 2024-02-18 21:56:06 +06:00
1ce654fa06
Merge pull request #94 from InsanusMokrassar/2.2.7
2.2.7
2024-01-31 17:56:23 +06:00
6da04f54da update dependencies 2024-01-31 17:40:15 +06:00
e1d716688a start 2.2.7 2024-01-31 17:26:48 +06:00
e247de9e24
Merge pull request #92 from InsanusMokrassar/2.2.6
2.2.6
2024-01-12 14:52:32 +06:00
a153bc7996 update klock 2024-01-12 14:45:14 +06:00
9753cda961 start 2.2.6 2024-01-12 14:33:22 +06:00
e8ef6e6935 update publication script 2023-12-28 15:51:33 +06:00
b41bf0ea07
Merge pull request #91 from InsanusMokrassar/2.2.5
2.2.5
2023-12-28 15:47:39 +06:00
09d811e2c4
Update CHANGELOG.md 2023-12-28 15:47:27 +06:00
3faadefced update dependencies 2023-12-27 19:15:35 +06:00
f3883a8678 start 2.2.5 2023-12-27 01:14:50 +06:00
5ff02a2a9d
Merge pull request #85 from InsanusMokrassar/2.2.4
2.2.4
2023-11-26 21:23:18 +06:00
f56215ee10 start 2.2.4 and update dependencies 2023-11-26 21:20:39 +06:00
e66acb34de
Update kdocs.yml 2023-11-03 00:31:23 +06:00
78ac446311
Update publishing_packages.yml 2023-11-03 00:31:12 +06:00
697b4e81b8
Merge pull request #81 from InsanusMokrassar/2.2.3
2.2.3
2023-11-02 23:28:22 +06:00
d1288de032 build fixes 2023-11-02 23:27:53 +06:00
eb867e7366 update dependencies and gradle environment 2023-11-02 23:22:19 +06:00
4aa10e00cf start 2.2.3 2023-11-02 23:19:24 +06:00
170d443517
Merge pull request #80 from InsanusMokrassar/2.2.2
2.2.2
2023-10-23 01:27:57 +06:00
d1aab7ccd6 add arm target 2023-10-23 01:27:19 +06:00
58d758f141 Update gradle-wrapper.properties 2023-10-23 01:27:19 +06:00
71bd3f0eb4 update dependencies 2023-10-23 01:27:19 +06:00
bd2a852b16 start 2.2.2 2023-10-23 01:27:19 +06:00
8e844b5875 update kotlinx serialization 2023-10-23 01:26:17 +06:00
e0f7dddf42 update dependencies 2023-10-23 01:26:17 +06:00
d64acc9806 potential fix of #65 2023-10-23 01:26:17 +06:00
58375452c7 start 2.2.1 2023-10-23 01:26:17 +06:00
cbfbdc1b33 files replacements 2023-08-09 13:01:20 +06:00
715d414f51 update dependencies 2023-08-09 12:35:13 +06:00
2ecd8c4292 start 2.2.0 2023-08-09 12:24:41 +06:00
1be8d947f7
Merge pull request #67 from InsanusMokrassar/2.1.2
2.1.2
2023-07-05 20:07:15 +06:00
f66c1c2477 remove redundant change in string parse change 2023-07-05 20:05:39 +06:00
69cf7afd4e make inline functions callbacks inline again 2023-07-02 16:41:52 +06:00
92df91edd3 update coroutines 2023-07-02 16:06:47 +06:00
f7a388f438 start 2.1.2 2023-07-02 16:05:46 +06:00
9a10c7eb87
Merge pull request #64 from InsanusMokrassar/2.1.1
2.1.1
2023-06-20 11:44:13 +06:00
d67be582af nullable since in asFlowWithoutDelays and asTzFlowWithoutDelays 2023-06-20 11:35:57 +06:00
171bb687ea start 2.1.1 2023-06-20 11:26:57 +06:00
7807d10121
Merge pull request #63 from InsanusMokrassar/2.1.0
2.1.0
2023-06-20 10:42:01 +06:00
e5bbea84d9 update changelog and kdocs 2023-06-20 10:41:46 +06:00
a01a18a6d1 KrontabTemplateWrapper deprecation, extensions like "daily" 2023-06-20 10:25:15 +06:00
132986d275 migration onto 2.1.0 2023-06-20 10:13:03 +06:00
04118902e8 KrontabConfig, KronScheduler.Companion and its builders extensions 2023-06-20 10:12:32 +06:00
96857aa7bc update dependencies 2023-06-20 09:45:07 +06:00
f8b677406f start 2.0.1 2023-06-20 09:43:04 +06:00
53f34f0a27
Update README.md 2023-06-10 18:16:53 +06:00
989780243a update dokka 2023-06-09 12:10:41 +06:00
7b5417ccc5
Merge pull request #58 from InsanusMokrassar/2.0.0
2.0.0
2023-05-25 21:19:39 +06:00
2004a7dd05 update changelog 2023-05-25 20:53:58 +06:00
7c4217bda6 migration onto new klock and fixes in android manifest 2023-05-25 20:53:10 +06:00
3d6fee7257 migrate onto 2.0.0 due to changes in klock 2023-05-25 20:50:48 +06:00
6cec25eca0 update kotlin version 2023-05-25 20:41:33 +06:00
a0972eaff9 start 1.1.0 2023-05-25 20:38:51 +06:00
cc75501b04
Merge pull request #49 from InsanusMokrassar/1.0.0
1.0.0
2023-04-25 00:28:01 +06:00
89e500ff33
Merge pull request #48 from InsanusMokrassar/renovate/configure
Configure Renovate
2023-04-25 00:15:38 +06:00
a61cd61602 update dependencies 2023-04-25 00:15:13 +06:00
renovate[bot]
5fdb2ea049
Add renovate.json 2023-04-24 18:11:35 +00:00
68ed562b19 start 1.0.0 2023-04-25 00:10:11 +06:00
87b5dfe1aa
Update README.md 2023-04-03 16:18:18 +06:00
6a6bfe0552
Merge pull request #47 from InsanusMokrassar/0.10.0
0.10.0
2023-03-18 14:01:14 +06:00
ede47ae664 remove redundant FlowPrevies on flows API 2023-03-18 12:34:06 +06:00
890ab5b15d fill changelogs 2023-03-18 12:24:25 +06:00
5a1ed2f933 start 0.10.0 + rework of flows 2023-03-18 12:23:53 +06:00
7da67386cf
Update gradle.properties 2023-03-16 20:00:52 +06:00
7027719fe5
Update gradle-wrapper.properties 2023-03-16 20:00:20 +06:00
a81c7c7c3f
Merge pull request #46 from Tolsi/patch-1
typo
2023-03-16 18:20:18 +06:00
Sergey Tolmachev
e5658998d4
typo 2023-03-16 15:16:35 +03:00
6b3cb981ab
Merge pull request #45 from InsanusMokrassar/0.9.0
0.9.0
2023-02-28 12:48:51 +06:00
f6518d4d25 add kotlin-js-store folder in gitignore 2023-02-28 12:25:23 +06:00
97a8f39826 update dependencies 2023-02-28 12:24:30 +06:00
469861f7e3 start 0.9.0 2023-02-28 12:23:12 +06:00
69305506ce
Update README.md 2023-02-19 16:35:31 +06:00
63ea3f0f1a
Merge pull request #44 from InsanusMokrassar/0.8.5
0.8.5
2022-12-15 08:38:54 +06:00
13a8bacb52 fix changelog 2022-12-15 08:37:54 +06:00
9c80e29e71 update autobuild script 2022-12-14 23:00:44 +06:00
cfd2de8fe9 update dependencies 2022-12-14 22:45:34 +06:00
e3490d432e fix in timezoned crontab scheduler 2022-12-14 22:44:27 +06:00
1c95636b71 start 0.8.5 2022-12-14 22:30:34 +06:00
b31a6ab86a
Merge pull request #43 from InsanusMokrassar/0.8.4
0.8.4
2022-12-08 09:32:25 +06:00
c35a679827 update kotlin 2022-12-05 15:27:41 +06:00
06f56026db start 0.8.4 2022-12-05 15:26:31 +06:00
54be57d709
Merge pull request #42 from InsanusMokrassar/0.8.3
0.8.3
2022-11-15 10:04:46 +06:00
fae297e4a6
Update CHANGELOG.md 2022-11-14 10:11:46 +06:00
bd81298ac9
Update gradle.properties 2022-11-11 10:25:26 +06:00
cfac5fcd1c
Merge pull request #41 from InsanusMokrassar/0.8.2
0.8.2
2022-10-02 22:37:13 +06:00
93d38f8945
Update CHANGELOG.md 2022-10-02 22:26:24 +06:00
97dcd77a8a
Update gradle.properties 2022-10-02 22:22:04 +06:00
afa2521b5d
Update gradle-wrapper.properties 2022-10-02 22:21:23 +06:00
37b0ec9fb7
start 0.8.2 2022-10-02 11:40:48 +06:00
d36888173a
Merge pull request #40 from InsanusMokrassar/0.8.1
0.8.1
2022-09-13 00:58:24 +06:00
30011cd309
Update CHANGELOG.md 2022-09-11 14:18:46 +06:00
fd37a5ddc3
Update gradle.properties 2022-09-11 14:17:48 +06:00
3874f6a42c
start 0.8.1 2022-09-11 14:16:52 +06:00
8a98ebd5f9
Merge pull request #39 from InsanusMokrassar/0.8.0
0.8.0
2022-08-06 10:01:18 +06:00
8d0c55129f
Update CHANGELOG.md 2022-08-06 09:54:31 +06:00
54b06bf9e6
start 0.8.0 2022-08-06 09:50:59 +06:00
da436ab432
Merge pull request #38 from InsanusMokrassar/0.7.5
0.7.5
2022-07-22 17:09:31 +06:00
154d211514 change kdocs link 2022-07-22 17:09:00 +06:00
0b843ada4f update yarn.lock 2022-07-22 17:03:20 +06:00
25f204a488 update dependencies 2022-07-22 17:00:41 +06:00
2e4ccb9253 start 0.7.5 2022-07-22 16:59:07 +06:00
411a52b85e
Update github_release.gradle 2022-07-02 18:38:03 +06:00
5a5bde6f20
Merge pull request #37 from InsanusMokrassar/0.7.4
0.7.4
2022-07-02 12:20:28 +06:00
8f9b84ecc7
Update CHANGELOG.md 2022-07-02 12:20:04 +06:00
62c5a0f98d
Update gradle.properties 2022-07-01 23:01:29 +06:00
dfd1a5a909
Update gradle.properties 2022-07-01 23:00:34 +06:00
04390f0726
Update README.md 2022-06-15 15:18:41 +06:00
d0f02e37ce
Merge pull request #36 from InsanusMokrassar/0.7.3
0.7.3
2022-06-15 14:48:10 +06:00
681043db5a
Update NearDateTimeCalculator.kt 2022-06-15 14:46:12 +06:00
f63ed1a873 rename check infinity loop test class and fun 2022-06-15 14:43:28 +06:00
9ea8194d7d fill changelog 2022-06-15 13:46:37 +06:00
0c37ccc902 fix of #33 2022-06-15 13:37:48 +06:00
2abef58335 fix of #35 2022-06-15 13:31:26 +06:00
8ac6cd3e62 fix of #34 and upfix for #27 2022-06-15 13:27:21 +06:00
58f30aef15 fix of #27 2022-06-15 12:40:03 +06:00
f163a64964
start 0.7.3 2022-06-14 00:14:15 +06:00
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
3a0870144b
Update publishing_packages.yml 2021-03-13 22:03:38 +06:00
40adf80d6f
Update publishing_packages.yml 2021-03-13 21:49:08 +06:00
0d1e382182
Update publishing_packages.yml 2021-03-13 21:34:56 +06:00
a15b4162fb
Update publishing_packages.yml 2021-03-13 21:17:42 +06:00
d52435a014 include publishing to packages 2021-03-13 20:31:50 +06:00
9ce69efad5 update publishing scripts 2021-03-13 20:30:02 +06:00
092fc6bc9b update gradle wrapper 2021-03-13 20:23:15 +06:00
ec85fd77f9 update dependencies 2021-03-13 20:22:55 +06:00
496d4cf100 start 0.5.1 2021-03-13 20:19:03 +06:00
dce0fd87a1
Update CHANGELOG.md 2021-01-03 20:01:25 +06:00
fd842b17fa
Merge pull request #12 from InsanusMokrassar/0.5.0
0.5.0
2021-01-03 13:32:04 +06:00
726824fb4f disable jetifier 2021-01-03 12:52:04 +06:00
e04d14ccb1 fixed issues 2021-01-03 12:45:38 +06:00
3f132da62d add KronSchedulerWork 2021-01-02 23:56:31 +06:00
3cce44664f fix tests issue 2021-01-02 22:56:35 +06:00
0376eb7910 add android target 2021-01-02 22:43:32 +06:00
bbb4f41723 fill changelog 2021-01-02 21:55:06 +06:00
365adb1016 add years, KrontabWrapper, new function to create scheduler from template and update readme 2021-01-02 21:35:08 +06:00
476239faa6 CronDateTimeScheduler now is internal (again :( ) 2021-01-02 20:13:47 +06:00
0a2b5ef6a0 start 0.5.0 2021-01-02 19:42:10 +06:00
aedb49f6f8
Merge pull request #11 from InsanusMokrassar/0.4.2
0.4.2
2021-01-01 21:11:16 +06:00
e8071fa15c deprecation messages fix in CronDateTimeScheduler 2021-01-01 21:09:31 +06:00
1cc8ff5519 small update of readme 2021-01-01 21:00:11 +06:00
aebd32cb5e update dependencies 2021-01-01 20:58:45 +06:00
d11f106f19 deprecation of CronDateTImeScheduler 2021-01-01 20:51:09 +06:00
36f316357f start 0.4.2 2021-01-01 20:50:31 +06:00
5fcbe50e39 update publishing 2020-12-14 19:22:06 +06:00
86552d427c update module name in dokka 2020-12-07 14:24:41 +06:00
92d902c027 update dokka 2020-12-07 14:08:59 +06:00
9d8cb20d10
Merge pull request #10 from InsanusMokrassar/0.4.1
0.4.1
2020-12-06 02:14:08 +06:00
ca248a25ad compile fixes 2020-12-06 01:48:00 +06:00
4995a34c1a CronDateTimeScheduler adding 2020-12-06 01:44:24 +06:00
3162780447 update kdocs 2020-12-06 01:40:29 +06:00
05af4d1f67 update dependencies 2020-12-06 01:03:24 +06:00
3c0818cabf CollectionKronScheduler 2020-12-06 01:00:57 +06:00
a38c233bbf downgrade version to 0.4.1 2020-12-06 00:01:02 +06:00
346672b32b start 0.5.0 2020-12-04 22:59:30 +06:00
7458e4880d
Update README.md 2020-12-04 17:56:44 +06:00
46f227f9a3
Merge pull request #9 from InsanusMokrassar/0.4.0
0.4.0
2020-11-21 15:29:23 +06:00
35f5531d26
Update gradle-wrapper.properties 2020-11-21 15:20:04 +06:00
1e7acb2f4b update changelog 2020-11-21 15:08:17 +06:00
50e6868fba update scripts 2020-11-21 15:04:34 +06:00
e0ece2de33 package upfix 2020-11-21 15:00:30 +06:00
104e9b1c87 change package 2020-11-21 14:58:19 +06:00
c1d912f170 start 0.4.0 2020-11-21 14:48:57 +06:00
f4ace9ac37
Merge pull request #8 from InsanusMokrassar/0.3.3
0.3.3
2020-11-09 23:48:48 +06:00
67 changed files with 3037 additions and 879 deletions

21
.github/workflows/kdocs.yml vendored Normal file
View 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

View 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
View File

@ -9,4 +9,6 @@ settings.xml
build/
out/
kotlin-js-store/
local.properties

View File

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

View File

@ -1,11 +1,331 @@
# Changelog
### 0.3.3
## 2.3.0
* `doWhile` now will guarantee that it will not call `doOnce` more than once for time
## 2.2.9
* `Version`:
* `Kotlin`: `1.9.23`
* `Serialization`: `1.6.3`
* `Klock`: `5.3.2`
* Fixes in build-in schedulers
## 2.2.8
* `Version`:
* `Coroutines`: `1.8.0`
## 2.2.7
* `Version`:
* `Klock`: `5.3.1`
* `AndroidXWork`: `2.9.0`
## 2.2.6
**Since this version klock depdendency replaced with `com.soywiz.korge:korlibs-time`**
* `Version`:
* `Klock`: `5.3.0`
## 2.2.5
* `Versions`
* `Kotlin`: `1.9.22`
* `Serialization`: `1.6.2`
## 2.2.4
* `Versions`
* `Kotlin`: `1.9.21`
* `Serialization`: `1.6.1`
## 2.2.3
**THIS UPDATE CONTAINS UPGRADE OF USED JDK UP TO 17**
* Versions
* `Kotlin`: `1.9.20`
## 2.2.2
* Versions
* `Kotlin`: `1.9.20-RC`
* `Android` (min/target): `34`
## 2.2.1
* Potential fix of [#65](https://github.com/InsanusMokrassar/krontab/issues/65)
* Versions
* `Kotlin`: `1.9.20-Beta`
* `Klock`: `4.0.10`
* `Serialization`: `1.6.0`
## 2.2.0
* Versions
* `Kotlin`: `1.9.0`
* `Coroutines`: `1.7.3`
* `Klock`: `4.0.9`
## 2.1.2
* Versions
* `Coroutines`: `1.7.2`
* All callbacks for standard extensions like `doInfinity` now fully inline
## 2.1.1
* `asFlowWithoutDelays` and `asTzFlowWithoutDelays` will have nullable `since` parameters with default to `null`
to avoid any inconsistency of `Flow` idiom.
<details>
<summary>About the reason of changes</summary>
Cold flows should not contain some state by default. So, it was not right to save some `DateTime`/`DateTimeTz`
by default. Now it will not use some external state unless developers will set it manually
</details>
## 2.1.0
* Versions
* `Kotlin`: `1.8.22`
* `Klock`: `4.0.3`
* New value class `KrontabConfig`. It is preferable way to create `KronScheduler` instead of
`KrontabTemplate` since this update
* You may configure krontab with builders using simple `KronScheduler` invoke extension
* New useful extensions like `KronScheduler.daily`
* `KrontabTemplateWrapper` is obsolete in favor to `KrontabConfig`
## 2.0.0
* Versions
* `Kotlin`: `1.8.21`
* `Klock`: `4.0.1`
* Support of `mingwx64` platform
## 1.0.0
* Versions
* `Kotlin`: `1.8.20`
* `AndroidXWork`: `2.8.1`
## 0.10.0
* New extensions for `KronScheduler`:
* `asTzFlowWithoutDelays`/`asFlowWithoutDelays`
* `asTzFlowWithDelays`/`asFlowWithDelays`
* Old `KronScheduler.asFlow` and `KronScheduler.asTzFlow` temporarily marked as deprecated: after several versions their
behaviour will be changed to undelayed one
* All the flow extensions now use `cold` non-channel flows. Potentially it should increase performance and decrease memory usage
## 0.9.0
* Versions
* `Kotlin`: `1.8.10`
* `AndroidXWork`: `2.8.0`
## 0.8.5
* Project is now available in owner `Gitea`: https://git.inmo.dev/InsanusMokrassar/-/packages/maven/dev.inmo-krontab
* `KronSchedulerTz#next` with incoming `DateTime` now will use adjusted local time instead of unadjusted one
* `CronDateTimeSchedulerTz#next` with incoming `DateTime` will convert that parameter to internal offset directly
## 0.8.4
* Versions
* `Kotlin`: `1.7.22`
## 0.8.3
* Versions
* `Kotlin`: `1.7.21`
* `Klock`: `3.4.0`
## 0.8.2
**THIS VERSION HAS CHANGED COMPILE ANDROID SDK FROM 32 -> 33**
* Versions
* `Kotlin`: `1.7.20`
* `Klock`: `3.2.0`
## 0.8.1
* Versions
* `Klock`: `3.1.0`
## 0.8.0
**THIS VERSION HAS CHANGED MIN ANDROID SDK FROM 19 -> 21**
* Versions
* `Kotlin`: `1.7.10`
* `Klock`: `3.0.0`
## 0.7.5
* Versions
* `Coroutines`: `1.6.4`
## 0.7.4
* Versions
* `Coroutines`: `1.6.3`
## 0.7.3
* Versions
* `Kotlin`: `1.6.21`
* Several bug fixes ([#27](https://github.com/InsanusMokrassar/krontab/issues/27), [#34](https://github.com/InsanusMokrassar/krontab/issues/34))
* New factories for `KronScheduler` and `KronSchedulerTz` (fix of [#35](https://github.com/InsanusMokrassar/krontab/issues/35))
* Fill extensions for flow with time zones (fix of [#33](https://github.com/InsanusMokrassar/krontab/issues/33))
## 0.7.2
* Versions
* `Coroutines`: `1.6.1`
* `Klock`: `2.7.0`
* Deprecate `do*Local` due to their redundancy (use `do*` instead)
* Add support of milliseconds as optional parameter after month (fix of [#31](https://github.com/InsanusMokrassar/krontab/issues/31))
* Add support of filters for `Flow`s with `DateTime` (fix of [#30](https://github.com/InsanusMokrassar/krontab/issues/30))
## 0.7.1
* Versions
* `Klock`: `2.5.2`
## 0.7.0
**ALL DEPRECATIONS HAVE BEEN REMOVED**
* Versions
* `Kotlin`: `1.6.10`
* `Klock`: `2.4.10`
* `Coroutines`: `1.6.0`
* `Androidx Work`: `2.7.1`
## 0.6.6
* Versions
* `Klock`: `2.4.8`
* `Androidx Work`: `2.7.0`
## 0.6.5
* Flows now use `doWhile` functions
* `doWhile` now use additional delay (for 1 ms) for cases when `block` executing too fast
* New extensions for `KronScheduler`: `do(Once/While/Infinity)Local`/`do(Once/While/Infinity)Tz`
## 0.6.4
* Versions
* `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
## 0.3.2
* Function `TimeBuilder#each` was added (works as `at`)
* Add opportunity to use `first` shortcuts:
@ -15,7 +335,7 @@
* Value property `TimeBuilder#last` for including via functions like `TimeBuilder#at`
* Shortcut for kron string format `l` or `L`
### 0.3.1
## 0.3.1
* Versions:
* `Kotlin`: `1.4.0` -> `1.4.10`
@ -37,7 +357,7 @@
* Coroutines `1.3.7` -> `1.3.8`
* 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:
* Kotlin `1.3.70` -> `1.3.72`
@ -49,14 +369,14 @@
* `KronScheduler#doWhile` was rewritten to use `KronScheduler#doOnce` for calculations of `block` result
* New `buildSchedule(String)` function as a shortcut for `createSimpleScheduler(String)`
### 0.2.2
## 0.2.2
* Updates in libraries:
* Kotlin `1.3.61` -> `1.3.70`
* Coroutines `1.3.3` -> `1.3.5`
* 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

111
README.md
View File

@ -1,20 +1,20 @@
# 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/com.insanusmokrassar/krontab/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.insanusmokrassar/krontab)
[![Build Status](https://travis-ci.com/InsanusMokrassar/krontab.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/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)
[![Telegram Chat](https://img.shields.io/badge/Telegram%20Chat-0288D1?style=for-the-badge&logo=telegram&logoColor=white)](https://inmodev_chat.t.me)
![JVM](https://img.shields.io/badge/JVM-red?style=for-the-badge&logo=openjdk&logoColor=white)
![Android](https://img.shields.io/badge/Android-green?style=for-the-badge&logo=android&logoColor=white)
![Js](https://img.shields.io/badge/JavaScript-323330?style=for-the-badge&logo=javascript&logoColor=F7DF1E)
![Linux x64](https://img.shields.io/badge/Linux%20x64-white?style=for-the-badge&logo=linux&logoColor=black)
[![KDocs](https://img.shields.io/badge/KDocs-323330?style=for-the-badge&logo=Kotlin&logoColor=7F52FF)](https://insanusmokrassar.github.io/krontab/)
[![Tutorials](https://img.shields.io/badge/Tutorials-0288D1?style=for-the-badge&logo=mkdocs&logoColor=white)](https://docs.inmo.dev/krontab/index.html)
Library was created to give oppotunity to launch some things from time to time according to some schedule in
runtime of applications.
| Table of content |
|---|
| [ How to use ](#how-to-use) |
| [ How to use: Including in project ](#including-in-project) |
| [ How to use: Config from string ](#config-from-string) |
| [ How to use: Config via builder (DSL preview) ](#config-via-builder) |
| [ How to use: KronScheduler as a Flow ](#KronScheduler-as-a-Flow) |
## How to use
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
val kronScheduler = /* creating of KronScheduler instance */;
kronScheuler.doWhile {
kronScheduler.doWhile {
// some action
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:
```groovy
implementation "com.insanusmokrassar:krontab:$krontab_version"
implementation "dev.inmo:krontab:$krontab_version"
```
Next version is the latest currently for the library:
[ ![Download](https://api.bintray.com/packages/insanusmokrassar/InsanusMokrassar/krontab/images/download.svg) ](https://bintray.com/insanusmokrassar/InsanusMokrassar/krontab/_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`.
@ -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
like a `crontab`, but with a little bit different meanings:
```
/-------- Seconds
| /------ Minutes
| | /---- Hours
| | | /-- Days of months
| | | | / Months
| | | | |
* * * * *
/--------------- Seconds
| /------------- Minutes
| | /----------- Hours
| | | /--------- Days of 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
will use seconds and minutes with more probability than months (for example). In fact, developers will use something
like:
will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use
something like:
```kotlin
doWhile("/5 * * * *") {
@ -73,7 +76,7 @@ doWhile("/5 * * * *") {
}
```
Or more version:
An other version:
```kotlin
doInfinity("/5 * * * *") {
@ -85,7 +88,7 @@ Both of examples will print `Called` message every five seconds.
### 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
val kronScheduler = buildSchedule {
@ -128,6 +131,38 @@ kronScheduler.doInfinity {
All of these examples will do the same things: print `Called` message every five seconds.
### do\* functions
With regular `doOnce`/`doWhile`/`doInfinity` there are two types of their variations: **local** and **timezoned**. Local
variations (`doOnceLocal`/`doWhileLocal`/`doInfinityLocal`) will pass `DateTime` as an argument into the block:
```kotlin
doInfinityLocal("/5 * * * *") {
println(it) // will print current date time
}
```
Timezoned variations (`doOnceTz`/`doWhileTz`/`doInfinityTz`) will do the same thing but pass as an argument `DateTimeTz`:
```kotlin
doInfinityTz("/5 * * * * 0o") {
println(it) // will print current date time in UTC
}
```
It is useful in cases when you need to get the time of calling and avoid extra calls to system time.
#### Helpful table for
| | No args | Local `DateTime` | Local `DateTimeTz` with offset of `KronScheduler` |
|---| ------- | ---------------- | ------------------------------------------------- |
| **Call only near time** | doOnce | doOnceLocal | doOnceTz |
| **Call while condition is true** | doWhile | doWhileLocal | doWhileTz |
| **Work infinity*** | doInfinity | doInfinityLocal | doInfinityTz |
*Here there is an important notice, that `Work infinity` is not exactly `infinity`. Actually, that means that `do while
coroutine is alive` and in fact executing will be stopped when coroutine became cancelled.
### KronScheduler as a Flow
Any `KronScheduler`can e converted to a `Flow<DateTime` using extension `asFlow`:
@ -152,3 +187,27 @@ flow.takeWhile {
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,43 +1,67 @@
buildscript {
repositories {
mavenLocal()
jcenter()
// jcenter()
mavenCentral()
google()
}
dependencies {
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.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath "com.android.tools.build:gradle:$android_gradle_version"
}
}
plugins {
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
id "org.jetbrains.dokka" version "$dokka_version"
}
project.version = "0.3.3"
project.group = "com.insanusmokrassar"
// 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"
project.version = "$version"
project.group = "dev.inmo"
apply from: "publish.gradle"
apply from: "github_release.gradle"
repositories {
mavenLocal()
jcenter()
// jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
// maven { url "https://kotlin.bintray.com/kotlinx" }
google()
}
apply from: './dokka.gradle'
kotlin {
jvm()
js(BOTH) {
js(IR) {
browser()
nodejs()
}
android {
publishAllLibraryVariants()
}
linuxX64()
mingwX64()
linuxArm64()
sourceSets {
@ -45,18 +69,23 @@ kotlin {
dependencies {
implementation kotlin('stdlib')
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlin_serialization_version"
api "com.soywiz.korlibs.klock:klock:$klockVersion"
api "com.soywiz.korge:korlibs-time:$klockVersion"
}
}
androidMain {
dependencies {
api "androidx.work:work-runtime-ktx:$androidx_work_version"
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlin_coroutines_version"
}
}
jvmTest {
@ -64,10 +93,55 @@ kotlin {
implementation kotlin('test-junit')
}
}
jsMain {
jsTest {
dependencies {
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
}

View File

@ -1,42 +1,54 @@
dokka {
outputFormat = 'html'
dokkaHtml {
String dokkaOutputDirectoryPath = null
switch (true) {
case project.hasProperty("DOKKA_PATH"):
outputDirectory = project.property("DOKKA_PATH").toString()
dokkaOutputDirectoryPath = project.property("DOKKA_PATH").toString()
break
case System.getenv("DOKKA_PATH") != null:
outputDirectory = System.getenv("DOKKA_PATH")
dokkaOutputDirectoryPath = System.getenv("DOKKA_PATH")
break
}
if (dokkaOutputDirectoryPath != null) {
File outputDirectoryFile = new File(dokkaOutputDirectoryPath)
outputDirectoryFile.mkdirs()
outputDirectory = outputDirectoryFile
}
multiplatform {
global {
perPackageOption {
prefix = "com.insanusmokrassar"
skipDeprecated = true
includeNonPublic = true
reportUndocumented = true
moduleName.set("kdocs")
dokkaSourceSets {
switch (true) {
case project.hasProperty("DOKKA_PATH"):
outputDirectory = new File(project.property("DOKKA_PATH").toString())
break
case System.getenv("DOKKA_PATH") != null:
outputDirectory = new File(System.getenv("DOKKA_PATH"))
break
}
dokkaSourceSets {
configureEach {
skipDeprecated.set(true)
sourceLink {
localDirectory.set(file("./"))
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/krontab/blob/master/"))
remoteLineSuffix.set("#L")
}
}
sourceLink {
path = "./"
url = "https://github.com/InsanusMokrassar/krontab/blob/master/"
lineSuffix = "#L"
named("commonMain") {
sourceRoots.setFrom(findSourcesWithName("commonMain"))
}
}
common {
targets = ["JVM", "JS"]
sourceRoot { path = "src/commonMain" }
}
js {
targets = ["JS"]
sourceRoot { path = "src/jsMain" }
}
jvm {
targets = ["JVM"]
sourceRoot { path = "src/jvmMain" }
//
// named("jsMain") {
// sourceRoot { path = "src/jsMain" }
// }
//
// named("jvmMain") {
// sourceRoot { path = "src/jvmMain" }
// }
}
}
}

View File

@ -18,13 +18,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}"
owner "InsanusMokrassar"
repo "${rootProject.name}"
owner = "InsanusMokrassar"
repo = "${rootProject.name}"
tagName "${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"
tagName = "v${project.version}"
releaseName = "${project.version}"
targetCommitish = "${project.version}"
body getCurrentVersionChangelog("${project.version}")
body = getCurrentVersionChangelog("${project.version}")
}
}

View File

@ -1,19 +1,40 @@
org.gradle.jvmargs=-Xmx512m
kotlin.code.style=official
org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=true
kotlin.incremental.js=true
kotlin_version=1.4.10
kotlin_coroutines_version=1.4.1
dokka_version=0.10.1
gradle_bintray_plugin_version=1.8.5
klockVersion=1.12.1
github_release_plugin_version=2.2.12
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.3.0
android_code_version=40

View File

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

View File

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

View File

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

View File

@ -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 {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = "It is an analog of crontab util for Kotlin Coroutines"
name = "Krontab"
url = "https://github.com/InsanusMokrassar/krontab"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/krontab.git[push=]https://github.com/InsanusMokrassar/krontab.git"
url = "https://github.com/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://github.com/InsanusMokrassar/krontab/blob/master/LICENSE"
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "InsanusMokrassar"
name = "${project.name}-mpp"
vcsUrl = "https://git.insanusmokrassar.com/InsanusMokrassar/krontab"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
repositories {
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/krontab")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
}
}
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}
}
bintrayUpload.doFirst {
publications = publishing.publications.collect {
if (it.name.contains('kotlinMultiplatform')) {
null
} else {
it.name
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
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
View 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
View File

@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

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

View 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()
}

View File

@ -0,0 +1,193 @@
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) {
var latest: DateTime? = null
do {
delay(1L)
val result = doOnce {
if (latest != it) {
latest = it
block(it)
} else {
null
}
}
} while (result == null || result)
}
/**
* Will execute [block] while it will return true as a result of its calculation
*/
@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile"))
suspend inline fun KronScheduler.doWhileLocal(block: (DateTime) -> Boolean) = doWhile(block)
/**
* Will execute [block] while it will return true as a result of its calculation
*/
suspend inline fun KronScheduler.doWhileTz(noinline block: suspend (DateTimeTz) -> Boolean) {
var latest: DateTimeTz? = null
do {
delay(1L)
val result = doOnceTz {
if (latest != it) {
latest = it
block(it)
} else {
null
}
}
} while (result == null || result)
}
/**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
*
* @see buildSchedule
*/
suspend inline fun doWhile(
scheduleConfig: String,
block: (DateTime) -> Boolean
) = buildSchedule(scheduleConfig).doWhile(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
*
* @see buildSchedule
*/
@Deprecated("Replaceable", ReplaceWith("doWhile", "dev.inmo.krontab.doWhile"))
suspend inline fun doWhileLocal(
scheduleConfig: String,
block: (DateTime) -> Boolean
) = doWhile(scheduleConfig, block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doWhile] with [block]
*
* @see buildSchedule
*/
suspend inline fun doWhileTz(
scheduleConfig: String,
noinline block: suspend (DateTimeTz) -> Boolean
) = buildSchedule(scheduleConfig).doWhileTz(block)
/**
* Will execute [block] without any checking of result
*/
suspend inline fun KronScheduler.doInfinity(block: (DateTime) -> Unit) = doWhile {
block(it)
coroutineContext.isActive
}
/**
* Will execute [block] without any checking of result
*/
@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity"))
suspend inline fun KronScheduler.doInfinityLocal(block: (DateTime) -> Unit) = doInfinity(block)
/**
* Will execute [block] without any checking of result
*/
suspend inline fun KronScheduler.doInfinityTz(noinline block: suspend (DateTimeTz) -> Unit) = doWhileTz {
block(it)
coroutineContext.isActive
}
/**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
*
* @see buildSchedule
*/
suspend inline fun doInfinity(
scheduleConfig: String,
block: (DateTime) -> Unit
) = buildSchedule(scheduleConfig).doInfinity(block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
*
* @see buildSchedule
*/
@Deprecated("Replaceable", ReplaceWith("doInfinity", "dev.inmo.krontab.doInfinity"))
suspend inline fun doInfinityLocal(
scheduleConfig: String,
block: (DateTime) -> Unit
) = doInfinity(scheduleConfig, block)
/**
* Will [buildSchedule] using [scheduleConfig] and call [doInfinity] with [block]
*
* @see buildSchedule
*/
suspend inline fun doInfinityTz(
scheduleConfig: String,
noinline block: suspend (DateTimeTz) -> Unit
) = buildSchedule(scheduleConfig).doInfinityTz(block)

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

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

View 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()

View 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}
* * *&#47;{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)
)
}
}
}

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

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

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

View 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

View 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}
* * *&#47;{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)

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

View File

@ -1,14 +1,14 @@
package com.insanusmokrassar.krontab.builder
package dev.inmo.krontab.builder
import com.insanusmokrassar.krontab.internal.*
import com.insanusmokrassar.krontab.utils.clamp
import dev.inmo.krontab.internal.*
/**
* 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 (
private val restrictionsRange: IntRange
sealed class TimeBuilder<T : Number> (
private val restrictionsRange: IntRange,
private val converter: Converter<T>
) {
private var result: Set<Int>? = null
@ -36,7 +36,7 @@ sealed class TimeBuilder (
*/
@Suppress("MemberVisibilityCanBePrivate")
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()
}
@ -45,7 +45,7 @@ sealed class TimeBuilder (
*/
@Suppress("unused")
infix fun at(value: Int) {
result = (result ?: emptySet()) + value.clamp(restrictionsRange)
result = (result ?: emptySet()) + value.coerceIn(restrictionsRange)
}
@ -69,7 +69,7 @@ sealed class TimeBuilder (
* @see [from]
*/
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()
this@TimeBuilder include result
@ -87,7 +87,7 @@ sealed class TimeBuilder (
*/
@Suppress("MemberVisibilityCanBePrivate")
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()
this@TimeBuilder include result
@ -119,11 +119,14 @@ sealed class TimeBuilder (
*/
fun includeFirst() = at(restrictionsRange.first)
internal fun build() = result ?.map { it.toByte() } ?.toTypedArray()
internal fun build() = result ?.map(converter)
}
class SecondsBuilder : TimeBuilder(secondsRange)
class MinutesBuilder : TimeBuilder(minutesRange)
class HoursBuilder : TimeBuilder(hoursRange)
class DaysOfMonthBuilder : TimeBuilder(dayOfMonthRange)
class MonthsBuilder : TimeBuilder(monthRange)
class MillisecondsBuilder : TimeBuilder<Short>(millisecondsRange, intToShortConverter)
class SecondsBuilder : TimeBuilder<Byte>(secondsRange, intToByteConverter)
class MinutesBuilder : TimeBuilder<Byte>(minutesRange, intToByteConverter)
class HoursBuilder : TimeBuilder<Byte>(hoursRange, intToByteConverter)
class DaysOfMonthBuilder : TimeBuilder<Byte>(dayOfMonthRange, intToByteConverter)
class MonthsBuilder : TimeBuilder<Byte>(monthRange, intToByteConverter)
class YearsBuilder : TimeBuilder<Int>(yearRange, intToIntConverter)
class WeekDaysBuilder : TimeBuilder<Byte>(dayOfWeekRange, intToByteConverter)

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

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

View File

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

View File

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

View File

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

View File

@ -1,72 +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}
* * *&#47;{int}
* * {int}
* * F
* * L
*
* 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/5,L * * * *" for every five seconds triggering and on 59 second
* * "0/15 30 * * *" for every 15th seconds in a half of each hour
* * "1 2 3 F,4,L 5" for triggering in near first second of second minute of third hour of fourth day of may
*
* @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)

View File

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

View File

@ -1,106 +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).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(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())
}

View File

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

View File

@ -1,57 +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 {
val currentToken = it.toLowerCase().replace(
"f", dataRange.first.toString()
).replace(
"l", dataRange.last.toString()
)
when {
currentToken.contains("-") -> {
val splitted = currentToken.split("-")
(splitted.first().toInt().clamp(dataRange) .. splitted[1].toInt().clamp(dataRange)).toList()
}
currentToken.contains("/") -> {
val (start, step) = currentToken.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 }
}
currentToken == "*" -> return null
else -> listOf(currentToken.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))
}
}
}

View File

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

View File

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

View 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() },
)

View 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()
)

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

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

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

View File

@ -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 dayOfMonthRange = 0 .. 30
internal val hoursRange = 0 .. 23
internal val minutesRange = 0 .. 59
internal val secondsRange = minutesRange
internal val millisecondsRange = 0 .. 999

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

View 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()

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

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

View 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()

View 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()

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

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

View File

@ -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 {
delay(forTimeMillis)

View File

@ -1,10 +1,9 @@
package com.insanusmokrassar.krontab.utils
package dev.inmo.krontab.utils
import com.insanusmokrassar.krontab.builder.buildSchedule
import com.soywiz.klock.DateTime
import dev.inmo.krontab.builder.buildSchedule
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@ -19,7 +18,7 @@ class SchedulerFlowTests {
}
}
val flow = kronScheduler.asFlow()
val flow = kronScheduler.asFlowWithoutDelays()
runTest {
val mustBeCollected = 10
@ -41,14 +40,14 @@ class SchedulerFlowTests {
}
}
val flow = kronScheduler.asFlow()
val flow = kronScheduler.asFlowWithoutDelays()
runTest {
val testsCount = 10
val failJob = it.createFailJob((testsCount * 2) * 1000L)
val failJob = createFailJob((testsCount * 2) * 1000L)
val mustBeCollected = 10
val answers = (0 until testsCount).map { _ ->
it.async {
async {
var collected = 0
flow.takeWhile {
collected < mustBeCollected

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

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

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

View File

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

View File

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

View File

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

View File

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