From 188a182f9fb803eb5c9eb0c5bc6e62cfda6f7d46 Mon Sep 17 00:00:00 2001 From: Lamberto Basti Date: Fri, 2 Aug 2024 12:31:02 +0200 Subject: [PATCH 1/2] Added support for @JsExport for Instant class for JS target. The function Instant.toEpochMillisecondsDouble() has been added to allow usage from JS code. --- core/commonJs/src/Instant.kt | 37 +++++++++++++++++++++++++++++++ core/js/src/Instant.js.kt | 10 +++++++++ core/wasmJs/src/Instant.wasmJs.kt | 10 +++++++++ 3 files changed, 57 insertions(+) create mode 100644 core/js/src/Instant.js.kt create mode 100644 core/wasmJs/src/Instant.wasmJs.kt diff --git a/core/commonJs/src/Instant.kt b/core/commonJs/src/Instant.kt index af7cc868a..fefa6d00e 100644 --- a/core/commonJs/src/Instant.kt +++ b/core/commonJs/src/Instant.kt @@ -3,6 +3,8 @@ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. */ +@file:OptIn(ExperimentalJsExport::class) + package kotlinx.datetime import kotlinx.datetime.format.* @@ -19,17 +21,39 @@ import kotlin.time.* import kotlin.time.Duration.Companion.nanoseconds import kotlin.time.Duration.Companion.seconds +/** + * This annotation is a workaround for + * [KT-62385 @JsExport doesn't play well with the WasmTarget](https://youtrack.jetbrains.com/issue/KT-62385). + * The actual for the JS target is [JsExport], while the actual for WASM target is + * an annotation without any effect. + */ +public expect annotation class SafeJsExport() + +@SafeJsExport @Serializable(with = InstantIso8601Serializer::class) public actual class Instant internal constructor(internal val value: jtInstant) : Comparable { + @JsExport.Ignore public actual val epochSeconds: Long get() = value.epochSecond().toLong() + + @JsExport.Ignore public actual val nanosecondsOfSecond: Int get() = value.nano().toInt() + @JsExport.Ignore public actual fun toEpochMilliseconds(): Long = epochSeconds * MILLIS_PER_ONE + nanosecondsOfSecond / NANOS_PER_MILLI + /** + * Same as [toEpochMilliseconds], but returns a [Double] instead of [Long]. + * This function is actually [JsExport]able because the return type + * is [Double] and not [Long]. + */ + public fun toEpochMillisecondsDouble(): Double = + value.epochSecond() * MILLIS_PER_ONE + value.nano() / NANOS_PER_MILLI + + @JsExport.Ignore public actual operator fun plus(duration: Duration): Instant = duration.toComponents { seconds, nanoseconds -> return try { Instant(plusFix(seconds.toDouble(), nanoseconds)) @@ -45,15 +69,19 @@ public actual class Instant internal constructor(internal val value: jtInstant) return jsTry { jtInstant.ofEpochSecond(newSeconds, newNanos.toInt()) } } + @JsExport.Ignore public actual operator fun minus(duration: Duration): Instant = plus(-duration) + @JsExport.Ignore public actual operator fun minus(other: Instant): Duration { val diff = jtDuration.between(other.value, this.value) return diff.seconds().seconds + diff.nano().nanoseconds } + @JsExport.Ignore public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value) + @JsExport.Ignore override fun equals(other: Any?): Boolean = (this === other) || (other is Instant && (this.value === other.value || this.value.equals(other.value))) @@ -62,10 +90,13 @@ public actual class Instant internal constructor(internal val value: jtInstant) actual override fun toString(): String = value.toString() public actual companion object { + + @JsExport.Ignore @Deprecated("Use Clock.System.now() instead", ReplaceWith("Clock.System.now()", "kotlinx.datetime.Clock"), level = DeprecationLevel.ERROR) public actual fun now(): Instant = Instant(jtClock.systemUTC().instant()) + @JsExport.Ignore public actual fun fromEpochMilliseconds(epochMilliseconds: Long): Instant = try { fromEpochSeconds(epochMilliseconds / MILLIS_PER_ONE, epochMilliseconds % MILLIS_PER_ONE * NANOS_PER_MILLI) } catch (e: Throwable) { @@ -74,6 +105,7 @@ public actual class Instant internal constructor(internal val value: jtInstant) } // TODO: implement a custom parser to 1) help DCE get rid of the formatting machinery 2) move Instant to stdlib + @JsExport.Ignore public actual fun parse(input: CharSequence, format: DateTimeFormat): Instant = try { // This format is not supported properly by Joda-Time, so we can't delegate to it. format.parse(input).toInstantUsingOffset() @@ -82,8 +114,10 @@ public actual class Instant internal constructor(internal val value: jtInstant) } @Deprecated("This overload is only kept for binary compatibility", level = DeprecationLevel.HIDDEN) + @JsExport.Ignore public fun parse(isoString: String): Instant = parse(input = isoString) + @JsExport.Ignore public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = try { /* Performing normalization here because otherwise this fails: assertEquals((Long.MAX_VALUE % 1_000_000_000).toInt(), @@ -96,6 +130,7 @@ public actual class Instant internal constructor(internal val value: jtInstant) if (epochSeconds > 0) MAX else MIN } + @JsExport.Ignore public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = try { Instant(jsTry { jtInstant.ofEpochSecond(epochSeconds.toDouble(), nanosecondAdjustment) }) } catch (e: Throwable) { @@ -103,7 +138,9 @@ public actual class Instant internal constructor(internal val value: jtInstant) if (epochSeconds > 0) MAX else MIN } + @JsExport.Ignore public actual val DISTANT_PAST: Instant = Instant(jsTry { jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS.toDouble(), 999_999_999) }) + @JsExport.Ignore public actual val DISTANT_FUTURE: Instant = Instant(jsTry { jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS.toDouble(), 0) }) internal actual val MIN: Instant = Instant(jtInstant.MIN) diff --git a/core/js/src/Instant.js.kt b/core/js/src/Instant.js.kt new file mode 100644 index 000000000..abc5d1c59 --- /dev/null +++ b/core/js/src/Instant.js.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +@file:OptIn(ExperimentalJsExport::class) + +package kotlinx.datetime + +public actual typealias SafeJsExport = JsExport \ No newline at end of file diff --git a/core/wasmJs/src/Instant.wasmJs.kt b/core/wasmJs/src/Instant.wasmJs.kt new file mode 100644 index 000000000..359eea69d --- /dev/null +++ b/core/wasmJs/src/Instant.wasmJs.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2019-2024 JetBrains s.r.o. and contributors. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.datetime + + +@Retention(AnnotationRetention.SOURCE) +public actual annotation class SafeJsExport \ No newline at end of file From 9a4c3cc58246b32a5a564e0bda6e20d00545f17b Mon Sep 17 00:00:00 2001 From: Lamberto Basti Date: Fri, 2 Aug 2024 13:20:56 +0200 Subject: [PATCH 2/2] Update SafeJsExport annotations for JS and WASM targets Add annotation targets and retention policies to SafeJsExport for both JS and WASM targets. This ensures consistent annotation behavior across different platforms. --- core/commonJs/src/Instant.kt | 6 ++++++ core/wasmJs/src/Instant.wasmJs.kt | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/core/commonJs/src/Instant.kt b/core/commonJs/src/Instant.kt index fefa6d00e..159d88778 100644 --- a/core/commonJs/src/Instant.kt +++ b/core/commonJs/src/Instant.kt @@ -7,6 +7,10 @@ package kotlinx.datetime +import kotlin.annotation.AnnotationTarget.CLASS +import kotlin.annotation.AnnotationTarget.FILE +import kotlin.annotation.AnnotationTarget.FUNCTION +import kotlin.annotation.AnnotationTarget.PROPERTY import kotlinx.datetime.format.* import kotlinx.datetime.internal.JSJoda.Instant as jtInstant import kotlinx.datetime.internal.JSJoda.Duration as jtDuration @@ -27,6 +31,8 @@ import kotlin.time.Duration.Companion.seconds * The actual for the JS target is [JsExport], while the actual for WASM target is * an annotation without any effect. */ +@Target(CLASS, PROPERTY, FUNCTION, FILE) +@Retention(AnnotationRetention.BINARY) public expect annotation class SafeJsExport() @SafeJsExport diff --git a/core/wasmJs/src/Instant.wasmJs.kt b/core/wasmJs/src/Instant.wasmJs.kt index 359eea69d..884ebf9e0 100644 --- a/core/wasmJs/src/Instant.wasmJs.kt +++ b/core/wasmJs/src/Instant.wasmJs.kt @@ -5,6 +5,13 @@ package kotlinx.datetime +import kotlin.annotation.AnnotationRetention.BINARY +import kotlin.annotation.AnnotationTarget.CLASS +import kotlin.annotation.AnnotationTarget.FILE +import kotlin.annotation.AnnotationTarget.FUNCTION +import kotlin.annotation.AnnotationTarget.PROPERTY -@Retention(AnnotationRetention.SOURCE) + +@Retention(BINARY) +@Target(CLASS, PROPERTY, FUNCTION, FILE) public actual annotation class SafeJsExport \ No newline at end of file