From ece0f83ab22f19ef8fa7f806c47f34d7b6997cd7 Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Tue, 25 Jan 2022 13:03:23 +0100 Subject: [PATCH] [WASM] Implement std kotlin.time --- .../stdlib/wasm/src/kotlin/time/Duration.kt | 35 ++++++++++- .../wasm/src/kotlin/time/DurationUnit.kt | 60 +++++++++++++------ .../wasm/src/kotlin/time/TimeSources.kt | 26 ++++++-- 3 files changed, 95 insertions(+), 26 deletions(-) diff --git a/libraries/stdlib/wasm/src/kotlin/time/Duration.kt b/libraries/stdlib/wasm/src/kotlin/time/Duration.kt index 0f204149709..c35a207f63e 100644 --- a/libraries/stdlib/wasm/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/wasm/src/kotlin/time/Duration.kt @@ -1,9 +1,38 @@ /* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ + package kotlin.time +import kotlin.math.* + internal actual inline val durationAssertionsEnabled: Boolean get() = true -internal actual fun formatToExactDecimals(value: Double, decimals: Int): String = TODO("Wasm stdlib: Duration") -internal actual fun formatUpToDecimals(value: Double, decimals: Int): String = TODO("Wasm stdlib: Duration") \ No newline at end of file + +@JsFun("(value, decimals) => value.toFixed(decimals)") +private external fun toFixed(value: Double, decimals: Int): String + +@JsFun("(value, decimals) => value.toPrecision(decimals)") +private external fun toPrecision(value: Double, decimals: Int): String + +internal actual fun formatToExactDecimals(value: Double, decimals: Int): String { + val rounded = if (decimals == 0) { + value + } else { + val pow = (10.0).pow(decimals) + round(abs(value) * pow) / pow * sign(value) + round(abs(value) * pow) / pow * sign(value) + } + return if (abs(rounded) < 1e21) { + // toFixed switches to scientific format after 1e21 + toFixed(rounded, decimals) + } else { + // toPrecision outputs the specified number of digits, but only for positive numbers + val positive = abs(rounded) + val positiveString = toPrecision(positive, ceil(log10(positive)).toInt() + decimals) + if (rounded < 0) "-$positiveString" else positiveString + } +} + +@JsFun("(value, decimals) => value.toLocaleString(\"en-us\", ({\"maximumFractionDigits\": decimals}))") +external internal actual fun formatUpToDecimals(value: Double, decimals: Int): String \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/time/DurationUnit.kt b/libraries/stdlib/wasm/src/kotlin/time/DurationUnit.kt index 882ea2eb3be..a82577d5599 100644 --- a/libraries/stdlib/wasm/src/kotlin/time/DurationUnit.kt +++ b/libraries/stdlib/wasm/src/kotlin/time/DurationUnit.kt @@ -1,55 +1,77 @@ /* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ package kotlin.time - -/** - * The list of possible time measurement units, in which a duration can be expressed. - * - * The smallest time unit is [NANOSECONDS] and the largest is [DAYS], which corresponds to exactly 24 [HOURS]. - */ @SinceKotlin("1.6") @WasExperimental(ExperimentalTime::class) -public actual enum class DurationUnit { +public actual enum class DurationUnit(internal val scale: Double) { /** * Time unit representing one nanosecond, which is 1/1000 of a microsecond. */ - NANOSECONDS, + NANOSECONDS(1e0), /** * Time unit representing one microsecond, which is 1/1000 of a millisecond. */ - MICROSECONDS, + MICROSECONDS(1e3), /** * Time unit representing one millisecond, which is 1/1000 of a second. */ - MILLISECONDS, + MILLISECONDS(1e6), /** * Time unit representing one second. */ - SECONDS, + SECONDS(1e9), /** * Time unit representing one minute. */ - MINUTES, + MINUTES(60e9), /** * Time unit representing one hour. */ - HOURS, + HOURS(3600e9), /** * Time unit representing one day, which is always equal to 24 hours. */ - DAYS; + DAYS(86400e9); } -/** Converts the given time duration [value] expressed in the specified [sourceUnit] into the specified [targetUnit]. */ @SinceKotlin("1.3") -internal actual fun convertDurationUnit(value: Double, sourceUnit: DurationUnit, targetUnit: DurationUnit): Double = TODO("Wasm stdlib: convertDurationUnit Double") +internal actual fun convertDurationUnit(value: Double, sourceUnit: DurationUnit, targetUnit: DurationUnit): Double { + val sourceCompareTarget = sourceUnit.scale.compareTo(targetUnit.scale) + return when { + sourceCompareTarget > 0 -> value * (sourceUnit.scale / targetUnit.scale) + sourceCompareTarget < 0 -> value / (targetUnit.scale / sourceUnit.scale) + else -> value + } +} @SinceKotlin("1.5") -internal actual fun convertDurationUnitOverflow(value: Long, sourceUnit: DurationUnit, targetUnit: DurationUnit): Long = TODO("Wasm stdlib: convertDurationUnitOverflow Long") +internal actual fun convertDurationUnitOverflow(value: Long, sourceUnit: DurationUnit, targetUnit: DurationUnit): Long { + val sourceCompareTarget = sourceUnit.scale.compareTo(targetUnit.scale) + return when { + sourceCompareTarget > 0 -> value * (sourceUnit.scale / targetUnit.scale).toLong() + sourceCompareTarget < 0 -> value / (targetUnit.scale / sourceUnit.scale).toLong() + else -> value + } +} @SinceKotlin("1.5") -internal actual fun convertDurationUnit(value: Long, sourceUnit: DurationUnit, targetUnit: DurationUnit): Long = TODO("Wasm stdlib: convertDurationUnit Long") \ No newline at end of file +internal actual fun convertDurationUnit(value: Long, sourceUnit: DurationUnit, targetUnit: DurationUnit): Long { + val sourceCompareTarget = sourceUnit.scale.compareTo(targetUnit.scale) + return when { + sourceCompareTarget > 0 -> { + val scale = (sourceUnit.scale / targetUnit.scale).toLong() + val result = value * scale + when { + result / scale == value -> result + value > 0 -> Long.MAX_VALUE + else -> Long.MIN_VALUE + } + } + sourceCompareTarget < 0 -> value / (targetUnit.scale / sourceUnit.scale).toLong() + else -> value + } +} \ No newline at end of file diff --git a/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt b/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt index 2438fe3cebd..8af71d5596b 100644 --- a/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt +++ b/libraries/stdlib/wasm/src/kotlin/time/TimeSources.kt @@ -1,11 +1,29 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ + package kotlin.time +import kotlin.wasm.internal.ExternalInterfaceType + +@JsFun("() => typeof globalThis !== 'undefined' && typeof globalThis.performance !== 'undefined' ? globalThis.performance : null") +private external fun tryGetPerformance(): ExternalInterfaceType? + +@JsFun("(performance) => performance.now()") +private external fun getPerformanceNow(performance: ExternalInterfaceType): Double + +@JsFun("() => Date.now()") +private external fun dateNow(): Double + @SinceKotlin("1.3") @ExperimentalTime -internal actual object MonotonicTimeSource : TimeSource { - override fun markNow(): TimeMark = TODO("Wasm stdlib: MonotonicTimeSource::markNow") -} +internal actual object MonotonicTimeSource : TimeSource, AbstractDoubleTimeSource(unit = DurationUnit.MILLISECONDS) { + private val performance: ExternalInterfaceType? = tryGetPerformance() + + override fun read(): Double = + if (performance != null) getPerformanceNow(performance) else dateNow() + + override fun toString(): String = + if (performance != null) "TimeSource(globalThis.performance.now())" else "TimeSource(Date.now())" +} \ No newline at end of file