From 14042223052dbb8a394ab5d70229515f7f23326a Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 4 Sep 2020 11:13:12 +0300 Subject: [PATCH 1/4] Common: add Instant.fromEpochSeconds(Long, Int) This overload allows to pass an Int as the number of nanoseconds. --- core/commonMain/src/Instant.kt | 8 ++++++++ core/commonTest/src/InstantTest.kt | 24 ++++++++++++++++++------ core/jsMain/src/Instant.kt | 7 +++++++ core/jvmMain/src/Instant.kt | 3 +++ core/nativeMain/src/Instant.kt | 3 +++ 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/core/commonMain/src/Instant.kt b/core/commonMain/src/Instant.kt index de3f1ba86..b5e6c9eb0 100644 --- a/core/commonMain/src/Instant.kt +++ b/core/commonMain/src/Instant.kt @@ -111,6 +111,14 @@ public expect class Instant : Comparable { */ fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long = 0): Instant + /** + * Returns an [Instant] that is the [epochSeconds] number of seconds from the epoch instant `1970-01-01T00:00:00Z` + * and the [nanosecondAdjustment] number of nanoseconds from the whole second. + * + * The return value is clamped to the platform-specific boundaries for [Instant] if the result exceeds them. + */ + fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant + /** * Parses a string that represents an instant in ISO-8601 format including date and time components and * the mandatory `Z` designator of the UTC+0 time zone and returns the parsed [Instant] value. diff --git a/core/commonTest/src/InstantTest.kt b/core/commonTest/src/InstantTest.kt index 6a50ee400..3c069aed7 100644 --- a/core/commonTest/src/InstantTest.kt +++ b/core/commonTest/src/InstantTest.kt @@ -264,22 +264,34 @@ class InstantTest { @Test fun nanosecondAdjustment() { for (i in -2..2L) { - for (j in 0..9L) { + for (j in 0..9) { val t: Instant = Instant.fromEpochSeconds(i, j) + val t2: Instant = Instant.fromEpochSeconds(i, j.toLong()) assertEquals(i, t.epochSeconds) - assertEquals(j, t.nanosecondsOfSecond.toLong()) + assertEquals(j, t.nanosecondsOfSecond) + assertEquals(t, t2) } - for (j in -10..-1L) { + for (j in -10..-1) { val t: Instant = Instant.fromEpochSeconds(i, j) + val t2: Instant = Instant.fromEpochSeconds(i, j.toLong()) assertEquals(i - 1, t.epochSeconds) - assertEquals(j + 1000000000, t.nanosecondsOfSecond.toLong()) + assertEquals(j + 1000000000, t.nanosecondsOfSecond) + assertEquals(t, t2) } - for (j in 999_999_990..999_999_999L) { + for (j in 999_999_990..999_999_999) { val t: Instant = Instant.fromEpochSeconds(i, j) + val t2: Instant = Instant.fromEpochSeconds(i, j.toLong()) assertEquals(i, t.epochSeconds) - assertEquals(j, t.nanosecondsOfSecond.toLong()) + assertEquals(j, t.nanosecondsOfSecond) + assertEquals(t, t2) } } + val t = Instant.fromEpochSeconds(0, Int.MAX_VALUE) + assertEquals((Int.MAX_VALUE / 1_000_000_000).toLong(), t.epochSeconds) + assertEquals(Int.MAX_VALUE % 1_000_000_000, t.nanosecondsOfSecond) + val t2 = Instant.fromEpochSeconds(0, Long.MAX_VALUE) + assertEquals(Long.MAX_VALUE / 1_000_000_000, t2.epochSeconds) + assertEquals((Long.MAX_VALUE % 1_000_000_000).toInt(), t2.nanosecondsOfSecond) } /* Based on the ThreeTenBp project. diff --git a/core/jsMain/src/Instant.kt b/core/jsMain/src/Instant.kt index 33d9f9288..1d903a833 100644 --- a/core/jsMain/src/Instant.kt +++ b/core/jsMain/src/Instant.kt @@ -86,6 +86,13 @@ public actual class Instant internal constructor(internal val value: jtInstant) if (epochSeconds > 0) MAX else MIN } + actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = try { + Instant(jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment)) + } catch (e: Throwable) { + if (!e.isJodaDateTimeException()) throw e + if (epochSeconds > 0) MAX else MIN + } + actual val DISTANT_PAST: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999)) actual val DISTANT_FUTURE: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0)) diff --git a/core/jvmMain/src/Instant.kt b/core/jvmMain/src/Instant.kt index b92cc3fe1..286d6b01b 100644 --- a/core/jvmMain/src/Instant.kt +++ b/core/jvmMain/src/Instant.kt @@ -72,6 +72,9 @@ public actual class Instant internal constructor(internal val value: jtInstant) if (epochSeconds > 0) MAX else MIN } + actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) + actual val DISTANT_PAST: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_PAST_SECONDS, 999_999_999)) actual val DISTANT_FUTURE: Instant = Instant(jtInstant.ofEpochSecond(DISTANT_FUTURE_SECONDS, 0)) diff --git a/core/nativeMain/src/Instant.kt b/core/nativeMain/src/Instant.kt index bcad154fd..f6f698a63 100644 --- a/core/nativeMain/src/Instant.kt +++ b/core/nativeMain/src/Instant.kt @@ -250,6 +250,9 @@ public actual class Instant internal constructor(actual val epochSeconds: Long, if (epochSeconds > 0) MAX else MIN } + actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Int): Instant = + fromEpochSeconds(epochSeconds, nanosecondAdjustment.toLong()) + actual fun parse(isoString: String): Instant = instantParser.parse(isoString) From 0e24ce7571edaba8d97a9ccbc99dacb3a7047260 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 9 Sep 2020 11:54:37 +0300 Subject: [PATCH 2/4] JS: fix Instant.fromEpochSeconds on extreme values --- core/jsMain/src/Instant.kt | 6 ++++-- core/jsMain/src/mathJs.kt | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/jsMain/src/Instant.kt b/core/jsMain/src/Instant.kt index 1d903a833..1596cbb63 100644 --- a/core/jsMain/src/Instant.kt +++ b/core/jsMain/src/Instant.kt @@ -80,9 +80,11 @@ public actual class Instant internal constructor(internal val value: jtInstant) } actual fun fromEpochSeconds(epochSeconds: Long, nanosecondAdjustment: Long): Instant = try { - Instant(jtInstant.ofEpochSecond(epochSeconds, nanosecondAdjustment)) + val secs = safeAdd(epochSeconds, floorDiv(nanosecondAdjustment, NANOS_PER_ONE.toLong())) + val nos = floorMod(nanosecondAdjustment, NANOS_PER_ONE.toLong()).toInt() + Instant(jtInstant.ofEpochSecond(secs, nos)) } catch (e: Throwable) { - if (!e.isJodaDateTimeException()) throw e + if (!e.isJodaDateTimeException() && e !is ArithmeticException) throw e if (epochSeconds > 0) MAX else MIN } diff --git a/core/jsMain/src/mathJs.kt b/core/jsMain/src/mathJs.kt index fcd819262..ebcaa30bb 100644 --- a/core/jsMain/src/mathJs.kt +++ b/core/jsMain/src/mathJs.kt @@ -58,3 +58,35 @@ internal actual fun safeMultiply(a: Int, b: Int): Int { if (result > Int.MAX_VALUE || result < Int.MIN_VALUE) throw ArithmeticException("Multiplication overflows Int range: $a * $b.") return result.toInt() } + +/** + * Returns the floor division. + *

+ * This returns {@code 0} for {@code floorDiv(0, 4)}.
+ * This returns {@code -1} for {@code floorDiv(-1, 4)}.
+ * This returns {@code -1} for {@code floorDiv(-2, 4)}.
+ * This returns {@code -1} for {@code floorDiv(-3, 4)}.
+ * This returns {@code -1} for {@code floorDiv(-4, 4)}.
+ * This returns {@code -2} for {@code floorDiv(-5, 4)}.
+ * + * @param a the dividend + * @param b the divisor + * @return the floor division + */ +internal fun floorDiv(a: Long, b: Long): Long = if (a >= 0) a / b else (a + 1) / b - 1 + +/** + * Returns the floor modulus. + * + * + * This returns `0` for `floorMod(0, 4)`.

+ * This returns `1` for `floorMod(-1, 4)`.

+ * This returns `2` for `floorMod(-2, 4)`.

+ * This returns `3` for `floorMod(-3, 4)`.

+ * This returns `0` for `floorMod(-4, 4)`.

+ * + * @param a the dividend + * @param b the divisor + * @return the floor modulus (positive) + */ +internal fun floorMod(a: Long, b: Long): Long = (a % b + b) % b \ No newline at end of file From 29d7f12b99f03ed1d9e781410d449c55ba7e2bb1 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 10 Sep 2020 10:12:42 +0300 Subject: [PATCH 3/4] Fix KDoc --- core/jsMain/src/mathJs.kt | 25 ++++++++-------- core/nativeMain/src/mathNative.kt | 50 +++++++++++++++---------------- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/core/jsMain/src/mathJs.kt b/core/jsMain/src/mathJs.kt index ebcaa30bb..db3321d0a 100644 --- a/core/jsMain/src/mathJs.kt +++ b/core/jsMain/src/mathJs.kt @@ -61,13 +61,13 @@ internal actual fun safeMultiply(a: Int, b: Int): Int { /** * Returns the floor division. - *

- * This returns {@code 0} for {@code floorDiv(0, 4)}.
- * This returns {@code -1} for {@code floorDiv(-1, 4)}.
- * This returns {@code -1} for {@code floorDiv(-2, 4)}.
- * This returns {@code -1} for {@code floorDiv(-3, 4)}.
- * This returns {@code -1} for {@code floorDiv(-4, 4)}.
- * This returns {@code -2} for {@code floorDiv(-5, 4)}.
+ * + * This returns `0` for `floorDiv(0, 4)`. + * This returns `-1` for `floorDiv(-1, 4)`. + * This returns `-1` for `floorDiv(-2, 4)`. + * This returns `-1` for `floorDiv(-3, 4)`. + * This returns `-1` for `floorDiv(-4, 4)`. + * This returns `-2` for `floorDiv(-5, 4)`. * * @param a the dividend * @param b the divisor @@ -78,12 +78,11 @@ internal fun floorDiv(a: Long, b: Long): Long = if (a >= 0) a / b else (a + 1) / /** * Returns the floor modulus. * - * - * This returns `0` for `floorMod(0, 4)`.

- * This returns `1` for `floorMod(-1, 4)`.

- * This returns `2` for `floorMod(-2, 4)`.

- * This returns `3` for `floorMod(-3, 4)`.

- * This returns `0` for `floorMod(-4, 4)`.

+ * This returns `0` for `floorMod(0, 4)`. + * This returns `1` for `floorMod(-1, 4)`. + * This returns `2` for `floorMod(-2, 4)`. + * This returns `3` for `floorMod(-3, 4)`. + * This returns `0` for `floorMod(-4, 4)`. * * @param a the dividend * @param b the divisor diff --git a/core/nativeMain/src/mathNative.kt b/core/nativeMain/src/mathNative.kt index 04bc5213e..8280481cc 100644 --- a/core/nativeMain/src/mathNative.kt +++ b/core/nativeMain/src/mathNative.kt @@ -136,13 +136,13 @@ internal actual fun safeMultiply(a: Int, b: Int): Int { /** * Returns the floor division. - *

- * This returns {@code 0} for {@code floorDiv(0, 4)}.
- * This returns {@code -1} for {@code floorDiv(-1, 4)}.
- * This returns {@code -1} for {@code floorDiv(-2, 4)}.
- * This returns {@code -1} for {@code floorDiv(-3, 4)}.
- * This returns {@code -1} for {@code floorDiv(-4, 4)}.
- * This returns {@code -2} for {@code floorDiv(-5, 4)}.
+ * + * This returns `0` for `floorDiv(0, 4)`. + * This returns `-1` for `floorDiv(-1, 4)`. + * This returns `-1` for `floorDiv(-2, 4)`. + * This returns `-1` for `floorDiv(-3, 4)`. + * This returns `-1` for `floorDiv(-4, 4)`. + * This returns `-2` for `floorDiv(-5, 4)`. * * @param a the dividend * @param b the divisor @@ -152,13 +152,13 @@ internal fun floorDiv(a: Long, b: Long): Long = if (a >= 0) a / b else (a + 1) / /** * Returns the floor division. - *

- * This returns {@code 0} for {@code floorDiv(0, 4)}.
- * This returns {@code -1} for {@code floorDiv(-1, 4)}.
- * This returns {@code -1} for {@code floorDiv(-2, 4)}.
- * This returns {@code -1} for {@code floorDiv(-3, 4)}.
- * This returns {@code -1} for {@code floorDiv(-4, 4)}.
- * This returns {@code -2} for {@code floorDiv(-5, 4)}.
+ * + * This returns `0` for `floorDiv(0, 4)`. + * This returns `-1` for `floorDiv(-1, 4)`. + * This returns `-1` for `floorDiv(-2, 4)`. + * This returns `-1` for `floorDiv(-3, 4)`. + * This returns `-1` for `floorDiv(-4, 4)`. + * This returns `-2` for `floorDiv(-5, 4)`. * * @param a the dividend * @param b the divisor @@ -169,12 +169,11 @@ internal fun floorDiv(a: Int, b: Int): Int = if (a >= 0) a / b else (a + 1) / b /** * Returns the floor modulus. * - * - * This returns `0` for `floorMod(0, 4)`.

- * This returns `1` for `floorMod(-1, 4)`.

- * This returns `2` for `floorMod(-2, 4)`.

- * This returns `3` for `floorMod(-3, 4)`.

- * This returns `0` for `floorMod(-4, 4)`.

+ * This returns `0` for `floorMod(0, 4)`. + * This returns `1` for `floorMod(-1, 4)`. + * This returns `2` for `floorMod(-2, 4)`. + * This returns `3` for `floorMod(-3, 4)`. + * This returns `0` for `floorMod(-4, 4)`. * * @param a the dividend * @param b the divisor @@ -185,12 +184,11 @@ internal fun floorMod(a: Long, b: Long): Long = (a % b + b) % b /** * Returns the floor modulus. * - * - * This returns `0` for `floorMod(0, 4)`.

- * This returns `1` for `floorMod(-1, 4)`.

- * This returns `2` for `floorMod(-2, 4)`.

- * This returns `3` for `floorMod(-3, 4)`.

- * This returns `0` for `floorMod(-4, 4)`.

+ * This returns `0` for `floorMod(0, 4)`. + * This returns `1` for `floorMod(-1, 4)`. + * This returns `2` for `floorMod(-2, 4)`. + * This returns `3` for `floorMod(-3, 4)`. + * This returns `0` for `floorMod(-4, 4)`. * * @param a the dividend * @param b the divisor From 4df4076f2e81ef80c2124648ddf0c4312e71e568 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 11 Sep 2020 10:46:00 +0300 Subject: [PATCH 4/4] Add a comment explaining a workaround --- core/jsMain/src/Instant.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/jsMain/src/Instant.kt b/core/jsMain/src/Instant.kt index 1596cbb63..b45669349 100644 --- a/core/jsMain/src/Instant.kt +++ b/core/jsMain/src/Instant.kt @@ -80,6 +80,9 @@ public actual class Instant internal constructor(internal val value: jtInstant) } 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(), + Instant.fromEpochSeconds(0, Long.MAX_VALUE).nanosecondsOfSecond) */ val secs = safeAdd(epochSeconds, floorDiv(nanosecondAdjustment, NANOS_PER_ONE.toLong())) val nos = floorMod(nanosecondAdjustment, NANOS_PER_ONE.toLong()).toInt() Instant(jtInstant.ofEpochSecond(secs, nos))