Compare commits

...

206 Commits

Author SHA1 Message Date
837cac644d add extensions in JS 2024-09-20 13:14:50 +06:00
e83e0a8535 update dependencies 2024-09-20 12:44:10 +06:00
2e309c31a6 start 0.22.3 2024-09-20 12:18:17 +06:00
625db02651 Merge pull request #479 from InsanusMokrassar/0.22.2
0.22.2
2024-08-30 20:57:10 +06:00
e3144c08c1 update dependencies 2024-08-30 20:48:34 +06:00
40e25970ad start 0.22.2 2024-08-30 20:46:07 +06:00
4ccb8a8d11 Merge pull request #477 from InsanusMokrassar/0.22.1
0.22.1
2024-08-29 15:02:34 +06:00
26d1c3d9ef update dependencies 2024-08-29 14:49:29 +06:00
cc8fe7b001 start 0.22.1 2024-08-29 14:14:20 +06:00
4601eab864 update gradle scripts to improve performance of configuration 2024-08-27 18:57:59 +06:00
1ee9c88ffd Merge pull request #459 from InsanusMokrassar/0.22.0
0.22.0
2024-08-11 16:51:30 +06:00
7feac213f6 update android code version 2024-08-11 16:51:09 +06:00
c75a9c0f61 update dependencies 2024-08-11 16:44:08 +06:00
afd0c9784f Update libs.versions.toml 2024-08-11 12:41:30 +06:00
f1ddbea06e update android version 2024-08-09 23:06:50 +06:00
42bf3cd4e3 update kotlin-poet 2024-08-09 22:21:53 +06:00
27f5549f56 improve tests of repos 2024-08-09 21:58:09 +06:00
b0569f8421 add tests for cruds 2024-08-09 19:22:32 +06:00
8d86f29325 Revert "small change in FullKeyValuesCacheRepoTests and FullKeyValueCacheRepoTests"
This reverts commit 3db8d2291e73812f9eebcec1c0ad088ad0ca854a.
2024-08-07 16:50:13 +06:00
aeca32498a Revert "Update build.yml"
This reverts commit ab0b3a8f0d2c2a38eab5c9910f8ae5543a487c97.
2024-08-07 16:50:13 +06:00
e8232664f3 update delay on creating tests in cache 2024-08-07 16:50:13 +06:00
efa869f91a Update build.yml 2024-08-07 16:50:13 +06:00
dbff3d7cf1 small change in FullKeyValuesCacheRepoTests and FullKeyValueCacheRepoTests 2024-08-07 16:50:13 +06:00
9e70f73684 upgrade kotlin 2024-08-07 16:50:13 +06:00
9f2f0de0c4 add check of emptiness in map reslozations of KV/KVs repos 2024-08-07 16:50:13 +06:00
bf293a8f8f fix of tests 2024-08-07 16:50:13 +06:00
5fbc1a132f improvements in SmartRWLocker and breaking of FullKeyValueCacheRepo tests 2024-08-07 16:50:13 +06:00
36093b9741 fix of full caches initial actualization and realize MapKeyValuesRepo methods getAll 2024-08-07 16:50:13 +06:00
8713fb04c5 small change in MapWriteKeyValuesRepo.set and remove duration of FullKeyValuesCacheRepoTests.creatingWorksProperly 2024-08-07 16:50:13 +06:00
ea82a59f31 update gradle wrapper version 2024-08-07 16:50:13 +06:00
18f67cd4f2 small improvement of test data in Full Cache tests 2024-08-07 16:50:13 +06:00
120e7228c7 add FullKeyValuesCacheRepoTests and small rewrite of MapWriteKeyValuesRepo.set(Map<Key, List<Value>>) function 2024-08-07 16:50:13 +06:00
892fa90c37 start add cache repos tests 2024-08-07 16:50:13 +06:00
491dac5bf0 update exposed 2024-08-07 16:50:13 +06:00
2ab06fbafd change version 2024-08-07 16:50:11 +06:00
e68735d061 Revert "migrations onto new libraries"
This reverts commit 647cd7d7b4.
2024-08-07 16:46:49 +06:00
9ba4d98c30 migrations onto new libraries 2024-08-07 16:46:49 +06:00
19bbfd4916 downgrade kotlin and fix build 2024-08-07 16:46:49 +06:00
d94cd7ea94 some fixes 2024-08-07 16:46:49 +06:00
0dd8f41eb0 update dependencies 2024-08-07 16:46:49 +06:00
f1ab5ab51f update kotlinx serialization 2024-08-07 16:46:49 +06:00
a8056f3120 update dependencies 2024-08-07 16:46:49 +06:00
09c0cdebb5 update dependencies 2024-08-07 16:46:49 +06:00
629d7c7a82 temporal project in upgrade 2024-08-07 16:46:49 +06:00
a0dd1aec3d trying to update to new compose 2024-08-07 16:46:49 +06:00
17d6377902 Merge pull request #466 from InsanusMokrassar/0.21.6
0.21.6
2024-07-26 19:16:13 +06:00
ad401105a1 fixes in ksp sealed generator 2024-07-26 17:17:11 +06:00
4913e99c2e start 0.21.6 2024-07-26 16:40:30 +06:00
16b97029cf Merge pull request #465 from InsanusMokrassar/0.21.5
0.21.5
2024-07-25 02:48:27 +06:00
481130c9bb fill changelog 2024-07-25 02:30:10 +06:00
698ed6718d improve sealed workaround generation 2024-07-25 02:27:33 +06:00
d164813bb4 Revert "start moving to kotlinx-io"
This reverts commit 52157ee0e7.
2024-07-25 01:20:32 +06:00
52157ee0e7 start moving to kotlinx-io 2024-07-24 00:34:49 +06:00
877b62fe5d start 0.21.5 2024-07-24 00:06:18 +06:00
d823a02971 Merge pull request #464 from InsanusMokrassar/0.21.4
add additional targets with natives support
2024-07-16 17:58:02 +06:00
e950056e3b add additional targets with natives support 2024-07-16 17:18:26 +06:00
70014ba233 Merge pull request #463 from InsanusMokrassar/0.21.4
0.21.4
2024-07-16 14:23:41 +06:00
4425f24a20 add support of native platforms in common compose and coroutines compose 2024-07-16 14:16:08 +06:00
410964a44b start 0.21.4 2024-07-16 14:02:36 +06:00
30389e8536 Merge pull request #462 from InsanusMokrassar/0.21.3
0.21.3
2024-07-15 16:33:28 +06:00
5314833041 fix of #374 2024-07-15 08:43:23 +06:00
40f7cf7678 Color as a module 2024-07-15 08:13:23 +06:00
83a0b07062 start 0.21.3 2024-07-15 08:05:08 +06:00
1b4900d691 Merge pull request #461 from InsanusMokrassar/0.21.2
0.21.2
2024-06-30 22:56:52 +06:00
f9795d53a0 improve classcasts 2024-06-30 22:55:28 +06:00
2b9bb4f141 add ClassCasts generator 2024-06-30 20:56:54 +06:00
9196e4c367 start 0.21.2 2024-06-30 20:40:12 +06:00
374a5a1a37 Merge pull request #460 from InsanusMokrassar/0.21.1
0.21.1
2024-06-25 22:11:26 +06:00
827cf32c1b modules initialization 2024-06-25 22:04:25 +06:00
98ad6dbeb2 start 0.21.1 2024-06-25 22:03:30 +06:00
63c8f642ec Merge pull request #451 from InsanusMokrassar/0.21.0
0.21.0
2024-06-20 01:53:51 +06:00
3bfe64f797 Update CHANGELOG.md 2024-06-20 01:53:42 +06:00
ec98029467 get back old function to break less API 2024-06-19 20:03:14 +06:00
ab58478686 Revert "fix of build"
This reverts commit 90247667d1.
2024-06-19 19:50:19 +06:00
90247667d1 fix of build 2024-06-19 19:50:00 +06:00
e661185534 fill changelog 2024-06-19 19:36:02 +06:00
d73e4e8e1f migrate onto 0.21.0 2024-06-19 19:30:16 +06:00
a6905e73cb small improvements 2024-06-17 01:31:50 +06:00
93b054d55e add .kotlin in gitignore 2024-06-16 22:27:07 +06:00
5db4c5c717 improvements in coroutines launch safely 2024-06-15 17:37:26 +06:00
5e04521929 start 0.20.53 2024-06-15 17:32:15 +06:00
30440b4ed1 Merge pull request #457 from InsanusMokrassar/0.20.52
0.20.52
2024-06-14 23:57:26 +06:00
09bb90604d weaks rework 2024-06-14 17:09:04 +06:00
4d55ec6f36 add separated WeakScope 2024-06-14 16:58:40 +06:00
f373524f34 a little improve of weak extensions and add tests for weak scopes 2024-06-14 16:53:56 +06:00
0398a7bebd start 0.20.52 2024-06-14 16:36:52 +06:00
fa8a5bcd97 Merge pull request #453 from InsanusMokrassar/0.20.51
0.20.51
2024-05-26 23:53:46 +06:00
758a92410b more fixes to god of fixes in paginations 2024-05-26 23:51:46 +06:00
77f56c5dda make improvements in pagination 2024-05-26 23:35:24 +06:00
72f2fe3cc3 add "indices" cache in pagination with reversed functionality 2024-05-26 23:01:23 +06:00
7e0e520f03 add note about android fragment update 2024-05-26 23:00:11 +06:00
92c4784e42 paginations improvements 2024-05-26 22:59:19 +06:00
a634229dc0 start 0.20.51 2024-05-26 19:46:53 +06:00
de3f36ef2a Merge pull request #445 from InsanusMokrassar/0.20.50
0.20.50
2024-05-12 21:58:07 +06:00
c623a265ee fill changelog and remove redundant parts in coroutines compose build.gradle 2024-05-12 21:51:20 +06:00
e75125f6df now SmartMutex and SmartSemaphore (and SmartRWLocker as a result) works on SpecialMutableStateFlow 2024-05-12 21:44:33 +06:00
4901a8844c complete rework of states 2024-05-12 21:34:17 +06:00
a1854b68d8 almost updated dependencies and modules hierarchy 2024-05-12 19:17:16 +06:00
aee4a6243b start 0.20.50 2024-05-12 17:01:22 +06:00
4c0cb73d69 Merge pull request #438 from InsanusMokrassar/0.20.49
0.20.49
2024-05-04 23:04:10 +06:00
c6eab182f6 new crud repo extensions on* 2024-05-02 18:20:12 +06:00
3e3fbd97eb diff/applyDiff 2024-05-02 17:18:43 +06:00
feb695caa7 start 0.20.49 2024-05-02 15:08:53 +06:00
1bd46d9651 Merge pull request #437 from InsanusMokrassar/0.20.48
0.20.48
2024-05-02 14:09:47 +06:00
32eabb6b36 update dependencies 2024-05-02 14:06:46 +06:00
9bfe6dc6d8 start 0.20.48 2024-05-02 13:58:12 +06:00
3f366aeea4 Merge pull request #434 from InsanusMokrassar/0.20.47
0.20.47
2024-05-01 18:16:58 +06:00
4338fd46f2 update android gradle plugin 2024-05-01 18:14:28 +06:00
36974f5b49 update dependencies 2024-05-01 17:47:47 +06:00
d48f767408 start 0.20.47 2024-05-01 17:47:02 +06:00
bd2558e852 Merge pull request #430 from InsanusMokrassar/0.20.46
0.20.46
2024-04-27 15:41:22 +06:00
9f7c963cd5 add klock in common and DateTimeSerializer 2024-04-27 15:34:52 +06:00
2db0eadbfe start 0.20.46 2024-04-27 15:17:40 +06:00
580c7b8842 Merge pull request #429 from InsanusMokrassar/0.20.45
0.20.45
2024-04-22 11:34:25 +06:00
88fba347ea update android core ktx 2024-04-22 11:27:07 +06:00
c8c5255e62 start 0.20.45 2024-04-22 11:25:02 +06:00
01a1a2760a Merge pull request #427 from InsanusMokrassar/0.20.44
0.20.44
2024-04-15 00:30:41 +06:00
6d34df8d48 update dependencies 2024-04-15 00:29:22 +06:00
b124ea65e1 start 0.20.44 2024-04-15 00:19:11 +06:00
f3214347a9 Merge pull request #424 from InsanusMokrassar/0.20.43
0.20.43
2024-04-08 22:13:09 +06:00
6b8724b59f update dependencies 2024-04-08 21:56:02 +06:00
ac3e378edf start 0.20.43 2024-04-08 21:53:43 +06:00
5d31fd1c91 Merge pull request #420 from InsanusMokrassar/0.20.42
0.20.42
2024-04-05 15:09:27 +06:00
046fe1ec08 Update Processor.kt 2024-04-04 23:36:59 +06:00
721873c843 repos models generator improvements 2024-04-04 19:28:59 +06:00
f6ffbfc10a start 0.20.42 2024-04-04 19:28:11 +06:00
4e91649e0a Merge pull request #419 from InsanusMokrassar/0.20.41
0.20.41
2024-04-01 19:02:13 +06:00
4f7f8abec4 improve exposed repos 2024-04-01 18:20:05 +06:00
6370562dbc start 0.20.41 2024-04-01 18:12:25 +06:00
94e2e67522 Merge pull request #418 from InsanusMokrassar/0.20.40
0.20.40
2024-03-31 12:49:35 +06:00
1d8330015d update changelog 2024-03-31 12:48:55 +06:00
e5017b0258 update dependencies 2024-03-29 20:10:32 +06:00
cd412ca31b start 0.20.40 2024-03-29 20:03:36 +06:00
d2b6473095 Merge pull request #412 from InsanusMokrassar/0.20.39
0.20.39
2024-03-14 21:25:28 +06:00
ab42507275 Update CHANGELOG.md 2024-03-14 21:05:23 +06:00
dcef844e20 Update libs.versions.toml 2024-03-14 10:58:08 +06:00
3244708c1b Update gradle.properties 2024-03-14 10:44:57 +06:00
73ac1f1741 Merge pull request #405 from InsanusMokrassar/0.20.38
0.20.38
2024-03-06 02:05:30 +06:00
f104e9f352 update ktor dependency 2024-03-06 01:51:48 +06:00
393c9a7d06 start 0.20.38 2024-03-06 01:50:34 +06:00
ea497ea488 Merge pull request #404 from InsanusMokrassar/renovate/jb.dokka
Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20
2024-03-05 11:07:09 +06:00
renovate[bot]
5add89cad0 Update dependency org.jetbrains.dokka:dokka-gradle-plugin to v1.9.20 2024-03-04 15:11:31 +00:00
2db447d2ef Merge pull request #402 from InsanusMokrassar/0.20.37
0.20.37
2024-03-01 03:25:36 +06:00
31c83813e6 update dependencies 2024-03-01 03:15:25 +06:00
27483a282d start 0.20.37 2024-03-01 03:06:26 +06:00
48b816aa22 Merge pull request #396 from InsanusMokrassar/0.20.36
0.20.36
2024-02-26 17:10:05 +06:00
0065f94f52 update dependencies 2024-02-26 16:55:18 +06:00
ccc0002eb2 improve of actualize all 2024-02-21 00:05:57 +06:00
15a2eee141 start 0.20.36 2024-02-20 22:52:02 +06:00
b9faac71e5 Merge pull request #394 from InsanusMokrassar/0.20.35
0.20.35
2024-02-16 19:11:27 +06:00
0a4465de33 remove API dumps 2024-02-16 19:09:55 +06:00
f9dfd09628 update dependencies 2024-02-16 19:01:46 +06:00
8638d7afce start 0.20.35 2024-02-16 18:50:35 +06:00
8311793a43 Merge pull request #392 from InsanusMokrassar/0.20.34
0.20.34
2024-02-15 16:32:41 +06:00
0d552cfcd2 fix build 2024-02-15 16:18:31 +06:00
4b0f20dbd1 improve default 'set' of KeyValuesRepo 2024-02-15 15:36:32 +06:00
cf531c949d start 0.20.34 2024-02-15 15:14:38 +06:00
ba5c5f17d5 Merge pull request #391 from InsanusMokrassar/0.20.33
0.20.33
2024-02-12 14:42:31 +06:00
35825ad9b7 improve HEXAColorTests 2024-02-12 14:41:55 +06:00
b1eb26a89e fixes and testsAdditions in HEXAColor 2024-02-12 14:25:45 +06:00
c9d04b6698 add ahex to color 2024-02-12 13:58:06 +06:00
496133e014 start 0.20.33 2024-02-12 13:32:30 +06:00
f2857ee2be Merge pull request #388 from InsanusMokrassar/0.20.32
0.20.32
2024-02-10 17:14:04 +06:00
22541f2d5e Update CHANGELOG.md 2024-02-10 15:40:59 +06:00
e235c52b6c Update libs.versions.toml 2024-02-10 15:39:48 +06:00
e89b8c72dd Update gradle-wrapper.properties 2024-02-08 10:40:43 +06:00
a374e53a3a update StringResource 2024-02-06 19:56:44 +06:00
afb066c4ee start 0.20.32 2024-02-06 19:55:34 +06:00
f05761d4a5 Merge pull request #386 from InsanusMokrassar/0.20.31
0.20.31
2024-01-31 18:10:53 +06:00
76adc9ea33 update ktor 2024-01-31 17:37:27 +06:00
99dd291413 start 0.20.31 2024-01-31 17:36:30 +06:00
f904eb27e1 Merge pull request #384 from InsanusMokrassar/0.20.30
0.20.30
2024-01-31 17:27:17 +06:00
eeb8214812 update exposed 2024-01-31 17:05:43 +06:00
f7215b039e start 0.20.30 2024-01-31 17:04:38 +06:00
c07fe5a0f9 Merge pull request #382 from InsanusMokrassar/0.20.29
0.20.29
2024-01-30 23:03:30 +06:00
0d28cb6e20 update dependencies 2024-01-30 21:18:49 +06:00
a1a17bfd1f start 0.20.29 2024-01-30 20:21:45 +06:00
f386f09592 Merge pull request #377 from InsanusMokrassar/0.20.28
0.20.28
2024-01-19 12:59:11 +06:00
a47e17fe6e downgrade kotlin and compose 2024-01-18 23:19:17 +06:00
01dc3b63ff start 0.20.28 2024-01-18 23:12:36 +06:00
2d97e0699e Merge pull request #372 from InsanusMokrassar/0.20.27
0.20.27
2024-01-16 12:43:08 +06:00
75f514d99b update github release 2024-01-16 12:38:53 +06:00
9a687cfc1c update dependencies 2024-01-16 12:19:30 +06:00
14edf8b6b7 start 0.20.27 2024-01-16 12:13:07 +06:00
23aa2d8917 Merge pull request #371 from InsanusMokrassar/0.20.26
0.20.26
2024-01-11 23:29:05 +06:00
7651388b5c update exposed version 2024-01-11 23:09:17 +06:00
805ab32b24 improvements in resources 2024-01-11 23:01:10 +06:00
cc623b1097 start 0.20.26 2024-01-11 23:00:14 +06:00
1420416b3e Merge pull request #369 from InsanusMokrassar/0.20.25
0.20.25
2024-01-04 20:34:15 +06:00
9a0b67f938 fix of tests 2024-01-04 20:30:09 +06:00
303e1e6281 add rgb and rgba representations of HEXAColor 2024-01-04 20:20:55 +06:00
ff59b0cc9c add colors module 2024-01-04 19:53:43 +06:00
be5d2ee715 start 0.20.25 2024-01-04 18:14:09 +06:00
8dd2e3f6f9 Merge pull request #366 from InsanusMokrassar/0.20.24
0.20.24
2024-01-04 10:26:00 +06:00
2eedd196d2 update changelog 2024-01-04 10:25:50 +06:00
759a3f2784 make it possible to use encoder inside of serialize callback 2024-01-04 10:24:39 +06:00
386fa830c3 update soywiz dependencies and downgrade ksp 2024-01-04 09:58:03 +06:00
c382423d77 upgrade MapperSerializer 2023-12-31 13:44:50 +06:00
bb466ce66c start 0.20.24 2023-12-31 13:38:57 +06:00
72cd3dd8a1 Merge pull request #365 from InsanusMokrassar/renovate/ksp
Update ksp to v1.9.22-1.0.16
2023-12-25 21:53:05 +06:00
renovate[bot]
595cedaaf1 Update ksp to v1.9.22-1.0.16 2023-12-25 10:15:44 +00:00
eeaceb6cf5 Merge pull request #362 from InsanusMokrassar/0.20.23
0.20.23
2023-12-22 02:43:14 +06:00
1bd671685b update dependencies and remove redundant usages of old IetfLanguageCode 2023-12-21 23:25:36 +06:00
48d3fe41f2 a lot of improvements in language codes 2023-12-21 15:41:11 +06:00
7ab21871cd Revert "add klock module"
This reverts commit 65d01b1fb3.
2023-12-21 14:05:27 +06:00
c6ed821934 Merge pull request #360 from InsanusMokrassar/0.20.22
0.20.22
2023-12-14 23:57:51 +06:00
309 changed files with 10098 additions and 12281 deletions

7
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.idea
.kotlin
out/*
*.iml
target
@@ -14,3 +15,9 @@ local.properties
kotlin-js-store
publishing.sh
local.*
local/
**/*.local.*
.kotlin/

View File

@@ -1,9 +1,322 @@
# Changelog
## 0.22.3
* `Versions`:
* `Serialization`: `1.7.2` -> `1.7.3`
* `Coroutines`: `1.8.1` -> `1.9.0`
* `Compose`: `1.7.0-alpha03` -> `1.7.0-beta02`
* `Koin`: `3.5.6` -> `4.0.0`
* `Okio`: `3.9.0` -> `3.9.1`
* `AndroidFragment`: `1.8.2` -> `1.8.3`
* `androidx.compose.material3:material3` has been replaced with `org.jetbrains.compose.material3:material3`
* `Common`:
* `JS`:
* Add several useful extensions
* `Compose`:
* `JS`:
* Add several useful extensions
## 0.22.2
* `Versions`:
* `Exposed`: `0.53.0` -> `0.54.0`
* `SQLite`: `3.46.0.1` -> `3.46.1.0`
## 0.22.1
* `Versions`:
* `Kotlin`: `2.0.10` -> `2.0.20`
* `Serialization`: `1.7.1` -> `1.7.2`
* `KSLog`: `1.3.5` -> `1.3.6`
* `Compose`: `1.7.0-alpha02` -> `1.7.0-alpha03`
* `Ktor`: `2.3.11` -> `2.3.12`
## 0.22.0
**THIS UPDATE CONTAINS BREAKING CHANGES ACCORDING TO UPDATE UP TO KOTLIN 2.0**
* `Versions`:
* `Kotlin`: `1.9.23` -> `2.0.10`
* `Serialization`: `1.6.3` -> `1.7.1`
* `KSLog`: `1.3.4` -> `1.3.5`
* `Compose`: `1.6.2` -> `1.7.0-alpha02`
* `Exposed`: `0.50.1` -> `0.53.0`
* `AndroidAppCompat`: `1.6.1` -> `1.7.0`
* `AndroidFragment`: `1.7.1` -> `1.8.2`
## 0.21.6
* `KSP`:
* `Sealed`:
* Fixes in generation
## 0.21.5
* `KSP`:
* Add utility functions `KSClassDeclaration.findSubClasses`
* `Sealed`:
* Improve generation
## 0.21.4
* `Common`:
* `Compose`:
* Add support of mingw, linux, arm64 targets
* `Coroutines`:
* `Compose`:
* Add support of mingw, linux, arm64 targets
* `Koin`:
* Add support of mingw, linux, arm64 targets
* `KSP`:
* `ClassCasts`:
* Add support of mingw, linux, arm64 targets
* `Sealed`:
* Add support of mingw, linux, arm64 targets
## 0.21.3
* `Colors`:
* Added as a module. It should be used by default in case you wish all the API currently realized for `HEXAColor`
* `Coroutines`:
* Fix of [#374](https://github.com/InsanusMokrassar/MicroUtils/issues/374):
* Add vararg variants of `awaitFirst`
* Add `joinFirst`
## 0.21.2
* `KSP`:
* `ClassCasts`:
* Module has been initialized
## 0.21.1
* `KSP`:
* Module has been initialized
* `Generator`:
* Module has been initialized
* `Sealed`:
* Module has been initialized
## 0.21.0
**THIS UPDATE CONTAINS BREAKING CHANGES IN `safely*`-ORIENTED FUNCTIONS**
* `Coroutines`:
* **All `safely` functions lost their `supervisorScope` in favor to wrapping `runCatching`**
* `runCatchingSafely` is the main handling function of all `safely` functions
* `launchSafely*` and `asyncSafely*` blocks lost `CoroutineScope` as their receiver
## 0.20.52
* `Coroutines`:
* Small rework of weak jobs: add `WeakScope` factory, rename old weal extensions and add kdocs
## 0.20.51
* `Versions`:
* `Android Fragment`: `1.7.0` -> `1.7.1`
* `Pagination`:
* Add `Pagination.nextPageIfTrue` and `Pagination.thisPageIftrue` extensions to get the page according to condition
pass status
* Add `PaginationResult.nextPageIfNotEmptyOrLastPage` and `PaginationResult.thisPageIfNotEmptyOrLastPage`
* Change all `doForAll` and `getAll` extensions fo pagination to work basing on `nextPageIfNotEmptyOrLastPage` and
`thisPageIfNotEmptyOrLastPage`
## 0.20.50
* `Versions`:
* `Coroutines`: `1.8.0` -> `1.8.1`
* `KSLog`: `1.3.3` -> `1.3.4`
* `Exposed`: `0.50.0` -> `0.50.1`
* `Ktor`: `2.3.10` -> `2.3.11`
* A lot of inline functions became common functions due to inline with only noinline callbacks in arguments leads to
low performance
* `Coroutines`:
* `SmartMutex`, `SmartSemaphore` and `SmartRWLocker` as their user changed their state flow to `SpecialMutableStateFlow`
## 0.20.49
* `Repos`:
* `Common`:
* All `Repo`s get `diff` extensions
* `KeyValueRepo` and `KeyValuesRepo` get `applyDiff` extension
* Add new extensions `on*` flows for crud repos
## 0.20.48
* `Versions`:
* `Android Core KTX`: `1.13.0` -> `1.13.1`
* `AndroidX Fragment`: `1.6.2` -> `1.7.0`
## 0.20.47
* `Versions`:
* `Exposed`: `0.49.0` -> `0.50.0`
## 0.20.46
* `Common`:
* Now this repo depends on `klock`
* Add new object-serializer `DateTimeSerializer` for `klock` serializer
## 0.20.45
* `Versions`:
* `Android Core KTX`: `1.12.0` -> `1.13.0`
## 0.20.44
* `Versions`:
* `Compose`: `1.6.1` -> `1.6.2`
* `Koin`: `3.5.4` -> `3.5.6`
## 0.20.43
* `Versions`:
* `Ktor`: `2.3.9` -> `2.3.10`
* `Koin`: `3.5.3` -> `3.5.4`
## 0.20.42
* `Repos`:
* `Generator`:
* Improvements
## 0.20.41
* `Repos`:
* `Exposed`:
* `AbstractExposedKeyValueRepo`, `ExposedKeyValueRepo`, `AbstractExposedKeyValuesRepo`, `ExposedKeyValuesRepo` got opportunity to setup some part of their flows
## 0.20.40
* `Versions`:
* `KSLog`: `1.3.2` -> `1.3.3`
* `Exposed`: `0.48.0` -> `0.49.0`
* `UUID`: `0.8.2` -> `0.8.4`
## 0.20.39
* `Versions`:
* `Kotlin`: `1.9.22` -> `1.9.23`
* `Korlibs`: `5.3.2` -> `5.4.0`
* `Okio`: `3.8.0` -> `3.9.0`
* `Compose`: `1.6.0` -> `1.6.1`
* `ComposeMaterial3`: `1.2.0` -> `1.2.1`
## 0.20.38
* `Versions`:
* `Ktor`: `2.3.8` -> `2.3.9`
## 0.20.37
* `Versions`:
* `Compose`: `1.5.12` -> `1.6.0`
* `Exposed`: `0.47.0` -> `0.48.0`
## 0.20.36
* `Versions`:
* `Serialization`: `1.6.2` -> `1.6.3`
* `Korlibs`: `5.3.1` -> `5.3.2`
* `Repos`:
* `Cache`:
* Improve work and functionality of `actualizeAll` and subsequent functions
* All internal repos `invalidate`/`actualizeAll` now use common `actualizeAll` functions
## 0.20.35
* `Versions`:
* `Coroutines`: `1.7.3` -> `1.8.0`
* `Material3`: `1.1.2` -> `1.2.0`
## 0.20.34
* `Repos`:
* `Common`:
* Improve default `set` realization of `KeyValuesRepo`
## 0.20.33
* `Colors`
* `Common`:
* Add opportunity to use `HEXAColor` with `ahex` colors
## 0.20.32
* `Versions`:
* `Okio`: `3.7.0` -> `3.8.0`
* `Resources`:
* Make `StringResource` serializable
* Add several variants of builder usages
## 0.20.31
* `Versions`:
* `Ktor`: `2.3.7` -> `2.3.8`
## 0.20.30
* `Versions`:
* `Exposed`: `0.46.0` -> `0.47.0`
## 0.20.29
* `Versions`:
* `Kotlin`: `1.9.21` -> `1.9.22`
* `Compose`: `1.5.11` -> `1.5.12`
* `Korlibs`: `5.3.0` -> `5.3.1`
## 0.20.28
* `Versions`:
* `Kotlin`: `1.9.22` -> `1.9.21` (downgrade)
* `Compose`: `1.6.0-dev13691` -> `1.5.11` (downgrade)
## 0.20.27
* `Versions`:
* `Kotlin`: `1.9.21` -> `1.9.22`
* `Compose`: `1.5.11` -> `1.6.0-dev13691`
## 0.20.26
* `Versions`:
* `Exposed`: `0.45.0` -> `0.46.0`. **This update brinds new api deprecations in exposed**
* `Resources`:
* Add opportunity to get default translation by passing `null` as `IetfLang` argument
* Add several useful extensions to get translations in `JS` target
## 0.20.25
* `Colors`:
* `Common`:
* Module inited
## 0.20.24
**Since this version depdendencies of klock and krypto replaced with `com.soywiz.korge:korlibs-time` and `com.soywiz.korge:korlibs-crypto`**
* `Versions`:
* `Klock` (since now `KorlibsTime`): `4.0.10` -> `5.3.0`
* `Krypto` (since now `KorlibsCrypto`): `4.0.10` -> `5.3.0`
* `Serialization`:
* `Mapper`:
* `Mapper` pass decoder into callback of deserialization strategy
* `Mapper` pass encoder into callback of serialization strategy
## 0.20.23
* `Klock`:
* Inited as copypaste from [korlibs/korge](https://github.com/korlibs/korge) and [korlibs/korlibs4](https://github.com/korlibs/korlibs4)
* `Versions`:
* `Koin`: `3.5.0` -> `3.5.3`
* `Okio`: `3.6.0` -> `3.7.0`
* `LanguageCodes`:
* Fixes in intermediate language codes (like `Chinese.Hans`)
* Rename `IetfLanguageCode` to `IetfLang`
* Rename all subsequent functions (including serializer)
* New lazy properties `knownLanguageCodesMap`, `knownLanguageCodesMapByLowerCasedKeys` and several others
## 0.20.22

View File

@@ -1,11 +1,5 @@
# MicroUtils
---
**`Klock` module initial commit based on [korlibs/korge](https://github.com/korlibs/korge) and [korlibs/korlibs4](https://github.com/korlibs/korlibs4)**
---
This is a library with collection of tools for working in Kotlin environment. First of all, this library collection is oriented to use next technologies:
* [`Kotlin Coroutines`](https://github.com/Kotlin/kotlinx.coroutines)

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
apply from: "$mppAndroidProject"
kotlin {
sourceSets {

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
apply from: "$mppAndroidProject"
kotlin {
sourceSets {

View File

@@ -3,9 +3,10 @@ plugins {
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
apply from: "$mppProjectWithSerializationAndCompose"
kotlin {
sourceSets {

View File

@@ -7,9 +7,6 @@ import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ContentAlpha
import androidx.compose.material.IconButton
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
@@ -75,7 +72,7 @@ fun NumberPicker(
}
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val animatedStateValue = animatedStateValue(animatedOffset.value)
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
val inputFieldShown = if (allowUseManualInput) {
remember { mutableStateOf(false) }

View File

@@ -5,7 +5,6 @@ import androidx.compose.animation.core.exponentialDecay
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
@@ -62,9 +61,7 @@ fun <T> SetPicker(
(index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt())
}
val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx
val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset
val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled)
val scrollState = rememberScrollState()
val disabledArrowsColor = arrowsColor.copy(alpha = 0f)
Column(
modifier = modifier

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppAndroidProjectPresetPath"
apply from: "$mppAndroidProject"
kotlin {
sourceSets {

View File

@@ -3,15 +3,16 @@ plugins {
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
apply from: "$mppProjectWithSerializationAndCompose"
kotlin {
sourceSets {
androidMain {
dependencies {
api libs.android.compose.material3
api libs.jb.compose.material3
}
}
}

View File

@@ -29,15 +29,6 @@ allprojects {
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
}
// temporal crutch until legacy tests will be stabled or legacy target will be removed
if (it != rootProject.findProject("docs")) {
tasks.whenTaskAdded { task ->
if(task.name == "jsLegacyBrowserTest" || task.name == "jsLegacyNodeTest") {
task.enabled = false
}
}
}
}
apply from: "./extensions.gradle"

17
colors/build.gradle Normal file
View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {
commonMain {
dependencies {
api project(":micro_utils.colors.common")
}
}
}
}

View File

@@ -4,4 +4,4 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"

View File

@@ -0,0 +1,174 @@
package dev.inmo.micro_utils.colors.common
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
import kotlin.math.floor
/**
* Wrapper for RGBA colors. Receiving [UInt] in main constructor. Each part in main constructor
* configured with `0x00 - 0xff` range. Examples:
*
* * Red: `0xff0000ffu`
* * Red (0.5 capacity): `0xff000088u`
*
* Anyway it is recommended to use
*
* @param hexaUInt rgba [UInt] in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
*/
@Serializable
@JvmInline
value class HEXAColor (
val hexaUInt: UInt
) : Comparable<HEXAColor> {
/**
* @returns [hexaUInt] as a string with format `#FFEEBBAA` where FF - red, EE - green, BB - blue and AA - alpha
*/
val hexa: String
get() = "#${hexaUInt.toString(16).padStart(8, '0')}"
/**
* @returns [hexaUInt] as a string with format `#FFEEBB` where FF - red, EE - green and BB - blue
*/
val hex: String
get() = hexa.take(7)
/**
* @returns [hexaUInt] as a string with format `#AAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue
*/
val ahex: String
get() = "#${a.toString(16).padStart(2, '2')}${hex.drop(1)}"
val rgba: String
get() = "rgba($r,$g,$b,${aOfOne.toString().take(5)})"
val rgb: String
get() = "rgb($r,$g,$b)"
val shortHex: String
get() = "#${r.shortPart()}${g.shortPart()}${b.shortPart()}"
val shortHexa: String
get() = "$shortHex${a.shortPart()}"
val rgbUInt: UInt
get() = (hexaUInt / 256u)
val rgbInt: Int
get() = rgbUInt.toInt()
val ahexUInt
get() = (a * 0x1000000).toUInt() + rgbUInt
val r: Int
get() = ((hexaUInt and 0xff000000u) / 0x1000000u).toInt()
val g: Int
get() = ((hexaUInt and 0x00ff0000u) / 0x10000u).toInt()
val b: Int
get() = ((hexaUInt and 0x0000ff00u) / 0x100u).toInt()
val a: Int
get() = ((hexaUInt and 0x000000ffu)).toInt()
val aOfOne: Float
get() = a.toFloat() / (0xff)
init {
require(hexaUInt in 0u ..0xffffffffu)
}
constructor(r: Int, g: Int, b: Int, a: Int) : this(
((r * 0x1000000).toLong() + g * 0x10000 + b * 0x100 + a).toUInt()
) {
require(r in 0 ..0xff)
require(g in 0 ..0xff)
require(b in 0 ..0xff)
require(a in 0 ..0xff)
}
constructor(r: Int, g: Int, b: Int, aOfOne: Float = 1f) : this(
r = r, g = g, b = b, a = (aOfOne * 0xff).toInt()
)
override fun toString(): String {
return hexa
}
override fun compareTo(other: HEXAColor): Int = (hexaUInt - other.hexaUInt).coerceIn(Int.MIN_VALUE.toUInt(), Int.MAX_VALUE.toLong().toUInt()).toInt()
fun copy(
r: Int = this.r,
g: Int = this.g,
b: Int = this.b,
aOfOne: Float = this.aOfOne
) = HEXAColor(r = r, g = g, b = b, aOfOne = aOfOne)
fun copy(
r: Int = this.r,
g: Int = this.g,
b: Int = this.b,
a: Int
) = HEXAColor(r = r, g = g, b = b, a = a)
companion object {
/**
* Parsing color from [color]
*
* Supported formats samples (on Red color based):
*
* * `#f00`
* * `#f00f`
* * `#ff0000`
* * `#ff0000ff`
* * `rgb(255, 0, 0)`
* * `rgba(255, 0, 0, 1)`
*/
fun parseStringColor(color: String): HEXAColor = when {
color.startsWith("#") -> color.removePrefix("#").let { color ->
when (color.length) {
3 -> color.map { "$it$it" }.joinToString(separator = "", postfix = "ff")
4 -> color.take(3).map { "$it$it" }.joinToString(separator = "", postfix = color.takeLast(1).let { "${it}0" })
6 -> "${color}ff"
8 -> color
else -> error("Malfurmed color string: $color. It is expected that color started with # will contains 3, 6 or 8 valuable parts")
}
}
color.startsWith("rgb(") -> color
.removePrefix("rgb(")
.removeSuffix(")")
.replace(Regex("\\s"), "")
.split(",")
.joinToString("", postfix = "ff") {
it.toInt().toString(16).padStart(2, '0')
}
color.startsWith("rgba(") -> color
.removePrefix("rgba(")
.removeSuffix(")")
.replace(Regex("\\s"), "")
.split(",").let {
it.take(3).map { it.toInt().toString(16).padStart(2, '0') } + (it.last().toFloat() * 0xff).toInt().toString(16).padStart(2, '0')
}
.joinToString("")
else -> color
}.lowercase().toUInt(16).let(::HEXAColor)
/**
* Creates [HEXAColor] from [uint] presume it is in format `0xFFEEBBAA` where FF - red, EE - green, BB - blue` and AA - alpha
*/
fun fromHexa(uint: UInt) = HEXAColor(uint)
/**
* Creates [HEXAColor] from [uint] presume it is in format `0xAAFFEEBB` where AA - alpha, FF - red, EE - green and BB - blue`
*/
fun fromAhex(uint: UInt) = HEXAColor(
a = ((uint and 0xff000000u) / 0x1000000u).toInt(),
r = ((uint and 0x00ff0000u) / 0x10000u).toInt(),
g = ((uint and 0x0000ff00u) / 0x100u).toInt(),
b = ((uint and 0x000000ffu)).toInt()
)
/**
* Parsing color from [color]
*
* Supported formats samples (on Red color based):
*
* * `#f00`
* * `#ff0000`
* * `#ff0000ff`
* * `rgb(255, 0, 0)`
* * `rgba(255, 0, 0, 1)`
*/
operator fun invoke(color: String) = parseStringColor(color)
private fun Int.shortPart(): String {
return (floor(toFloat() / 16)).toInt().toString(16)
}
}
}

View File

@@ -0,0 +1,209 @@
package dev.inmo.micro_utils.colors.common
import kotlin.math.floor
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class HexColorTests {
val alphaRgbaPrecision = 5
class TestColor(
val color: HEXAColor,
val shortHex: String,
val shortHexa: String,
val hex: String,
val hexa: String,
val ahex: String,
val ahexUInt: UInt,
val rgbUInt: UInt,
val rgb: String,
val rgba: String,
val r: Int,
val g: Int,
val b: Int,
val a: Int,
vararg val additionalRGBAVariants: String
)
val testColors: List<TestColor>
get() = listOf(
TestColor(
color = HEXAColor(hexaUInt = 0xff0000ffu),
shortHex = "#f00",
shortHexa = "#f00f",
hex = "#ff0000",
hexa = "#ff0000ff",
ahex = "#ffff0000",
ahexUInt = 0xffff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,1.0)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0xff,
"rgba(255,0,0,1)",
),
TestColor(
color = HEXAColor(hexaUInt = 0x00ff00ffu),
shortHex = "#0f0",
shortHexa = "#0f0f",
hex = "#00ff00",
hexa = "#00ff00ff",
ahex = "#ff00ff00",
ahexUInt = 0xff00ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,1.0)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0xff,
"rgba(0,255,0,1)"
),
TestColor(
color = HEXAColor(0x0000ffffu),
shortHex = "#00f",
shortHexa = "#00ff",
hex = "#0000ff",
hexa = "#0000ffff",
ahex = "#ff0000ff",
ahexUInt = 0xff0000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,1.0)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0xff,
"rgba(0,0,255,1)"
),
TestColor(
color = HEXAColor(0xff000088u),
shortHex = "#f00",
shortHexa = "#f008",
hex = "#ff0000",
hexa = "#ff000088",
ahex = "#88ff0000",
ahexUInt = 0x88ff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,0.533)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0x88,
),
TestColor(
color = HEXAColor(0x00ff0088u),
shortHex = "#0f0",
shortHexa = "#0f08",
hex = "#00ff00",
hexa = "#00ff0088",
ahex = "#8800ff00",
ahexUInt = 0x8800ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,0.533)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0x88,
),
TestColor(
color = HEXAColor(0x0000ff88u),
shortHex = "#00f",
shortHexa = "#00f8",
hex = "#0000ff",
hexa = "#0000ff88",
ahex = "#880000ff",
ahexUInt = 0x880000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,0.533)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0x88,
),
TestColor(
color = HEXAColor(0xff000022u),
shortHex = "#f00",
shortHexa = "#f002",
hex = "#ff0000",
hexa = "#ff000022",
ahex = "#22ff0000",
ahexUInt = 0x22ff0000u,
rgbUInt = 0xff0000u,
rgb = "rgb(255,0,0)",
rgba = "rgba(255,0,0,0.133)",
r = 0xff,
g = 0x00,
b = 0x00,
a = 0x22,
),
TestColor(
color = HEXAColor(0x00ff0022u),
shortHex = "#0f0",
shortHexa = "#0f02",
hex = "#00ff00",
hexa = "#00ff0022",
ahex = "#2200ff00",
ahexUInt = 0x2200ff00u,
rgbUInt = 0x00ff00u,
rgb = "rgb(0,255,0)",
rgba = "rgba(0,255,0,0.133)",
r = 0x00,
g = 0xff,
b = 0x00,
a = 0x22,
),
TestColor(
color = HEXAColor(0x0000ff22u),
shortHex = "#00f",
shortHexa = "#00f2",
hex = "#0000ff",
hexa = "#0000ff22",
ahex = "#220000ff",
ahexUInt = 0x220000ffu,
rgbUInt = 0x0000ffu,
rgb = "rgb(0,0,255)",
rgba = "rgba(0,0,255,0.133)",
r = 0x00,
g = 0x00,
b = 0xff,
a = 0x22,
),
)
@Test
fun baseTest() {
testColors.forEach {
assertEquals(it.hex, it.color.hex)
assertEquals(it.hexa, it.color.hexa)
assertEquals(it.ahex, it.color.ahex)
assertEquals(it.rgbUInt, it.color.rgbUInt)
assertEquals(it.ahexUInt, it.color.ahexUInt)
assertEquals(it.shortHex, it.color.shortHex)
assertEquals(it.shortHexa, it.color.shortHexa)
assertEquals(it.rgb, it.color.rgb)
assertTrue(it.rgba == it.color.rgba || it.color.rgba in it.additionalRGBAVariants)
assertEquals(it.r, it.color.r)
assertEquals(it.g, it.color.g)
assertEquals(it.b, it.color.b)
assertEquals(it.a, it.color.a)
assertEquals(it.color, HEXAColor.fromAhex(it.ahexUInt))
}
}
@Test
fun testHexParseColor() {
testColors.forEach {
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.hex))
assertEquals(it.color, HEXAColor.parseStringColor(it.hexa))
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.rgb))
assertTrue(it.color.hexaUInt.toInt() - HEXAColor.parseStringColor(it.rgba).hexaUInt.toInt() in -0x1 .. 0x1, )
assertEquals(it.color.copy(aOfOne = 1f), HEXAColor.parseStringColor(it.shortHex))
assertEquals(it.color.copy(a = floor(it.color.a.toFloat() / 16).toInt() * 0x10), HEXAColor.parseStringColor(it.shortHexa))
}
}
}

View File

@@ -0,0 +1,298 @@
package dev.inmo.micro_utils.colors
import dev.inmo.micro_utils.colors.common.HEXAColor
val HEXAColor.Companion.aliceblue
get() = HEXAColor(0xF0F8FFFFu)
val HEXAColor.Companion.antiquewhite
get() = HEXAColor(0xFAEBD7FFu)
val HEXAColor.Companion.aqua
get() = HEXAColor(0x00FFFFFFu)
val HEXAColor.Companion.aquamarine
get() = HEXAColor(0x7FFFD4FFu)
val HEXAColor.Companion.azure
get() = HEXAColor(0xF0FFFFFFu)
val HEXAColor.Companion.beige
get() = HEXAColor(0xF5F5DCFFu)
val HEXAColor.Companion.bisque
get() = HEXAColor(0xFFE4C4FFu)
val HEXAColor.Companion.black
get() = HEXAColor(0x000000FFu)
val HEXAColor.Companion.blanchedalmond
get() = HEXAColor(0xFFEBCDFFu)
val HEXAColor.Companion.blue
get() = HEXAColor(0x0000FFFFu)
val HEXAColor.Companion.blueviolet
get() = HEXAColor(0x8A2BE2FFu)
val HEXAColor.Companion.brown
get() = HEXAColor(0xA52A2AFFu)
val HEXAColor.Companion.burlywood
get() = HEXAColor(0xDEB887FFu)
val HEXAColor.Companion.cadetblue
get() = HEXAColor(0x5F9EA0FFu)
val HEXAColor.Companion.chartreuse
get() = HEXAColor(0x7FFF00FFu)
val HEXAColor.Companion.chocolate
get() = HEXAColor(0xD2691EFFu)
val HEXAColor.Companion.coral
get() = HEXAColor(0xFF7F50FFu)
val HEXAColor.Companion.cornflowerblue
get() = HEXAColor(0x6495EDFFu)
val HEXAColor.Companion.cornsilk
get() = HEXAColor(0xFFF8DCFFu)
val HEXAColor.Companion.crimson
get() = HEXAColor(0xDC143CFFu)
val HEXAColor.Companion.cyan
get() = HEXAColor(0x00FFFFFFu)
val HEXAColor.Companion.darkblue
get() = HEXAColor(0x00008BFFu)
val HEXAColor.Companion.darkcyan
get() = HEXAColor(0x008B8BFFu)
val HEXAColor.Companion.darkgoldenrod
get() = HEXAColor(0xB8860BFFu)
val HEXAColor.Companion.darkgray
get() = HEXAColor(0xA9A9A9FFu)
val HEXAColor.Companion.darkgreen
get() = HEXAColor(0x006400FFu)
val HEXAColor.Companion.darkgrey
get() = HEXAColor(0xA9A9A9FFu)
val HEXAColor.Companion.darkkhaki
get() = HEXAColor(0xBDB76BFFu)
val HEXAColor.Companion.darkmagenta
get() = HEXAColor(0x8B008BFFu)
val HEXAColor.Companion.darkolivegreen
get() = HEXAColor(0x556B2FFFu)
val HEXAColor.Companion.darkorange
get() = HEXAColor(0xFF8C00FFu)
val HEXAColor.Companion.darkorchid
get() = HEXAColor(0x9932CCFFu)
val HEXAColor.Companion.darkred
get() = HEXAColor(0x8B0000FFu)
val HEXAColor.Companion.darksalmon
get() = HEXAColor(0xE9967AFFu)
val HEXAColor.Companion.darkseagreen
get() = HEXAColor(0x8FBC8FFFu)
val HEXAColor.Companion.darkslateblue
get() = HEXAColor(0x483D8BFFu)
val HEXAColor.Companion.darkslategray
get() = HEXAColor(0x2F4F4FFFu)
val HEXAColor.Companion.darkslategrey
get() = HEXAColor(0x2F4F4FFFu)
val HEXAColor.Companion.darkturquoise
get() = HEXAColor(0x00CED1FFu)
val HEXAColor.Companion.darkviolet
get() = HEXAColor(0x9400D3FFu)
val HEXAColor.Companion.deeppink
get() = HEXAColor(0xFF1493FFu)
val HEXAColor.Companion.deepskyblue
get() = HEXAColor(0x00BFFFFFu)
val HEXAColor.Companion.dimgray
get() = HEXAColor(0x696969FFu)
val HEXAColor.Companion.dimgrey
get() = HEXAColor(0x696969FFu)
val HEXAColor.Companion.dodgerblue
get() = HEXAColor(0x1E90FFFFu)
val HEXAColor.Companion.firebrick
get() = HEXAColor(0xB22222FFu)
val HEXAColor.Companion.floralwhite
get() = HEXAColor(0xFFFAF0FFu)
val HEXAColor.Companion.forestgreen
get() = HEXAColor(0x228B22FFu)
val HEXAColor.Companion.fuchsia
get() = HEXAColor(0xFF00FFFFu)
val HEXAColor.Companion.gainsboro
get() = HEXAColor(0xDCDCDCFFu)
val HEXAColor.Companion.ghostwhite
get() = HEXAColor(0xF8F8FFFFu)
val HEXAColor.Companion.gold
get() = HEXAColor(0xFFD700FFu)
val HEXAColor.Companion.goldenrod
get() = HEXAColor(0xDAA520FFu)
val HEXAColor.Companion.gray
get() = HEXAColor(0x808080FFu)
val HEXAColor.Companion.green
get() = HEXAColor(0x008000FFu)
val HEXAColor.Companion.greenyellow
get() = HEXAColor(0xADFF2FFFu)
val HEXAColor.Companion.grey
get() = HEXAColor(0x808080FFu)
val HEXAColor.Companion.honeydew
get() = HEXAColor(0xF0FFF0FFu)
val HEXAColor.Companion.hotpink
get() = HEXAColor(0xFF69B4FFu)
val HEXAColor.Companion.indianred
get() = HEXAColor(0xCD5C5CFFu)
val HEXAColor.Companion.indigo
get() = HEXAColor(0x4B0082FFu)
val HEXAColor.Companion.ivory
get() = HEXAColor(0xFFFFF0FFu)
val HEXAColor.Companion.khaki
get() = HEXAColor(0xF0E68CFFu)
val HEXAColor.Companion.lavender
get() = HEXAColor(0xE6E6FAFFu)
val HEXAColor.Companion.lavenderblush
get() = HEXAColor(0xFFF0F5FFu)
val HEXAColor.Companion.lawngreen
get() = HEXAColor(0x7CFC00FFu)
val HEXAColor.Companion.lemonchiffon
get() = HEXAColor(0xFFFACDFFu)
val HEXAColor.Companion.lightblue
get() = HEXAColor(0xADD8E6FFu)
val HEXAColor.Companion.lightcoral
get() = HEXAColor(0xF08080FFu)
val HEXAColor.Companion.lightcyan
get() = HEXAColor(0xE0FFFFFFu)
val HEXAColor.Companion.lightgoldenrodyellow
get() = HEXAColor(0xFAFAD2FFu)
val HEXAColor.Companion.lightgray
get() = HEXAColor(0xD3D3D3FFu)
val HEXAColor.Companion.lightgreen
get() = HEXAColor(0x90EE90FFu)
val HEXAColor.Companion.lightgrey
get() = HEXAColor(0xD3D3D3FFu)
val HEXAColor.Companion.lightpink
get() = HEXAColor(0xFFB6C1FFu)
val HEXAColor.Companion.lightsalmon
get() = HEXAColor(0xFFA07AFFu)
val HEXAColor.Companion.lightseagreen
get() = HEXAColor(0x20B2AAFFu)
val HEXAColor.Companion.lightskyblue
get() = HEXAColor(0x87CEFAFFu)
val HEXAColor.Companion.lightslategray
get() = HEXAColor(0x778899FFu)
val HEXAColor.Companion.lightslategrey
get() = HEXAColor(0x778899FFu)
val HEXAColor.Companion.lightsteelblue
get() = HEXAColor(0xB0C4DEFFu)
val HEXAColor.Companion.lightyellow
get() = HEXAColor(0xFFFFE0FFu)
val HEXAColor.Companion.lime
get() = HEXAColor(0x00FF00FFu)
val HEXAColor.Companion.limegreen
get() = HEXAColor(0x32CD32FFu)
val HEXAColor.Companion.linen
get() = HEXAColor(0xFAF0E6FFu)
val HEXAColor.Companion.magenta
get() = HEXAColor(0xFF00FFFFu)
val HEXAColor.Companion.maroon
get() = HEXAColor(0x800000FFu)
val HEXAColor.Companion.mediumaquamarine
get() = HEXAColor(0x66CDAAFFu)
val HEXAColor.Companion.mediumblue
get() = HEXAColor(0x0000CDFFu)
val HEXAColor.Companion.mediumorchid
get() = HEXAColor(0xBA55D3FFu)
val HEXAColor.Companion.mediumpurple
get() = HEXAColor(0x9370DBFFu)
val HEXAColor.Companion.mediumseagreen
get() = HEXAColor(0x3CB371FFu)
val HEXAColor.Companion.mediumslateblue
get() = HEXAColor(0x7B68EEFFu)
val HEXAColor.Companion.mediumspringgreen
get() = HEXAColor(0x00FA9AFFu)
val HEXAColor.Companion.mediumturquoise
get() = HEXAColor(0x48D1CCFFu)
val HEXAColor.Companion.mediumvioletred
get() = HEXAColor(0xC71585FFu)
val HEXAColor.Companion.midnightblue
get() = HEXAColor(0x191970FFu)
val HEXAColor.Companion.mintcream
get() = HEXAColor(0xF5FFFAFFu)
val HEXAColor.Companion.mistyrose
get() = HEXAColor(0xFFE4E1FFu)
val HEXAColor.Companion.moccasin
get() = HEXAColor(0xFFE4B5FFu)
val HEXAColor.Companion.navajowhite
get() = HEXAColor(0xFFDEADFFu)
val HEXAColor.Companion.navy
get() = HEXAColor(0x000080FFu)
val HEXAColor.Companion.oldlace
get() = HEXAColor(0xFDF5E6FFu)
val HEXAColor.Companion.olive
get() = HEXAColor(0x808000FFu)
val HEXAColor.Companion.olivedrab
get() = HEXAColor(0x6B8E23FFu)
val HEXAColor.Companion.orange
get() = HEXAColor(0xFFA500FFu)
val HEXAColor.Companion.orangered
get() = HEXAColor(0xFF4500FFu)
val HEXAColor.Companion.orchid
get() = HEXAColor(0xDA70D6FFu)
val HEXAColor.Companion.palegoldenrod
get() = HEXAColor(0xEEE8AAFFu)
val HEXAColor.Companion.palegreen
get() = HEXAColor(0x98FB98FFu)
val HEXAColor.Companion.paleturquoise
get() = HEXAColor(0xAFEEEEFFu)
val HEXAColor.Companion.palevioletred
get() = HEXAColor(0xDB7093FFu)
val HEXAColor.Companion.papayawhip
get() = HEXAColor(0xFFEFD5FFu)
val HEXAColor.Companion.peachpuff
get() = HEXAColor(0xFFDAB9FFu)
val HEXAColor.Companion.peru
get() = HEXAColor(0xCD853FFFu)
val HEXAColor.Companion.pink
get() = HEXAColor(0xFFC0CBFFu)
val HEXAColor.Companion.plum
get() = HEXAColor(0xDDA0DDFFu)
val HEXAColor.Companion.powderblue
get() = HEXAColor(0xB0E0E6FFu)
val HEXAColor.Companion.purple
get() = HEXAColor(0x800080FFu)
val HEXAColor.Companion.red
get() = HEXAColor(0xFF0000FFu)
val HEXAColor.Companion.rosybrown
get() = HEXAColor(0xBC8F8FFFu)
val HEXAColor.Companion.royalblue
get() = HEXAColor(0x4169E1FFu)
val HEXAColor.Companion.saddlebrown
get() = HEXAColor(0x8B4513FFu)
val HEXAColor.Companion.salmon
get() = HEXAColor(0xFA8072FFu)
val HEXAColor.Companion.sandybrown
get() = HEXAColor(0xF4A460FFu)
val HEXAColor.Companion.seagreen
get() = HEXAColor(0x2E8B57FFu)
val HEXAColor.Companion.seashell
get() = HEXAColor(0xFFF5EEFFu)
val HEXAColor.Companion.sienna
get() = HEXAColor(0xA0522DFFu)
val HEXAColor.Companion.silver
get() = HEXAColor(0xC0C0C0FFu)
val HEXAColor.Companion.skyblue
get() = HEXAColor(0x87CEEBFFu)
val HEXAColor.Companion.slateblue
get() = HEXAColor(0x6A5ACDFFu)
val HEXAColor.Companion.slategray
get() = HEXAColor(0x708090FFu)
val HEXAColor.Companion.slategrey
get() = HEXAColor(0x708090FFu)
val HEXAColor.Companion.snow
get() = HEXAColor(0xFFFAFAFFu)
val HEXAColor.Companion.springgreen
get() = HEXAColor(0x00FF7FFFu)
val HEXAColor.Companion.steelblue
get() = HEXAColor(0x4682B4FFu)
val HEXAColor.Companion.tan
get() = HEXAColor(0xD2B48CFFu)
val HEXAColor.Companion.teal
get() = HEXAColor(0x008080FFu)
val HEXAColor.Companion.thistle
get() = HEXAColor(0xD8BFD8FFu)
val HEXAColor.Companion.tomato
get() = HEXAColor(0xFF6347FFu)
val HEXAColor.Companion.turquoise
get() = HEXAColor(0x40E0D0FFu)
val HEXAColor.Companion.violet
get() = HEXAColor(0xEE82EEFFu)
val HEXAColor.Companion.wheat
get() = HEXAColor(0xF5DEB3FFu)
val HEXAColor.Companion.white
get() = HEXAColor(0xFFFFFFFFu)
val HEXAColor.Companion.whitesmoke
get() = HEXAColor(0xF5F5F5FFu)
val HEXAColor.Companion.yellow
get() = HEXAColor(0xFFFF00FFu)
val HEXAColor.Companion.yellowgreen
get() = HEXAColor(0x9ACD32FFu)

View File

@@ -4,10 +4,15 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {
commonMain {
dependencies {
api libs.klock
}
}
jvmMain {
dependencies {
api project(":micro_utils.coroutines")
@@ -15,23 +20,11 @@ kotlin {
}
androidMain {
dependencies {
api project(":micro_utils.coroutines")
api libs.android.fragment
}
dependsOn jvmMain
}
linuxX64Main {
dependencies {
api libs.okio
}
}
mingwX64Main {
dependencies {
api libs.okio
}
}
linuxArm64Main {
nativeMain {
dependencies {
api libs.okio
}

View File

@@ -3,9 +3,10 @@ plugins {
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

@@ -0,0 +1,19 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.dom.AttrBuilderContext
import org.w3c.dom.Element
operator fun <T : Element> AttrBuilderContext<T>?.plus(
other: AttrBuilderContext<T>?
) = when (this) {
null -> other ?: {}
else -> when (other) {
null -> this ?: {}
else -> {
{
invoke(this)
other(this)
}
}
}
}

View File

@@ -0,0 +1,22 @@
package dev.inmo.micro_utils.common.compose
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.DisposableEffectResult
import androidx.compose.runtime.DisposableEffectScope
import org.jetbrains.compose.web.attributes.AttrsScope
import org.jetbrains.compose.web.dom.ElementScope
import org.w3c.dom.Element
/**
* This function must be called in the context of your tag content. It works like default [AttrsScope.ref],
* but able to be used several times. Uses [DisposableEffect] under the hood
*/
@Composable
fun <T : Element> ElementScope<T>.ref(
block: DisposableEffectScope.(T) -> DisposableEffectResult
) {
DisposableEffect(0) {
block(scopeElement)
}
}

View File

@@ -0,0 +1,11 @@
package dev.inmo.micro_utils.common.compose
import org.jetbrains.compose.web.dom.AttrBuilderContext
fun tagClasses(vararg classnames: String): AttrBuilderContext<*> = {
classes(*classnames)
}
fun tagId(id: String): AttrBuilderContext<*> = {
id(id)
}

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -0,0 +1,25 @@
package dev.inmo.micro_utils.common
import korlibs.time.DateTime
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
/**
* Serializes [DateTime] as its raw [DateTime.unixMillis] and deserializes in the same way
*/
object DateTimeSerializer : KSerializer<DateTime> {
override val descriptor: SerialDescriptor
get() = Double.serializer().descriptor
override fun deserialize(decoder: Decoder): DateTime {
return DateTime(decoder.decodeDouble())
}
override fun serialize(encoder: Encoder, value: DateTime) {
encoder.encodeDouble(value.unixMillis)
}
}

View File

@@ -8,7 +8,7 @@ private inline fun <T> getObject(
additional: MutableList<T>,
iterator: Iterator<T>
): T? = when {
additional.isNotEmpty() -> additional.removeFirst()
additional.isNotEmpty() -> additional.removeAt(0)
iterator.hasNext() -> iterator.next()
else -> null
}

View File

@@ -0,0 +1,13 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
fun copyToClipboard(text: String): Boolean {
return runCatching {
window.navigator.clipboard.writeText(
text
)
}.onFailure {
it.printStackTrace()
}.isSuccess
}

View File

@@ -0,0 +1,29 @@
package dev.inmo.micro_utils.common
import kotlinx.browser.window
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import kotlin.js.json
external class ClipboardItem(data: dynamic)
inline fun Blob.convertToClipboardItem(): ClipboardItem {
val itemData: dynamic = json(this.type to this)
return ClipboardItem(itemData)
}
suspend fun copyImageURLToClipboard(imageUrl: String): Boolean {
return runCatching {
val response = window.fetch(imageUrl).await()
val blob = response.blob().await()
val data = arrayOf(
Blob(
arrayOf(blob),
BlobPropertyBag("image/png")
).convertToClipboardItem()
).asDynamic()
window.navigator.clipboard.write(data)
}.onFailure {
it.printStackTrace()
}.isSuccess
}

View File

@@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@@ -1,25 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -1,36 +0,0 @@
package dev.inmo.micro_utils.common
import okio.FileSystem
import okio.Path
import okio.use
actual typealias MPPFile = Path
/**
* @suppress
*/
actual val MPPFile.filename: FileName
get() = FileName(toString())
/**
* @suppress
*/
actual val MPPFile.filesize: Long
get() = FileSystem.SYSTEM.openReadOnly(this).use {
it.size()
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
FileSystem.SYSTEM.read(this) {
readByteArray()
}
}
/**
* @suppress
*/
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@@ -1,25 +0,0 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.*
import platform.posix.snprintf
import platform.posix.sprintf
@OptIn(ExperimentalForeignApi::class)
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
@OptIn(ExperimentalForeignApi::class)
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {
@@ -22,7 +22,6 @@ kotlin {
dependencies {
api libs.kt.coroutines.android
}
dependsOn(jvmMain)
}
}
}

View File

@@ -3,9 +3,10 @@ plugins {
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.jb.compose)
alias(libs.plugins.kt.jb.compose)
}
apply from: "$mppProjectWithSerializationAndComposePresetPath"
apply from: "$mppComposeJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

@@ -1 +0,0 @@
<manifest/>

View File

@@ -1,46 +0,0 @@
package dev.inmo.micro_utils.coroutines.compose
import androidx.compose.runtime.MutableState
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow].
* Based on [SpecialMutableStateFlow]
*/
@Deprecated("Will be removed soon")
class FlowState<T>(
initial: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableState<T>,
SpecialMutableStateFlow<T>(initial, internalScope) {
private var internalValue: T = initial
override var value: T
get() = internalValue
set(value) {
internalValue = value
tryEmit(value)
}
override fun onChangeWithoutSync(value: T) {
internalValue = value
super.onChangeWithoutSync(value)
}
override fun component1(): T = value
override fun component2(): (T) -> Unit = { tryEmit(it) }
override fun tryEmit(value: T): Boolean {
internalValue = value
return super.tryEmit(value)
}
override suspend fun emit(value: T) {
internalValue = value
super.emit(value)
}
}
//fun <T> MutableState<T>.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope)

View File

@@ -0,0 +1,24 @@
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.test.*
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import org.jetbrains.annotations.TestOnly
import kotlin.test.Test
class FlowStateTests {
@OptIn(ExperimentalTestApi::class)
@Test
@TestOnly
fun simpleTest() = runComposeUiTest {
val flowState = SpecialMutableStateFlow(0)
setContent {
Button({ flowState.value++ }) { Text("Click") }
Text(flowState.collectAsState().value.toString())
}
onNodeWithText(0.toString()).assertExists()
onNodeWithText("Click").performClick()
onNodeWithText(1.toString()).assertExists()
}
}

View File

@@ -3,6 +3,12 @@ package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.*
/**
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s
*
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
*/
suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
scope: CoroutineScope,
cancelOnResult: Boolean = true
@@ -24,10 +30,45 @@ suspend fun <T> Iterable<Deferred<T>>.awaitFirstWithDeferred(
}
}
/**
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [Deferred]s
*
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
*/
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
scope: CoroutineScope,
cancelOnResult: Boolean = true
): T = awaitFirstWithDeferred(scope, cancelOnResult).second
/**
* Trying to [Deferred.await] on all [this] [Deferred]s. The first [Deferred] completed its work will interrupt all
* others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Deferred]s
*
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
*/
suspend fun <T> Iterable<Deferred<T>>.awaitFirst(
cancelOthers: Boolean = true
): T = awaitFirst(CoroutineScope(coroutineContext), cancelOthers)
/**
* Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all
* others awaits and, if [cancelOnResult] passed as true (**by default**), will also cancel all the others [deferreds]
*
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
*/
suspend fun <T> awaitFirst(
vararg deferreds: Deferred<T>,
scope: CoroutineScope,
cancelOnResult: Boolean = true
): T = deferreds.toList().awaitFirstWithDeferred(scope, cancelOnResult).second
/**
* Trying to [Deferred.await] on all [deferreds]. The first [Deferred] completed its work will interrupt all
* others awaits and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [deferreds]
*
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
*/
suspend fun <T> awaitFirst(
vararg deferreds: Deferred<T>,
cancelOthers: Boolean = true
): T = awaitFirst(*deferreds, scope = CoroutineScope(coroutineContext), cancelOnResult = cancelOthers)

View File

@@ -0,0 +1,44 @@
package dev.inmo.micro_utils.coroutines
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
typealias ExceptionHandler<T> = suspend (Throwable) -> T
/**
* This instance will be used in all calls of [safely] where exception handler has not been passed
*/
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
/**
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
*/
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
try {
defaultSafelyExceptionHandler(it)
} catch (e: Throwable) {
// do nothing
}
}
/**
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandler
*/
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
/**
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
class ContextSafelyExceptionHandler(
val handler: ExceptionHandler<Unit>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*>
get() = ContextSafelyExceptionHandlerKey
}

View File

@@ -16,8 +16,8 @@ class DoWithFirstBuilder<T>(
operator fun plus(block: suspend CoroutineScope.() -> T) {
deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block))
}
inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block)
inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block)
fun add(block: suspend CoroutineScope.() -> T) = plus(block)
fun include(block: suspend CoroutineScope.() -> T) = plus(block)
fun build() = deferreds.toList()
}

View File

@@ -85,32 +85,32 @@ fun <T, M> Flow<T>.subscribeAsync(
return job
}
inline fun <T, M> Flow<T>.subscribeSafelyAsync(
fun <T, M> Flow<T>.subscribeSafelyAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend (T) -> Unit
markerFactory: suspend (T) -> M,
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safely(onException) {
block(it)
}
}
inline fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
fun <T, M> Flow<T>.subscribeSafelyWithoutExceptionsAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend (T) -> Unit
markerFactory: suspend (T) -> M,
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions(onException) {
block(it)
}
}
inline fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
fun <T, M> Flow<T>.subscribeSafelySkippingExceptionsAsync(
scope: CoroutineScope,
noinline markerFactory: suspend (T) -> M,
noinline block: suspend (T) -> Unit
markerFactory: suspend (T) -> M,
block: suspend (T) -> Unit
) = subscribeAsync(scope, markerFactory) {
safelyWithoutExceptions({ /* do nothing */}) {
block(it)

View File

@@ -4,46 +4,58 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.coroutineContext
typealias ExceptionHandler<T> = suspend (Throwable) -> T
/**
* This instance will be used in all calls of [safely] where exception handler has not been passed
* Launching [block] in [runCatching]. In case of failure, it will:
*
* * Try to get [ContextSafelyExceptionHandler] from current [coroutineContext] and call its
* [ContextSafelyExceptionHandler.handler] invoke. **Thrown exception from its handler
* will pass out of [runCatchingSafely]**
* * Execute [onException] inside of new [runCatching] and return its result. Throws exception
* will be caught by [runCatching] and wrapped in [Result]
*
* @return [Result] with result of [block] if no exceptions or [Result] from [onException] execution
*/
var defaultSafelyExceptionHandler: ExceptionHandler<Nothing> = { throw it }
/**
* This instance will be used in all calls of [safelyWithoutExceptions] as an exception handler for [safely] call
*/
var defaultSafelyWithoutExceptionHandler: ExceptionHandler<Unit> = {
try {
defaultSafelyExceptionHandler(it)
} catch (e: Throwable) {
// do nothing
suspend inline fun <T> runCatchingSafely(
onException: ExceptionHandler<T>,
block: suspend () -> T
): Result<T> {
return runCatching {
block()
}.onFailure {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(it)
return runCatching {
onException(it)
}
}
}
/**
* This key can (and will) be used to get [ContextSafelyExceptionHandler] from [coroutineContext] of suspend functions
* and in [ContextSafelyExceptionHandler] for defining of its [CoroutineContext.Element.key]
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandler
*/
object ContextSafelyExceptionHandlerKey : CoroutineContext.Key<ContextSafelyExceptionHandler>
suspend inline fun <T, R> R.runCatchingSafely(
onException: ExceptionHandler<T>,
block: suspend R.() -> T
): Result<T> = runCatchingSafely<T>(onException) {
block()
}
/**
* [ExceptionHandler] wrapper which was created to make possible to use [handler] across all coroutines calls
*
* @see safelyWithContextExceptionHandler
* @see ContextSafelyExceptionHandlerKey
* Launching [runCatchingSafely] with [defaultSafelyExceptionHandler] as `onException` parameter
*/
class ContextSafelyExceptionHandler(
val handler: ExceptionHandler<Unit>
) : CoroutineContext.Element {
override val key: CoroutineContext.Key<*>
get() = ContextSafelyExceptionHandlerKey
suspend inline fun <T> runCatchingSafely(
block: suspend () -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> R.runCatchingSafely(
block: suspend R.() -> T
): Result<T> = runCatchingSafely<T> {
block()
}
//suspend inline fun <T, R> T.runCatchingSafely(
// onException: ExceptionHandler<R>,
// block: suspend T.() -> R
//): Result<R> = runCatchingSafely(onException) {
// block()
//}
/**
* @return [ContextSafelyExceptionHandler] from [coroutineContext] by key [ContextSafelyExceptionHandlerKey] if
* exists
@@ -51,7 +63,7 @@ class ContextSafelyExceptionHandler(
* @see ContextSafelyExceptionHandler
* @see ContextSafelyExceptionHandlerKey
*/
suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey]
/**
* This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext]
@@ -64,7 +76,7 @@ suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSaf
suspend fun <T> safelyWithContextExceptionHandler(
contextExceptionHandler: ExceptionHandler<Unit>,
safelyExceptionHandler: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
block: suspend () -> T
): T {
val contextSafelyExceptionHandler = contextSafelyExceptionHandler() ?.handler ?.let { oldHandler ->
ContextSafelyExceptionHandler {
@@ -78,57 +90,35 @@ suspend fun <T> safelyWithContextExceptionHandler(
}
/**
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
* Calls [runCatchingSafely] and getting the result via [Result.getOrThrow]
*
* Priorities of [ExceptionHandler]s:
*
* * [onException] In case if custom (will be used anyway if not [defaultSafelyExceptionHandler])
* * [CoroutineContext.get] with [SafelyExceptionHandlerKey] as key
* * [defaultSafelyExceptionHandler]
*
* Remember, that [ExceptionHandler] from [CoroutineContext.get] will be used anyway if it is available. After it will
* be called [onException]
*
* @param [onException] Will be called when happen exception inside of [block]. By default will throw exception - this
* exception will be available for catching
*
* @see defaultSafelyExceptionHandler
* @see safelyWithoutExceptions
* @see safelyWithContextExceptionHandler
* @see runCatchingSafely
*/
suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
): T {
return try {
supervisorScope(block)
} catch (e: Throwable) {
coroutineContext[ContextSafelyExceptionHandlerKey] ?.handler ?.invoke(e)
onException(e)
}
}
onException: ExceptionHandler<T>,
block: suspend () -> T
): T = runCatchingSafely(onException, block).getOrThrow()
suspend inline fun <T> runCatchingSafely(
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
): Result<T> = runCatching {
safely(onException, block)
}
/**
* Calls [safely] with passing of [defaultSafelyExceptionHandler] as `onException`
*
* @see runCatchingSafely
*/
suspend inline fun <T> safely(
block: suspend () -> T
): T = safely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> R.safely(
block: suspend R.() -> T
): T = safely<T> { block() }
suspend inline fun <T, R> T.runCatchingSafely(
noinline onException: ExceptionHandler<R> = defaultSafelyExceptionHandler,
noinline block: suspend T.() -> R
): Result<R> = runCatching {
safely(onException) { block() }
}
suspend inline fun <T> safelyWithResult(
noinline block: suspend CoroutineScope.() -> T
@Deprecated("Renamed", ReplaceWith("runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
suspend fun <T> safelyWithResult(
block: suspend () -> T
): Result<T> = runCatchingSafely(defaultSafelyExceptionHandler, block)
suspend inline fun <T, R> T.safelyWithResult(
noinline block: suspend T.() -> R
): Result<R> = runCatchingSafely(defaultSafelyExceptionHandler, block)
@Deprecated("Renamed", ReplaceWith("this.runCatchingSafely(block)", "dev.inmo.micro_utils.coroutines.runCatchingSafely"))
suspend fun <T, R> R.safelyWithResult(
block: suspend R.() -> T
): Result<T> = safelyWithResult<T> { block() }
/**
* Use this handler in cases you wish to include handling of exceptions by [defaultSafelyWithoutExceptionHandler] and
@@ -147,21 +137,23 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler<Nothing?> = {
* Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of
* result from exception (instead of throwing it, by default always returns null)
*/
suspend inline fun <T> safelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
): T? = safely(onException, block)
suspend fun <T> safelyWithoutExceptions(
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend () -> T
): T? = runCatchingSafely(onException, block).getOrNull()
suspend inline fun <T> runCatchingSafelyWithoutExceptions(
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
): Result<T?> = runCatching {
safelyWithoutExceptions(onException, block)
suspend fun <T> runCatchingSafelyWithoutExceptions(
onException: ExceptionHandler<T?> = defaultSafelyExceptionHandler,
block: suspend () -> T
): Result<T?> = runCatchingSafely(onException, block).let {
if (it.isFailure) return Result.success<T?>(null)
it
}
inline fun CoroutineScope(
fun CoroutineScopeWithDefaultFallback(
context: CoroutineContext,
noinline defaultExceptionsHandler: ExceptionHandler<Unit>
defaultExceptionsHandler: ExceptionHandler<Unit>
) = CoroutineScope(
context + ContextSafelyExceptionHandler(defaultExceptionsHandler)
)

View File

@@ -0,0 +1,64 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import kotlin.coroutines.*
/**
* Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
*
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
*/
suspend fun Iterable<Job>.joinFirst(
scope: CoroutineScope,
cancelOthers: Boolean = true
): Job {
val resultDeferred = CompletableDeferred<Job>()
val scope = scope.LinkedSupervisorScope()
forEach {
scope.launch {
it.join()
resultDeferred.complete(it)
scope.cancel()
}
}
return resultDeferred.await().also {
if (cancelOthers) {
forEach {
runCatchingSafely { it.cancel() }
}
}
}
}
/**
* Trying to [Job.join] on all [this] [Job]s. The first [Job] completed its work will interrupt all others joins
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
*
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
*/
suspend fun Iterable<Job>.joinFirst(
cancelOthers: Boolean = true
): Job = joinFirst(CoroutineScope(coroutineContext), cancelOthers)
/**
* Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
*
* @param scope Will be used to create [CoroutineScope.LinkedSupervisorScope] and launch joining of all [Job]s in it
*/
suspend fun joinFirst(
vararg jobs: Job,
scope: CoroutineScope,
cancelOthers: Boolean = true
): Job = jobs.toList().joinFirst(scope, cancelOthers)
/**
* Trying to [Job.join] on all [jobs]. The first [Job] completed its work will interrupt all others joins
* and, if [cancelOthers] passed as true (**by default**), will also cancel all the others [Job]s
*
* Creates [CoroutineScope] using [coroutineContext] for internal purposes
*/
suspend fun joinFirst(
vararg jobs: Job,
cancelOthers: Boolean = true
): Job = joinFirst(*jobs, scope = CoroutineScope(coroutineContext), cancelOthers = cancelOthers)

View File

@@ -4,38 +4,46 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
inline fun CoroutineScope.launchSafely(
fun CoroutineScope.launchSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> Unit
onException: ExceptionHandler<Unit> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safely(onException, block)
runCatchingSafely(onException) {
block()
}
}
inline fun CoroutineScope.launchSafelyWithoutExceptions(
fun CoroutineScope.launchSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> Unit
onException: ExceptionHandler<Unit?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> Unit
) = launch(context, start) {
safelyWithoutExceptions(onException, block)
runCatchingSafelyWithoutExceptions(onException) {
block()
}
}
inline fun <T> CoroutineScope.asyncSafely(
fun <T> CoroutineScope.asyncSafely(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
noinline block: suspend CoroutineScope.() -> T
onException: ExceptionHandler<T> = defaultSafelyExceptionHandler,
block: suspend CoroutineScope.() -> T
) = async(context, start) {
safely(onException, block)
runCatchingSafely(onException) {
block()
}
}
inline fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
fun <T> CoroutineScope.asyncSafelyWithoutExceptions(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
noinline onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
noinline block: suspend CoroutineScope.() -> T
onException: ExceptionHandler<T?> = defaultSafelyWithoutExceptionHandlerWithNull,
block: suspend CoroutineScope.() -> T
) = async(context, start) {
safelyWithoutExceptions(onException, block)
runCatchingSafelyWithoutExceptions(onException) {
block()
}
}

View File

@@ -44,7 +44,7 @@ sealed interface SmartMutex {
* @param locked Preset state of [isLocked] and its internal [_lockStateFlow]
*/
class Mutable(locked: Boolean = false) : SmartMutex {
private val _lockStateFlow = MutableStateFlow<Boolean>(locked)
private val _lockStateFlow = SpecialMutableStateFlow<Boolean>(locked)
override val lockStateFlow: StateFlow<Boolean> = _lockStateFlow.asStateFlow()
private val internalChangesMutex = Mutex()

View File

@@ -13,7 +13,7 @@ import kotlin.contracts.contract
* * [unlockWrite] will just unlock [writeMutex]
*/
class SmartRWLocker(private val readPermits: Int = Int.MAX_VALUE, writeIsLocked: Boolean = false) {
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = 0)
private val _readSemaphore = SmartSemaphore.Mutable(permits = readPermits, acquiredPermits = if (writeIsLocked) readPermits else 0)
private val _writeMutex = SmartMutex.Mutable(locked = writeIsLocked)
val readSemaphore: SmartSemaphore.Immutable = _readSemaphore.immutable()

View File

@@ -45,7 +45,7 @@ sealed interface SmartSemaphore {
* @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow]
*/
class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore {
private val _freePermitsStateFlow = MutableStateFlow<Int>(permits - acquiredPermits)
private val _freePermitsStateFlow = SpecialMutableStateFlow<Int>(permits - acquiredPermits)
override val permitsStateFlow: StateFlow<Int> = _freePermitsStateFlow.asStateFlow()
private val internalChangesMutex = Mutex(false)

View File

@@ -11,76 +11,60 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.internal.SynchronizedObject
import kotlinx.coroutines.internal.synchronized
import kotlin.coroutines.CoroutineContext
/**
* Works like [StateFlow], but guarantee that latest value update will always be delivered to
* each active subscriber
*/
open class SpecialMutableStateFlow<T>(
initialValue: T,
internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
initialValue: T
) : MutableStateFlow<T>, FlowCollector<T>, MutableSharedFlow<T> {
@OptIn(InternalCoroutinesApi::class)
private val syncObject = SynchronizedObject()
protected val internalSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected val publicSharedFlow: MutableSharedFlow<T> = MutableSharedFlow(
protected val sharingFlow: MutableSharedFlow<T> = MutableSharedFlow(
replay = 1,
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
protected var _value: T = initialValue
override var value: T
get() = _value
@OptIn(InternalCoroutinesApi::class)
override var value: T = initialValue
set(value) {
doOnChangeAction(value)
synchronized(syncObject) {
if (field != value) {
field = value
sharingFlow.tryEmit(value)
}
}
}
protected val job = internalSharedFlow.subscribe(internalScope) {
doOnChangeAction(it)
}
override val replayCache: List<T>
get() = publicSharedFlow.replayCache
get() = sharingFlow.replayCache
override val subscriptionCount: StateFlow<Int>
get() = publicSharedFlow.subscriptionCount
get() = sharingFlow.subscriptionCount
init {
sharingFlow.tryEmit(initialValue)
}
@OptIn(InternalCoroutinesApi::class)
override fun compareAndSet(expect: T, update: T): Boolean {
return synchronized(syncObject) {
if (expect == _value && update != _value) {
doOnChangeAction(update)
}
expect == _value
}
}
protected open fun onChangeWithoutSync(value: T) {
_value = value
publicSharedFlow.tryEmit(value)
}
@OptIn(InternalCoroutinesApi::class)
protected open fun doOnChangeAction(value: T) {
synchronized(syncObject) {
if (_value != value) {
onChangeWithoutSync(value)
}
if (expect == value) {
value = update
}
return expect == value
}
@ExperimentalCoroutinesApi
override fun resetReplayCache() = publicSharedFlow.resetReplayCache()
override fun resetReplayCache() = sharingFlow.resetReplayCache()
override fun tryEmit(value: T): Boolean {
return internalSharedFlow.tryEmit(value)
return compareAndSet(this.value, value)
}
override suspend fun emit(value: T) {
internalSharedFlow.emit(value)
compareAndSet(this.value, value)
}
override suspend fun collect(collector: FlowCollector<T>) = publicSharedFlow.collect(collector)
override suspend fun collect(collector: FlowCollector<T>) = sharingFlow.collect(collector)
}

View File

@@ -10,8 +10,12 @@ fun CoroutineScope.LinkedSupervisorJob(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorJob(additionalContext)
fun CoroutineScope.LinkedSupervisorScope(
fun CoroutineContext.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = CoroutineScope(
coroutineContext + LinkedSupervisorJob(additionalContext)
this + LinkedSupervisorJob(additionalContext)
)
fun CoroutineScope.LinkedSupervisorScope(
additionalContext: CoroutineContext? = null
) = coroutineContext.LinkedSupervisorScope(additionalContext)

View File

@@ -4,28 +4,71 @@ import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
private fun CoroutineScope.createWeakSubScope() = CoroutineScope(coroutineContext.minusKey(Job)).also { newScope ->
coroutineContext.job.invokeOnCompletion { newScope.cancel() }
/**
* Created [CoroutineScope] which will [launch] listening of [context] job completing and drop itself. Current weak
* scope **will not** be attached to [context] directly. So, this [CoroutineScope] will not prevent parent one from
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
* of parent [Job]
*/
fun WeakScope(
context: CoroutineContext
) = CoroutineScope(context.minusKey(Job) + Job()).also { newScope ->
newScope.launch {
context.job.join()
newScope.cancel()
}
}
fun CoroutineScope.weakLaunch(
/**
* Created [CoroutineScope] which will [launch] listening of [scope] [CoroutineContext] job completing and drop itself. Current weak
* scope **will not** be attached to [scope] [CoroutineContext] directly. So, this [CoroutineScope] will not prevent parent one from
* cancelling if it is launched with [supervisorScope] or [coroutineScope], but still will follow closing status
* of parent [Job]
*/
fun WeakScope(
scope: CoroutineScope
) = WeakScope(scope.coroutineContext)
/**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to [launch] [Job]
*/
fun CoroutineScope.launchWeak(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val scope = createWeakSubScope()
val scope = WeakScope(this)
val job = scope.launch(context, start, block)
job.invokeOnCompletion { scope.cancel() }
return job
}
fun <T> CoroutineScope.weakAsync(
/**
* [this] [CoroutineScope] will be used as base for [WeakScope]. Other parameters ([context], [start], [block])
* will be used to create [async] [Deferred]
*/
fun <T> CoroutineScope.asyncWeak(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val scope = createWeakSubScope()
val scope = WeakScope(this)
val deferred = scope.async(context, start, block)
deferred.invokeOnCompletion { scope.cancel() }
return deferred
}
@Deprecated("Renamed", ReplaceWith("launchWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.launchWeak"))
fun CoroutineScope.weakLaunch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = launchWeak(context, start, block)
@Deprecated("Renamed", ReplaceWith("asyncWeak(context, start, block)", "dev.inmo.micro_utils.coroutines.asyncWeak"))
fun <T> CoroutineScope.weakAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> = asyncWeak(context, start, block)

View File

@@ -0,0 +1,33 @@
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.micro_utils.coroutines.asDeferred
import dev.inmo.micro_utils.coroutines.subscribe
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SpecialMutableStateFlowTests {
@Test
fun simpleTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
specialMutableStateFlow.value = 1
specialMutableStateFlow.first { it == 1 }
assertEquals(1, specialMutableStateFlow.value)
}
@Test
fun specialTest() = runTest {
val specialMutableStateFlow = SpecialMutableStateFlow(0)
lateinit var subscriberJob: Job
subscriberJob = specialMutableStateFlow.subscribe(this) {
when (it) {
1 -> specialMutableStateFlow.value = 2
2 -> subscriberJob.cancel()
}
}
specialMutableStateFlow.value = 1
subscriberJob.join()
assertEquals(2, specialMutableStateFlow.value)
}
}

View File

@@ -0,0 +1,64 @@
import dev.inmo.micro_utils.coroutines.asyncWeak
import dev.inmo.micro_utils.coroutines.launchWeak
import kotlinx.coroutines.*
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertTrue
class WeakJobTests {
@Test
fun testWeakJob() = runTest {
var commonJobDone = false
var weakJobStarted = false
try {
coroutineScope {
launch {
delay(1000)
commonJobDone = true
}
asyncWeak {
weakJobStarted = true
delay(100500L)
error("This must never happen")
}
}.await()
} catch (error: Throwable) {
assertTrue(error is CancellationException)
assertTrue(commonJobDone)
assertTrue(weakJobStarted)
return@runTest
}
error("Cancellation exception has not been thrown")
}
@Test
fun testThatWeakJobsWorksCorrectly() = runTest {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = launchWeak {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = asyncWeak {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
assertTrue(!weakLaunchJob.isActive)
assertTrue(!weakAsyncJob.isActive)
}
}

View File

@@ -1,40 +0,0 @@
package dev.inmo.micro_utils.coroutines
import kotlinx.coroutines.*
import org.junit.Test
class WeakJob {
@Test
fun `test that weak jobs works correctly`() {
val scope = CoroutineScope(Dispatchers.Default)
lateinit var weakLaunchJob: Job
lateinit var weakAsyncJob: Job
scope.launchSynchronously {
val completeDeferred = Job()
coroutineScope {
weakLaunchJob = weakLaunch {
while (isActive) {
delay(100L)
}
}
weakAsyncJob = weakAsync {
while (isActive) {
delay(100L)
}
}
coroutineContext.job.invokeOnCompletion {
scope.launch {
delay(1000L)
completeDeferred.complete()
}
}
launch { delay(1000L); cancel() }
}
completeDeferred.join()
}
assert(!weakLaunchJob.isActive)
assert(!weakAsyncJob.isActive)
}
}

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

@@ -130,4 +130,4 @@ tasks.dokkaHtml {
}
}
apply from: "$defaultAndroidSettingsPresetPath"
apply from: "$defaultAndroidSettings"

View File

@@ -1,42 +1,32 @@
allprojects {
ext {
projectByName = { name ->
for (subproject in rootProject.subprojects) {
if (subproject.name == name) {
return subproject
}
}
return null
}
File templatesFolder = new File("$rootProject.projectDir.absolutePath${File.separatorChar}gradle${File.separatorChar}templates")
internalProject = { name ->
// if (releaseMode) {
// "$group:$name:$version"
// } else {
// projectByName("$name")
// }
projectByName("$name")
}
Map properties = new HashMap<String, String>()
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
// String compilerPluginVersionFromProperties = (String) project.properties["compose.kotlinCompilerPluginVersion"]
// String compilerPluginVersionFromLibrariesVersions = libs.versions.compose.kotlin.get()
// composePluginKotlinVersion = compilerPluginVersionFromProperties
// if (compilerPluginVersionFromProperties == null) {
// composePluginKotlinVersion = compilerPluginVersionFromLibrariesVersions
// }
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidProject.gradle"
mppProjectWithSerializationAndComposePresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerializationAndCompose.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJvmJsLinuxMingwProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwProject.gradle"
mppJvmJsLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsLinuxMingwLinuxArm64Project.gradle"
mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
publishJvmOnlyPath = "${rootProject.projectDir.absolutePath}/jvm.publish.gradle"
if (templatesFolder.exists() && templatesFolder.isDirectory()) {
templatesFolder.listFiles().each {
properties[it.name - ".gradle"] = it.absolutePath
}
}
properties["projectByName"] = { name ->
for (subproject in rootProject.subprojects) {
if (subproject.name == name) {
return subproject
}
}
return null
}
properties["internalProject"] = { name ->
projectByName("$name")
}
allprojects {
ext {
releaseMode = (project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true"
properties.forEach { k, v ->
it[k] = v
}
}
}

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

@@ -1,7 +1,6 @@
package dev.inmo.micro_utils.fsm.common
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler
import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler
@@ -118,23 +117,28 @@ open class DefaultStatesMachine <T: State>(
* [launchStateHandling] will returns some [State] then [statesManager] [StatesManager.update] will be used, otherwise
* [StatesManager.endChain].
*/
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
}
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState ->
launch {
statesJobsMutex.withLock {
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
if (stateInMap === removedState) {
statesJobs[stateInMap] ?.cancel()
override fun start(scope: CoroutineScope): Job {
val supervisorScope = scope.LinkedSupervisorScope()
supervisorScope.launchSafelyWithoutExceptions {
(statesManager.getActiveStates().asFlow() + statesManager.onStartChain).subscribeSafelyWithoutExceptions(supervisorScope) {
supervisorScope.launch { performStateUpdate(Optional.absent(), it, supervisorScope) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(supervisorScope) {
supervisorScope.launch { performStateUpdate(Optional.presented(it.first), it.second, supervisorScope) }
}
statesManager.onEndChain.subscribeSafelyWithoutExceptions(supervisorScope) { removedState ->
supervisorScope.launch {
statesJobsMutex.withLock {
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
if (stateInMap === removedState) {
statesJobs[stateInMap] ?.cancel()
}
}
}
}
}
return supervisorScope.coroutineContext.job
}
/**

View File

@@ -4,7 +4,7 @@ plugins {
id "com.android.library"
}
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath"
apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project"
kotlin {
sourceSets {

View File

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

View File

@@ -15,5 +15,5 @@ crypto_js_version=4.1.1
# Project data
group=dev.inmo
version=0.20.23
android_code_version=229
version=0.22.3
android_code_version=269

View File

@@ -1,45 +1,47 @@
[versions]
kt = "1.9.21"
kt-serialization = "1.6.2"
kt-coroutines = "1.7.3"
kt = "2.0.20"
kt-serialization = "1.7.3"
kt-coroutines = "1.9.0"
kslog = "1.3.1"
kslog = "1.3.6"
jb-compose = "1.5.11"
jb-exposed = "0.45.0"
jb-dokka = "1.9.10"
jb-compose = "1.7.0-beta02"
jb-exposed = "0.54.0"
jb-dokka = "1.9.20"
korlibs = "4.0.10"
uuid = "0.8.2"
sqlite = "3.46.1.0"
ktor = "2.3.7"
korlibs = "5.4.0"
uuid = "0.8.4"
gh-release = "2.4.1"
ktor = "2.3.12"
koin = "3.5.0"
gh-release = "2.5.2"
okio = "3.6.0"
koin = "4.0.0"
ksp = "1.9.21-1.0.16"
kotlin-poet = "1.15.3"
okio = "3.9.1"
versions = "0.50.0"
ksp = "2.0.20-1.0.25"
kotlin-poet = "1.18.1"
android-gradle = "8.2.0"
versions = "0.51.0"
android-gradle = "8.2.2"
dexcount = "4.0.0"
android-coreKtx = "1.12.0"
android-coreKtx = "1.13.1"
android-recyclerView = "1.3.2"
android-appCompat = "1.6.1"
android-fragment = "1.6.2"
android-espresso = "3.5.1"
android-test = "1.1.5"
android-compose-material3 = "1.1.2"
android-appCompat = "1.7.0"
android-fragment = "1.8.3"
android-espresso = "3.6.1"
android-test = "1.2.1"
android-compose-material3 = "1.3.0"
android-props-minSdk = "21"
android-props-compileSdk = "34"
android-props-buildTools = "34.0.0"
android-props-compileSdk = "35"
android-props-buildTools = "35.0.0"
[libraries]
@@ -52,6 +54,7 @@ kt-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
kt-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kt-coroutines" }
kt-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kt-coroutines" }
kt-coroutines-debug = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-debug", version.ref = "kt-coroutines" }
ktor-io = { module = "io.ktor:ktor-io", version.ref = "ktor" }
@@ -72,19 +75,21 @@ ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negoti
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korlibs.krypto:krypto", version.ref = "korlibs" }
klock = { module = "com.soywiz.korge:korlibs-time", version.ref = "korlibs" }
krypto = { module = "com.soywiz.korge:korlibs-crypto", version.ref = "korlibs" }
uuid = { module = "com.benasher44:uuid", version.ref = "uuid" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
jb-exposed = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jb-exposed" }
jb-exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "jb-exposed" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
jb-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "jb-compose" }
android-coreKtx = { module = "androidx.core:core-ktx", version.ref = "android-coreKtx" }
android-recyclerView = { module = "androidx.recyclerview:recyclerview", version.ref = "android-recyclerView" }
android-appCompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "android-appCompat" }
android-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "android-compose-material3" }
android-fragment = { module = "androidx.fragment:fragment", version.ref = "android-fragment" }
android-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso" }
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test" }
@@ -113,5 +118,6 @@ buildscript-android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gra
[plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }
kt-jb-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kt" }
versions = { id = "com.github.ben-manes.versions", version.ref = "versions" }

View File

@@ -6,6 +6,7 @@ android {
defaultConfig {
minSdkVersion libs.versions.android.props.minSdk.get().toInteger()
compileSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
targetSdkVersion libs.versions.android.props.compileSdk.get().toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"

View File

@@ -1,7 +1,7 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
apply from: "$publish"
kotlin {
androidTarget {
@@ -29,7 +29,7 @@ kotlin {
}
}
apply from: "$defaultAndroidSettingsPresetPath"
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17

View File

@@ -0,0 +1,108 @@
project.version = "$version"
project.group = "$group"
apply from: "$publish"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) {
browser {
testTask {
useMocha {
timeout = "60000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "60000"
}
}
}
}
androidTarget {
publishAllLibraryVariants()
compilations.all {
kotlinOptions {
jvmTarget = "17"
}
}
}
linuxX64()
mingwX64()
linuxArm64()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
implementation compose.runtime
api libs.kt.serialization
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
androidInstrumentedTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
jvmMain {
dependencies {
implementation compose.desktop.currentOs
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation compose.uiTest
}
}
jsMain {
dependencies {
implementation compose.web.core
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View File

@@ -1,7 +1,7 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
apply from: "$publish"
kotlin {
jvm {

View File

@@ -1,7 +1,7 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
apply from: "$publish"
kotlin {
jvm {
@@ -12,8 +12,20 @@ kotlin {
}
}
js (IR) {
browser()
nodejs()
browser {
testTask {
useMocha {
timeout = "60000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "60000"
}
}
}
}
androidTarget {
publishAllLibraryVariants()
@@ -25,6 +37,7 @@ kotlin {
}
linuxX64()
mingwX64()
linuxArm64()
sourceSets {
commonMain {
@@ -35,11 +48,19 @@ kotlin {
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test')
implementation kotlin('test-annotations-common')
implementation libs.kt.coroutines.test
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
@@ -48,32 +69,18 @@ kotlin {
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
androidUnitTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
}
}
mingwX64Test {
dependencies {
implementation kotlin('test-junit')
}
}
linuxX64Test {
dependencies {
implementation kotlin('test-junit')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
linuxArm64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}
apply from: "$defaultAndroidSettingsPresetPath"
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17

View File

@@ -1,7 +1,7 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
apply from: "$publish"
kotlin {
jvm {
@@ -12,8 +12,20 @@ kotlin {
}
}
js (IR) {
browser()
nodejs()
browser {
testTask {
useMocha {
timeout = "60000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "60000"
}
}
}
}
linuxX64()
mingwX64()
@@ -41,20 +53,13 @@ kotlin {
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
mingwX64Test {
dependencies {
implementation kotlin('test-junit')
}
}
linuxX64Test {
dependencies {
implementation kotlin('test-junit')
}
}
nativeMain.dependsOn commonMain
linuxX64Main.dependsOn nativeMain
mingwX64Main.dependsOn nativeMain
androidMain.dependsOn jvmMain
}
}

View File

@@ -1,7 +1,7 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
apply from: "$publish"
kotlin {
jvm {
@@ -12,8 +12,20 @@ kotlin {
}
}
js (IR) {
browser()
nodejs()
browser {
testTask {
useMocha {
timeout = "60000"
}
}
}
nodejs {
testTask {
useMocha {
timeout = "60000"
}
}
}
}
androidTarget {
publishAllLibraryVariants()
@@ -28,8 +40,8 @@ kotlin {
commonMain {
dependencies {
implementation kotlin('stdlib')
api libs.kt.serialization
implementation compose.runtime
api libs.kt.serialization
}
}
commonTest {
@@ -47,6 +59,7 @@ kotlin {
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation compose.uiTest
}
}
jsMain {
@@ -57,7 +70,6 @@ kotlin {
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
androidUnitTest {
@@ -65,14 +77,13 @@ kotlin {
implementation kotlin('test-junit')
implementation libs.android.test.junit
implementation libs.android.espresso
implementation compose.uiTest
}
}
androidMain.dependsOn jvmMain
}
}
apply from: "$defaultAndroidSettingsPresetPath"
apply from: "$defaultAndroidSettings"
java {
sourceCompatibility = JavaVersion.VERSION_17

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,47 +0,0 @@
MIT License
Copyright (c) 2023 Ovsiannikov Aleksei
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--------------------
MIT License
Copyright (c) 2017-2019 Carlos Ballesteros Velasco and contributors
* https://github.com/korlibs/korge/graphs/contributors
* https://github.com/korlibs-archive/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,37 +0,0 @@
@file:Suppress("PackageDirectoryMismatch")
package korlibs.time.internal
import korlibs.time.*
import java.util.*
internal actual object KlockInternal {
actual val currentTime: Double get() = CurrentKlockInternalJvm.currentTime
actual val now: TimeSpan get() = CurrentKlockInternalJvm.hrNow
actual fun localTimezoneOffsetMinutes(time: DateTime): TimeSpan = CurrentKlockInternalJvm.localTimezoneOffsetMinutes(time)
actual fun sleep(time: TimeSpan) {
val nanos = time.nanoseconds.toLong()
Thread.sleep(nanos / 1_000_000, (nanos % 1_000_000).toInt())
}
}
inline fun <T> TemporalKlockInternalJvm(impl: KlockInternalJvm, callback: () -> T): T {
val old = CurrentKlockInternalJvm
CurrentKlockInternalJvm = impl
try {
return callback()
} finally {
CurrentKlockInternalJvm = old
}
}
var CurrentKlockInternalJvm = object : KlockInternalJvm {
}
interface KlockInternalJvm {
val currentTime: Double get() = (System.currentTimeMillis()).toDouble()
val microClock: Double get() = hrNow.microseconds
val hrNow: TimeSpan get() = TimeSpan.fromNanoseconds(System.nanoTime().toDouble())
fun localTimezoneOffsetMinutes(time: DateTime): TimeSpan = TimeZone.getDefault().getOffset(time.unixMillisLong).milliseconds
}
actual typealias Serializable = java.io.Serializable

View File

@@ -1,8 +0,0 @@
@file:Suppress("PackageDirectoryMismatch")
package korlibs.time.internal
import korlibs.time.*
import java.util.Date
fun Date.toDateTime() = DateTime(this.time)
fun DateTime.toDate() = Date(this.unixMillisLong)

View File

@@ -1,72 +0,0 @@
package korlibs.time
import korlibs.time.internal.Serializable
import kotlin.jvm.JvmInline
import kotlin.math.abs
/**
* Represents a triple of [year], [month] and [day].
*
* It is packed in a value class wrapping an Int to prevent allocations.
*/
@JvmInline
value class Date(val encoded: Int) : Comparable<Date>, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
/** Constructs a new [Date] from the [year], [month] and [day] components. */
operator fun invoke(year: Int, month: Int, day: Int) = Date((year shl 16) or (month shl 8) or (day shl 0))
/** Constructs a new [Date] from the [year], [month] and [day] components. */
operator fun invoke(year: Int, month: Month, day: Int) = Date(year, month.index1, day)
/** Constructs a new [Date] from the [year], [month] and [day] components. */
operator fun invoke(year: Year, month: Month, day: Int) = Date(year.year, month.index1, day)
/** Constructs a new [Date] from the [yearMonth] and [day] components. */
operator fun invoke(yearMonth: YearMonth, day: Int) = Date(yearMonth.yearInt, yearMonth.month1, day)
}
/** The [year] part as [Int]. */
val year: Int get() = encoded shr 16
/** The [month] part as [Int] where [Month.January] is 1. */
val month1: Int get() = (encoded ushr 8) and 0xFF
/** The [month] part. */
val month: Month get() = Month[month1]
/** The [day] part. */
val day: Int get() = (encoded ushr 0) and 0xFF
/** The [year] part as [Year]. */
val yearYear: Year get() = Year(year)
/** A [DateTime] instance representing this date and time from the beginning of the [day]. */
val dateTimeDayStart get() = DateTime(year, month, day)
/** The [dayOfYear] part. */
val dayOfYear get() = dateTimeDayStart.dayOfYear
/** The [dayOfWeek] part. */
val dayOfWeek get() = dateTimeDayStart.dayOfWeek
/** The [dayOfWeek] part as [Int]. */
val dayOfWeekInt get() = dateTimeDayStart.dayOfWeekInt
/** Converts this date to String using [format] for representing it. */
fun format(format: String) = dateTimeDayStart.format(format)
/** Converts this date to String using [format] for representing it. */
fun format(format: DateFormat) = dateTimeDayStart.format(format)
/** Converts this date to String formatting it like "2020-01-01", "2020-12-31" or "-2020-12-31" if the [year] is negative */
override fun toString(): String = "${if (year < 0) "-" else ""}${abs(year).toString()}-${abs(month1).toString().padStart(2, '0')}-${abs(day).toString().padStart(2, '0')}"
override fun compareTo(other: Date): Int = this.encoded.compareTo(other.encoded)
}
operator fun Date.plus(time: TimeSpan) = (this.dateTimeDayStart + time).date
operator fun Date.plus(time: MonthSpan) = (this.dateTimeDayStart + time).date
operator fun Date.plus(time: DateTimeSpan) = (this.dateTimeDayStart + time).date
operator fun Date.plus(time: Time) = DateTime.createAdjusted(year, month1, day, time.hour, time.minute, time.second, time.millisecond)
operator fun Date.minus(time: TimeSpan) = (this.dateTimeDayStart - time).date
operator fun Date.minus(time: MonthSpan) = (this.dateTimeDayStart - time).date
operator fun Date.minus(time: DateTimeSpan) = (this.dateTimeDayStart - time).date
operator fun Date.minus(time: Time) = DateTime.createAdjusted(year, month1, day, -time.hour, -time.minute, -time.second, -time.millisecond)
fun Date.inThisWeek(dayOfWeek: DayOfWeekWithLocale): Date =
this + (dayOfWeek.index0 - this.dayOfWeek.withLocale(dayOfWeek.locale).index0).days
fun Date.inThisWeek(dayOfWeek: DayOfWeek, locale: KlockLocale = KlockLocale.default): Date = inThisWeek(dayOfWeek.withLocale(locale))

View File

@@ -1,6 +0,0 @@
package korlibs.time
/**
* An exception for Date operations.
*/
class DateException(msg: String) : RuntimeException(msg)

View File

@@ -1,43 +0,0 @@
package korlibs.time
/** Allows to [format] and [parse] instances of [Date], [DateTime] and [DateTimeTz] */
interface DateFormat {
fun format(dd: DateTimeTz): String
fun tryParse(str: String, doThrow: Boolean = false, doAdjust: Boolean = true): DateTimeTz?
companion object {
val DEFAULT_FORMAT = DateFormat("EEE, dd MMM yyyy HH:mm:ss z")
val FORMAT1 = DateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
val FORMAT2 = DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
val FORMAT_DATE = DateFormat("yyyy-MM-dd")
val FORMATS = listOf(DEFAULT_FORMAT, FORMAT1, FORMAT2, FORMAT_DATE)
fun parse(date: String): DateTimeTz {
var lastError: Throwable? = null
for (format in FORMATS) {
try {
return format.parse(date)
} catch (e: Throwable) {
lastError = e
}
}
throw lastError!!
}
operator fun invoke(pattern: String) = PatternDateFormat(pattern)
}
}
fun DateFormat.parse(str: String, doAdjust: Boolean = true): DateTimeTz =
tryParse(str, doThrow = true, doAdjust = doAdjust) ?: throw DateException("Not a valid format: '$str' for '$this'")
fun DateFormat.parseDate(str: String): Date = parse(str).local.date
fun DateFormat.parseUtc(str: String): DateTime = parse(str).utc
fun DateFormat.parseLocal(str: String): DateTime = parse(str).local
fun DateFormat.format(date: Double): String = format(DateTime.fromUnixMillis(date))
fun DateFormat.format(date: Long): String = format(DateTime.fromUnixMillis(date))
fun DateFormat.format(dd: DateTime): String = format(dd.toOffsetUnadjusted(0.minutes))
fun DateFormat.format(dd: Date): String = format(dd.dateTimeDayStart)

View File

@@ -1,458 +0,0 @@
package korlibs.time
import korlibs.time.DateTime.Companion.EPOCH
import korlibs.time.internal.*
import kotlin.jvm.JvmInline
import kotlin.math.*
/**
* Represents a Date in UTC (GMT+00) with millisecond precision.
*
* It is internally represented as an inlined double, thus doesn't allocate in any target including JS.
* It can represent without loss dates between (-(2 ** 52) and (2 ** 52)):
* - Thu Aug 10 -140744 07:15:45 GMT-0014 (Central European Summer Time)
* - Wed May 23 144683 18:29:30 GMT+0200 (Central European Summer Time)
*/
@JvmInline
value class DateTime(
/** Number of milliseconds since UNIX [EPOCH] */
val unixMillis: Double
) : Comparable<DateTime>, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
/** It is a [DateTime] instance representing 00:00:00 UTC, Thursday, 1 January 1970. */
val EPOCH = DateTime(0.0)
/**
* Constructs a new [DateTime] from date and time information.
*
* This might throw a [DateException] on invalid dates.
*/
operator fun invoke(
year: Year,
month: Month,
day: Int,
hour: Int = 0,
minute: Int = 0,
second: Int = 0,
milliseconds: Int = 0
): DateTime = DateTime(
DateTime.dateToMillis(year.year, month.index1, day) + DateTime.timeToMillis(
hour,
minute,
second
) + milliseconds
)
/**
* Constructs a new [DateTime] from date and time information.
*
* This might throw a [DateException] on invalid dates.
*/
operator fun invoke(
date: Date,
time: Time = Time(0.milliseconds)
): DateTime = DateTime(
date.year, date.month1, date.day,
time.hour, time.minute, time.second, time.millisecond
)
/**
* Constructs a new [DateTime] from date and time information.
*
* This might throw a [DateException] on invalid dates.
*/
operator fun invoke(
year: Int,
month: Month,
day: Int,
hour: Int = 0,
minute: Int = 0,
second: Int = 0,
milliseconds: Int = 0
): DateTime = DateTime(
DateTime.dateToMillis(year, month.index1, day) + DateTime.timeToMillis(
hour,
minute,
second
) + milliseconds
)
/**
* Constructs a new [DateTime] from date and time information.
*
* This might throw a [DateException] on invalid dates.
*/
operator fun invoke(
year: Int,
month: Int,
day: Int,
hour: Int = 0,
minute: Int = 0,
second: Int = 0,
milliseconds: Int = 0
): DateTime = DateTime(
DateTime.dateToMillis(year, month, day) + DateTime.timeToMillis(
hour,
minute,
second
) + milliseconds
)
/**
* Constructs a new [DateTime] from date and time information.
*
* On invalid dates, this function will try to adjust the specified invalid date to a valid one by clamping components.
*/
fun createClamped(
year: Int,
month: Int,
day: Int,
hour: Int = 0,
minute: Int = 0,
second: Int = 0,
milliseconds: Int = 0
): DateTime {
val clampedMonth = month.coerceIn(1, 12)
return createUnchecked(
year = year,
month = clampedMonth,
day = day.coerceIn(1, Month(month).days(year)),
hour = hour.coerceIn(0, 23),
minute = minute.coerceIn(0, 59),
second = second.coerceIn(0, 59),
milliseconds = milliseconds
)
}
/**
* Constructs a new [DateTime] from date and time information.
*
* On invalid dates, this function will try to adjust the specified invalid date to a valid one by adjusting other components.
*/
fun createAdjusted(
year: Int,
month: Int,
day: Int,
hour: Int = 0,
minute: Int = 0,
second: Int = 0,
milliseconds: Int = 0
): DateTime {
var dy = year
var dm = month
var dd = day
var th = hour
var tm = minute
var ts = second
tm += ts.cycleSteps(0, 59); ts = ts.cycle(0, 59) // Adjust seconds, adding minutes
th += tm.cycleSteps(0, 59); tm = tm.cycle(0, 59) // Adjust minutes, adding hours
dd += th.cycleSteps(0, 23); th = th.cycle(0, 23) // Adjust hours, adding days
while (true) {
val dup = Month(dm).days(dy)
dm += dd.cycleSteps(1, dup); dd = dd.cycle(1, dup) // Adjust days, adding months
dy += dm.cycleSteps(1, 12); dm = dm.cycle(1, 12) // Adjust months, adding years
// We have already found a day that is valid for the adjusted month!
if (dd.cycle(1, Month(dm).days(dy)) == dd) {
break
}
}
return createUnchecked(dy, dm, dd, th, tm, ts, milliseconds)
}
/**
* Constructs a new [DateTime] from date and time information.
*
* On invalid dates, this function will have an undefined behaviour.
*/
fun createUnchecked(
year: Int,
month: Int,
day: Int,
hour: Int = 0,
minute: Int = 0,
second: Int = 0,
milliseconds: Int = 0
): DateTime {
return DateTime(
DateTime.dateToMillisUnchecked(year, month, day) + DateTime.timeToMillisUnchecked(
hour,
minute,
second
) + milliseconds
)
}
/** Constructs a new [DateTime] from a [unix] timestamp in milliseconds. */
operator fun invoke(unix: Long) = fromUnixMillis(unix)
/** Constructs a new [DateTime] from a [unix] timestamp in milliseconds. */
operator fun invoke(unix: Double) = fromUnixMillis(unix)
/** Constructs a new [DateTime] from a [unix] timestamp in milliseconds. */
fun fromUnixMillis(unix: Double): DateTime = DateTime(unix)
/** Constructs a new [DateTime] from a [unix] timestamp in milliseconds. */
fun fromUnixMillis(unix: Long): DateTime = fromUnixMillis(unix.toDouble())
/** Constructs a new [DateTime] by parsing the [str] using standard date formats. */
fun fromString(str: String) = DateFormat.parse(str)
/** Constructs a new [DateTime] by parsing the [str] using standard date formats. */
fun parse(str: String) = DateFormat.parse(str)
/** Returns the current time as [DateTime]. Note that since [DateTime] is inline, this property doesn't allocate on JavaScript. */
fun now(): DateTime = DateTime(KlockInternal.currentTime)
/** Returns the current local time as [DateTimeTz]. */
fun nowLocal(): DateTimeTz = DateTimeTz.nowLocal()
/** Returns the total milliseconds since unix epoch. The same as [nowUnixMillisLong] but as double. To prevent allocation on targets without Long support. */
fun nowUnixMillis(): Double = KlockInternal.currentTime
/** Returns the total milliseconds since unix epoch. */
fun nowUnixMillisLong(): Long = KlockInternal.currentTime.toLong()
internal const val EPOCH_INTERNAL_MILLIS =
62135596800000.0 // Millis since 00-00-0000 00:00 UTC to UNIX EPOCH
internal enum class DatePart { Year, DayOfYear, Month, Day }
internal fun dateToMillisUnchecked(year: Int, month: Int, day: Int): Double =
(Year(year).daysSinceOne + Month(month).daysToStart(year) + day - 1) * MILLIS_PER_DAY.toDouble() - EPOCH_INTERNAL_MILLIS
private fun timeToMillisUnchecked(hour: Int, minute: Int, second: Int): Double =
hour.toDouble() * MILLIS_PER_HOUR + minute.toDouble() * MILLIS_PER_MINUTE + second.toDouble() * MILLIS_PER_SECOND
private fun dateToMillis(year: Int, month: Int, day: Int): Double {
//Year.checked(year)
Month.checked(month)
if (day !in 1..Month(month).days(year)) throw DateException("Day $day not valid for year=$year and month=$month")
return dateToMillisUnchecked(year, month, day)
}
private fun timeToMillis(hour: Int, minute: Int, second: Int): Double {
if (hour !in 0..23) throw DateException("Hour $hour not in 0..23")
if (minute !in 0..59) throw DateException("Minute $minute not in 0..59")
if (second !in 0..59) throw DateException("Second $second not in 0..59")
return timeToMillisUnchecked(hour, minute, second)
}
// millis are 00-00-0000 based.
internal fun getDatePart(millis: Double, part: DatePart): Int {
val totalDays = (millis / MILLIS_PER_DAY).toInt2()
// Year
val year = Year.fromDays(totalDays)
if (part == DatePart.Year) return year.year
// Day of Year
val isLeap = year.isLeap
val startYearDays = year.daysSinceOne
val dayOfYear = 1 + ((totalDays - startYearDays) umod year.days)
if (part == DatePart.DayOfYear) return dayOfYear
// Month
val month = Month.fromDayOfYear(dayOfYear, isLeap)
?: error("Invalid dayOfYear=$dayOfYear, isLeap=$isLeap")
if (part == DatePart.Month) return month.index1
// Day
val dayOfMonth = dayOfYear - month.daysToStart(isLeap)
if (part == DatePart.Day) return dayOfMonth
error("Invalid DATE_PART")
}
}
/** Number of milliseconds since the 00:00:00 UTC, Monday, 1 January 1 */
val yearOneMillis: Double get() = EPOCH_INTERNAL_MILLIS + unixMillis
/** The local offset for this date for the timezone of the device */
val localOffset: TimezoneOffset get() = TimezoneOffset.local(DateTime(unixMillisDouble))
/** Number of milliseconds since UNIX [EPOCH] as [Double] */
val unixMillisDouble: Double get() = unixMillis
/** Number of milliseconds since UNIX [EPOCH] as [Long] */
val unixMillisLong: Long get() = unixMillisDouble.toLong()
/** The [Year] part */
val year: Year get() = Year(yearInt)
/** The [Year] part as [Int] */
val yearInt: Int get() = getDatePart(yearOneMillis, DatePart.Year)
/** The [Month] part */
val month: Month get() = Month[month1]
/** The [Month] part as [Int] where January is represented as 0 */
val month0: Int get() = month1 - 1
/** The [Month] part as [Int] where January is represented as 1 */
val month1: Int get() = getDatePart(yearOneMillis, DatePart.Month)
/** Represents a couple of [Year] and [Month] that has leap information and thus allows to get the number of days of that month */
val yearMonth: YearMonth get() = YearMonth(year, month)
/** The [dayOfMonth] part */
val dayOfMonth: Int get() = getDatePart(yearOneMillis, DatePart.Day)
/** The [dayOfWeek] part */
val dayOfWeek: DayOfWeek get() = DayOfWeek[dayOfWeekInt]
/** The [dayOfWeek] part as [Int] */
val dayOfWeekInt: Int get() = (yearOneMillis / MILLIS_PER_DAY + 1).toIntMod(7)
/** The [dayOfYear] part */
val dayOfYear: Int get() = getDatePart(yearOneMillis, DatePart.DayOfYear)
/** The [hours] part */
val hours: Int get() = (yearOneMillis / MILLIS_PER_HOUR).toIntMod(24)
/** The [minutes] part */
val minutes: Int get() = (yearOneMillis / MILLIS_PER_MINUTE).toIntMod(60)
/** The [seconds] part */
val seconds: Int get() = (yearOneMillis / MILLIS_PER_SECOND).toIntMod(60)
/** The [milliseconds] part */
val milliseconds: Int get() = (yearOneMillis).toIntMod(1000)
/** Returns a new local date that will match these components. */
val localUnadjusted: DateTimeTz get() = DateTimeTz.local(this, localOffset)
/** Returns a new local date that will match these components but with a different [offset]. */
fun toOffsetUnadjusted(offset: TimeSpan) = toOffsetUnadjusted(offset.offset)
/** Returns a new local date that will match these components but with a different [offset]. */
fun toOffsetUnadjusted(offset: TimezoneOffset) = DateTimeTz.local(this, offset)
/** Returns this date with the local offset of this device. Components might change because of the offset. */
val local: DateTimeTz get() = DateTimeTz.utc(this, localOffset)
/** Returns this date with a local offset. Components might change because of the [offset]. */
fun toOffset(offset: TimeSpan) = toOffset(offset.offset)
/** Returns this date with a local offset. Components might change because of the [offset]. */
fun toOffset(offset: TimezoneOffset) = DateTimeTz.utc(this, offset)
/** Returns this date with a local offset. Components might change because of the [timeZone]. */
fun toTimezone(timeZone: Timezone) = toOffset(timeZone.offset)
/** Returns this date with a 0 offset. Components are equal. */
val utc: DateTimeTz get() = DateTimeTz.utc(this, TimezoneOffset(0.minutes))
/** Returns a [DateTime] of [this] day with the hour at 00:00:00 */
val dateDayStart get() = DateTime(year, month, dayOfMonth, 0, 0, 0, 0)
/** Returns a [DateTime] of [this] day with the hour at 23:59:59.999 */
val dateDayEnd get() = DateTime(year, month, dayOfMonth, 23, 59, 59, 999)
/** Returns the quarter 1, 2, 3 or 4 */
val quarter get() = (month0 / 3) + 1
// startOf
val startOfYear get() = DateTime(year, Month.January, 1)
val startOfMonth get() = DateTime(year, month, 1)
val startOfQuarter get() = DateTime(year, Month[(quarter - 1) * 3 + 1], 1)
fun startOfDayOfWeek(day: DayOfWeek): DateTime {
for (n in 0 until 7) {
val date = (this - n.days)
if (date.dayOfWeek == day) return date.startOfDay
}
error("Shouldn't happen")
}
val startOfWeek: DateTime get() = startOfDayOfWeek(DayOfWeek.Sunday)
val startOfIsoWeek: DateTime get() = startOfDayOfWeek(DayOfWeek.Monday)
val startOfDay get() = DateTime(year, month, dayOfMonth)
val startOfHour get() = DateTime(year, month, dayOfMonth, hours)
val startOfMinute get() = DateTime(year, month, dayOfMonth, hours, minutes)
val startOfSecond get() = DateTime(year, month, dayOfMonth, hours, minutes, seconds)
// endOf
val endOfYear get() = DateTime(year, Month.December, 31, 23, 59, 59, 999)
val endOfMonth get() = DateTime(year, month, month.days(year), 23, 59, 59, 999)
val endOfQuarter get() = DateTime(year, Month[(quarter - 1) * 3 + 3], month.days(year), 23, 59, 59, 999)
fun endOfDayOfWeek(day: DayOfWeek): DateTime {
for (n in 0 until 7) {
val date = (this + n.days)
if (date.dayOfWeek == day) return date.endOfDay
}
error("Shouldn't happen")
}
val endOfWeek: DateTime get() = endOfDayOfWeek(DayOfWeek.Monday)
val endOfIsoWeek: DateTime get() = endOfDayOfWeek(DayOfWeek.Sunday)
val endOfDay get() = DateTime(year, month, dayOfMonth, 23, 59, 59, 999)
val endOfHour get() = DateTime(year, month, dayOfMonth, hours, 59, 59, 999)
val endOfMinute get() = DateTime(year, month, dayOfMonth, hours, minutes, 59, 999)
val endOfSecond get() = DateTime(year, month, dayOfMonth, hours, minutes, seconds, 999)
val date get() = Date(yearInt, month1, dayOfMonth)
val time get() = Time(hours, minutes, seconds, milliseconds)
operator fun plus(delta: MonthSpan): DateTime = this.add(delta.totalMonths, 0.0)
operator fun plus(delta: DateTimeSpan): DateTime = this.add(delta.totalMonths, delta.totalMilliseconds)
operator fun plus(delta: TimeSpan): DateTime = add(0, delta.milliseconds)
operator fun minus(delta: MonthSpan): DateTime = this + -delta
operator fun minus(delta: DateTimeSpan): DateTime = this + -delta
operator fun minus(delta: TimeSpan): DateTime = this + (-delta)
operator fun minus(other: DateTime): TimeSpan = (this.unixMillisDouble - other.unixMillisDouble).milliseconds
override fun compareTo(other: DateTime): Int = this.unixMillis.compareTo(other.unixMillis)
/** Constructs a new [DateTime] after adding [deltaMonths] and [deltaMilliseconds] */
fun add(deltaMonths: Int, deltaMilliseconds: Double): DateTime = when {
deltaMonths == 0 && deltaMilliseconds == 0.0 -> this
deltaMonths == 0 -> DateTime(this.unixMillis + deltaMilliseconds)
else -> {
var year = this.year
var month = this.month.index1
var day = this.dayOfMonth
val i = month - 1 + deltaMonths
if (i >= 0) {
month = i % Month.Count + 1
year += i / Month.Count
} else {
month = Month.Count + (i + 1) % Month.Count
year += (i - (Month.Count - 1)) / Month.Count
}
//Year.checked(year)
val days = Month(month).days(year)
if (day > days) day = days
DateTime(dateToMillisUnchecked(year.year, month, day) + (yearOneMillis % MILLIS_PER_DAY) + deltaMilliseconds)
}
}
/** Constructs a new [DateTime] after adding [dateSpan] and [timeSpan] */
fun add(dateSpan: MonthSpan, timeSpan: TimeSpan): DateTime = add(dateSpan.totalMonths, timeSpan.milliseconds)
fun copyDayOfMonth(
year: Year = this.year,
month: Month = this.month,
dayOfMonth: Int = this.dayOfMonth,
hours: Int = this.hours,
minutes: Int = this.minutes,
seconds: Int = this.seconds,
milliseconds: Int = this.milliseconds
) = DateTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds)
/** Converts this date to String using [format] for representing it */
fun format(format: DateFormat): String = format.format(this)
/** Converts this date to String using [format] for representing it */
fun format(format: String): String = DateFormat(format).format(this)
/** Converts this date to String using [format] for representing it */
fun toString(format: String): String = DateFormat(format).format(this)
/** Converts this date to String using [format] for representing it */
fun toString(format: DateFormat): String = format.format(this)
/** Converts this date to String using the [DateFormat.DEFAULT_FORMAT] for representing it */
fun toStringDefault(): String = DateFormat.DEFAULT_FORMAT.format(this)
//override fun toString(): String = DateFormat.DEFAULT_FORMAT.format(this)
override fun toString(): String = "DateTime($unixMillisLong)"
}
fun max(a: DateTime, b: DateTime): DateTime =
DateTime.fromUnixMillis(max(a.unixMillis, b.unixMillis))
fun min(a: DateTime, b: DateTime): DateTime =
DateTime.fromUnixMillis(min(a.unixMillis, b.unixMillis))
fun DateTime.clamp(min: DateTime, max: DateTime): DateTime = when {
this < min -> min
this > max -> max
else -> this
}

View File

@@ -1,154 +0,0 @@
package korlibs.time
import korlibs.time.internal.Serializable
/**
* Represents a right-opened range between two dates.
*/
data class DateTimeRange(val from: DateTime, val to: DateTime) : Comparable<DateTime>, Serializable {
val valid get() = from <= to
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
operator fun invoke(base: Date, from: Time, to: Time): DateTimeRange = DateTimeRange(base + from, base + to)
}
val size: TimeSpan get() = to - from
val min get() = from
val max get() = to
/**
* Duration [TimeSpan] without having into account actual months/years.
*/
val duration: TimeSpan get() = to - from
/**
* [DateTimeSpan] distance between two dates, month and year aware.
*/
val span: DateTimeSpan by lazy {
val reverse = to < from
val rfrom = if (!reverse) from else to
val rto = if (!reverse) to else from
var years = 0
var months = 0
var pivot = rfrom
// Compute years
val diffYears = (rto.year - pivot.year)
pivot += diffYears.years
years += diffYears
if (pivot > rto) {
pivot -= 1.years
years--
}
// Compute months (at most an iteration of 12)
while (true) {
val t = pivot + 1.months
if (t <= rto) {
months++
pivot = t
} else {
break
}
}
val out = DateTimeSpan(years.years + months.months, rto - pivot)
if (reverse) -out else out
}
/**
* Checks if a date is contained in this range.
*/
operator fun contains(date: DateTime): Boolean {
val unix = date.unixMillisDouble
val from = from.unixMillisDouble
val to = to.unixMillisDouble
return if (unix < from) false else unix < to
}
operator fun contains(other: DateTimeRange): Boolean {
return other.min >= this.min && other.max <= this.max
}
private inline fun <T> _intersectionWith(that: DateTimeRange, rightOpen: Boolean, handler: (from: DateTime, to: DateTime, matches: Boolean) -> T): T {
val from = max(this.from, that.from)
val to = min(this.to, that.to)
return handler(from, to, if (rightOpen) from < to else from <= to)
}
/**
* Returns new [DateTimeRange] or null - the result of intersection of this and [that] DateTimeRanges.
*/
fun intersectionWith(that: DateTimeRange, rightOpen: Boolean = true): DateTimeRange? {
return _intersectionWith(that, rightOpen) { from, to, matches ->
when {
matches -> DateTimeRange(from, to)
else -> null
}
}
}
/**
* Returns true if this and [that] DateTimeRanges have intersection otherwise false.
*/
fun intersectsWith(that: DateTimeRange, rightOpen: Boolean = true): Boolean = _intersectionWith(that, rightOpen) { _, _, matches -> matches }
/**
* Returns true if this and [that] DateTimeRanges have intersection or at least a common end otherwise false.
*/
fun intersectsOrInContactWith(that: DateTimeRange): Boolean = intersectsWith(that, rightOpen = false)
/**
* Returns new [DateTimeRange] or null - the result of merging this and [that] DateTimeRanges if they have intersection.
*/
fun mergeOnContactOrNull(that: DateTimeRange): DateTimeRange? {
if (!intersectsOrInContactWith(that)) return null
val min = min(this.min, that.min)
val max = max(this.max, that.max)
return DateTimeRange(min, max)
}
/**
* Returns a [List] of 0, 1 or 2 [DateTimeRange]s - the result of removing [that] DateTimeRange from this one
*/
fun without(that: DateTimeRange): List<DateTimeRange> = when {
// Full remove
(that.min <= this.min) && (that.max >= this.max) -> listOf()
// To the right or left, nothing to remove
(that.min >= this.max) || (that.max <= this.min) -> listOf(this)
// In the middle
else -> {
val p0 = this.min
val p1 = that.min
val p2 = that.max
val p3 = this.max
val c1 = if (p0 < p1) DateTimeRange(p0, p1) else null
val c2 = if (p2 < p3) DateTimeRange(p2, p3) else null
listOfNotNull(c1, c2)
}
}
fun toString(format: DateFormat): String = "${min.toString(format)}..${max.toString(format)}"
fun toStringLongs(): String = "${min.unixMillisLong}..${max.unixMillisLong}"
fun toStringDefault(): String = toString(DateFormat.FORMAT1)
//override fun toString(): String = toString(DateFormat.FORMAT1)
override fun toString(): String = "$min..$max"
override fun compareTo(other: DateTime): Int {
if (this.max <= other) return -1
if (this.min > other) return +1
return 0
}
}
fun List<DateTimeRange>.toStringLongs() = this.map { it.toStringLongs() }.toString()
/**
* Generates a right-opened range between two [DateTime]s
*/
infix fun DateTime.until(other: DateTime) = DateTimeRange(this, other)

View File

@@ -1,270 +0,0 @@
package korlibs.time
import korlibs.time.internal.BSearchResult
import korlibs.time.internal.Serializable
import korlibs.time.internal.fastForEach
import korlibs.time.internal.genericBinarySearch
// Properties:
// - ranges are sorted
// - ranges do not overlap/intersect between each other (they are merged and normalized)
// These properties allows to do some tricks and optimizations like binary search and a lot of O(n) operations.
data class DateTimeRangeSet private constructor(val dummy: Boolean, val ranges: List<DateTimeRange>) : Serializable {
/** [DateTimeRange] from the beginning of the first element to the end of the last one. */
val bounds = DateTimeRange(
ranges.firstOrNull()?.from ?: DateTime.EPOCH,
ranges.lastOrNull()?.to ?: DateTime.EPOCH
)
/** Total time of all [ranges]. */
val size: TimeSpan by lazy {
var out = 0.seconds
ranges.fastForEach { out += it.size }
out
}
constructor(ranges: List<DateTimeRange>) : this(false, Fast.combine(ranges))
constructor(range: DateTimeRange) : this(listOf(range))
constructor(vararg ranges: DateTimeRange) : this(ranges.toList())
operator fun plus(range: DateTimeRange): DateTimeRangeSet = this + DateTimeRangeSet(range)
operator fun plus(right: DateTimeRangeSet): DateTimeRangeSet = DateTimeRangeSet(this.ranges + right.ranges)
operator fun minus(range: DateTimeRange): DateTimeRangeSet = this - DateTimeRangeSet(range)
operator fun minus(right: DateTimeRangeSet): DateTimeRangeSet = Fast.minus(this, right)
operator fun contains(time: DateTime): Boolean = Fast.contains(time, this)
operator fun contains(time: DateTimeRange): Boolean = Fast.contains(time, this)
fun intersection(range: DateTimeRange): DateTimeRangeSet = this.intersection(DateTimeRangeSet(range))
fun intersection(vararg range: DateTimeRange): DateTimeRangeSet = this.intersection(DateTimeRangeSet(*range))
fun intersection(right: DateTimeRangeSet): DateTimeRangeSet = Fast.intersection(this, right)
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
fun toStringLongs(ranges: List<DateTimeRange>): String = "${ranges.map { it.toStringLongs() }}"
}
object Fast {
internal fun combine(ranges: List<DateTimeRange>): List<DateTimeRange> {
if (ranges.isEmpty()) return ranges
val sorted = ranges.sortedBy { it.from.unixMillis }
val out = arrayListOf<DateTimeRange>()
var pivot = sorted.first()
for (n in 1 until sorted.size) {
val current = sorted[n]
val result = pivot.mergeOnContactOrNull(current)
pivot = if (result != null) {
result
} else {
out.add(pivot)
current
}
}
return out + listOf(pivot)
}
internal fun minus(left: DateTimeRangeSet, right: DateTimeRangeSet): DateTimeRangeSet {
if (left.ranges.isEmpty() || right.ranges.isEmpty()) return left
val ll = left.ranges
val rr = right.ranges.filter { it.intersectsWith(left.bounds) }
var lpos = 0
var rpos = 0
var l = ll.getOrNull(lpos++)
var r = rr.getOrNull(rpos++)
val out = arrayListOf<DateTimeRange>()
//debug { "-----------------" }
//debug { "Minus:" }
//debug { " - ll=${toStringLongs(ll)}" }
//debug { " - rr=${toStringLongs(rr)}" }
while (l != null && r != null) {
val result = l.without(r)
//debug { "Minus ${l!!.toStringLongs()} with ${r!!.toStringLongs()} -- ${toStringLongs(result)}" }
when (result.size) {
0 -> {
//debug { " - Full remove" }
l = ll.getOrNull(lpos++)
}
1 -> {
//debug { " - Result 1" }
when {
r.from >= l.to -> {
//debug { " - Move left. Emit ${result[0].toStringLongs()}" }
out.add(result[0])
l = ll.getOrNull(lpos++)
}
l == result[0] -> {
//debug { " - Move right. Change l from ${l!!.toStringLongs()} to ${result[0].toStringLongs()}" }
r = rr.getOrNull(rpos++)
}
else -> {
//debug { " - Use this l=${result[0].toStringLongs()} from ${l!!.toStringLongs()}" }
l = result[0]
}
}
}
else -> {
//debug { " - One chunk removed: ${result.map { it.toStringLongs() }}" }
//debug { " - Emit: ${result[0].toStringLongs()}" }
//debug { " - Keep: ${result[1].toStringLongs()}" }
out.add(result[0])
l = result[1]
}
}
}
if (l != null) {
out.add(l)
}
while (lpos < ll.size) out.add(ll[lpos++])
//debug { toStringLongs(out) }
return DateTimeRangeSet(out)
}
fun intersection(left: DateTimeRangeSet, right: DateTimeRangeSet): DateTimeRangeSet {
if (left.ranges.isEmpty() || right.ranges.isEmpty()) return DateTimeRangeSet(listOf())
val ll = left.ranges.filter { it.intersectsWith(right.bounds) }
val rr = right.ranges.filter { it.intersectsWith(left.bounds) }
val out = arrayListOf<DateTimeRange>()
//debug { "-----------------" }
//debug { "Intersection:" }
//debug { " - ll=${toStringLongs(ll)}" }
//debug { " - rr=${toStringLongs(rr)}" }
var rpos = 0
for (l in ll) {
rpos = 0
// We should be able to do this because the time ranges doesn't intersect each other
//while (rpos > 0) {
// val r = rr.getOrNull(rpos) ?: break
// if ((r.from < l.from) && (r.to < l.from)) break // End since we are already
// rpos--
//}
while (rpos < rr.size) {
val r = rr.getOrNull(rpos) ?: break
if (r.min > l.max) break // End since the rest are going to be farther
val res = l.intersectionWith(r)
if (res != null) {
out.add(res)
}
rpos++
}
}
//debug { toStringLongs(out) }
return DateTimeRangeSet(out)
}
fun contains(time: DateTime, rangeSet: DateTimeRangeSet): Boolean {
if (time !in rangeSet.bounds) return false // Early guard clause
val ranges = rangeSet.ranges
val result = BSearchResult(genericBinarySearch(0, ranges.size) { index -> ranges[index].compareTo(time) })
return result.found
}
fun contains(time: DateTimeRange, rangeSet: DateTimeRangeSet): Boolean {
if (time !in rangeSet.bounds) return false // Early guard clause
val ranges = rangeSet.ranges
val result = BSearchResult(genericBinarySearch(0, ranges.size) { index ->
val range = ranges[index]
when {
time in range -> 0
time.min < range.min -> +1
else -> -1
}
})
return result.found
}
//private inline fun debug(gen: () -> String) { println(gen()) }
}
object Slow {
// @TODO: Optimize
internal fun minus(l: DateTimeRangeSet, r: DateTimeRangeSet): DateTimeRangeSet {
val rightList = r.ranges
var out = l.ranges.toMutableList()
restart@ while (true) {
for ((leftIndex, left) in out.withIndex()) {
for (right in rightList) {
val result = left.without(right)
if (result.size != 1 || result[0] != left) {
out = (out.slice(0 until leftIndex) + result + out.slice(leftIndex + 1 until out.size)).toMutableList()
continue@restart
}
}
}
break
}
return DateTimeRangeSet(out)
}
internal fun combine(ranges: List<DateTimeRange>): List<DateTimeRange> {
// @TODO: Improve performance and verify fast combiner
val ranges = ranges.toMutableList()
restart@ while (true) {
for (i in ranges.indices) {
for (j in ranges.indices) {
if (i == j) continue
val ri = ranges[i]
val rj = ranges[j]
val concat = ri.mergeOnContactOrNull(rj)
if (concat != null) {
//println("Combining $ri and $rj : $concat")
ranges.remove(rj)
ranges[i] = concat
continue@restart
}
}
}
break
}
return ranges
}
fun intersection(left: DateTimeRangeSet, right: DateTimeRangeSet): DateTimeRangeSet {
val leftList = left.ranges
val rightList = right.ranges
val out = arrayListOf<DateTimeRange>()
for (l in leftList) {
for (r in rightList) {
if (r.min > l.max) break
val result = l.intersectionWith(r)
if (result != null) {
out.add(result)
}
}
//val chunks = rightList.mapNotNull { r -> l.intersectionWith(r) }
//out.addAll(DateTimeRangeSet(chunks).ranges)
}
return DateTimeRangeSet(out)
}
fun contains(time: DateTime, rangeSet: DateTimeRangeSet): Boolean {
if (time !in rangeSet.bounds) return false // Early guard clause
// @TODO: Fast binary search, since the ranges doesn't intersect each other
rangeSet.ranges.fastForEach { range ->
if (time in range) return true
}
return false
}
fun contains(time: DateTimeRange, rangeSet: DateTimeRangeSet): Boolean {
if (time !in rangeSet.bounds) return false // Early guard clause
// @TODO: Fast binary search, since the ranges doesn't intersect each other
rangeSet.ranges.fastForEach { range ->
if (time in range) return true
}
return false
}
}
fun toStringLongs(): String = "${ranges.map { it.toStringLongs() }}"
override fun toString(): String = "$ranges"
}
fun Iterable<DateTimeRange>.toRangeSet() = DateTimeRangeSet(this.toList())

View File

@@ -1,143 +0,0 @@
package korlibs.time
import korlibs.time.internal.MILLIS_PER_DAY
import korlibs.time.internal.MILLIS_PER_HOUR
import korlibs.time.internal.MILLIS_PER_MINUTE
import korlibs.time.internal.MILLIS_PER_SECOND
import korlibs.time.internal.MILLIS_PER_WEEK
import korlibs.time.internal.Moduler
import korlibs.time.internal.Serializable
/**
* Immutable structure representing a set of a [monthSpan] and a [timeSpan].
* This structure loses information about which months are included, that makes it impossible to generate a real [TimeSpan] including months.
* You can use [DateTimeRange.duration] to get this information from two real [DateTime].
*/
data class DateTimeSpan(
/** The [MonthSpan] part */
val monthSpan: MonthSpan,
/** The [TimeSpan] part */
val timeSpan: TimeSpan
) : Comparable<DateTimeSpan>, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
}
constructor(
years: Int = 0,
months: Int = 0,
weeks: Int = 0,
days: Int = 0,
hours: Int = 0,
minutes: Int = 0,
seconds: Int = 0,
milliseconds: Double = 0.0
) : this(
years.years + months.months,
weeks.weeks + days.days + hours.hours + minutes.minutes + seconds.seconds + milliseconds.milliseconds
)
operator fun unaryMinus() = DateTimeSpan(-monthSpan, -timeSpan)
operator fun unaryPlus() = DateTimeSpan(+monthSpan, +timeSpan)
operator fun plus(other: TimeSpan) = DateTimeSpan(monthSpan, timeSpan + other)
operator fun plus(other: MonthSpan) = DateTimeSpan(monthSpan + other, timeSpan)
operator fun plus(other: DateTimeSpan) = DateTimeSpan(monthSpan + other.monthSpan, timeSpan + other.timeSpan)
operator fun minus(other: TimeSpan) = this + -other
operator fun minus(other: MonthSpan) = this + -other
operator fun minus(other: DateTimeSpan) = this + -other
operator fun times(times: Double) = DateTimeSpan((monthSpan * times), (timeSpan * times))
operator fun times(times: Int) = this * times.toDouble()
operator fun times(times: Float) = this * times.toDouble()
operator fun div(times: Double) = times(1.0 / times)
operator fun div(times: Int) = this / times.toDouble()
operator fun div(times: Float) = this / times.toDouble()
/** From the date part, all months represented as a [totalYears] [Double] */
val totalYears: Double get() = monthSpan.totalYears
/** From the date part, all months including months and years */
val totalMonths: Int get() = monthSpan.totalMonths
/** From the time part, all the milliseconds including milliseconds, seconds, minutes, hours, days and weeks */
val totalMilliseconds: Double get() = timeSpan.milliseconds
/** The [years] part as an integer. */
val years: Int get() = monthSpan.years
/** The [months] part as an integer. */
val months: Int get() = monthSpan.months
/** The [weeks] part as an integer. */
val weeks: Int get() = computed.weeks
val daysNotIncludingWeeks: Int get() = days
/** The [daysIncludingWeeks] part as an integer including days and weeks. */
val daysIncludingWeeks: Int get() = computed.days + (computed.weeks * DayOfWeek.Count)
/** The [days] part as an integer. */
val days: Int get() = computed.days
/** The [hours] part as an integer. */
val hours: Int get() = computed.hours
/** The [minutes] part as an integer. */
val minutes: Int get() = computed.minutes
/** The [seconds] part as an integer. */
val seconds: Int get() = computed.seconds
/** The [milliseconds] part as a double. */
val milliseconds: Double get() = computed.milliseconds
/** The [secondsIncludingMilliseconds] part as a doble including seconds and milliseconds. */
val secondsIncludingMilliseconds: Double get() = computed.seconds + computed.milliseconds / MILLIS_PER_SECOND
/**
* Note that if milliseconds overflow months this could not be exactly true. But probably will work in most cases.
* This structure doesn't have information about which months are counted. So some months could have 28-31 days and thus can't be done.
* You can use [DateTimeRange.duration] to compare this with real precision using a range between two [DateTime].
*/
override fun compareTo(other: DateTimeSpan): Int {
if (this.totalMonths != other.totalMonths) return this.monthSpan.compareTo(other.monthSpan)
return this.timeSpan.compareTo(other.timeSpan)
}
/**
* Represents this [DateTimeSpan] as a string like `50Y 10M 3W 6DH 30m 15s`.
* Parts that are zero, won't be included. You can omit weeks and represent them
* as days by adjusting the [includeWeeks] parameter.
*/
fun toString(includeWeeks: Boolean): String = arrayListOf<String>().apply {
if (years != 0) add("${years}Y")
if (months != 0) add("${months}M")
if (includeWeeks && weeks != 0) add("${weeks}W")
if (days != 0 || (!includeWeeks && weeks != 0)) add("${if (includeWeeks) days else daysIncludingWeeks}D")
if (hours != 0) add("${hours}H")
if (minutes != 0) add("${minutes}m")
if (seconds != 0 || milliseconds != 0.0) add("${secondsIncludingMilliseconds}s")
if (monthSpan == 0.years && ((timeSpan == 0.seconds) || (timeSpan == (-0).seconds))) add("0s")
}.joinToString(" ")
override fun toString(): String = toString(includeWeeks = true)
private class ComputedTime(val weeks: Int, val days: Int, val hours: Int, val minutes: Int, val seconds: Int, val milliseconds: Double) {
companion object {
operator fun invoke(time: TimeSpan): ComputedTime = Moduler(time.milliseconds).run {
val weeks = int(MILLIS_PER_WEEK)
val days = int(MILLIS_PER_DAY)
val hours = int(MILLIS_PER_HOUR)
val minutes = int(MILLIS_PER_MINUTE)
val seconds = int(MILLIS_PER_SECOND)
val milliseconds = double(1)
return ComputedTime(weeks, days, hours, minutes, seconds, milliseconds)
}
}
}
private val computed by lazy { ComputedTime(timeSpan) }
}

View File

@@ -1,12 +0,0 @@
package korlibs.time
interface DateTimeSpanFormat {
fun format(dd: DateTimeSpan): String
fun tryParse(str: String, doThrow: Boolean): DateTimeSpan?
}
fun DateTimeSpanFormat.format(dd: TimeSpan): String = format(dd + 0.months)
fun DateTimeSpanFormat.format(dd: MonthSpan): String = format(dd + 0.seconds)
fun DateTimeSpanFormat.parse(str: String): DateTimeSpan =
tryParse(str, doThrow = true) ?: throw DateException("Not a valid format: '$str' for '$this'")

View File

@@ -1,125 +0,0 @@
package korlibs.time
import korlibs.time.internal.Serializable
/** [DateTime] with an associated [TimezoneOffset] */
class DateTimeTz private constructor(
/** The [adjusted] part */
private val adjusted: DateTime,
/** The [offset] part */
val offset: TimezoneOffset
) : Comparable<DateTimeTz>, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
/** Creates a new [DateTimeTz] with the [utc] date and an [offset]. The [utc] components will be the same as this independently on the [offset]. */
fun local(local: DateTime, offset: TimezoneOffset) = DateTimeTz(local, offset)
/** Creates a new [DateTimeTz] with the [utc] date and an [offset]. The [utc] components might be different depending on the [offset]. */
fun utc(utc: DateTime, offset: TimezoneOffset) = DateTimeTz(utc + offset.time, offset)
/** Creates a new local [DateTimeTz] from a [unix] time */
fun fromUnixLocal(unix: Long): DateTimeTz = DateTime(unix).localUnadjusted
/** Creates a new local [DateTimeTz] from a [unix] time applied*/
fun fromUnix(unix: Long): DateTimeTz {
val unixDateTime = DateTime(unix)
return utc(unixDateTime, TimezoneOffset.local(unixDateTime))
}
/** Returns the current local [DateTimeTz] */
fun nowLocal(): DateTimeTz = DateTime.now().local
}
/** Returns a new UTC date that will match these components without being the same time */
val local: DateTime get() = adjusted
/** Returns a new UTC date that might not match these components, but it is the same time as UTC */
val utc: DateTime get() = (adjusted - offset.time)
/** The [Year] part */
val year: Year get() = adjusted.year
/** The [Year] part as [Int] */
val yearInt: Int get() = adjusted.yearInt
/** The [Month] part */
val month: Month get() = adjusted.month
/** The [Month] part as [Int] where January is represented as 0 */
val month0: Int get() = adjusted.month0
/** The [Month] part as [Int] where January is represented as 1 */
val month1: Int get() = adjusted.month1
/** Represents a couple of [Year] and [Month] that has leap information and thus allows to get the number of days of that month */
val yearMonth: YearMonth get() = adjusted.yearMonth
/** The [dayOfMonth] part */
val dayOfMonth: Int get() = adjusted.dayOfMonth
/** The [dayOfWeek] part */
val dayOfWeek: DayOfWeek get() = adjusted.dayOfWeek
/** The [dayOfWeek] part as [Int] */
val dayOfWeekInt: Int get() = adjusted.dayOfWeekInt
/** The [dayOfYear] part */
val dayOfYear: Int get() = adjusted.dayOfYear
/** The [hours] part */
val hours: Int get() = adjusted.hours
/** The [minutes] part */
val minutes: Int get() = adjusted.minutes
/** The [seconds] part */
val seconds: Int get() = adjusted.seconds
/** The [milliseconds] part */
val milliseconds: Int get() = adjusted.milliseconds
/** Constructs this local date with a new [offset] without changing its components */
fun toOffsetUnadjusted(offset: TimeSpan) = toOffsetUnadjusted(offset.offset)
/** Constructs this local date with a new [offset] without changing its components */
fun toOffsetUnadjusted(offset: TimezoneOffset) = DateTimeTz.local(this.local, offset)
/** Constructs this local date by adding an additional [offset] without changing its components */
fun addOffsetUnadjusted(offset: TimeSpan) = addOffsetUnadjusted(offset.offset)
/** Constructs this local date by adding an additional [offset] without changing its components */
fun addOffsetUnadjusted(offset: TimezoneOffset) = DateTimeTz.local(this.local, (this.offset.time + offset.time).offset)
/** Constructs the UTC part of this date with a new [offset] */
fun toOffset(offset: TimeSpan) = toOffset(offset.offset)
/** Constructs the UTC part of this date with a new [offset] */
fun toOffset(offset: TimezoneOffset) = DateTimeTz.utc(this.utc, offset)
/** Constructs the UTC part of this date by adding an additional [offset] */
fun addOffset(offset: TimeSpan) = addOffset(offset.offset)
/** Constructs the UTC part of this date by adding an additional [offset] */
fun addOffset(offset: TimezoneOffset) = DateTimeTz.utc(this.utc, (this.offset.time + offset.time).offset)
/** Constructs a new [DateTimeTz] after adding [dateSpan] and [timeSpan] */
fun add(dateSpan: MonthSpan, timeSpan: TimeSpan): DateTimeTz = DateTimeTz(adjusted.add(dateSpan, timeSpan), offset)
operator fun plus(delta: MonthSpan) = add(delta, 0.milliseconds)
operator fun plus(delta: DateTimeSpan) = add(delta.monthSpan, delta.timeSpan)
operator fun plus(delta: TimeSpan) = add(0.months, delta)
operator fun minus(delta: MonthSpan) = this + (-delta)
operator fun minus(delta: DateTimeSpan) = this + (-delta)
operator fun minus(delta: TimeSpan) = this + (-delta)
operator fun minus(other: DateTimeTz) = (this.utc.unixMillisDouble - other.utc.unixMillisDouble).milliseconds
override fun hashCode(): Int = this.local.hashCode() + offset.totalMinutesInt
override fun equals(other: Any?): Boolean = other is DateTimeTz && this.utc.unixMillisDouble == other.utc.unixMillisDouble
override fun compareTo(other: DateTimeTz): Int = this.utc.unixMillis.compareTo(other.utc.unixMillis)
/** Converts this date to String using [format] for representing it */
fun format(format: DateFormat): String = format.format(this)
/** Converts this date to String using [format] for representing it */
fun format(format: String): String = DateFormat(format).format(this)
/** Converts this date to String using [format] for representing it */
fun toString(format: DateFormat): String = format.format(this)
/** Converts this date to String using [format] for representing it */
fun toString(format: String): String = DateFormat(format).format(this)
/** Converts this date to String using the [DateFormat.DEFAULT_FORMAT] for representing it */
fun toStringDefault(): String = DateFormat.DEFAULT_FORMAT.format(this)
override fun toString(): String = "DateTimeTz($adjusted, $offset)"
}

View File

@@ -1,95 +0,0 @@
package korlibs.time
import korlibs.time.DayOfWeek.Friday
import korlibs.time.DayOfWeek.Monday
import korlibs.time.DayOfWeek.Saturday
import korlibs.time.DayOfWeek.Sunday
import korlibs.time.DayOfWeek.Thursday
import korlibs.time.DayOfWeek.Tuesday
import korlibs.time.DayOfWeek.Wednesday
import korlibs.time.internal.*
/** Represents the day of the week. [Sunday], [Monday], [Tuesday], [Wednesday], [Thursday], [Friday], [Saturday]. */
enum class DayOfWeek(
/** 0: [Sunday], 1: [Monday], 2: [Tuesday], 3: [Wednesday], 4: [Thursday], 5: [Friday], 6: [Saturday] */
val index0: Int
) : Serializable {
Sunday(0),
Monday(1),
Tuesday(2),
Wednesday(3),
Thursday(4),
Friday(5),
Saturday(6);
/**
* 1: [Sunday], 2: [Monday], 3: [Tuesday], 4: [Wednesday], 5: [Thursday], 6: [Friday], 7: [Saturday]
*/
val index1 get() = index0 + 1
val index0Sunday get() = index0
val index1Sunday get() = index1
/** 0: [Monday], 1: [Tuesday], 2: [Wednesday], 3: [Thursday], 4: [Friday], 5: [Saturday], 6: [Sunday] */
val index0Monday get() = (index0 - 1) umod 7
/** 1: [Monday], 2: [Tuesday], 3: [Wednesday], 4: [Thursday], 5: [Friday], 6: [Saturday], 7: [Sunday] */
val index1Monday get() = index0Monday + 1
fun index0Locale(locale: KlockLocale): Int = (index0 - locale.firstDayOfWeek.index0) umod 7
fun index1Locale(locale: KlockLocale): Int = index0Locale(locale) + 1
/** Returns if this day of the week is weekend for a specific [locale] */
fun isWeekend(locale: KlockLocale = KlockLocale.default) = locale.isWeekend(this)
val localName get() = localName(KlockLocale.default)
fun localName(locale: KlockLocale) = locale.daysOfWeek[index0]
val localShortName get() = localShortName(KlockLocale.default)
fun localShortName(locale: KlockLocale) = locale.daysOfWeekShort[index0]
val prev get() = DayOfWeek[index0 - 1]
val next get() = DayOfWeek[index0 + 1]
fun prev(offset: Int = 1) = DayOfWeek[index0 - offset]
fun next(offset: Int = 1) = DayOfWeek[index0 + offset]
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
/**
* Number of days in a wekk.
*/
const val Count = 7
private val BY_INDEX0 = values()
/**
* 0: [Sunday], 1: [Monday], 2: [Tuesday], 3: [Wednesday], 4: [Thursday], 5: [Friday], 6: [Saturday]
*/
operator fun get(index0: Int) = BY_INDEX0[index0 umod 7]
fun get0(index0: Int, locale: KlockLocale = KlockLocale.default): DayOfWeek = DayOfWeek[index0 + locale.firstDayOfWeek.index0]
fun get1(index1: Int, locale: KlockLocale = KlockLocale.default): DayOfWeek = get0((index1 - 1) umod 7, locale)
/**
* Returns the first day of the week for a specific [locale].
*/
fun firstDayOfWeek(locale: KlockLocale = KlockLocale.default) = locale.firstDayOfWeek
fun comparator(locale: KlockLocale = KlockLocale.default) = locale.daysOfWeekComparator
}
}
fun DayOfWeek.withLocale(locale: KlockLocale) = locale.localizedDayOfWeek(this)
data class DayOfWeekWithLocale(val dayOfWeek: DayOfWeek, val locale: KlockLocale) : Comparable<DayOfWeekWithLocale> {
val index0: Int get() = dayOfWeek.index0Locale(locale)
val index1: Int get() = dayOfWeek.index1Locale(locale)
override fun compareTo(other: DayOfWeekWithLocale): Int {
if (other.locale != this.locale) error("Can't compare two day of weeks with different locales")
return locale.daysOfWeekComparator.compare(dayOfWeek, other.dayOfWeek)
}
}

View File

@@ -1,42 +0,0 @@
package korlibs.time
import korlibs.time.internal.*
import kotlin.jvm.*
val TimeSpan.hz: Frequency get() = timesPerSecond
val Int.hz: Frequency get() = timesPerSecond
val Double.hz: Frequency get() = timesPerSecond
fun TimeSpan.toFrequency(): Frequency = timesPerSecond
val TimeSpan.timesPerSecond get() = Frequency(1.0 / this.seconds)
val Int.timesPerSecond get() = Frequency(this.toDouble())
val Double.timesPerSecond get() = Frequency(this)
@JvmInline
value class Frequency(val hertz: Double) : Comparable<Frequency>, Serializable {
companion object {
fun from(timeSpan: TimeSpan) = timeSpan.toFrequency()
}
override fun compareTo(other: Frequency): Int = this.hertz.compareTo(other.hertz)
operator fun unaryMinus() = Frequency(-this.hertz)
operator fun unaryPlus() = this
operator fun plus(other: Frequency): Frequency = Frequency(this.hertz + other.hertz)
operator fun minus(other: Frequency): Frequency = Frequency(this.hertz - other.hertz)
operator fun times(scale: Int): Frequency = Frequency(this.hertz * scale)
operator fun times(scale: Float): Frequency = Frequency(this.hertz * scale)
operator fun times(scale: Double): Frequency = Frequency(this.hertz * scale)
operator fun div(scale: Int): Frequency = Frequency(this.hertz / scale)
operator fun div(scale: Float): Frequency = Frequency(this.hertz / scale)
operator fun div(scale: Double): Frequency = Frequency(this.hertz / scale)
operator fun rem(other: Frequency): Frequency = Frequency(this.hertz % other.hertz)
infix fun umod(other: Frequency): Frequency = Frequency(this.hertz umod other.hertz)
val timeSpan get() = (1.0 / this.hertz).seconds
}

View File

@@ -1,446 +0,0 @@
package korlibs.time
import korlibs.time.internal.MicroStrReader
import korlibs.time.internal.fastForEach
import korlibs.time.internal.padded
import korlibs.time.internal.readTimeZoneOffset
import kotlin.math.absoluteValue
// https://en.wikipedia.org/wiki/ISO_8601
object ISO8601 {
data class BaseIsoTimeFormat(val format: String) : TimeFormat {
companion object {
private val ref = DateTime(1900, 1, 1)
}
private val dateTimeFormat = BaseIsoDateTimeFormat(format)
override fun format(dd: TimeSpan): String = dateTimeFormat.format(ref + dd)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? =
dateTimeFormat.tryParse(str, doThrow, doAdjust)?.let { it.utc - ref }
}
data class BaseIsoDateTimeFormat(val format: String, val twoDigitBaseYear: Int = 1900) : DateFormat {
override fun format(dd: DateTimeTz): String = buildString {
val d = dd.local
val s = d.copyDayOfMonth(hours = 0, minutes = 0, seconds = 0, milliseconds = 0)
val time = d - s
val fmtReader = MicroStrReader(format)
while (fmtReader.hasMore) {
when {
fmtReader.tryRead("Z") -> {
//if (dd.offset != TimezoneOffset.UTC) {
if (dd.offset != TimezoneOffset.UTC) {
dd.offset.deltaHoursAbs
append(if (dd.offset.positive) "+" else "-")
append(dd.offset.deltaHoursAbs.padded(2))
append(":")
append(dd.offset.deltaMinutesAbs.padded(2))
} else {
append("Z")
}
}
fmtReader.tryRead("YYYYYY") -> append(d.yearInt.absoluteValue.padded(6))
fmtReader.tryRead("YYYY") -> append(d.yearInt.absoluteValue.padded(4))
fmtReader.tryRead("YY") -> append((d.yearInt.absoluteValue % 100).padded(2))
fmtReader.tryRead("MM") -> append(d.month1.padded(2))
fmtReader.tryRead("DD") -> append(d.dayOfMonth.padded(2))
fmtReader.tryRead("DDD") -> append(d.dayOfWeekInt.padded(3))
fmtReader.tryRead("ww") -> append(d.weekOfYear1.padded(2))
fmtReader.tryRead("D") -> append(d.dayOfWeek.index1Monday)
fmtReader.tryRead("hh") -> {
val nextComma = fmtReader.tryRead(',')
val result = if (nextComma || fmtReader.tryRead('.')) {
var decCount = 0
while (fmtReader.tryRead('h')) decCount++
time.hours.padded(2, decCount)
} else {
d.hours.padded(2)
}
append(if (nextComma) result.replace('.', ',') else result)
}
fmtReader.tryRead("mm") -> {
val nextComma = fmtReader.tryRead(',')
val result = if (nextComma || fmtReader.tryRead('.')) {
var decCount = 0
while (fmtReader.tryRead('m')) decCount++
(time.minutes % 60.0).padded(2, decCount)
} else {
d.minutes.padded(2)
}
append(if (nextComma) result.replace('.', ',') else result)
}
fmtReader.tryRead("ss") -> {
val nextComma = fmtReader.tryRead(',')
val result = if (nextComma || fmtReader.tryRead('.')) {
var decCount = 0
while (fmtReader.tryRead('s')) decCount++
(time.seconds % 60.0).padded(2, decCount)
} else {
d.seconds.padded(2)
}
append(if (nextComma) result.replace('.', ',') else result)
}
fmtReader.tryRead("±") -> append(if (d.yearInt < 0) "-" else "+")
else -> append(fmtReader.readChar())
}
}
}
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? {
return _tryParse(str, doAdjust).also {
if (doThrow && it == null) throw DateException("Can't parse $str with $format")
}
}
private fun reportParse(reason: String): DateTimeTz? {
//println("reason: $reason")
return null
}
private fun _tryParse(str: String, doAdjust: Boolean): DateTimeTz? {
var sign = +1
var tzOffset: TimeSpan? = null
var year = twoDigitBaseYear
var month = 1
var dayOfMonth = 1
var dayOfWeek = -1
var dayOfYear = -1
var weekOfYear = -1
var hours = 0.0
var minutes = 0.0
var seconds = 0.0
val reader = MicroStrReader(str)
val fmtReader = MicroStrReader(format)
while (fmtReader.hasMore) {
when {
fmtReader.tryRead("Z") -> tzOffset = reader.readTimeZoneOffset()
fmtReader.tryRead("YYYYYY") -> year = reader.tryReadInt(6) ?: return reportParse("YYYYYY")
fmtReader.tryRead("YYYY") -> year = reader.tryReadInt(4) ?: return reportParse("YYYY")
//fmtReader.tryRead("YY") -> year = twoDigitBaseYear + (reader.tryReadInt(2) ?: return null) // @TODO: Kotlin compiler BUG?
fmtReader.tryRead("YY") -> {
val base = reader.tryReadInt(2) ?: return reportParse("YY")
year = twoDigitBaseYear + base
}
fmtReader.tryRead("MM") -> month = reader.tryReadInt(2) ?: return reportParse("MM")
fmtReader.tryRead("DD") -> dayOfMonth = reader.tryReadInt(2) ?: return reportParse("DD")
fmtReader.tryRead("DDD") -> dayOfYear = reader.tryReadInt(3) ?: return reportParse("DDD")
fmtReader.tryRead("ww") -> weekOfYear = reader.tryReadInt(2) ?: return reportParse("ww")
fmtReader.tryRead("D") -> dayOfWeek = reader.tryReadInt(1) ?: return reportParse("D")
fmtReader.tryRead("hh") -> {
val nextComma = fmtReader.tryRead(',')
hours = if (nextComma || fmtReader.tryRead('.')) {
var count = 3
while (fmtReader.tryRead('h')) count++
reader.tryReadDouble(count) ?: return reportParse("incorrect hours")
} else {
reader.tryReadDouble(2) ?: return reportParse("incorrect hours")
}
}
fmtReader.tryRead("mm") -> {
val nextComma = fmtReader.tryRead(',')
minutes = if (nextComma || fmtReader.tryRead('.')) {
var count = 3
while (fmtReader.tryRead('m')) count++
reader.tryReadDouble(count) ?: return reportParse("incorrect minutes")
} else {
reader.tryReadDouble(2) ?: return reportParse("incorrect seconds")
}
}
fmtReader.tryRead("ss") -> {
val nextComma = fmtReader.tryRead(',')
seconds = if (nextComma || fmtReader.tryRead('.')) {
var count = 3
while (fmtReader.tryRead('s')) count++
reader.tryReadDouble(count) ?: return reportParse("incorrect seconds")
} else {
reader.tryReadDouble(2) ?: return reportParse("incorrect seconds")
}
}
fmtReader.tryRead("±") -> {
sign = when (reader.readChar()) {
'+' -> +1
'-' -> -1
else -> return reportParse("±")
}
}
else -> if (fmtReader.readChar() != reader.readChar()) return reportParse("separator")
}
}
if (reader.hasMore) return reportParse("uncomplete")
val dateTime = when {
dayOfYear >= 0 -> DateTime(year, 1, 1) + (dayOfYear - 1).days
weekOfYear >= 0 -> {
val reference = Year(year).first(DayOfWeek.Thursday) - 3.days
val days = ((weekOfYear - 1) * 7 + (dayOfWeek - 1))
reference + days.days
}
else -> DateTime(year, month, dayOfMonth)
}
val baseDateTime = dateTime + hours.hours + minutes.minutes + seconds.seconds
return if (tzOffset != null) DateTimeTz.local(baseDateTime, TimezoneOffset(tzOffset)) else baseDateTime.local
}
fun withTwoDigitBaseYear(twoDigitBaseYear: Int = 1900) = BaseIsoDateTimeFormat(format, twoDigitBaseYear)
}
class IsoIntervalFormat(val format: String) : DateTimeSpanFormat {
override fun format(dd: DateTimeSpan): String = buildString {
val fmtReader = MicroStrReader(format)
var time = false
while (fmtReader.hasMore) {
when {
fmtReader.tryRead("T") -> append('T').also { time = true }
fmtReader.tryRead("nnY") -> append(dd.years).append('Y')
fmtReader.tryRead("nnM") -> append(if (time) dd.minutes else dd.months).append('M')
fmtReader.tryRead("nnD") -> append(dd.daysIncludingWeeks).append('D')
fmtReader.tryRead("nnH") -> append(dd.hours).append('H')
fmtReader.tryRead("nnS") -> append(dd.seconds).append('S')
else -> append(fmtReader.readChar())
}
}
}
override fun tryParse(str: String, doThrow: Boolean): DateTimeSpan? {
var time = false
var years = 0.0
var months = 0.0
var days = 0.0
var hours = 0.0
var minutes = 0.0
var seconds = 0.0
val reader = MicroStrReader(str)
val fmtReader = MicroStrReader(format)
while (fmtReader.hasMore) {
when {
fmtReader.tryRead("nn,nnY") || fmtReader.tryRead("nnY") -> {
years = reader.tryReadDouble() ?: return null
if (!reader.tryRead("Y")) return null
}
fmtReader.tryRead("nn,nnM") || fmtReader.tryRead("nnM") -> {
if (time) {
minutes = reader.tryReadDouble() ?: return null
} else {
months = reader.tryReadDouble() ?: return null
}
if (!reader.tryRead("M")) return null
}
fmtReader.tryRead("nn,nnD") || fmtReader.tryRead("nnD") -> {
days = reader.tryReadDouble() ?: return null
if (!reader.tryRead("D")) return null
}
fmtReader.tryRead("nn,nnH") || fmtReader.tryRead("nnH") -> {
hours = reader.tryReadDouble() ?: return null
if (!reader.tryRead("H")) return null
}
fmtReader.tryRead("nn,nnS") || fmtReader.tryRead("nnS") -> {
seconds = reader.tryReadDouble() ?: return null
if (!reader.tryRead("S")) return null
}
else -> {
val char = fmtReader.readChar()
if (char != reader.readChar()) return null
if (char == 'T') time = true
}
}
}
return ((years * 12) + months).toInt().months + (days.days + hours.hours + minutes.minutes + seconds.seconds)
}
}
data class IsoTimeFormat(val basicFormat: String?, val extendedFormat: String?) : TimeFormat {
val basic = BaseIsoTimeFormat(basicFormat ?: extendedFormat ?: TODO())
val extended = BaseIsoTimeFormat(extendedFormat ?: basicFormat ?: TODO())
override fun format(dd: TimeSpan): String = extended.format(dd)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? =
basic.tryParse(str, false, doAdjust) ?: extended.tryParse(str, false, doAdjust)
?: (if (doThrow) throw DateException("Invalid format $str") else null)
}
data class IsoDateTimeFormat(val basicFormat: String?, val extendedFormat: String?) : DateFormat {
val basic = BaseIsoDateTimeFormat(basicFormat ?: extendedFormat ?: TODO())
val extended = BaseIsoDateTimeFormat(extendedFormat ?: basicFormat ?: TODO())
override fun format(dd: DateTimeTz): String = extended.format(dd)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? = null
?: basic.tryParse(str, false, doAdjust)
?: extended.tryParse(str, false, doAdjust)
?: (if (doThrow) throw DateException("Invalid format $str") else null)
}
// Date Calendar Variants
val DATE_CALENDAR_COMPLETE = IsoDateTimeFormat("YYYYMMDD", "YYYY-MM-DD")
val DATE_CALENDAR_REDUCED0 = IsoDateTimeFormat(null, "YYYY-MM")
val DATE_CALENDAR_REDUCED1 = IsoDateTimeFormat("YYYY", null)
val DATE_CALENDAR_REDUCED2 = IsoDateTimeFormat("YY", null)
val DATE_CALENDAR_EXPANDED0 = IsoDateTimeFormat("±YYYYYYMMDD", "±YYYYYY-MM-DD")
val DATE_CALENDAR_EXPANDED1 = IsoDateTimeFormat("±YYYYYYMM", "±YYYYYY-MM")
val DATE_CALENDAR_EXPANDED2 = IsoDateTimeFormat("±YYYYYY", null)
val DATE_CALENDAR_EXPANDED3 = IsoDateTimeFormat("±YYY", null)
// Date Ordinal Variants
val DATE_ORDINAL_COMPLETE = IsoDateTimeFormat("YYYYDDD", "YYYY-DDD")
val DATE_ORDINAL_EXPANDED = IsoDateTimeFormat("±YYYYYYDDD", "±YYYYYY-DDD")
// Date Week Variants
val DATE_WEEK_COMPLETE = IsoDateTimeFormat("YYYYWwwD", "YYYY-Www-D")
val DATE_WEEK_REDUCED = IsoDateTimeFormat("YYYYWww", "YYYY-Www")
val DATE_WEEK_EXPANDED0 = IsoDateTimeFormat("±YYYYYYWwwD", "±YYYYYY-Www-D")
val DATE_WEEK_EXPANDED1 = IsoDateTimeFormat("±YYYYYYWww", "±YYYYYY-Www")
val DATE_ALL = listOf(
DATE_CALENDAR_COMPLETE, DATE_CALENDAR_REDUCED0, DATE_CALENDAR_REDUCED1, DATE_CALENDAR_REDUCED2,
DATE_CALENDAR_EXPANDED0, DATE_CALENDAR_EXPANDED1, DATE_CALENDAR_EXPANDED2, DATE_CALENDAR_EXPANDED3,
DATE_ORDINAL_COMPLETE, DATE_ORDINAL_EXPANDED,
DATE_WEEK_COMPLETE, DATE_WEEK_REDUCED, DATE_WEEK_EXPANDED0, DATE_WEEK_EXPANDED1
)
// Time Variants
val TIME_LOCAL_COMPLETE = IsoTimeFormat("hhmmss", "hh:mm:ss")
val TIME_LOCAL_REDUCED0 = IsoTimeFormat("hhmm", "hh:mm")
val TIME_LOCAL_REDUCED1 = IsoTimeFormat("hh", null)
val TIME_LOCAL_FRACTION0 = IsoTimeFormat("hhmmss,ss", "hh:mm:ss,ss")
val TIME_LOCAL_FRACTION1 = IsoTimeFormat("hhmm,mm", "hh:mm,mm")
val TIME_LOCAL_FRACTION2 = IsoTimeFormat("hh,hh", null)
// Time UTC Variants
val TIME_UTC_COMPLETE = IsoTimeFormat("hhmmssZ", "hh:mm:ssZ")
val TIME_UTC_REDUCED0 = IsoTimeFormat("hhmmZ", "hh:mmZ")
val TIME_UTC_REDUCED1 = IsoTimeFormat("hhZ", null)
val TIME_UTC_FRACTION0 = IsoTimeFormat("hhmmss,ssZ", "hh:mm:ss,ssZ")
val TIME_UTC_FRACTION1 = IsoTimeFormat("hhmm,mmZ", "hh:mm,mmZ")
val TIME_UTC_FRACTION2 = IsoTimeFormat("hh,hhZ", null)
// Time Relative Variants
val TIME_RELATIVE0 = IsoTimeFormat("±hhmm", "±hh:mm")
val TIME_RELATIVE1 = IsoTimeFormat("±hh", null)
val TIME_ALL = listOf(
TIME_LOCAL_COMPLETE,
TIME_LOCAL_REDUCED0,
TIME_LOCAL_REDUCED1,
TIME_LOCAL_FRACTION0,
TIME_LOCAL_FRACTION1,
TIME_LOCAL_FRACTION2,
TIME_UTC_COMPLETE,
TIME_UTC_REDUCED0,
TIME_UTC_REDUCED1,
TIME_UTC_FRACTION0,
TIME_UTC_FRACTION1,
TIME_UTC_FRACTION2,
TIME_RELATIVE0,
TIME_RELATIVE1
)
// Date + Time Variants
val DATETIME_COMPLETE = IsoDateTimeFormat("YYYYMMDDThhmmss", "YYYY-MM-DDThh:mm:ss")
val DATETIME_UTC_COMPLETE = IsoDateTimeFormat("YYYYMMDDThhmmssZ", "YYYY-MM-DDThh:mm:ssZ")
val DATETIME_UTC_COMPLETE_FRACTION = IsoDateTimeFormat("YYYYMMDDThhmmss.sssZ", "YYYY-MM-DDThh:mm:ss.sssZ")
// Interval Variants
val INTERVAL_COMPLETE0 = IsoIntervalFormat("PnnYnnMnnDTnnHnnMnnS")
val INTERVAL_COMPLETE1 = IsoIntervalFormat("PnnYnnW")
val INTERVAL_REDUCED0 = IsoIntervalFormat("PnnYnnMnnDTnnHnnM")
val INTERVAL_REDUCED1 = IsoIntervalFormat("PnnYnnMnnDTnnH")
val INTERVAL_REDUCED2 = IsoIntervalFormat("PnnYnnMnnD")
val INTERVAL_REDUCED3 = IsoIntervalFormat("PnnYnnM")
val INTERVAL_REDUCED4 = IsoIntervalFormat("PnnY")
val INTERVAL_DECIMAL0 = IsoIntervalFormat("PnnYnnMnnDTnnHnnMnn,nnS")
val INTERVAL_DECIMAL1 = IsoIntervalFormat("PnnYnnMnnDTnnHnn,nnM")
val INTERVAL_DECIMAL2 = IsoIntervalFormat("PnnYnnMnnDTnn,nnH")
val INTERVAL_DECIMAL3 = IsoIntervalFormat("PnnYnnMnn,nnD")
val INTERVAL_DECIMAL4 = IsoIntervalFormat("PnnYnn,nnM")
val INTERVAL_DECIMAL5 = IsoIntervalFormat("PnnYnn,nnW")
val INTERVAL_DECIMAL6 = IsoIntervalFormat("PnnY")
val INTERVAL_ZERO_OMIT0 = IsoIntervalFormat("PnnYnnDTnnHnnMnnS")
val INTERVAL_ZERO_OMIT1 = IsoIntervalFormat("PnnYnnDTnnHnnM")
val INTERVAL_ZERO_OMIT2 = IsoIntervalFormat("PnnYnnDTnnH")
val INTERVAL_ZERO_OMIT3 = IsoIntervalFormat("PnnYnnD")
val INTERVAL_ALL = listOf(
INTERVAL_COMPLETE0, INTERVAL_COMPLETE1,
INTERVAL_REDUCED0, INTERVAL_REDUCED1, INTERVAL_REDUCED2, INTERVAL_REDUCED3, INTERVAL_REDUCED4,
INTERVAL_DECIMAL0, INTERVAL_DECIMAL1, INTERVAL_DECIMAL2, INTERVAL_DECIMAL3, INTERVAL_DECIMAL4,
INTERVAL_DECIMAL5, INTERVAL_DECIMAL6,
INTERVAL_ZERO_OMIT0, INTERVAL_ZERO_OMIT1, INTERVAL_ZERO_OMIT2, INTERVAL_ZERO_OMIT3
)
// Detects and parses all the variants
val DATE = object : DateFormat {
override fun format(dd: DateTimeTz): String = DATE_CALENDAR_COMPLETE.format(dd)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? {
DATE_ALL.fastForEach { format ->
val result = format.extended.tryParse(str, false, doAdjust)
if (result != null) return result
}
DATE_ALL.fastForEach { format ->
val result = format.basic.tryParse(str, false, doAdjust)
if (result != null) return result
}
return if (doThrow) throw DateException("Invalid format") else null
}
}
val TIME = object : TimeFormat {
override fun format(dd: TimeSpan): String = TIME_LOCAL_FRACTION0.format(dd)
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? {
TIME_ALL.fastForEach { format ->
val result = format.extended.tryParse(str, false, doAdjust)
if (result != null) return result
}
TIME_ALL.fastForEach { format ->
val result = format.basic.tryParse(str, false, doAdjust)
if (result != null) return result
}
return if (doThrow) throw DateException("Invalid format") else null
}
}
val INTERVAL = object : DateTimeSpanFormat {
override fun format(dd: DateTimeSpan): String = INTERVAL_DECIMAL0.format(dd)
override fun tryParse(str: String, doThrow: Boolean): DateTimeSpan? {
INTERVAL_ALL.fastForEach { format ->
val result = format.tryParse(str, false)
if (result != null) return result
}
return if (doThrow) throw DateException("Invalid format") else null
}
}
}
// ISO 8601 (first week is the one after 1 containing a thursday)
fun Year.first(dayOfWeek: DayOfWeek): DateTime {
val start = DateTime(this.year, 1, 1)
var n = 0
while (true) {
val time = (start + n.days)
if (time.dayOfWeek == dayOfWeek) return time
n++
}
}
val DateTime.weekOfYear0: Int
get() {
val firstThursday = year.first(DayOfWeek.Thursday)
val offset = firstThursday.dayOfMonth - 3
return (dayOfYear - offset) / 7
}
val DateTime.weekOfYear1: Int get() = weekOfYear0 + 1
val DateTimeTz.weekOfYear0: Int get() = local.weekOfYear0
val DateTimeTz.weekOfYear1: Int get() = local.weekOfYear1

View File

@@ -1,122 +0,0 @@
package korlibs.time
import korlibs.time.internal.substr
import kotlin.native.concurrent.ThreadLocal
private var KlockLocale_default: KlockLocale? = null
abstract class KlockLocale {
abstract val ISO639_1: String
abstract val daysOfWeek: List<String>
abstract val months: List<String>
abstract val firstDayOfWeek: DayOfWeek
open val monthsShort: List<String> get() = months.map { it.substr(0, 3) }
open val daysOfWeekShort: List<String> get() = daysOfWeek.map { it.substr(0, 3) }
//private val daysOfWeekWithLocaleList: Array<DayOfWeekWithLocale> = Array(7) { DayOfWeekWithLocale(DayOfWeek[it], this) }
//fun localizedDayOfWeek(dayOfWeek: DayOfWeek) = daysOfWeekWithLocaleList[dayOfWeek.index0]
fun localizedDayOfWeek(dayOfWeek: DayOfWeek) = DayOfWeekWithLocale(DayOfWeek[dayOfWeek.index0], this)
val daysOfWeekComparator get() = Comparator<DayOfWeek> { a, b ->
a.index0Locale(this).compareTo(b.index0Locale(this))
}
open val ordinals get() = Array(32) {
if (it in 11..13) {
"${it}th"
} else {
when (it % 10) {
1 -> "${it}st"
2 -> "${it}nd"
3 -> "${it}rd"
else -> "${it}th"
}
}
}
open fun getOrdinalByDay(day: Int, context: KlockLocaleContext = KlockLocaleContext.Default): String = ordinals[day]
open fun getDayByOrdinal(ordinal: String): Int = ordinals.indexOf(ordinal)
//open val monthsShort: List<String> by klockAtomicLazy { months.map { it.substr(0, 3) } }
//open val daysOfWeekShort: List<String> by klockAtomicLazy { daysOfWeek.map { it.substr(0, 3) } }
/*
private val _lock = KlockLock()
private val _monthsShort = KlockAtomicRef<List<String>?>(null)
private val _daysOfWeekShort = KlockAtomicRef<List<String>?>(null)
//open val monthsShort by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { months.map { it.substr(0, 3) } }
open val monthsShort: List<String> get() = _lock {
if (_monthsShort.value == null) {
_monthsShort.value = months.map { it.substr(0, 3) }
}
_monthsShort.value!!
}
open val daysOfWeekShort: List<String> get() = _lock {
if (_daysOfWeekShort.value == null) {
_daysOfWeekShort.value = daysOfWeek.map { it.substr(0, 3) }
}
_daysOfWeekShort.value!!
}
*/
open val h12Marker: List<String> get() = listOf("am", "pm")
// This might be required for some languages like chinese?
open fun intToString(value: Int) = "$value"
open fun isWeekend(dayOfWeek: DayOfWeek): Boolean = dayOfWeek == DayOfWeek.Saturday || dayOfWeek == DayOfWeek.Sunday
protected fun format(str: String) = PatternDateFormat(str, this)
open val formatDateTimeMedium get() = format("MMM d, y h:mm:ss a")
open val formatDateTimeShort get() = format("M/d/yy h:mm a")
open val formatDateFull get() = format("EEEE, MMMM d, y")
open val formatDateLong get() = format("MMMM d, y")
open val formatDateMedium get() = format("MMM d, y")
open val formatDateShort get() = format("M/d/yy")
open val formatTimeMedium get() = format("HH:mm:ss")
open val formatTimeShort get() = format("HH:mm")
companion object {
val english get() = English
var default: KlockLocale
set(value) { KlockLocale_default = value }
get() = KlockLocale_default ?: English
inline fun <R> setTemporarily(locale: KlockLocale, callback: () -> R): R {
val old = default
default = locale
try {
return callback()
} finally {
default = old
}
}
}
open class English : KlockLocale() {
companion object : English()
override val ISO639_1 get() = "en"
override val firstDayOfWeek: DayOfWeek get() = DayOfWeek.Sunday
override val daysOfWeek: List<String> get() = listOf(
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
)
override val months: List<String> get() = listOf(
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
)
override val formatTimeMedium get() = format("h:mm:ss a")
override val formatTimeShort get() = format("h:mm a")
}
}
fun DateTime.format(format: String, locale: KlockLocale): String = DateFormat(format).withLocale(locale).format(this)
fun DateTimeTz.format(format: String, locale: KlockLocale): String = DateFormat(format).withLocale(locale).format(this)

View File

@@ -1,14 +0,0 @@
package korlibs.time
data class KlockLocaleContext(val gender: KlockLocaleGender = KlockLocaleGender.Neuter) {
companion object {
val Default = KlockLocaleContext()
}
}
enum class KlockLocaleGender {
Neuter,
Masculine,
}

View File

@@ -1,37 +0,0 @@
package korlibs.time
/**
* Executes a [callback] and measure the time it takes to complete.
*/
inline fun measureTime(callback: () -> Unit): TimeSpan {
val start = PerformanceCounter.microseconds
callback()
val end = PerformanceCounter.microseconds
return (end - start).microseconds
}
inline fun <T> measureTime(callback: () -> T, handleTime: (TimeSpan) -> Unit): T {
val start = PerformanceCounter.microseconds
val result = callback()
val end = PerformanceCounter.microseconds
val elapsed = (end - start).microseconds
handleTime(elapsed)
return result
}
/**
* Executes the [callback] measuring the time it takes to complete.
* Returns a [TimedResult] with the time and the return value of the callback.
*/
inline fun <T> measureTimeWithResult(callback: () -> T): TimedResult<T> {
val start = PerformanceCounter.microseconds
val result = callback()
val end = PerformanceCounter.microseconds
val elapsed = (end - start).microseconds
return TimedResult(result, elapsed)
}
/**
* Represents a [result] associated to a [time].
*/
data class TimedResult<T>(val result: T, val time: TimeSpan)

View File

@@ -1,131 +0,0 @@
package korlibs.time
import korlibs.time.Month.*
import korlibs.time.internal.*
import kotlin.math.*
/** Represents one of the twelve months of the year. */
enum class Month(
/** 1: [January], 2: [February], 3: [March], 4: [April], 5: [May], 6: [June], 7: [July], 8: [August], 9: [September], 10: [October], 11: [November], 12: [December] */
val index1: Int,
/** Number of days of this month in a common year */
val daysCommon: Int,
/** Number of days of this month in a leap year */
val daysLeap: Int = daysCommon
) : Serializable {
January(1, daysCommon = 31),
February(2, daysCommon = 28, daysLeap = 29),
March(3, daysCommon = 31),
April(4, daysCommon = 30),
May(5, daysCommon = 31),
June(6, daysCommon = 30),
July(7, daysCommon = 31),
August(8, daysCommon = 31),
September(9, daysCommon = 30),
October(10, daysCommon = 31),
November(11, daysCommon = 30),
December(12, daysCommon = 31);
/** 0: [January], 1: [February], 2: [March], 3: [April], 4: [May], 5: [June], 6: [July], 7: [August], 8: [September], 9: [October], 10: [November], 11: [December] */
val index0: Int get() = index1 - 1
/** Number of days in a specific month (28-31) depending whether the year is [leap] or not. */
fun days(leap: Boolean): Int = if (leap) daysLeap else daysCommon
/** Number of days in a specific month (28-31) depending whether the [year] or not. */
fun days(year: Int): Int = days(Year(year).isLeap)
/** Number of days in a specific month (28-31) depending whether the [year] or not. */
fun days(year: Year): Int = days(year.isLeap)
/** Number of days since the start of the [leap] year to reach this month. */
fun daysToStart(leap: Boolean): Int = YEAR_DAYS(leap)[index0]
/** Number of days since the start of the [year] to reach this month. */
fun daysToStart(year: Int): Int = daysToStart(Year(year).isLeap)
/** Number of days since the start of the [year] to reach this month. */
fun daysToStart(year: Year): Int = daysToStart(year.isLeap)
/** Number of days since the start of the [leap] year to reach next month. */
fun daysToEnd(leap: Boolean): Int = YEAR_DAYS(leap)[index1]
/** Number of days since the start of the [year] to reach next month. */
fun daysToEnd(year: Int): Int = daysToEnd(Year(year).isLeap)
/** Number of days since the start of the [year] to reach next month. */
fun daysToEnd(year: Year): Int = daysToEnd(year.isLeap)
/** Previous [Month]. */
val previous: Month get() = this - 1
/** Next [Month]. */
val next: Month get() = this + 1
operator fun plus(delta: Int): Month = Month[index1 + delta]
operator fun minus(delta: Int): Month = Month[index1 - delta]
operator fun minus(other: Month): Int = abs(this.index0 - other.index0)
val localName get() = localName(KlockLocale.default)
fun localName(locale: KlockLocale) = locale.months[index0]
val localShortName get() = localShortName(KlockLocale.default)
fun localShortName(locale: KlockLocale) = locale.monthsShort[index0]
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
/**
* Number of months in a year (12).
*/
const val Count = 12
/** 1: [January], 2: [February], 3: [March], 4: [April], 5: [May], 6: [June], 7: [July], 8: [August], 9: [September], 10: [October], 11: [November], 12: [December] */
operator fun invoke(index1: Int) = adjusted(index1)
/** 1: [January], 2: [February], 3: [March], 4: [April], 5: [May], 6: [June], 7: [July], 8: [August], 9: [September], 10: [October], 11: [November], 12: [December] */
operator fun get(index1: Int) = adjusted(index1)
/**
* Gets the [Month] from a month index where [January]=1 wrapping the index to valid values.
*
* For example 0 and 12=[December], 1 and 13=[January], -1 and 11=[November].
*/
fun adjusted(index1: Int) = BY_INDEX0[(index1 - 1) umod 12]
/**
* Gets the [Month] from a month index where [January]=1 checking that the provided [index1] is valid between 1..12.
*/
fun checked(index1: Int) = BY_INDEX0[index1.also { if (index1 !in 1..12) throw DateException("Month $index1 not in 1..12") } - 1]
/**
* Gets the [Month] of a [dayOfYear] in a [leap] year.
*
* Returns null if the year doesn't contain that [dayOfYear].
*/
fun fromDayOfYear(dayOfYear: Int, leap: Boolean): Month? {
val days = YEAR_DAYS(leap)
val day0 = dayOfYear - 1
val guess = day0 / 32
if (guess in 0..11 && day0 in days[guess] until days[guess + 1]) return Month[guess + 1]
if (guess in 0..10 && day0 in days[guess + 1] until days[guess + 2]) return Month[guess + 2]
return null
}
/**
* Gets the [Month] of a [dayOfYear] in the specified [year].
*
* Returns null if the year doesn't contain that [dayOfYear].
*/
fun fromDayOfYear(dayOfYear: Int, year: Year): Month? = fromDayOfYear(dayOfYear, year.isLeap)
private val BY_INDEX0 = values()
private fun YEAR_DAYS(isLeap: Boolean): IntArray = if (isLeap) YEAR_DAYS_LEAP else YEAR_DAYS_COMMON
private val YEAR_DAYS_LEAP = generateDaysToStart(leap = true)
private val YEAR_DAYS_COMMON = generateDaysToStart(leap = false)
private fun generateDaysToStart(leap: Boolean): IntArray {
var total = 0
return IntArray(13) {
total += if (it == 0) 0 else BY_INDEX0[it - 1].days(leap)
total
}
}
}
}

View File

@@ -1,66 +0,0 @@
package korlibs.time
import korlibs.time.internal.Serializable
import kotlin.jvm.JvmInline
/**
* Creates a [MonthSpan] representing these years.
*/
inline val Int.years get() = MonthSpan(12 * this)
/**
* Creates a [MonthSpan] representing these months.
*/
inline val Int.months get() = MonthSpan(this)
/**
* Represents a number of years and months temporal distance.
*/
@JvmInline
value class MonthSpan(
/** Total months of this [MonthSpan] as integer */
val totalMonths: Int
) : Comparable<MonthSpan>, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
}
operator fun unaryMinus() = MonthSpan(-totalMonths)
operator fun unaryPlus() = MonthSpan(+totalMonths)
operator fun plus(other: TimeSpan) = DateTimeSpan(this, other)
operator fun plus(other: MonthSpan) = MonthSpan(totalMonths + other.totalMonths)
operator fun plus(other: DateTimeSpan) = DateTimeSpan(other.monthSpan + this, other.timeSpan)
operator fun minus(other: TimeSpan) = this + -other
operator fun minus(other: MonthSpan) = this + -other
operator fun minus(other: DateTimeSpan) = this + -other
operator fun times(times: Double) = MonthSpan((totalMonths * times).toInt())
operator fun times(times: Int) = this * times.toDouble()
operator fun times(times: Float) = this * times.toDouble()
operator fun div(times: Double) = MonthSpan((totalMonths / times).toInt())
operator fun div(times: Int) = this / times.toDouble()
operator fun div(times: Float) = this / times.toDouble()
override fun compareTo(other: MonthSpan): Int = this.totalMonths.compareTo(other.totalMonths)
/** Converts this time to String formatting it like "20Y", "20Y 1M", "1M" or "0M". */
override fun toString(): String {
val list = arrayListOf<String>()
if (years != 0) list.add("${years}Y")
if (months != 0 || years == 0) list.add("${months}M")
return list.joinToString(" ")
}
}
/** Total years of this [MonthSpan] as double (might contain decimals) */
val MonthSpan.totalYears: Double get() = totalMonths.toDouble() / 12.0
/** Years part of this [MonthSpan] as integer */
val MonthSpan.years: Int get() = totalMonths / 12
/** Months part of this [MonthSpan] as integer */
val MonthSpan.months: Int get() = totalMonths % 12

View File

@@ -1,28 +0,0 @@
package korlibs.time
import kotlin.jvm.JvmInline
val infiniteTimes get() = NumberOfTimes.INFINITE
inline val Int.times get() = NumberOfTimes(this)
@JvmInline
value class NumberOfTimes(val count: Int) {
companion object {
val ZERO = NumberOfTimes(0)
val ONE = NumberOfTimes(1)
val INFINITE = NumberOfTimes(Int.MIN_VALUE)
}
val isInfinite get() = this == INFINITE
val isFinite get() = !isInfinite
val hasMore get() = this != ZERO
val oneLess get() = if (this == INFINITE) INFINITE else NumberOfTimes(count - 1)
operator fun plus(other: NumberOfTimes) = if (this == INFINITE || other == INFINITE) INFINITE else NumberOfTimes(this.count + other.count)
operator fun minus(other: NumberOfTimes) = when {
this == other -> ZERO
this == INFINITE || other == INFINITE -> INFINITE
else -> NumberOfTimes(this.count - other.count)
}
operator fun times(other: Int) = if (this == INFINITE) INFINITE else NumberOfTimes(this.count * other)
operator fun div(other: Int) = if (this == INFINITE) INFINITE else NumberOfTimes(this.count / other)
override fun toString(): String = if (this == INFINITE) "$count times" else "Infinite times"
}

View File

@@ -1,318 +0,0 @@
package korlibs.time
import korlibs.time.internal.*
import korlibs.time.internal.MicroStrReader
import korlibs.time.internal.increment
import korlibs.time.internal.padded
import korlibs.time.internal.readTimeZoneOffset
import korlibs.time.internal.substr
import kotlin.jvm.JvmOverloads
import kotlin.math.absoluteValue
import kotlin.math.log10
import kotlin.math.pow
data class PatternDateFormat @JvmOverloads constructor(
val format: String,
val locale: KlockLocale? = null,
val tzNames: TimezoneNames = TimezoneNames.DEFAULT,
val options: Options = Options.DEFAULT
) : DateFormat, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
}
val realLocale get() = locale ?: KlockLocale.default
data class Options(val optionalSupport: Boolean = false) : Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
val DEFAULT = Options(optionalSupport = false)
val WITH_OPTIONAL = Options(optionalSupport = true)
}
}
fun withLocale(locale: KlockLocale?) = this.copy(locale = locale)
fun withTimezoneNames(tzNames: TimezoneNames) = this.copy(tzNames = this.tzNames + tzNames)
fun withOptions(options: Options) = this.copy(options = options)
fun withOptional() = this.copy(options = options.copy(optionalSupport = true))
fun withNonOptional() = this.copy(options = options.copy(optionalSupport = false))
private val openOffsets = LinkedHashMap<Int, Int>()
private val closeOffsets = LinkedHashMap<Int, Int>()
internal val chunks = arrayListOf<String>().also { chunks ->
val s = MicroStrReader(format)
while (s.hasMore) {
if (s.peekChar() == '\'') {
val escapedChunk = s.readChunk {
s.tryRead('\'')
while (s.hasMore && s.readChar() != '\'') Unit
}
chunks.add(escapedChunk)
continue
}
if (options.optionalSupport) {
val offset = chunks.size
if (s.tryRead('[')) {
openOffsets.increment(offset)
continue
}
if (s.tryRead(']')) {
closeOffsets.increment(offset - 1)
continue
}
}
chunks.add(s.tryReadOrNull("do") ?: s.readRepeatedChar())
}
}.toList()
internal val regexChunks: List<String> = chunks.map {
when (it) {
"E", "EE", "EEE", "EEEE", "EEEEE", "EEEEEE" -> """(\w+)"""
"z", "zzz" -> """([\w\s\-\+:]+)"""
"do" -> """(\d{1,2}\w+)"""
"d" -> """(\d{1,2})"""
"dd" -> """(\d{2})"""
"M" -> """(\d{1,5})"""
"MM" -> """(\d{2})"""
"MMM", "MMMM", "MMMMM" -> """(\w+)"""
"y" -> """(\d{1,5})"""
"yy" -> """(\d{2})"""
"yyy" -> """(\d{3})"""
"yyyy" -> """(\d{4})"""
"YYYY" -> """(\d{4})"""
"H", "k" -> """(\d{1,2})"""
"HH", "kk" -> """(\d{2})"""
"h", "K" -> """(\d{1,2})"""
"hh", "KK" -> """(\d{2})"""
"m" -> """(\d{1,2})"""
"mm" -> """(\d{2})"""
"s" -> """(\d{1,2})"""
"ss" -> """(\d{2})"""
"S" -> """(\d{1,9})"""
"SS" -> """(\d{2})"""
"SSS" -> """(\d{3})"""
"SSSS" -> """(\d{4})"""
"SSSSS" -> """(\d{5})"""
"SSSSSS" -> """(\d{6})"""
"SSSSSSS" -> """(\d{7})"""
"SSSSSSSS" -> """(\d{8})"""
"SSSSSSSSS" -> """(\d{9})"""
"X", "XX", "XXX", "x", "xx", "xxx", "Z" -> """([\w:\+\-]+)"""
"a" -> """(\w+)"""
" " -> """(\s+)"""
else -> when {
it.startsWith('\'') -> "(" + Regex.escape(it.substr(1, it.length - 2)) + ")"
else -> "(" + Regex.escape(it) + ")"
}
}
}
/**
* @return the regular expression string used for matching this format, able to be composed into another regex
*/
fun matchingRegexString(): String = regexChunks.mapIndexed { index, it ->
if (options.optionalSupport) {
val opens = openOffsets.getOrElse(index) { 0 }
val closes = closeOffsets.getOrElse(index) { 0 }
buildString {
repeat(opens) { append("(?:") }
append(it)
repeat(closes) { append(")?") }
}
} else {
it
}
}.joinToString("")
//val escapedFormat = Regex.escape(format)
internal val rx2: Regex = Regex("^" + matchingRegexString() + "$")
// EEE, dd MMM yyyy HH:mm:ss z -- > Sun, 06 Nov 1994 08:49:37 GMT
// YYYY-MM-dd HH:mm:ss
override fun format(dd: DateTimeTz): String {
val utc = dd.local
var out = ""
for (name in chunks) {
val nlen = name.length
out += when (name) {
"E", "EE", "EEE" -> DayOfWeek[utc.dayOfWeek.index0].localShortName(realLocale)
"EEEE", "EEEEE", "EEEEEE" -> DayOfWeek[utc.dayOfWeek.index0].localName(realLocale)
"z", "zzz" -> dd.offset.timeZone
"d", "dd" -> utc.dayOfMonth.padded(nlen)
"do" -> realLocale.getOrdinalByDay(utc.dayOfMonth)
"M", "MM" -> utc.month1.padded(nlen)
"MMM" -> Month[utc.month1].localName(realLocale).substr(0, 3)
"MMMM" -> Month[utc.month1].localName(realLocale)
"MMMMM" -> Month[utc.month1].localName(realLocale).substr(0, 1)
"y" -> utc.yearInt
"yy" -> (utc.yearInt % 100).padded(2)
"yyy" -> (utc.yearInt % 1000).padded(3)
"yyyy" -> utc.yearInt.padded(4)
"YYYY" -> utc.yearInt.padded(4)
"H", "HH" -> mconvertRangeZero(utc.hours, 24).padded(nlen)
"k", "kk" -> mconvertRangeNonZero(utc.hours, 24).padded(nlen)
"h", "hh" -> mconvertRangeNonZero(utc.hours, 12).padded(nlen)
"K", "KK" -> mconvertRangeZero(utc.hours, 12).padded(nlen)
"m", "mm" -> utc.minutes.padded(nlen)
"s", "ss" -> utc.seconds.padded(nlen)
"S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS", "SSSSSSSSS" -> {
val milli = utc.milliseconds
val base10length = log10(utc.milliseconds.toDouble()).toInt() + 1
if (base10length > name.length) {
(milli.toDouble() * 10.0.pow(-1 * (base10length - name.length))).toInt()
} else {
"${milli.padded(3)}000000".substr(0, name.length)
}
}
"X", "XX", "XXX", "x", "xx", "xxx" -> {
when {
name.startsWith("X") && dd.offset.totalMinutesInt == 0 -> "Z"
else -> {
val p = if (dd.offset.totalMinutesInt >= 0) "+" else "-"
val hours = (dd.offset.totalMinutesInt / 60).absoluteValue
val minutes = (dd.offset.totalMinutesInt % 60).absoluteValue
when (name) {
"X", "x" -> "$p${hours.padded(2)}"
"XX", "xx" -> "$p${hours.padded(2)}${minutes.padded(2)}"
"XXX", "xxx" -> "$p${hours.padded(2)}:${minutes.padded(2)}"
else -> name
}
}
}
}
"a" -> realLocale.h12Marker[if (utc.hours < 12) 0 else 1]
else -> when {
name.startsWith('\'') -> name.substring(1, name.length - 1)
else -> name
}
}
}
return out
}
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): DateTimeTz? {
var millisecond = 0
var second = 0
var minute = 0
var hour = 0
var day = 1
var month = 1
var fullYear = 1970
var offset: TimeSpan? = null
var isPm = false
var is12HourFormat = false
val result = rx2.find(str) ?: return null //println("Parser error: Not match, $str, $rx2");
for ((name, value) in chunks.zip(result.groupValues.drop(1))) {
if (value.isEmpty()) continue
when (name) {
"E", "EE", "EEE", "EEEE", "EEEEE", "EEEEEE" -> Unit // day of week (Sun | Sunday)
"z", "zzz" -> { // timezone (GMT)
offset = MicroStrReader(value).readTimeZoneOffset(tzNames)
}
"d", "dd" -> day = value.toInt()
"do" -> day = realLocale.getDayByOrdinal(value)
"M", "MM" -> month = value.toInt()
"MMM" -> month = realLocale.monthsShort.indexOf(value) + 1
"y", "yyyy", "YYYY" -> fullYear = value.toInt()
"yy" -> if (doThrow) throw RuntimeException("Not guessing years from two digits.") else return null
"yyy" -> fullYear = value.toInt() + if (value.toInt() < 800) 2000 else 1000 // guessing year...
"H", "HH", "k", "kk" -> hour = value.toInt()
"h", "hh", "K", "KK" -> {
hour = value.toInt()
is12HourFormat = true
}
"m", "mm" -> minute = value.toInt()
"s", "ss" -> second = value.toInt()
"S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS", "SSSSSSSSS" -> {
val base10length = log10(value.toDouble()).toInt() + 1
millisecond = if (base10length > 3) {
// only precision to millisecond supported, ignore the rest. ex: 9999999 => 999"
(value.toDouble() * 10.0.pow(-1 * (base10length - 3))).toInt()
} else {
value.toInt()
}
}
"X", "XX", "XXX", "x", "xx", "xxx" -> {
when {
name.startsWith("X") && value.first() == 'Z' -> offset = 0.hours
name.startsWith("x") && value.first() == 'Z' -> {
if (doThrow) throw RuntimeException("Zulu Time Zone is only accepted with X-XXX formats.") else return null
}
value.first() != 'Z' -> {
val valueUnsigned = value.replace(":", "").removePrefix("-").removePrefix("+")
val hours = when (name.length) {
1 -> valueUnsigned.toInt()
else -> valueUnsigned.take(2).toInt()
}
val minutes = when (name.length) {
1 -> 0
else -> valueUnsigned.drop(2).toInt()
}
offset = hours.hours + minutes.minutes
if (value.first() == '-') {
offset = -offset
}
}
}
}
"MMMM" -> month = realLocale.months.indexOf(value) + 1
"MMMMM" -> if (doThrow) throw RuntimeException("Not possible to get the month from one letter.") else return null
"a" -> isPm = value.equals("pm", ignoreCase = true)
else -> {
// ...
}
}
}
//return DateTime.createClamped(fullYear, month, day, hour, minute, second)
if (is12HourFormat) {
if (isPm) {
if (hour != 12) {
hour += 12
}
} else {
if (hour == 12) {
hour = 0
}
}
}
if (!doAdjust) {
if (month !in 1..12) if (doThrow) error("Invalid month $month") else return null
if (day !in 1..32) if (doThrow) error("Invalid day $day") else return null
if (hour !in 0..24) if (doThrow) error("Invalid hour $hour") else return null
if (minute !in 0..59) if (doThrow) error("Invalid minute $minute") else return null
if (second !in 0..59) if (doThrow) error("Invalid second $second") else return null
if (millisecond !in 0..999) if (doThrow) error("Invalid millisecond $millisecond") else return null
}
val dateTime = DateTime.createAdjusted(fullYear, month, day, hour umod 24, minute, second, millisecond)
return dateTime.toOffsetUnadjusted(offset ?: 0.hours)
}
override fun toString(): String = format
}
private fun mconvertRangeZero(value: Int, size: Int): Int {
return (value umod size)
}
private fun mconvertRangeNonZero(value: Int, size: Int): Int {
val res = (value umod size)
return if (res == 0) size else res
}
private fun MicroStrReader.readRepeatedChar(): String {
return readChunk {
val c = readChar()
while (hasMore && (tryRead(c))) Unit
}
}

View File

@@ -1,183 +0,0 @@
package korlibs.time
import korlibs.time.internal.*
import korlibs.time.internal.MicroStrReader
import korlibs.time.internal.increment
import korlibs.time.internal.padded
import korlibs.time.internal.substr
import kotlin.math.log10
import kotlin.math.pow
data class PatternTimeFormat(
val format: String,
val options: Options = Options.DEFAULT
) : TimeFormat, Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
}
data class Options(val optionalSupport: Boolean = false) : Serializable {
companion object {
@Suppress("MayBeConstant", "unused")
private const val serialVersionUID = 1L
val DEFAULT = Options(optionalSupport = false)
val WITH_OPTIONAL = Options(optionalSupport = true)
}
}
fun withOptions(options: Options) = this.copy(options = options)
fun withOptional() = this.copy(options = options.copy(optionalSupport = true))
fun withNonOptional() = this.copy(options = options.copy(optionalSupport = false))
private val openOffsets = LinkedHashMap<Int, Int>()
private val closeOffsets = LinkedHashMap<Int, Int>()
internal val chunks = arrayListOf<String>().also { chunks ->
val s = MicroStrReader(format)
while (s.hasMore) {
if (s.peekChar() == '\'') {
val escapedChunk = s.readChunk {
s.tryRead('\'')
while (s.hasMore && s.readChar() != '\'') Unit
}
chunks.add(escapedChunk)
continue
}
if (options.optionalSupport) {
val offset = chunks.size
if (s.tryRead('[')) {
openOffsets.increment(offset)
continue
}
if (s.tryRead(']')) {
closeOffsets.increment(offset - 1)
continue
}
}
val chunk = s.readChunk {
val c = s.readChar()
while (s.hasMore && s.tryRead(c)) Unit
}
chunks.add(chunk)
}
}.toList()
private val regexChunks = chunks.map {
when (it) {
"H", "k" -> """(\d{1,})"""
"HH", "kk" -> """(\d{2,})"""
"h", "K" -> """(\d{1,2})"""
"hh", "KK" -> """(\d{2})"""
"m" -> """(\d{1,2})"""
"mm" -> """(\d{2})"""
"s" -> """(\d{1,2})"""
"ss" -> """(\d{2})"""
"S" -> """(\d{1,6})"""
"SS" -> """(\d{2})"""
"SSS" -> """(\d{3})"""
"SSSS" -> """(\d{4})"""
"SSSSS" -> """(\d{5})"""
"SSSSSS" -> """(\d{6})"""
"SSSSSSS" -> """(\d{7})"""
"SSSSSSSS" -> """(\d{8})"""
"a" -> """(\w+)"""
" " -> """(\s+)"""
else -> when {
it.startsWith('\'') -> "(" + Regex.escapeReplacement(it.substr(1, it.length - 2)) + ")"
else -> "(" + Regex.escapeReplacement(it) + ")"
}
}
}
private val rx2: Regex = Regex("^" + regexChunks.mapIndexed { index, it ->
if (options.optionalSupport) {
val opens = openOffsets.getOrElse(index) { 0 }
val closes = closeOffsets.getOrElse(index) { 0 }
buildString {
repeat(opens) { append("(?:") }
append(it)
repeat(closes) { append(")?") }
}
} else {
it
}
}.joinToString("") + "$")
private fun clampZero(value: Int, size: Int) = (value umod size)
private fun clampNonZero(value: Int, size: Int) = (value umod size).let { if (it == 0) size else it }
override fun format(dd: TimeSpan): String {
val time = Time(dd)
var out = ""
for (name in chunks) {
val nlen = name.length
out += when (name) {
"H", "HH" -> time.hour.padded(nlen)
"k", "kk" -> time.hour.padded(nlen)
"h", "hh" -> clampNonZero(time.hour, 12).padded(nlen)
"K", "KK" -> clampZero(time.hour, 12).padded(nlen)
"m", "mm" -> time.minute.padded(nlen)
"s", "ss" -> time.second.padded(nlen)
"S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS", "SSSSSSS", "SSSSSSSS" -> {
val milli = time.millisecond
val numberLength = log10(time.millisecond.toDouble()).toInt() + 1
if (numberLength > name.length) {
(milli.toDouble() / 10.0.pow(numberLength - name.length)).toInt()
} else {
"${milli.padded(3)}00000".substr(0, name.length)
}
}
"a" -> if (time.hour < 12) "am" else if (time.hour < 24) "pm" else ""
else -> if (name.startsWith('\'')) name.substring(1, name.length - 1) else name
}
}
return out
}
override fun tryParse(str: String, doThrow: Boolean, doAdjust: Boolean): TimeSpan? {
var millisecond = 0
var second = 0
var minute = 0
var hour = 0
var isPm = false
var is12HourFormat = false
val result = rx2.find(str) ?: return null //println("Parser error: Not match, $str, $rx2");
for ((name, value) in chunks.zip(result.groupValues.drop(1))) {
if (value.isEmpty()) continue
when (name) {
"H", "HH", "k", "kk" -> hour = value.toInt()
"h", "hh", "K", "KK" -> {
hour = value.toInt() umod 24
is12HourFormat = true
}
"m", "mm" -> minute = value.toInt()
"s", "ss" -> second = value.toInt()
"S", "SS", "SSS", "SSSS", "SSSSS", "SSSSSS" -> {
val numberLength = log10(value.toDouble()).toInt() + 1
millisecond = if (numberLength > 3) {
// only precision to millisecond supported, ignore the rest: 9999999 => 999
(value.toDouble() * 10.0.pow(-1 * (numberLength - 3))).toInt()
} else {
value.toInt()
}
}
"a" -> isPm = value == "pm"
else -> {
// ...
}
}
}
if (is12HourFormat && isPm) {
hour += 12
}
return hour.hours + minute.minutes + second.seconds + millisecond.milliseconds
}
override fun toString(): String = format
}

View File

@@ -1,29 +0,0 @@
package korlibs.time
import korlibs.time.internal.*
import kotlin.time.*
/**
* Class for measuring relative times with as much precision as possible.
*/
object PerformanceCounter {
/**
* Returns a performance counter measure in nanoseconds.
*/
val nanoseconds: Double get() = KlockInternal.now.nanoseconds
/**
* Returns a performance counter measure in microseconds.
*/
val microseconds: Double get() = KlockInternal.now.microseconds
/**
* Returns a performance counter measure in milliseconds.
*/
val milliseconds: Double get() = KlockInternal.now.milliseconds
/**
* Returns a performance counter as a [Duration].
*/
val reference: Duration get() = KlockInternal.now
}

View File

@@ -1,4 +0,0 @@
package korlibs.time
public fun TimeSpan.convertRange(srcMin: TimeSpan, srcMax: TimeSpan, dstMin: TimeSpan, dstMax: TimeSpan): TimeSpan = (dstMin + (dstMax - dstMin) * ((this - srcMin) / (srcMax - srcMin)))
public fun DateTime.convertRange(srcMin: DateTime, srcMax: DateTime, dstMin: DateTime, dstMax: DateTime): DateTime = (dstMin + (dstMax - dstMin) * ((this - srcMin) / (srcMax - srcMin)))

View File

@@ -1,7 +0,0 @@
package korlibs.time
import korlibs.time.internal.*
/** Sleeps the thread during the specified time. Spinlocks on JS */
@ExperimentalStdlibApi
fun blockingSleep(time: TimeSpan) = KlockInternal.sleep(time)

Some files were not shown because too many files have changed in this diff Show More