Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wasm #315

Merged
merged 2 commits into from
Dec 1, 2023
Merged

Wasm #315

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ allprojects {
compilerOptions { freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR") }
}
}

// Disable NPM to NodeJS nightly compatibility check.
// Drop this when NodeJs version that supports latest Wasm become stable
tasks.withType<org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask>().configureEach {
args.add("--ignore-engines")
ilya-g marked this conversation as resolved.
Show resolved Hide resolved
}
49 changes: 45 additions & 4 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import kotlinx.team.infra.mavenPublicationsPom
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.net.URL
import java.util.Locale
import javax.xml.parsers.DocumentBuilderFactory
import java.io.ByteArrayOutputStream
import java.io.PrintWriter
Expand Down Expand Up @@ -107,6 +106,16 @@ kotlin {
// }
}

wasmJs {
nodejs {
testTask {
useMocha {
timeout = "30s"
}
}
}
}

sourceSets.all {
val suffixIndex = name.indexOfLast { it.isUpperCase() }
val targetName = name.substring(0, suffixIndex)
Expand Down Expand Up @@ -193,20 +202,37 @@ kotlin {
}
}

val jsMain by getting {
val commonJsMain by creating {
dependsOn(commonMain.get())
dependencies {
api("org.jetbrains.kotlin:kotlin-stdlib-js")
api("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
implementation(npm("@js-joda/core", "3.2.0"))
}
}

val jsTest by getting {
val commonJsTest by creating {
dependsOn(commonTest.get())
dependencies {
implementation(npm("@js-joda/timezone", "2.3.0"))
}
}

val jsMain by getting {
dependsOn(commonJsMain)
}

val jsTest by getting {
dependsOn(commonJsTest)
}

val wasmJsMain by getting {
dependsOn(commonJsMain)
}

val wasmJsTest by getting {
dependsOn(commonJsTest)
}

val nativeMain by getting {
dependsOn(commonMain.get())
dependencies {
Expand Down Expand Up @@ -383,3 +409,18 @@ tasks.withType<AbstractDokkaLeafTask>().configureEach {
}
}
}

// Disable intermediate sourceSet compilation because we do not need js-wasmJs artifact
tasks.configureEach {
if (name == "compileCommonJsMainKotlinMetadata") {
enabled = false
}
}

// Drop this configuration when the Node.JS version in KGP will support wasm gc milestone 4
// check it here:
// https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt
with(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.apply(rootProject)) {
nodeVersion = "21.0.0-v8-canary202309167e82ab1fa2"
nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
}
ilya-g marked this conversation as resolved.
Show resolved Hide resolved
45 changes: 30 additions & 15 deletions core/common/src/serializers/DateTimeUnitSerializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ import kotlin.reflect.KClass
*/
public object TimeBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.TimeBased> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("TimeBased") {
element<Long>("nanoseconds")
// https://youtrack.jetbrains.com/issue/KT-63939
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
buildClassSerialDescriptor("TimeBased") {
element<Long>("nanoseconds")
}
}

override fun serialize(encoder: Encoder, value: DateTimeUnit.TimeBased) {
Expand Down Expand Up @@ -65,8 +68,11 @@ public object TimeBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.TimeBase
*/
public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DayBased> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("DayBased") {
element<Int>("days")
// https://youtrack.jetbrains.com/issue/KT-63939
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
buildClassSerialDescriptor("DayBased") {
element<Int>("days")
}
}

override fun serialize(encoder: Encoder, value: DateTimeUnit.DayBased) {
Expand Down Expand Up @@ -109,8 +115,11 @@ public object DayBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.DayBased>
*/
public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBased> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("MonthBased") {
element<Int>("months")
// https://youtrack.jetbrains.com/issue/KT-63939
override val descriptor: SerialDescriptor by lazy(LazyThreadSafetyMode.PUBLICATION) {
buildClassSerialDescriptor("MonthBased") {
element<Int>("months")
}
}

override fun serialize(encoder: Encoder, value: DateTimeUnit.MonthBased) {
Expand Down Expand Up @@ -155,10 +164,13 @@ public object MonthBasedDateTimeUnitSerializer: KSerializer<DateTimeUnit.MonthBa
@OptIn(InternalSerializationApi::class)
public object DateBasedDateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit.DateBased>() {

private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased",
DateTimeUnit.DateBased::class,
arrayOf(DateTimeUnit.DayBased::class, DateTimeUnit.MonthBased::class),
arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer))
// https://youtrack.jetbrains.com/issue/KT-63939
private val impl by lazy(LazyThreadSafetyMode.PUBLICATION) {
SealedClassSerializer("kotlinx.datetime.DateTimeUnit.DateBased",
DateTimeUnit.DateBased::class,
arrayOf(DateTimeUnit.DayBased::class, DateTimeUnit.MonthBased::class),
arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer))
}

@InternalSerializationApi
override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?):
Expand Down Expand Up @@ -189,10 +201,13 @@ public object DateBasedDateTimeUnitSerializer: AbstractPolymorphicSerializer<Dat
@OptIn(InternalSerializationApi::class)
public object DateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit>() {

private val impl = SealedClassSerializer("kotlinx.datetime.DateTimeUnit",
DateTimeUnit::class,
arrayOf(DateTimeUnit.DayBased::class, DateTimeUnit.MonthBased::class, DateTimeUnit.TimeBased::class),
arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer, TimeBasedDateTimeUnitSerializer))
// https://youtrack.jetbrains.com/issue/KT-63939
private val impl by lazy(LazyThreadSafetyMode.PUBLICATION) {
SealedClassSerializer("kotlinx.datetime.DateTimeUnit",
DateTimeUnit::class,
arrayOf(DateTimeUnit.DayBased::class, DateTimeUnit.MonthBased::class, DateTimeUnit.TimeBased::class),
arrayOf(DayBasedDateTimeUnitSerializer, MonthBasedDateTimeUnitSerializer, TimeBasedDateTimeUnitSerializer))
}

@InternalSerializationApi
override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy<DateTimeUnit>? =
Expand All @@ -209,4 +224,4 @@ public object DateTimeUnitSerializer: AbstractPolymorphicSerializer<DateTimeUnit
override val descriptor: SerialDescriptor
get() = impl.descriptor

}
}
12 changes: 12 additions & 0 deletions core/common/test/InstantTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.datetime.internal.*
import kotlin.random.*
import kotlin.test.*
import kotlin.time.*
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.nanoseconds
Expand Down Expand Up @@ -251,6 +252,10 @@ class InstantTest {
val instant3 = instant2 - 2.hours
val offset3 = instant3.offsetIn(zone)
assertEquals(offset1, offset3)

// TODO: fails on JS
// // without the minus, this test fails on JVM
// (Instant.MAX - (2 * 365).days).offsetIn(zone)
}

@Test
Expand Down Expand Up @@ -618,4 +623,11 @@ class InstantRangeTest {
assertEquals(expected = Instant.MIN, actual = Instant.MIN - smallDuration)
}
}

@Test
fun subtractInstants() {
val max = Instant.fromEpochSeconds(31494816403199L)
val min = Instant.fromEpochSeconds(-31619119219200L)
assertEquals(max.epochSeconds - min.epochSeconds, (max - min).inWholeSeconds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ public actual enum class DayOfWeek {
SUNDAY;
}

internal fun jsDayOfWeek.toDayOfWeek(): DayOfWeek = DayOfWeek(this.value().toInt())
internal fun jsDayOfWeek.toDayOfWeek(): DayOfWeek = DayOfWeek(this.value())
70 changes: 35 additions & 35 deletions core/js/src/Instant.kt → core/commonJs/src/Instant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

package kotlinx.datetime

import kotlinx.datetime.internal.JSJoda.ZonedDateTime
import kotlinx.datetime.internal.JSJoda.Instant as jtInstant
import kotlinx.datetime.internal.JSJoda.OffsetDateTime as jtOffsetDateTime
import kotlinx.datetime.internal.JSJoda.Duration as jtDuration
import kotlinx.datetime.internal.JSJoda.Clock as jtClock
import kotlinx.datetime.internal.JSJoda.ChronoUnit
import kotlinx.datetime.internal.JSJoda.ChronoUnit as jtChronoUnit
import kotlinx.datetime.internal.JSJoda.ZonedDateTime as jtZonedDateTime
import kotlinx.datetime.internal.safeAdd
import kotlinx.datetime.internal.*
import kotlinx.datetime.serializers.InstantIso8601Serializer
Expand Down Expand Up @@ -40,24 +40,24 @@ public actual class Instant internal constructor(internal val value: jtInstant)
}

internal fun plusFix(seconds: Double, nanos: Int): jtInstant {
val newSeconds = value.epochSecond().toDouble() + seconds
val newNanos = value.nano().toDouble() + nanos
return jtInstant.ofEpochSecond(newSeconds, newNanos)
val newSeconds = value.epochSecond() + seconds
val newNanos = value.nano() + nanos
return jsTry { jtInstant.ofEpochSecond(newSeconds, newNanos.toInt()) }
}

public actual operator fun minus(duration: Duration): Instant = plus(-duration)

public actual operator fun minus(other: Instant): Duration {
val diff = jtDuration.between(other.value, this.value)
return diff.seconds().toDouble().seconds + diff.nano().toDouble().nanoseconds
return diff.seconds().seconds + diff.nano().nanoseconds
}

public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value).toInt()
public actual override operator fun compareTo(other: Instant): Int = this.value.compareTo(other.value)

override fun equals(other: Any?): Boolean =
(this === other) || (other is Instant && this.value == other.value)
(this === other) || (other is Instant && (this.value === other.value || this.value.equals(other.value)))

override fun hashCode(): Int = value.hashCode().toInt()
override fun hashCode(): Int = value.hashCode()

actual override fun toString(): String = value.toString()

Expand All @@ -74,7 +74,7 @@ public actual class Instant internal constructor(internal val value: jtInstant)
}

public actual fun parse(isoString: String): Instant = try {
Instant(jtOffsetDateTime.parse(fixOffsetRepresentation(isoString)).toInstant())
Instant(jsTry { jtOffsetDateTime.parse(fixOffsetRepresentation(isoString)) }.toInstant())
} catch (e: Throwable) {
if (e.isJodaDateTimeParseException()) throw DateTimeFormatException(e)
throw e
Expand All @@ -97,21 +97,21 @@ public actual class Instant internal constructor(internal val value: jtInstant)
Instant.fromEpochSeconds(0, Long.MAX_VALUE).nanosecondsOfSecond) */
val secs = safeAdd(epochSeconds, nanosecondAdjustment.floorDiv(NANOS_PER_ONE.toLong()))
val nos = nanosecondAdjustment.mod(NANOS_PER_ONE.toLong()).toInt()
Instant(jtInstant.ofEpochSecond(secs, nos))
Instant(jsTry { jtInstant.ofEpochSecond(secs.toDouble(), nos) })
} catch (e: Throwable) {
if (!e.isJodaDateTimeException() && e !is ArithmeticException) throw e
if (epochSeconds > 0) MAX else MIN
}

public actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = try {
Instant(jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment))
Instant(jsTry { jtInstant.ofEpochSecond(epochSeconds.toDouble(), nanosecondAdjustment) })
} catch (e: Throwable) {
if (!e.isJodaDateTimeException()) throw e
if (epochSeconds > 0) MAX else MIN
}

public actual val DISTANT_PAST: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999))
public actual val DISTANT_FUTURE: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0))
public actual val DISTANT_PAST: Instant = Instant(jsTry { jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS.toDouble(), 999_999_999) })
public actual val DISTANT_FUTURE: Instant = Instant(jsTry { jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS.toDouble(), 0) })

internal actual val MIN: Instant = Instant(jtInstant.MIN)
internal actual val MAX: Instant = Instant(jtInstant.MAX)
Expand All @@ -120,23 +120,23 @@ public actual class Instant internal constructor(internal val value: jtInstant)


public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
val thisZdt = this.value.atZone(timeZone.zoneId)
val thisZdt = jsTry { this.value.atZone(timeZone.zoneId) }
with(period) {
thisZdt
.run { if (totalMonths != 0) plusMonths(totalMonths) else this }
.run { if (days != 0) plusDays(days) else this }
.run { if (hours != 0) plusHours(hours) else this }
.run { if (minutes != 0) plusMinutes(minutes) else this }
.run { if (seconds != 0) plusSeconds(seconds) else this }
.run { if (nanoseconds != 0) plusNanos(nanoseconds.toDouble()) else this }
.run { if (totalMonths != 0) jsTry { plusMonths(totalMonths) } else this }
.run { if (days != 0) jsTry { plusDays(days) } else this }
.run { if (hours != 0) jsTry { plusHours(hours) } else this }
.run { if (minutes != 0) jsTry { plusMinutes(minutes) } else this }
.run { if (seconds != 0) jsTry { plusSeconds(seconds) } else this }
.run { if (nanoseconds != 0) jsTry { plusNanos(nanoseconds.toDouble()) } else this }
}.toInstant().let(::Instant)
} catch (e: Throwable) {
if (e.isJodaDateTimeException()) throw DateTimeArithmeticException(e)
throw e
}

private fun Instant.atZone(zone: TimeZone): ZonedDateTime = value.atZone(zone.zoneId)
private fun jtInstant.checkZone(zone: TimeZone): jtInstant = apply { atZone(zone.zoneId) }
private fun Instant.atZone(zone: TimeZone): jtZonedDateTime = jsTry { value.atZone(zone.zoneId) }
private fun jtInstant.checkZone(zone: TimeZone): jtInstant = apply { jsTry { atZone(zone.zoneId) } }

@Deprecated("Use the plus overload with an explicit number of units", ReplaceWith("this.plus(1, unit, timeZone)"))
public actual fun Instant.plus(unit: DateTimeUnit, timeZone: TimeZone): Instant =
Expand All @@ -150,9 +150,9 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
plus(value, unit).value.checkZone(timeZone)
}
is DateTimeUnit.DayBased ->
thisZdt.plusDays(value.toDouble() * unit.days).toInstant()
jsTry {thisZdt.plusDays(value.toDouble() * unit.days) }.toInstant()
is DateTimeUnit.MonthBased ->
thisZdt.plusMonths(value.toDouble() * unit.months).toInstant()
jsTry { thisZdt.plusMonths(value.toDouble() * unit.months) }.toInstant()
}.let(::Instant)
} catch (e: Throwable) {
if (e.isJodaDateTimeException()) throw DateTimeArithmeticException(e)
Expand All @@ -166,9 +166,9 @@ public actual fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZon
is DateTimeUnit.TimeBased ->
plus(value.toLong(), unit).value.checkZone(timeZone)
is DateTimeUnit.DayBased ->
thisZdt.plusDays(value.toDouble() * unit.days).toInstant()
jsTry { thisZdt.plusDays(value.toDouble() * unit.days) }.toInstant()
is DateTimeUnit.MonthBased ->
thisZdt.plusMonths(value.toDouble() * unit.months).toInstant()
jsTry { thisZdt.plusMonths(value.toDouble() * unit.months) }.toInstant()
}.let(::Instant)
} catch (e: Throwable) {
if (e.isJodaDateTimeException()) throw DateTimeArithmeticException(e)
Expand All @@ -194,12 +194,12 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
}

public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod = try {
var thisZdt = this.value.atZone(timeZone.zoneId)
val otherZdt = other.value.atZone(timeZone.zoneId)
var thisZdt = jsTry { this.value.atZone(timeZone.zoneId) }
val otherZdt = jsTry { other.value.atZone(timeZone.zoneId) }

val months = thisZdt.until(otherZdt, ChronoUnit.MONTHS).toDouble(); thisZdt = thisZdt.plusMonths(months)
val days = thisZdt.until(otherZdt, ChronoUnit.DAYS).toDouble(); thisZdt = thisZdt.plusDays(days)
val nanoseconds = thisZdt.until(otherZdt, ChronoUnit.NANOS).toDouble()
val months = thisZdt.until(otherZdt, jtChronoUnit.MONTHS); thisZdt = jsTry { thisZdt.plusMonths(months) }
val days = thisZdt.until(otherZdt, jtChronoUnit.DAYS); thisZdt = jsTry { thisZdt.plusDays(days) }
val nanoseconds = thisZdt.until(otherZdt, jtChronoUnit.NANOS)

buildDateTimePeriod(months.toInt(), days.toInt(), nanoseconds.toLong())
} catch (e: Throwable) {
Expand All @@ -211,8 +211,8 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti
val otherZdt = other.atZone(timeZone)
when(unit) {
is DateTimeUnit.TimeBased -> until(other, unit)
is DateTimeUnit.DayBased -> (thisZdt.until(otherZdt, ChronoUnit.DAYS).toDouble() / unit.days).toLong()
is DateTimeUnit.MonthBased -> (thisZdt.until(otherZdt, ChronoUnit.MONTHS).toDouble() / unit.months).toLong()
is DateTimeUnit.DayBased -> (thisZdt.until(otherZdt, jtChronoUnit.DAYS) / unit.days).toLong()
is DateTimeUnit.MonthBased -> (thisZdt.until(otherZdt, jtChronoUnit.MONTHS) / unit.months).toLong()
}
} catch (e: ArithmeticException) {
if (this < other) Long.MAX_VALUE else Long.MIN_VALUE
Expand All @@ -221,4 +221,4 @@ public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: Ti
}

internal actual fun Instant.toStringWithOffset(offset: UtcOffset): String =
jtOffsetDateTime.ofInstant(this.value, offset.zoneOffset).toString()
jtOffsetDateTime.ofInstant(this.value, offset.zoneOffset).toString()