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

Add some more Duration and Instant helper utils #139

Merged
merged 4 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 90 additions & 8 deletions src/main/kotlin/org/jitsi/utils/Duration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
65 changes: 65 additions & 0 deletions src/main/kotlin/org/jitsi/utils/Instant.kt
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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)
}
15 changes: 15 additions & 0 deletions src/test/kotlin/org/jitsi/utils/DurationTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,29 @@ 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)
5.hours shouldBe Duration.ofHours(5)
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
}
}
}
31 changes: 31 additions & 0 deletions src/test/kotlin/org/jitsi/utils/InstantTest.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading