Skip to content

Commit 6a16657

Browse files
committed
Simplify the code for Instant arithmetics
1 parent 0295867 commit 6a16657

File tree

6 files changed

+57
-74
lines changed

6 files changed

+57
-74
lines changed

core/native/src/Instant.kt

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -252,33 +252,39 @@ public actual class Instant internal constructor(public actual val epochSeconds:
252252

253253
}
254254

255-
private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try {
256-
toZonedDateTime(zone)
255+
private fun Instant.toLocalDateTimeFailing(offset: UtcOffset): LocalDateTime = try {
256+
toLocalDateTimeImpl(offset)
257257
} catch (e: IllegalArgumentException) {
258258
throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e)
259259
}
260260

261-
/**
262-
* @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime]
263-
*/
264-
private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime {
265-
val currentOffset = zone.offsetAt(this)
266-
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), currentOffset)
267-
}
268-
269-
/** Check that [Instant] fits in [ZonedDateTime].
261+
/** Check that [Instant] fits in [LocalDateTime].
270262
* This is done on the results of computations for consistency with other platforms.
271263
*/
272264
private fun Instant.check(zone: TimeZone): Instant = this@check.also {
273-
toZonedDateTimeFailing(zone)
265+
toLocalDateTimeFailing(offsetIn(zone))
274266
}
275267

276268
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
277269
with(period) {
278-
val withDate = toZonedDateTimeFailing(timeZone)
279-
.run { if (totalMonths != 0) timeZone.atZone(dateTime.plus(totalMonths, DateTimeUnit.MONTH), offset) else this }
280-
.run { if (days != 0) timeZone.atZone(dateTime.plus(days, DateTimeUnit.DAY), offset) else this }
281-
withDate.toInstant()
270+
val initialOffset = offsetIn(timeZone)
271+
val initialLdt = toLocalDateTimeFailing(initialOffset)
272+
val offsetAfterMonths: UtcOffset
273+
val ldtAfterMonths: LocalDateTime
274+
if (totalMonths != 0) {
275+
val (ldt, offset) = timeZone.atZone(initialLdt.plus(totalMonths, DateTimeUnit.MONTH), initialOffset)
276+
offsetAfterMonths = offset
277+
ldtAfterMonths = ldt
278+
} else {
279+
offsetAfterMonths = initialOffset
280+
ldtAfterMonths = initialLdt
281+
}
282+
val instantAfterMonthsAndDays = if (days != 0) {
283+
timeZone.atZone(ldtAfterMonths.plus(days, DateTimeUnit.DAY), offsetAfterMonths).toInstant()
284+
} else {
285+
ldtAfterMonths.toInstant(offsetAfterMonths)
286+
}
287+
instantAfterMonthsAndDays
282288
.run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this }
283289
}.check(timeZone)
284290
} catch (e: ArithmeticException) {
@@ -299,11 +305,9 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
299305
is DateTimeUnit.DateBased -> {
300306
if (value < Int.MIN_VALUE || value > Int.MAX_VALUE)
301307
throw ArithmeticException("Can't add a Long date-based value, as it would cause an overflow")
302-
val toZonedDateTimeFailing = toZonedDateTimeFailing(timeZone)
303-
timeZone.atZone(
304-
toZonedDateTimeFailing.dateTime.plus(value.toInt(), unit),
305-
toZonedDateTimeFailing.offset
306-
).toInstant()
308+
val preferredOffset = offsetIn(timeZone)
309+
val initialLdt = toLocalDateTimeFailing(preferredOffset)
310+
timeZone.atZone(initialLdt.plus(value.toInt(), unit), preferredOffset).toInstant()
307311
}
308312
is DateTimeUnit.TimeBased ->
309313
check(timeZone).plus(value, unit).check(timeZone)
@@ -326,31 +330,23 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
326330
}
327331

328332
public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod {
329-
var thisLdt = toZonedDateTimeFailing(timeZone)
330-
val otherLdt = other.toZonedDateTimeFailing(timeZone)
331-
332-
val months =
333-
thisLdt.dateTime.until(otherLdt.dateTime, DateTimeUnit.MONTH).toLong().toInt() // `until` on dates never fails
334-
thisLdt = timeZone.atZone(
335-
thisLdt.dateTime.plus(months, DateTimeUnit.MONTH),
336-
thisLdt.offset
337-
) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
338-
val days =
339-
thisLdt.dateTime.until(otherLdt.dateTime, DateTimeUnit.DAY).toLong().toInt() // `until` on dates never fails
340-
thisLdt = timeZone.atZone(
341-
thisLdt.dateTime.plus(days, DateTimeUnit.DAY),
342-
thisLdt.offset
343-
) // won't throw: thisLdt + days <= otherLdt
344-
val nanoseconds =
345-
thisLdt.toInstant().until(otherLdt.toInstant(), DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
333+
val thisOffset1 = offsetIn(timeZone)
334+
val thisLdt1 = toLocalDateTimeFailing(thisOffset1)
335+
val otherLdt = other.toLocalDateTimeFailing(other.offsetIn(timeZone))
336+
337+
val months = thisLdt1.until(otherLdt, DateTimeUnit.MONTH).toLong().toInt() // `until` on dates never fails
338+
val (thisLdt2, thisOffset2) = timeZone.atZone(thisLdt1.plus(months, DateTimeUnit.MONTH), thisOffset1) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
339+
val days = thisLdt2.until(otherLdt, DateTimeUnit.DAY).toLong().toInt() // `until` on dates never fails
340+
val (thisLdt3, thisOffset3) = timeZone.atZone(thisLdt2.plus(days, DateTimeUnit.DAY), thisOffset2) // won't throw: thisLdt + days <= otherLdt
341+
val nanoseconds = thisLdt3.toInstant(thisOffset3).until(other, DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
346342

347343
return buildDateTimePeriod(months, days, nanoseconds)
348344
}
349345

350346
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long =
351347
when (unit) {
352348
is DateTimeUnit.DateBased ->
353-
toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit)
349+
toLocalDateTimeFailing(offsetIn(timeZone)).until(other.toLocalDateTimeFailing(other.offsetIn(timeZone)), unit)
354350
.toLong()
355351
is DateTimeUnit.TimeBased -> {
356352
check(timeZone); other.check(timeZone)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2019-2020 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
/* Based on the ThreeTenBp project.
6+
* Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
7+
*/
8+
9+
package kotlinx.datetime
10+
11+
internal data class LocalDateTimeWithOffset(val dateTime: LocalDateTime, val offset: UtcOffset)
12+
13+
internal fun LocalDateTimeWithOffset.toInstant(): Instant =
14+
Instant(dateTime.toEpochSecond(offset), dateTime.nanosecond)

core/native/src/TimeZone.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public actual open class TimeZone internal constructor() {
8888
internal open fun localDateTimeToInstant(dateTime: LocalDateTime): Instant =
8989
atZone(dateTime).toInstant()
9090

91-
internal open fun atZone(dateTime: LocalDateTime, preferred: UtcOffset? = null): ZonedDateTime =
91+
internal open fun atZone(dateTime: LocalDateTime, preferred: UtcOffset? = null): LocalDateTimeWithOffset =
9292
error("Should be overridden")
9393

9494
override fun equals(other: Any?): Boolean =
@@ -112,8 +112,8 @@ public actual class FixedOffsetTimeZone internal constructor(public actual val o
112112

113113
override fun offsetAtImpl(instant: Instant): UtcOffset = offset
114114

115-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
116-
ZonedDateTime(dateTime, offset)
115+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): LocalDateTimeWithOffset =
116+
LocalDateTimeWithOffset(dateTime, offset)
117117

118118
override fun instantToLocalDateTime(instant: Instant): LocalDateTime = instant.toLocalDateTime(offset)
119119
override fun localDateTimeToInstant(dateTime: LocalDateTime): Instant = dateTime.toInstant(offset)

core/native/src/ZonedDateTime.kt

Lines changed: 0 additions & 27 deletions
This file was deleted.

core/native/src/internal/RegionTimeZone.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id:
2727
}
2828
}
2929

30-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
30+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): LocalDateTimeWithOffset =
3131
when (val info = tzid.infoAtDatetime(dateTime)) {
32-
is OffsetInfo.Regular -> ZonedDateTime(dateTime, info.offset)
32+
is OffsetInfo.Regular -> LocalDateTimeWithOffset(dateTime, info.offset)
3333
is OffsetInfo.Gap -> {
3434
try {
35-
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), info.offsetAfter)
35+
LocalDateTimeWithOffset(dateTime.plusSeconds(info.transitionDurationSeconds), info.offsetAfter)
3636
} catch (e: IllegalArgumentException) {
3737
throw DateTimeArithmeticException(
3838
"Overflow whet correcting the date-time to not be in the transition gap",
@@ -41,7 +41,7 @@ internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id:
4141
}
4242
}
4343

44-
is OffsetInfo.Overlap -> ZonedDateTime(
44+
is OffsetInfo.Overlap -> LocalDateTimeWithOffset(
4545
dateTime,
4646
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore
4747
)

core/native/test/ThreeTenBpTimeZoneTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ThreeTenBpTimeZoneTest {
4141
fun overlappingLocalTime() {
4242
val t = LocalDateTime(2007, 10, 28, 2, 30, 0, 0)
4343
val zone = TimeZone.of("Europe/Paris")
44-
assertEquals(ZonedDateTime(
44+
assertEquals(LocalDateTimeWithOffset(
4545
LocalDateTime(2007, 10, 28, 2, 30, 0, 0),
4646
UtcOffset(seconds = 2 * 3600)
4747
), zone.atZone(t))

0 commit comments

Comments
 (0)