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..b45669349 100644 --- a/core/jsMain/src/Instant.kt +++ b/core/jsMain/src/Instant.kt @@ -80,6 +80,18 @@ 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)) + } catch (e: Throwable) { + if (!e.isJodaDateTimeException() && e !is ArithmeticException) throw e + 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 diff --git a/core/jsMain/src/mathJs.kt b/core/jsMain/src/mathJs.kt index fcd819262..db3321d0a 100644 --- a/core/jsMain/src/mathJs.kt +++ b/core/jsMain/src/mathJs.kt @@ -58,3 +58,34 @@ 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 `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 + * @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 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) 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