Skip to content

Commit a76d541

Browse files
committed
Specialize Instant.until to avoid intermediate result clamping
1 parent 4167c60 commit a76d541

File tree

4 files changed

+28
-52
lines changed

4 files changed

+28
-52
lines changed

core/commonMain/src/DateTimeUnit.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,9 @@ sealed class DateTimeUnit {
1313

1414
abstract operator fun times(scalar: Int): DateTimeUnit
1515

16-
internal abstract val calendarUnit: CalendarUnit
17-
internal abstract val calendarScale: Long
18-
1916
class TimeBased(val nanoseconds: Long) : DateTimeUnit() {
20-
internal override val calendarUnit: CalendarUnit
21-
internal override val calendarScale: Long
17+
internal val calendarUnit: CalendarUnit
18+
internal val calendarScale: Long
2219

2320
init {
2421
require(nanoseconds > 0) { "Unit duration must be positive, but was $nanoseconds ns." }
@@ -72,9 +69,6 @@ sealed class DateTimeUnit {
7269

7370
override fun times(scalar: Int): DayBased = DayBased(safeMultiply(days, scalar))
7471

75-
internal override val calendarUnit: CalendarUnit get() = CalendarUnit.DAY
76-
internal override val calendarScale: Long get() = days.toLong()
77-
7872
override fun equals(other: Any?): Boolean =
7973
this === other || (other is DayBased && this.days == other.days)
8074

@@ -92,9 +86,6 @@ sealed class DateTimeUnit {
9286

9387
override fun times(scalar: Int): MonthBased = MonthBased(safeMultiply(months, scalar))
9488

95-
internal override val calendarUnit: CalendarUnit get() = CalendarUnit.MONTH
96-
internal override val calendarScale: Long get() = months.toLong()
97-
9889
override fun equals(other: Any?): Boolean =
9990
this === other || (other is MonthBased && this.months == other.months)
10091

core/commonTest/src/InstantTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,9 @@ class InstantTest {
170170
val end = start.plus(300, DateTimeUnit.YEAR, zone)
171171
val diffNs = start.until(end, unit500ns, zone)
172172
val diffUs = start.until(end, DateTimeUnit.MICROSECOND, zone)
173-
// TODO: avoid clamping/overflowing in intermediate results in JVM
174-
// assertEquals(diffUs * 2, diffNs)
173+
assertEquals(diffUs * 2, diffNs)
175174

176-
// assertEquals(end, start.plus(diffNs, unit500ns, zone))
175+
assertEquals(end, start.plus(diffNs, unit500ns, zone))
177176
assertEquals(start, end.plus(-diffUs, DateTimeUnit.MICROSECOND, zone))
178177
}
179178

core/jsMain/src/Instant.kt

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -200,31 +200,20 @@ public actual fun Instant.periodUntil(other: Instant, zone: TimeZone): DateTimeP
200200
}
201201

202202
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, zone: TimeZone): Long = try {
203-
when (unit) {
204-
is DateTimeUnit.DateBased ->
205-
this.value.atZone(zone).until(other.value.atZone(zone), unit.calendarUnit.toChronoUnit()).toLong() / unit.calendarScale
203+
val thisZdt = this.value.atZone(zone)
204+
val otherZdt = other.value.atZone(zone)
205+
when(unit) {
206206
is DateTimeUnit.TimeBased -> {
207-
this.value.atZone(zone)
208-
other.value.atZone(zone)
209-
try {
210-
multiplyAddAndDivide(
211-
(other.value.epochSecond().toDouble() - this.value.epochSecond().toDouble()).toLong(),
212-
NANOS_PER_ONE.toLong(),
213-
(other.nanosecondsOfSecond - this.nanosecondsOfSecond).toLong(),
214-
unit.nanoseconds)
215-
} catch (e: ArithmeticException) {
216-
if (this < other) Long.MAX_VALUE else Long.MIN_VALUE
217-
}
207+
multiplyAddAndDivide(other.epochSeconds - epochSeconds,
208+
NANOS_PER_ONE.toLong(),
209+
(other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(),
210+
unit.nanoseconds)
218211
}
212+
is DateTimeUnit.DateBased.DayBased -> (thisZdt.until(otherZdt, ChronoUnit.DAYS).toDouble() / unit.days).toLong()
213+
is DateTimeUnit.DateBased.MonthBased -> (thisZdt.until(otherZdt, ChronoUnit.MONTHS).toDouble() / unit.months).toLong()
219214
}
215+
} catch (e: ArithmeticException) {
216+
if (this < other) Long.MAX_VALUE else Long.MIN_VALUE
220217
} catch (e: Throwable) {
221218
if (e.isJodaDateTimeException()) throw DateTimeArithmeticException(e) else throw e
222219
}
223-
224-
225-
private fun CalendarUnit.toChronoUnit(): ChronoUnit = when(this) {
226-
CalendarUnit.YEAR -> ChronoUnit.YEARS
227-
CalendarUnit.MONTH -> ChronoUnit.MONTHS
228-
CalendarUnit.DAY -> ChronoUnit.DAYS
229-
else -> error("CalendarUnit $this should not be used")
230-
}

core/jvmMain/src/Instant.kt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -145,25 +145,22 @@ public actual fun Instant.periodUntil(other: Instant, zone: TimeZone): DateTimeP
145145
}
146146
}
147147

148-
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, zone: TimeZone): Long =
149-
until(other, unit.calendarUnit.toChronoUnit(), zone.zoneId) / unit.calendarScale
150-
151-
private fun Instant.until(other: Instant, unit: ChronoUnit, zone: ZoneId): Long = try {
152-
this.value.atZone(zone).until(other.value.atZone(zone), unit)
148+
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, zone: TimeZone): Long = try {
149+
val thisZdt = this.atZone(zone)
150+
val otherZdt = other.atZone(zone)
151+
when(unit) {
152+
is DateTimeUnit.TimeBased -> {
153+
multiplyAddAndDivide(other.epochSeconds - epochSeconds,
154+
NANOS_PER_ONE.toLong(),
155+
(other.nanosecondsOfSecond - nanosecondsOfSecond).toLong(),
156+
unit.nanoseconds)
157+
}
158+
is DateTimeUnit.DateBased.DayBased -> thisZdt.until(otherZdt, ChronoUnit.DAYS) / unit.days
159+
is DateTimeUnit.DateBased.MonthBased -> thisZdt.until(otherZdt, ChronoUnit.MONTHS) / unit.months
160+
}
153161
} catch (e: DateTimeException) {
154162
throw DateTimeArithmeticException(e)
155163
} catch (e: ArithmeticException) {
156164
if (this.value < other.value) Long.MAX_VALUE else Long.MIN_VALUE
157165
}
158166

159-
private fun CalendarUnit.toChronoUnit(): ChronoUnit = when(this) {
160-
CalendarUnit.YEAR -> ChronoUnit.YEARS
161-
CalendarUnit.MONTH -> ChronoUnit.MONTHS
162-
CalendarUnit.DAY -> ChronoUnit.DAYS
163-
CalendarUnit.HOUR -> ChronoUnit.HOURS
164-
CalendarUnit.MINUTE -> ChronoUnit.MINUTES
165-
CalendarUnit.SECOND -> ChronoUnit.SECONDS
166-
CalendarUnit.MILLISECOND -> ChronoUnit.MILLIS
167-
CalendarUnit.MICROSECOND -> ChronoUnit.MICROS
168-
CalendarUnit.NANOSECOND -> ChronoUnit.NANOS
169-
}

0 commit comments

Comments
 (0)