diff --git a/src/main/kotlin/org/jitsi/utils/Duration.kt b/src/main/kotlin/org/jitsi/utils/Duration.kt index 0362159..e735d0a 100644 --- a/src/main/kotlin/org/jitsi/utils/Duration.kt +++ b/src/main/kotlin/org/jitsi/utils/Duration.kt @@ -17,28 +17,110 @@ package org.jitsi.utils import java.time.Duration +import java.time.temporal.ChronoUnit /** - * Helpers to create instance of [Duration] more easily + * Helpers to create instances of [Duration] more easily, and Kotlin operators for it */ -val Number.nanos: Duration +val Int.nanos: Duration get() = Duration.ofNanos(this.toLong()) -val Number.ms: Duration +val Int.micros: Duration + get() = Duration.of(this.toLong(), ChronoUnit.MICROS) + +val Int.ms: Duration get() = Duration.ofMillis(this.toLong()) -val Number.secs: Duration +val Int.secs: Duration get() = Duration.ofSeconds(this.toLong()) -val Number.hours: Duration +val Int.hours: Duration get() = Duration.ofHours(this.toLong()) -val Number.mins: Duration +val Int.mins: Duration get() = Duration.ofMinutes(this.toLong()) -val Number.days: Duration +val Int.days: Duration get() = Duration.ofDays(this.toLong()) -operator fun Duration.times(x: Int): Duration = Duration.ofNanos(toNanos() * x) +val Long.nanos: Duration + get() = Duration.ofNanos(this) + +val Long.micros: Duration + get() = Duration.of(this, ChronoUnit.MICROS) + +val Long.ms: Duration + get() = Duration.ofMillis(this) + +val Long.secs: Duration + get() = Duration.ofSeconds(this) + +val Long.hours: Duration + get() = Duration.ofHours(this) + +val Long.mins: Duration + get() = Duration.ofMinutes(this) + +val Long.days: Duration + get() = Duration.ofDays(this) + +operator fun Duration.times(x: Int): Duration = this.multipliedBy(x.toLong()) +operator fun Duration.times(x: Long): Duration = this.multipliedBy(x) + +operator fun Int.times(x: Duration): Duration = x.multipliedBy(this.toLong()) +operator fun Long.times(x: Duration): Duration = x.multipliedBy(this) + operator fun Duration.div(other: Duration): Double = toNanos().toDouble() / other.toNanos() + +/** Converts this duration to the total length in milliseconds, rounded to nearest. */ +fun Duration.toRoundedMillis(): Long { + var millis = this.toMillis() + val remainder = nano % NANOS_PER_MILLI + if (remainder > 499_999) { + millis++ + } + return millis +} + +/** + * Converts this duration to the total length in microseconds. + * + * If this duration is too large to fit in a `long` microseconds, then an + * exception is thrown. + * + * If this duration has greater than microseconds precision, then the conversion + * will drop any excess precision information as though the amount in nanoseconds + * was subject to integer division by one thousand. + * + * @return the total length of the duration in microseconds + * @throws ArithmeticException if numeric overflow occurs + */ +fun Duration.toMicros(): Long { + var tempSeconds: Long = seconds + var tempNanos: Long = nano.toLong() + if (tempSeconds < 0) { + // change the seconds and nano value to + // handle Long.MIN_VALUE case + tempSeconds += 1 + tempNanos -= NANOS_PER_SECOND + } + var micros = Math.multiplyExact(tempSeconds, MICROS_PER_SECOND) + micros = Math.addExact(micros, tempNanos / NANOS_PER_MICRO) + return micros +} + +/** Converts this duration to the total length in microseconds, rounded to nearest. */ +fun Duration.toRoundedMicros(): Long { + var micros = this.toMicros() + val remainder = nano % NANOS_PER_MICRO + if (remainder > 499) { + micros++ + } + return micros +} + +private const val NANOS_PER_MICRO = 1_000 +private const val NANOS_PER_MILLI = 1_000_000 +private const val MICROS_PER_SECOND = 1_000_000 +private const val NANOS_PER_SECOND = 1_000_000_000 diff --git a/src/main/kotlin/org/jitsi/utils/Instant.kt b/src/main/kotlin/org/jitsi/utils/Instant.kt new file mode 100644 index 0000000..b990b88 --- /dev/null +++ b/src/main/kotlin/org/jitsi/utils/Instant.kt @@ -0,0 +1,65 @@ +/* + * Copyright @ 2018 - present 8x8, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jitsi.utils + +import java.time.Instant + +/** + * Helpers to create instances of [Instant] more easily, and Kotlin operators for it + */ + +/** + * Converts this instant to the number of microseconds from the epoch + * of 1970-01-01T00:00:00Z. + * + * + * If this instant represents a point on the time-line too far in the future + * or past to fit in a `long` microseconds, then an exception is thrown. + * + * If this instant has greater than microseconds precision, then the conversion + * will drop any excess precision information as though the amount in nanoseconds + * was subject to integer division by one thousand. + * + * @return the number of microseconds since the epoch of 1970-01-01T00:00:00Z + * @throws ArithmeticException if numeric overflow occurs + */ +fun Instant.toEpochMicro(): Long { + if (epochSecond < 0 && nano > 0) { + val millis = Math.multiplyExact(epochSecond + 1, 1_000_000).toLong() + val adjustment: Long = (nano / 1_000 - 1).toLong() + return Math.addExact(millis, adjustment) + } else { + val millis = Math.multiplyExact(epochSecond, 1_000_000).toLong() + return Math.addExact(millis, (nano / 1000).toLong()) + } +} + +/** + * Obtains an instance of {@code Instant} using microseconds from the + * epoch of 1970-01-01T00:00:00Z. + *

+ * The seconds and nanoseconds are extracted from the specified microseconds. + * + * @param epochMicro the number of microseconds from 1970-01-01T00:00:00Z + * @return an instant, not null + * @throws DateTimeException if the instant exceeds the maximum or minimum instant + */ +fun instantOfEpochMicro(epochMicro: Long): Instant { + val secs = Math.floorDiv(epochMicro, 1_000_000) + val mos = Math.floorMod(epochMicro, 1_000_000).toLong() + return Instant.ofEpochSecond(secs, mos * 1000) +} diff --git a/src/test/kotlin/org/jitsi/utils/DurationTest.kt b/src/test/kotlin/org/jitsi/utils/DurationTest.kt index 93d34ba..7341f89 100644 --- a/src/test/kotlin/org/jitsi/utils/DurationTest.kt +++ b/src/test/kotlin/org/jitsi/utils/DurationTest.kt @@ -24,6 +24,7 @@ class DurationTest : ShouldSpec() { init { context("the duration helpers should work") { 5.nanos shouldBe Duration.ofNanos(5) + 5.micros shouldBe Duration.ofNanos(5_000) 5.ms shouldBe Duration.ofMillis(5) 5.secs shouldBe Duration.ofSeconds(5) 5.mins shouldBe Duration.ofMinutes(5) @@ -31,7 +32,21 @@ class DurationTest : ShouldSpec() { 5.days shouldBe Duration.ofDays(5) 1.days * 2 shouldBe Duration.ofDays(2) + 2 * 4.hours shouldBe Duration.ofHours(8) 10.days / 2.days shouldBe 5.0 + 5.hours / 2.hours shouldBe 2.5 + + 4.ms.toMicros() shouldBe 4000 + 2200.nanos.toMicros() shouldBe 2 + 2900.nanos.toMicros() shouldBe 2 + + 2200.nanos.toRoundedMicros() shouldBe 2 + 2500.nanos.toRoundedMicros() shouldBe 3 + 2900.nanos.toRoundedMicros() shouldBe 3 + + 2200.micros.toRoundedMillis() shouldBe 2 + 2500.micros.toRoundedMillis() shouldBe 3 + 2900.micros.toRoundedMillis() shouldBe 3 } } } diff --git a/src/test/kotlin/org/jitsi/utils/InstantTest.kt b/src/test/kotlin/org/jitsi/utils/InstantTest.kt new file mode 100644 index 0000000..426021e --- /dev/null +++ b/src/test/kotlin/org/jitsi/utils/InstantTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright @ 2018 - present 8x8, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jitsi.utils + +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import java.time.Instant + +class InstantTest : ShouldSpec() { + init { + context("The instant helpers should work") { + Instant.ofEpochMilli(123).toEpochMicro() shouldBe 123000L + instantOfEpochMicro(123456).toEpochMilli() shouldBe 123L + instantOfEpochMicro(123987).toEpochMilli() shouldBe 123L + } + } +}