From 637d44d8a2d32c6b38e6c0a97f6594f58c310ddf Mon Sep 17 00:00:00 2001 From: Jan Seeger Date: Sat, 22 Jun 2024 14:21:13 +0200 Subject: [PATCH 1/5] Add kotlinx-datetime module (#198) --- gradle/libs.versions.toml | 3 ++ kotlinx-uuid-datetime/README.md | 21 +++++++++++ kotlinx-uuid-datetime/build.gradle.kts | 36 +++++++++++++++++++ .../main/kotlin/kotlinx/uuid/datetime/Dsl.kt | 20 +++++++++++ .../kotlinx/uuid/datetime/InstantTest.kt | 27 ++++++++++++++ settings.gradle.kts | 1 + 6 files changed, 108 insertions(+) create mode 100644 kotlinx-uuid-datetime/README.md create mode 100644 kotlinx-uuid-datetime/build.gradle.kts create mode 100644 kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt create mode 100644 kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1413167..8873056 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,6 +2,7 @@ kotlin = "2.0.0" serialization = "1.7.0" exposed = "0.51.1" +datetime = "0.6.0" [libraries] serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" } @@ -16,6 +17,8 @@ slf4j = { module = "org.slf4j:slf4j-simple", version = "2.0.13" } sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version = "2.0.2" } +datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "datetime" } + [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/kotlinx-uuid-datetime/README.md b/kotlinx-uuid-datetime/README.md new file mode 100644 index 0000000..764663a --- /dev/null +++ b/kotlinx-uuid-datetime/README.md @@ -0,0 +1,21 @@ +# Module kotlinx-uuid-datetime + +Provides support for UUIDv7 using kotlinx-datetime. + +```kotlin +dependencies { + implementation("app.softwork:kotlinx-uuid-datetime:LATEST") +} +``` + +UUIDv7 with the current timestamp and default SecureRandom can be created using: + +```kotlin +val uuid = UUIDv7() +``` + +When processing existing UUIDv7s, the timestamp bits can be interpreted as an Instant with millisecond precision using: + +```kotlin +UUIDv7().instant +``` diff --git a/kotlinx-uuid-datetime/build.gradle.kts b/kotlinx-uuid-datetime/build.gradle.kts new file mode 100644 index 0000000..4cdf491 --- /dev/null +++ b/kotlinx-uuid-datetime/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright 2020-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2021 hfhbd and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +plugins { + id("kotlinJvm") + id("publish") + id("dokkaLicensee") + id("kover") +} + +kotlin.jvmToolchain(11) + +dependencies { + api(projects.kotlinxUuidCore) + api(libs.datetime) + + testImplementation(kotlin("test-junit")) + testRuntimeOnly(libs.slf4j) +} + +licensee { + allow("MIT") +} + +publishing { + publications.register("maven") { + from(components["java"]) + } +} + +java { + withJavadocJar() + withSourcesJar() +} diff --git a/kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt b/kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt new file mode 100644 index 0000000..cf5175a --- /dev/null +++ b/kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt @@ -0,0 +1,20 @@ +package kotlinx.uuid.datetime + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.uuid.SecureRandom +import kotlinx.uuid.UUID +import kotlinx.uuid.UUIDExperimentalAPI +import kotlinx.uuid.UUIDv7 +import kotlinx.uuid.unixTimeStamp +import kotlin.random.Random + +@UUIDExperimentalAPI +public fun UUIDv7(random: Random = SecureRandom): UUID = + UUIDv7(timeStamp = Clock.System.now().toEpochMilliseconds(), random = random) + +/** + * The UUIDv7 48 bit big-endian unsigned number of Unix epoch timestamp in milliseconds + */ +@UUIDExperimentalAPI +public val UUID.instant: Instant get() = Instant.fromEpochMilliseconds(unixTimeStamp) diff --git a/kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt b/kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt new file mode 100644 index 0000000..19f5042 --- /dev/null +++ b/kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt @@ -0,0 +1,27 @@ +package kotlinx.uuid.datetime + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.uuid.UUIDExperimentalAPI +import kotlinx.uuid.UUIDv7 +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(UUIDExperimentalAPI::class) +class InstantTest { + + @Test + fun testConversionInstant() { + val timestamp = Clock.System.now() + val expected = timestamp.clampToMillisecondPrecision() + + val uuid = UUIDv7(timeStamp = timestamp.toEpochMilliseconds()) + val result = uuid.instant + + assertEquals(expected, result) + } + + private fun Instant.clampToMillisecondPrecision(): Instant { + return Instant.fromEpochMilliseconds(this.toEpochMilliseconds()) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 15fa4e4..362dfb0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,3 +40,4 @@ include(":kotlinx-uuid-core") include(":kotlinx-uuid-exposed") include(":kotlinx-uuid-sqldelight") +include(":kotlinx-uuid-datetime") From 5621f4a19ce2382f1ba4b7459a724464157b70ae Mon Sep 17 00:00:00 2001 From: hfhbd <22521688+hfhbd@users.noreply.github.com> Date: Fri, 28 Jun 2024 14:51:30 +0200 Subject: [PATCH 2/5] Migrate to MPP --- detekt-baseline.xml | 1 + kotlin-js-store/yarn.lock | 5 +++ kotlinx-uuid-datetime/README.md | 2 +- kotlinx-uuid-datetime/build.gradle.kts | 35 +++++++------------ .../kotlin/kotlinx/uuid/datetime/Dsl.kt | 4 +-- .../kotlinx/uuid/datetime/InstantTest.kt | 17 +++++++++ .../kotlinx/uuid/datetime/InstantTest.kt | 27 -------------- 7 files changed, 38 insertions(+), 53 deletions(-) rename kotlinx-uuid-datetime/src/{main => commonMain}/kotlin/kotlinx/uuid/datetime/Dsl.kt (74%) create mode 100644 kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt delete mode 100644 kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 1a34df6..a22eb8a 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -2,6 +2,7 @@ + FunctionNaming:Dsl.kt$@UUIDExperimentalAPI public fun UUIDv7(timeStamp: Instant = Clock.System.now(), random: Random = SecureRandom): UUID FunctionNaming:UUID7.kt$@UUIDExperimentalAPI public fun UUIDv7(timeStamp: Long, random: Random = SecureRandom): UUID MagicNumber:Encoding.kt$0xff MagicNumber:Encoding.kt$56 diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index e028908..f4858df 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -70,6 +70,11 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" diff --git a/kotlinx-uuid-datetime/README.md b/kotlinx-uuid-datetime/README.md index 764663a..6100275 100644 --- a/kotlinx-uuid-datetime/README.md +++ b/kotlinx-uuid-datetime/README.md @@ -8,7 +8,7 @@ dependencies { } ``` -UUIDv7 with the current timestamp and default SecureRandom can be created using: +UUIDv7 with the current timestamp using `Clock.System` and default SecureRandom can be created using: ```kotlin val uuid = UUIDv7() diff --git a/kotlinx-uuid-datetime/build.gradle.kts b/kotlinx-uuid-datetime/build.gradle.kts index 4cdf491..234978f 100644 --- a/kotlinx-uuid-datetime/build.gradle.kts +++ b/kotlinx-uuid-datetime/build.gradle.kts @@ -4,33 +4,22 @@ */ plugins { - id("kotlinJvm") + id("kotlinMPP") id("publish") id("dokkaLicensee") id("kover") } -kotlin.jvmToolchain(11) - -dependencies { - api(projects.kotlinxUuidCore) - api(libs.datetime) - - testImplementation(kotlin("test-junit")) - testRuntimeOnly(libs.slf4j) -} - -licensee { - allow("MIT") -} - -publishing { - publications.register("maven") { - from(components["java"]) +kotlin.sourceSets { + commonMain { + dependencies { + api(projects.kotlinxUuidCore) + api(libs.datetime) + } + } + commonTest { + dependencies { + implementation(kotlin("test")) + } } -} - -java { - withJavadocJar() - withSourcesJar() } diff --git a/kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt b/kotlinx-uuid-datetime/src/commonMain/kotlin/kotlinx/uuid/datetime/Dsl.kt similarity index 74% rename from kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt rename to kotlinx-uuid-datetime/src/commonMain/kotlin/kotlinx/uuid/datetime/Dsl.kt index cf5175a..572192b 100644 --- a/kotlinx-uuid-datetime/src/main/kotlin/kotlinx/uuid/datetime/Dsl.kt +++ b/kotlinx-uuid-datetime/src/commonMain/kotlin/kotlinx/uuid/datetime/Dsl.kt @@ -10,8 +10,8 @@ import kotlinx.uuid.unixTimeStamp import kotlin.random.Random @UUIDExperimentalAPI -public fun UUIDv7(random: Random = SecureRandom): UUID = - UUIDv7(timeStamp = Clock.System.now().toEpochMilliseconds(), random = random) +public fun UUIDv7(timeStamp: Instant = Clock.System.now(), random: Random = SecureRandom): UUID = + UUIDv7(timeStamp = timeStamp.toEpochMilliseconds(), random = random) /** * The UUIDv7 48 bit big-endian unsigned number of Unix epoch timestamp in milliseconds diff --git a/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt b/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt new file mode 100644 index 0000000..c3740f8 --- /dev/null +++ b/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt @@ -0,0 +1,17 @@ +package kotlinx.uuid.datetime + +import kotlinx.datetime.Instant +import kotlinx.uuid.UUIDExperimentalAPI +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(UUIDExperimentalAPI::class) +class InstantTest { + + @Test + fun testConversionInstant() { + val timestamp = Instant.parse("2020-02-20T10:21:42Z") + val uuid = UUIDv7(timeStamp = timestamp) + assertEquals(timestamp, uuid.instant) + } +} diff --git a/kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt b/kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt deleted file mode 100644 index 19f5042..0000000 --- a/kotlinx-uuid-datetime/src/test/kotlin/kotlinx/uuid/datetime/InstantTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package kotlinx.uuid.datetime - -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import kotlinx.uuid.UUIDExperimentalAPI -import kotlinx.uuid.UUIDv7 -import kotlin.test.Test -import kotlin.test.assertEquals - -@OptIn(UUIDExperimentalAPI::class) -class InstantTest { - - @Test - fun testConversionInstant() { - val timestamp = Clock.System.now() - val expected = timestamp.clampToMillisecondPrecision() - - val uuid = UUIDv7(timeStamp = timestamp.toEpochMilliseconds()) - val result = uuid.instant - - assertEquals(expected, result) - } - - private fun Instant.clampToMillisecondPrecision(): Instant { - return Instant.fromEpochMilliseconds(this.toEpochMilliseconds()) - } -} \ No newline at end of file From ab367c3766f09c29968425bb30cfafa8377d091a Mon Sep 17 00:00:00 2001 From: hfhbd <22521688+hfhbd@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:11:00 +0200 Subject: [PATCH 3/5] Add api --- kotlinx-uuid-datetime/api/kotlinx-uuid-datetime.api | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 kotlinx-uuid-datetime/api/kotlinx-uuid-datetime.api diff --git a/kotlinx-uuid-datetime/api/kotlinx-uuid-datetime.api b/kotlinx-uuid-datetime/api/kotlinx-uuid-datetime.api new file mode 100644 index 0000000..ddfcd26 --- /dev/null +++ b/kotlinx-uuid-datetime/api/kotlinx-uuid-datetime.api @@ -0,0 +1,6 @@ +public final class kotlinx/uuid/datetime/DslKt { + public static final fun UUIDv7 (Lkotlinx/datetime/Instant;Lkotlin/random/Random;)Lkotlinx/uuid/UUID; + public static synthetic fun UUIDv7$default (Lkotlinx/datetime/Instant;Lkotlin/random/Random;ILjava/lang/Object;)Lkotlinx/uuid/UUID; + public static final fun getInstant (Lkotlinx/uuid/UUID;)Lkotlinx/datetime/Instant; +} + From af4349186591008220c7f8915a42d503f3bb2417 Mon Sep 17 00:00:00 2001 From: hfhbd <22521688+hfhbd@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:56:35 +0200 Subject: [PATCH 4/5] Fix UUIDv7 --- detekt-baseline.xml | 10 ++++------ .../src/commonMain/kotlin/kotlinx/uuid/UUID7.kt | 14 +++++++------- .../commonTest/kotlin/kotlinx/uuid/UUIDv7Test.kt | 3 +-- .../kotlin/kotlinx/uuid/datetime/InstantTest.kt | 5 ++++- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 0c385c6..929a06f 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -86,18 +86,16 @@ MagicNumber:UUID.kt$UUID.Version.NAME_BASED_MD5$3 MagicNumber:UUID.kt$UUID.Version.NAME_BASED_SHA1$5 MagicNumber:UUID.kt$UUID.Version.RANDOM_BASED$4 - MagicNumber:UUID7.kt$0x38 - MagicNumber:UUID7.kt$0x3fffffffffffffffL - MagicNumber:UUID7.kt$0x7000L - MagicNumber:UUID7.kt$0x80L - MagicNumber:UUID7.kt$12 MagicNumber:UUID7.kt$16 + MagicNumber:UUID7.kt$28672 + MagicNumber:UUID7.kt$4095 + MagicNumber:UUID7.kt$62 MatchingDeclarationName:CommonParcelable.apple.kt$CommonParcelable MatchingDeclarationName:CommonParcelable.js.kt$CommonParcelable MatchingDeclarationName:CommonParcelable.jvm.kt$CommonParcelable MatchingDeclarationName:CommonParcelable.linux.kt$CommonParcelable MatchingDeclarationName:CommonParcelable.ming.kt$CommonParcelable TooManyFunctions:SHA1.kt$SHA1 - VariableNaming:UUID7.kt$val rand_a = random.nextBits(12).toLong() + VariableNaming:UUID7.kt$val rand_a = helper.timeStamp and 4095 diff --git a/kotlinx-uuid-core/src/commonMain/kotlin/kotlinx/uuid/UUID7.kt b/kotlinx-uuid-core/src/commonMain/kotlin/kotlinx/uuid/UUID7.kt index be36623..09f27e1 100644 --- a/kotlinx-uuid-core/src/commonMain/kotlin/kotlinx/uuid/UUID7.kt +++ b/kotlinx-uuid-core/src/commonMain/kotlin/kotlinx/uuid/UUID7.kt @@ -16,14 +16,14 @@ public fun UUIDv7(timeStamp: Long, random: Random = SecureRandom): UUID { require(timeStamp <= UNIX_48_TIMESTAMP) { "timeStamp $timeStamp must be <= 48 bits, was $timeStamp." } + val helper = random.nextUUID() val leftTimeStamp = timeStamp shl 16 - val rand_a = random.nextBits(12).toLong() - val timeStampAndVersionRaw = (leftTimeStamp or rand_a) and -0xf001L or 0x7000L - - // set variant to 4 or 5 - // we keep the lower variant bit random as it is defined as "don't care" - val clockSequenceVariantAndNodeRaw: Long = random.nextLong() and - 0x3fffffffffffffffL or (0x80L shl 0x38) + // set version to 0b0111 + val leftTimeStampAndVersion = leftTimeStamp or 28672 + val rand_a = helper.timeStamp and 4095 + val timeStampAndVersionRaw = leftTimeStampAndVersion or rand_a + // set variant to 0b10 + val clockSequenceVariantAndNodeRaw = (2L shl 62) or (helper.clockSequenceVariantAndNodeRaw ushr 2) return create(timeStampAndVersionRaw, clockSequenceVariantAndNodeRaw) } diff --git a/kotlinx-uuid-core/src/commonTest/kotlin/kotlinx/uuid/UUIDv7Test.kt b/kotlinx-uuid-core/src/commonTest/kotlin/kotlinx/uuid/UUIDv7Test.kt index c6fb52b..ac3a68b 100644 --- a/kotlinx-uuid-core/src/commonTest/kotlin/kotlinx/uuid/UUIDv7Test.kt +++ b/kotlinx-uuid-core/src/commonTest/kotlin/kotlinx/uuid/UUIDv7Test.kt @@ -13,8 +13,7 @@ class UUIDv7Test { assertEquals(1645557742000, one.unixTimeStamp) assertEquals(1645557742000, two.unixTimeStamp) - assertEquals("017f22e2-79b0-735c-9e4a-5c16cd08736d", one.toString()) + assertEquals("017f22e2-79b0-7493-a342-1cdb22b5d84b", one.toString()) assertEquals(7, one.versionNumber) - assertEquals(4, one.variant) } } diff --git a/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt b/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt index c3740f8..ba2e95d 100644 --- a/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt +++ b/kotlinx-uuid-datetime/src/commonTest/kotlin/kotlinx/uuid/datetime/InstantTest.kt @@ -2,6 +2,7 @@ package kotlinx.uuid.datetime import kotlinx.datetime.Instant import kotlinx.uuid.UUIDExperimentalAPI +import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -11,7 +12,9 @@ class InstantTest { @Test fun testConversionInstant() { val timestamp = Instant.parse("2020-02-20T10:21:42Z") - val uuid = UUIDv7(timeStamp = timestamp) + val uuid = UUIDv7(timeStamp = timestamp, random = Random(4242)) assertEquals(timestamp, uuid.instant) + assertEquals("0170621e-0ef0-7493-a342-1cdb22b5d84b", uuid.toString()) + assertEquals(7, uuid.versionNumber) } } From f95c3bc9572bde211de5063fd31b6ce0d1e83528 Mon Sep 17 00:00:00 2001 From: hfhbd <22521688+hfhbd@users.noreply.github.com> Date: Sat, 29 Jun 2024 01:40:53 +0200 Subject: [PATCH 5/5] Disable kover --- kotlinx-uuid-datetime/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/kotlinx-uuid-datetime/build.gradle.kts b/kotlinx-uuid-datetime/build.gradle.kts index 234978f..b30df21 100644 --- a/kotlinx-uuid-datetime/build.gradle.kts +++ b/kotlinx-uuid-datetime/build.gradle.kts @@ -7,7 +7,6 @@ plugins { id("kotlinMPP") id("publish") id("dokkaLicensee") - id("kover") } kotlin.sourceSets {