Skip to content

Commit f5aa7d4

Browse files
committed
Simplify the code for Instant arithmetics
1 parent 2d8ca25 commit f5aa7d4

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
@@ -150,33 +150,39 @@ public actual class Instant internal constructor(public actual val epochSeconds:
150150

151151
}
152152

153-
private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try {
154-
toZonedDateTime(zone)
153+
private fun Instant.toLocalDateTimeFailing(offset: UtcOffset): LocalDateTime = try {
154+
toLocalDateTimeImpl(offset)
155155
} catch (e: IllegalArgumentException) {
156156
throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e)
157157
}
158158

159-
/**
160-
* @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime]
161-
*/
162-
private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime {
163-
val currentOffset = zone.offsetAt(this)
164-
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), currentOffset)
165-
}
166-
167-
/** Check that [Instant] fits in [ZonedDateTime].
159+
/** Check that [Instant] fits in [LocalDateTime].
168160
* This is done on the results of computations for consistency with other platforms.
169161
*/
170162
private fun Instant.check(zone: TimeZone): Instant = this@check.also {
171-
toZonedDateTimeFailing(zone)
163+
toLocalDateTimeFailing(offsetIn(zone))
172164
}
173165

174166
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
175167
with(period) {
176-
val withDate = toZonedDateTimeFailing(timeZone)
177-
.run { if (totalMonths != 0) timeZone.atZone(dateTime.plus(totalMonths, DateTimeUnit.MONTH), offset) else this }
178-
.run { if (days != 0) timeZone.atZone(dateTime.plus(days, DateTimeUnit.DAY), offset) else this }
179-
withDate.toInstant()
168+
val initialOffset = offsetIn(timeZone)
169+
val initialLdt = toLocalDateTimeFailing(initialOffset)
170+
val offsetAfterMonths: UtcOffset
171+
val ldtAfterMonths: LocalDateTime
172+
if (totalMonths != 0) {
173+
val (ldt, offset) = timeZone.atZone(initialLdt.plus(totalMonths, DateTimeUnit.MONTH), initialOffset)
174+
offsetAfterMonths = offset
175+
ldtAfterMonths = ldt
176+
} else {
177+
offsetAfterMonths = initialOffset
178+
ldtAfterMonths = initialLdt
179+
}
180+
val instantAfterMonthsAndDays = if (days != 0) {
181+
timeZone.atZone(ldtAfterMonths.plus(days, DateTimeUnit.DAY), offsetAfterMonths).toInstant()
182+
} else {
183+
ldtAfterMonths.toInstant(offsetAfterMonths)
184+
}
185+
instantAfterMonthsAndDays
180186
.run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this }
181187
}.check(timeZone)
182188
} catch (e: ArithmeticException) {
@@ -197,11 +203,9 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
197203
is DateTimeUnit.DateBased -> {
198204
if (value < Int.MIN_VALUE || value > Int.MAX_VALUE)
199205
throw ArithmeticException("Can't add a Long date-based value, as it would cause an overflow")
200-
val toZonedDateTimeFailing = toZonedDateTimeFailing(timeZone)
201-
timeZone.atZone(
202-
toZonedDateTimeFailing.dateTime.plus(value.toInt(), unit),
203-
toZonedDateTimeFailing.offset
204-
).toInstant()
206+
val preferredOffset = offsetIn(timeZone)
207+
val initialLdt = toLocalDateTimeFailing(preferredOffset)
208+
timeZone.atZone(initialLdt.plus(value.toInt(), unit), preferredOffset).toInstant()
205209
}
206210
is DateTimeUnit.TimeBased ->
207211
check(timeZone).plus(value, unit).check(timeZone)
@@ -224,31 +228,23 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
224228
}
225229

226230
public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod {
227-
var thisLdt = toZonedDateTimeFailing(timeZone)
228-
val otherLdt = other.toZonedDateTimeFailing(timeZone)
229-
230-
val months =
231-
thisLdt.dateTime.until(otherLdt.dateTime, DateTimeUnit.MONTH).toLong().toInt() // `until` on dates never fails
232-
thisLdt = timeZone.atZone(
233-
thisLdt.dateTime.plus(months, DateTimeUnit.MONTH),
234-
thisLdt.offset
235-
) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
236-
val days =
237-
thisLdt.dateTime.until(otherLdt.dateTime, DateTimeUnit.DAY).toLong().toInt() // `until` on dates never fails
238-
thisLdt = timeZone.atZone(
239-
thisLdt.dateTime.plus(days, DateTimeUnit.DAY),
240-
thisLdt.offset
241-
) // won't throw: thisLdt + days <= otherLdt
242-
val nanoseconds =
243-
thisLdt.toInstant().until(otherLdt.toInstant(), DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
231+
val thisOffset1 = offsetIn(timeZone)
232+
val thisLdt1 = toLocalDateTimeFailing(thisOffset1)
233+
val otherLdt = other.toLocalDateTimeFailing(other.offsetIn(timeZone))
234+
235+
val months = thisLdt1.until(otherLdt, DateTimeUnit.MONTH).toLong().toInt() // `until` on dates never fails
236+
val (thisLdt2, thisOffset2) = timeZone.atZone(thisLdt1.plus(months, DateTimeUnit.MONTH), thisOffset1) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
237+
val days = thisLdt2.until(otherLdt, DateTimeUnit.DAY).toLong().toInt() // `until` on dates never fails
238+
val (thisLdt3, thisOffset3) = timeZone.atZone(thisLdt2.plus(days, DateTimeUnit.DAY), thisOffset2) // won't throw: thisLdt + days <= otherLdt
239+
val nanoseconds = thisLdt3.toInstant(thisOffset3).until(other, DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
244240

245241
return buildDateTimePeriod(months, days, nanoseconds)
246242
}
247243

248244
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long =
249245
when (unit) {
250246
is DateTimeUnit.DateBased ->
251-
toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit)
247+
toLocalDateTimeFailing(offsetIn(timeZone)).until(other.toLocalDateTimeFailing(other.offsetIn(timeZone)), unit)
252248
.toLong()
253249
is DateTimeUnit.TimeBased -> {
254250
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
@@ -95,7 +95,7 @@ public actual open class TimeZone internal constructor() {
9595
internal open fun localDateTimeToInstant(dateTime: LocalDateTime): Instant =
9696
atZone(dateTime).toInstant()
9797

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

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

120120
override fun offsetAtImpl(instant: Instant): UtcOffset = offset
121121

122-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
123-
ZonedDateTime(dateTime, offset)
122+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): LocalDateTimeWithOffset =
123+
LocalDateTimeWithOffset(dateTime, offset)
124124

125125
override fun instantToLocalDateTime(instant: Instant): LocalDateTime = instant.toLocalDateTime(offset)
126126
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
@@ -18,12 +18,12 @@ internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id:
1818
}
1919
}
2020

21-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
21+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): LocalDateTimeWithOffset =
2222
when (val info = tzid.infoAtDatetime(dateTime)) {
23-
is OffsetInfo.Regular -> ZonedDateTime(dateTime, info.offset)
23+
is OffsetInfo.Regular -> LocalDateTimeWithOffset(dateTime, info.offset)
2424
is OffsetInfo.Gap -> {
2525
try {
26-
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), info.offsetAfter)
26+
LocalDateTimeWithOffset(dateTime.plusSeconds(info.transitionDurationSeconds), info.offsetAfter)
2727
} catch (e: IllegalArgumentException) {
2828
throw DateTimeArithmeticException(
2929
"Overflow whet correcting the date-time to not be in the transition gap",
@@ -32,7 +32,7 @@ internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id:
3232
}
3333
}
3434

35-
is OffsetInfo.Overlap -> ZonedDateTime(
35+
is OffsetInfo.Overlap -> LocalDateTimeWithOffset(
3636
dateTime,
3737
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore
3838
)

core/native/test/ThreeTenBpTimeZoneTest.kt

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

0 commit comments

Comments
 (0)