Skip to content

Commit 6af1020

Browse files
Add some more Duration and Instant helper utils (#139)
* Add some more Duration helpers. * Add Duration.toMicros() / toRoundedMicros() / toRoundedMillis(). * Add some helpers for Instant. * Avoid using `Number` for helper functions that cast to Long. The results are unexpected and undesirable for Float and Double.
1 parent 83984af commit 6af1020

File tree

4 files changed

+201
-8
lines changed

4 files changed

+201
-8
lines changed

src/main/kotlin/org/jitsi/utils/Duration.kt

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,110 @@
1717
package org.jitsi.utils
1818

1919
import java.time.Duration
20+
import java.time.temporal.ChronoUnit
2021

2122
/**
22-
* Helpers to create instance of [Duration] more easily
23+
* Helpers to create instances of [Duration] more easily, and Kotlin operators for it
2324
*/
2425

25-
val Number.nanos: Duration
26+
val Int.nanos: Duration
2627
get() = Duration.ofNanos(this.toLong())
2728

28-
val Number.ms: Duration
29+
val Int.micros: Duration
30+
get() = Duration.of(this.toLong(), ChronoUnit.MICROS)
31+
32+
val Int.ms: Duration
2933
get() = Duration.ofMillis(this.toLong())
3034

31-
val Number.secs: Duration
35+
val Int.secs: Duration
3236
get() = Duration.ofSeconds(this.toLong())
3337

34-
val Number.hours: Duration
38+
val Int.hours: Duration
3539
get() = Duration.ofHours(this.toLong())
3640

37-
val Number.mins: Duration
41+
val Int.mins: Duration
3842
get() = Duration.ofMinutes(this.toLong())
3943

40-
val Number.days: Duration
44+
val Int.days: Duration
4145
get() = Duration.ofDays(this.toLong())
4246

43-
operator fun Duration.times(x: Int): Duration = Duration.ofNanos(toNanos() * x)
47+
val Long.nanos: Duration
48+
get() = Duration.ofNanos(this)
49+
50+
val Long.micros: Duration
51+
get() = Duration.of(this, ChronoUnit.MICROS)
52+
53+
val Long.ms: Duration
54+
get() = Duration.ofMillis(this)
55+
56+
val Long.secs: Duration
57+
get() = Duration.ofSeconds(this)
58+
59+
val Long.hours: Duration
60+
get() = Duration.ofHours(this)
61+
62+
val Long.mins: Duration
63+
get() = Duration.ofMinutes(this)
64+
65+
val Long.days: Duration
66+
get() = Duration.ofDays(this)
67+
68+
operator fun Duration.times(x: Int): Duration = this.multipliedBy(x.toLong())
69+
operator fun Duration.times(x: Long): Duration = this.multipliedBy(x)
70+
71+
operator fun Int.times(x: Duration): Duration = x.multipliedBy(this.toLong())
72+
operator fun Long.times(x: Duration): Duration = x.multipliedBy(this)
73+
4474
operator fun Duration.div(other: Duration): Double = toNanos().toDouble() / other.toNanos()
75+
76+
/** Converts this duration to the total length in milliseconds, rounded to nearest. */
77+
fun Duration.toRoundedMillis(): Long {
78+
var millis = this.toMillis()
79+
val remainder = nano % NANOS_PER_MILLI
80+
if (remainder > 499_999) {
81+
millis++
82+
}
83+
return millis
84+
}
85+
86+
/**
87+
* Converts this duration to the total length in microseconds.
88+
*
89+
* If this duration is too large to fit in a `long` microseconds, then an
90+
* exception is thrown.
91+
*
92+
* If this duration has greater than microseconds precision, then the conversion
93+
* will drop any excess precision information as though the amount in nanoseconds
94+
* was subject to integer division by one thousand.
95+
*
96+
* @return the total length of the duration in microseconds
97+
* @throws ArithmeticException if numeric overflow occurs
98+
*/
99+
fun Duration.toMicros(): Long {
100+
var tempSeconds: Long = seconds
101+
var tempNanos: Long = nano.toLong()
102+
if (tempSeconds < 0) {
103+
// change the seconds and nano value to
104+
// handle Long.MIN_VALUE case
105+
tempSeconds += 1
106+
tempNanos -= NANOS_PER_SECOND
107+
}
108+
var micros = Math.multiplyExact(tempSeconds, MICROS_PER_SECOND)
109+
micros = Math.addExact(micros, tempNanos / NANOS_PER_MICRO)
110+
return micros
111+
}
112+
113+
/** Converts this duration to the total length in microseconds, rounded to nearest. */
114+
fun Duration.toRoundedMicros(): Long {
115+
var micros = this.toMicros()
116+
val remainder = nano % NANOS_PER_MICRO
117+
if (remainder > 499) {
118+
micros++
119+
}
120+
return micros
121+
}
122+
123+
private const val NANOS_PER_MICRO = 1_000
124+
private const val NANOS_PER_MILLI = 1_000_000
125+
private const val MICROS_PER_SECOND = 1_000_000
126+
private const val NANOS_PER_SECOND = 1_000_000_000
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright @ 2018 - present 8x8, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jitsi.utils
18+
19+
import java.time.Instant
20+
21+
/**
22+
* Helpers to create instances of [Instant] more easily, and Kotlin operators for it
23+
*/
24+
25+
/**
26+
* Converts this instant to the number of microseconds from the epoch
27+
* of 1970-01-01T00:00:00Z.
28+
*
29+
*
30+
* If this instant represents a point on the time-line too far in the future
31+
* or past to fit in a `long` microseconds, then an exception is thrown.
32+
*
33+
* If this instant has greater than microseconds precision, then the conversion
34+
* will drop any excess precision information as though the amount in nanoseconds
35+
* was subject to integer division by one thousand.
36+
*
37+
* @return the number of microseconds since the epoch of 1970-01-01T00:00:00Z
38+
* @throws ArithmeticException if numeric overflow occurs
39+
*/
40+
fun Instant.toEpochMicro(): Long {
41+
if (epochSecond < 0 && nano > 0) {
42+
val millis = Math.multiplyExact(epochSecond + 1, 1_000_000).toLong()
43+
val adjustment: Long = (nano / 1_000 - 1).toLong()
44+
return Math.addExact(millis, adjustment)
45+
} else {
46+
val millis = Math.multiplyExact(epochSecond, 1_000_000).toLong()
47+
return Math.addExact(millis, (nano / 1000).toLong())
48+
}
49+
}
50+
51+
/**
52+
* Obtains an instance of {@code Instant} using microseconds from the
53+
* epoch of 1970-01-01T00:00:00Z.
54+
* <p>
55+
* The seconds and nanoseconds are extracted from the specified microseconds.
56+
*
57+
* @param epochMicro the number of microseconds from 1970-01-01T00:00:00Z
58+
* @return an instant, not null
59+
* @throws DateTimeException if the instant exceeds the maximum or minimum instant
60+
*/
61+
fun instantOfEpochMicro(epochMicro: Long): Instant {
62+
val secs = Math.floorDiv(epochMicro, 1_000_000)
63+
val mos = Math.floorMod(epochMicro, 1_000_000).toLong()
64+
return Instant.ofEpochSecond(secs, mos * 1000)
65+
}

src/test/kotlin/org/jitsi/utils/DurationTest.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,29 @@ class DurationTest : ShouldSpec() {
2424
init {
2525
context("the duration helpers should work") {
2626
5.nanos shouldBe Duration.ofNanos(5)
27+
5.micros shouldBe Duration.ofNanos(5_000)
2728
5.ms shouldBe Duration.ofMillis(5)
2829
5.secs shouldBe Duration.ofSeconds(5)
2930
5.mins shouldBe Duration.ofMinutes(5)
3031
5.hours shouldBe Duration.ofHours(5)
3132
5.days shouldBe Duration.ofDays(5)
3233

3334
1.days * 2 shouldBe Duration.ofDays(2)
35+
2 * 4.hours shouldBe Duration.ofHours(8)
3436
10.days / 2.days shouldBe 5.0
37+
5.hours / 2.hours shouldBe 2.5
38+
39+
4.ms.toMicros() shouldBe 4000
40+
2200.nanos.toMicros() shouldBe 2
41+
2900.nanos.toMicros() shouldBe 2
42+
43+
2200.nanos.toRoundedMicros() shouldBe 2
44+
2500.nanos.toRoundedMicros() shouldBe 3
45+
2900.nanos.toRoundedMicros() shouldBe 3
46+
47+
2200.micros.toRoundedMillis() shouldBe 2
48+
2500.micros.toRoundedMillis() shouldBe 3
49+
2900.micros.toRoundedMillis() shouldBe 3
3550
}
3651
}
3752
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright @ 2018 - present 8x8, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jitsi.utils
18+
19+
import io.kotest.core.spec.style.ShouldSpec
20+
import io.kotest.matchers.shouldBe
21+
import java.time.Instant
22+
23+
class InstantTest : ShouldSpec() {
24+
init {
25+
context("The instant helpers should work") {
26+
Instant.ofEpochMilli(123).toEpochMicro() shouldBe 123000L
27+
instantOfEpochMicro(123456).toEpochMilli() shouldBe 123L
28+
instantOfEpochMicro(123987).toEpochMilli() shouldBe 123L
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)