Skip to content

Commit cdbacce

Browse files
committed
Simplify the code for Instant arithmetics
1 parent dc9b88c commit cdbacce

File tree

7 files changed

+59
-76
lines changed

7 files changed

+59
-76
lines changed

core/commonJs/src/internal/Platform.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,14 @@ private object SystemTimeZone: TimeZone() {
103103
UtcOffset(minutes = -Date(instant.toEpochMilliseconds().toDouble()).getTimezoneOffset().toInt())
104104

105105
/* https://github.com/js-joda/js-joda/blob/8c1a7448db92ca014417346049fb64b55f7b1ac1/packages/core/src/zone/SystemDefaultZoneRules.js#L49-L55 */
106-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime {
106+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): LocalDateTimeWithOffset {
107107
val epochMilli = dateTime.toInstant(UTC).toEpochMilliseconds()
108108
val offsetInMinutesBeforePossibleTransition = Date(epochMilli.toDouble()).getTimezoneOffset().toInt()
109109
val epochMilliSystemZone = epochMilli +
110110
offsetInMinutesBeforePossibleTransition * SECONDS_PER_MINUTE * MILLIS_PER_ONE
111111
val offsetInMinutesAfterPossibleTransition = Date(epochMilliSystemZone.toDouble()).getTimezoneOffset().toInt()
112112
val offset = UtcOffset(minutes = -offsetInMinutesAfterPossibleTransition)
113-
return ZonedDateTime(dateTime, offset)
113+
return LocalDateTimeWithOffset(dateTime, offset)
114114
}
115115

116116
override fun equals(other: Any?): Boolean = other === this

core/commonKotlin/src/Instant.kt

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

155155
}
156156

157-
private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try {
158-
toZonedDateTime(zone)
157+
private fun Instant.toLocalDateTimeFailing(offset: UtcOffset): LocalDateTime = try {
158+
toLocalDateTimeImpl(offset)
159159
} catch (e: IllegalArgumentException) {
160160
throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e)
161161
}
162162

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

178170
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
179171
with(period) {
180-
val withDate = toZonedDateTimeFailing(timeZone)
181-
.run { if (totalMonths != 0L) timeZone.atZone(dateTime.plus(totalMonths, DateTimeUnit.MONTH), offset) else this }
182-
.run { if (days != 0) timeZone.atZone(dateTime.plus(days, DateTimeUnit.DAY), offset) else this }
183-
withDate.toInstant()
172+
val initialOffset = offsetIn(timeZone)
173+
val initialLdt = toLocalDateTimeFailing(initialOffset)
174+
val offsetAfterMonths: UtcOffset
175+
val ldtAfterMonths: LocalDateTime
176+
if (totalMonths != 0L) {
177+
val (ldt, offset) = timeZone.atZone(initialLdt.plus(totalMonths, DateTimeUnit.MONTH), initialOffset)
178+
offsetAfterMonths = offset
179+
ldtAfterMonths = ldt
180+
} else {
181+
offsetAfterMonths = initialOffset
182+
ldtAfterMonths = initialLdt
183+
}
184+
val instantAfterMonthsAndDays = if (days != 0) {
185+
timeZone.atZone(ldtAfterMonths.plus(days, DateTimeUnit.DAY), offsetAfterMonths).toInstant()
186+
} else {
187+
ldtAfterMonths.toInstant(offsetAfterMonths)
188+
}
189+
instantAfterMonthsAndDays
184190
.run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this }
185191
}.check(timeZone)
186192
} catch (e: ArithmeticException) {
@@ -199,11 +205,9 @@ public actual fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZo
199205
public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = try {
200206
when (unit) {
201207
is DateTimeUnit.DateBased -> {
202-
val toZonedDateTimeFailing = toZonedDateTimeFailing(timeZone)
203-
timeZone.atZone(
204-
toZonedDateTimeFailing.dateTime.plus(value, unit),
205-
toZonedDateTimeFailing.offset
206-
).toInstant()
208+
val preferredOffset = offsetIn(timeZone)
209+
val initialLdt = toLocalDateTimeFailing(preferredOffset)
210+
timeZone.atZone(initialLdt.plus(value, unit), preferredOffset).toInstant()
207211
}
208212
is DateTimeUnit.TimeBased ->
209213
check(timeZone).plus(value, unit).check(timeZone)
@@ -226,30 +230,23 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
226230
}
227231

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

246243
return buildDateTimePeriod(months, days.toInt(), nanoseconds)
247244
}
248245

249246
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long =
250247
when (unit) {
251248
is DateTimeUnit.DateBased ->
252-
toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit)
249+
toLocalDateTimeFailing(offsetIn(timeZone)).until(other.toLocalDateTimeFailing(other.offsetIn(timeZone)), unit)
253250
.toLong()
254251
is DateTimeUnit.TimeBased -> {
255252
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/commonKotlin/src/TimeZone.kt

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

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

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

122122
override fun offsetAtImpl(instant: Instant): UtcOffset = offset
123123

124-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
125-
ZonedDateTime(dateTime, offset)
124+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): LocalDateTimeWithOffset =
125+
LocalDateTimeWithOffset(dateTime, offset)
126126

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

core/commonKotlin/src/ZonedDateTime.kt

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

core/commonKotlin/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/commonKotlin/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)