Skip to content

Commit 5634598

Browse files
committed
Simplify the code for Instant arithmetics
1 parent 7f9c06c commit 5634598

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

169169
}
170170

171-
private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try {
172-
toZonedDateTime(zone)
171+
private fun Instant.toLocalDateTimeFailing(offset: UtcOffset): LocalDateTime = try {
172+
toLocalDateTimeImpl(offset)
173173
} catch (e: IllegalArgumentException) {
174174
throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e)
175175
}
176176

177-
/**
178-
* @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime]
179-
*/
180-
private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime {
181-
val currentOffset = zone.offsetAt(this)
182-
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), currentOffset)
183-
}
184-
185-
/** Check that [Instant] fits in [ZonedDateTime].
177+
/** Check that [Instant] fits in [LocalDateTime].
186178
* This is done on the results of computations for consistency with other platforms.
187179
*/
188180
private fun Instant.check(zone: TimeZone): Instant = this@check.also {
189-
toZonedDateTimeFailing(zone)
181+
toLocalDateTimeFailing(offsetIn(zone))
190182
}
191183

192184
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
193185
with(period) {
194-
val withDate = toZonedDateTimeFailing(timeZone)
195-
.run { if (totalMonths != 0L) timeZone.atZone(dateTime.plus(totalMonths, DateTimeUnit.MONTH), offset) else this }
196-
.run { if (days != 0) timeZone.atZone(dateTime.plus(days, DateTimeUnit.DAY), offset) else this }
197-
withDate.toInstant()
186+
val initialOffset = offsetIn(timeZone)
187+
val initialLdt = toLocalDateTimeFailing(initialOffset)
188+
val offsetAfterMonths: UtcOffset
189+
val ldtAfterMonths: LocalDateTime
190+
if (totalMonths != 0L) {
191+
val (ldt, offset) = timeZone.atZone(initialLdt.plus(totalMonths, DateTimeUnit.MONTH), initialOffset)
192+
offsetAfterMonths = offset
193+
ldtAfterMonths = ldt
194+
} else {
195+
offsetAfterMonths = initialOffset
196+
ldtAfterMonths = initialLdt
197+
}
198+
val instantAfterMonthsAndDays = if (days != 0) {
199+
timeZone.atZone(ldtAfterMonths.plus(days, DateTimeUnit.DAY), offsetAfterMonths).toInstant()
200+
} else {
201+
ldtAfterMonths.toInstant(offsetAfterMonths)
202+
}
203+
instantAfterMonthsAndDays
198204
.run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this }
199205
}.check(timeZone)
200206
} catch (e: ArithmeticException) {
@@ -213,11 +219,9 @@ public actual fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZo
213219
public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant = try {
214220
when (unit) {
215221
is DateTimeUnit.DateBased -> {
216-
val toZonedDateTimeFailing = toZonedDateTimeFailing(timeZone)
217-
timeZone.atZone(
218-
toZonedDateTimeFailing.dateTime.plus(value, unit),
219-
toZonedDateTimeFailing.offset
220-
).toInstant()
222+
val preferredOffset = offsetIn(timeZone)
223+
val initialLdt = toLocalDateTimeFailing(preferredOffset)
224+
timeZone.atZone(initialLdt.plus(value, unit), preferredOffset).toInstant()
221225
}
222226
is DateTimeUnit.TimeBased ->
223227
check(timeZone).plus(value, unit).check(timeZone)
@@ -240,30 +244,23 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
240244
}
241245

242246
public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod {
243-
var thisLdt = toZonedDateTimeFailing(timeZone)
244-
val otherLdt = other.toZonedDateTimeFailing(timeZone)
245-
246-
val months = thisLdt.dateTime.until(otherLdt.dateTime, DateTimeUnit.MONTH) // `until` on dates never fails
247-
thisLdt = timeZone.atZone(
248-
thisLdt.dateTime.plus(months, DateTimeUnit.MONTH),
249-
thisLdt.offset
250-
) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
251-
val days =
252-
thisLdt.dateTime.until(otherLdt.dateTime, DateTimeUnit.DAY) // `until` on dates never fails
253-
thisLdt = timeZone.atZone(
254-
thisLdt.dateTime.plus(days, DateTimeUnit.DAY),
255-
thisLdt.offset
256-
) // won't throw: thisLdt + days <= otherLdt
257-
val nanoseconds =
258-
thisLdt.toInstant().until(otherLdt.toInstant(), DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
247+
val thisOffset1 = offsetIn(timeZone)
248+
val thisLdt1 = toLocalDateTimeFailing(thisOffset1)
249+
val otherLdt = other.toLocalDateTimeFailing(other.offsetIn(timeZone))
250+
251+
val months = thisLdt1.until(otherLdt, DateTimeUnit.MONTH) // `until` on dates never fails
252+
val (thisLdt2, thisOffset2) = timeZone.atZone(thisLdt1.plus(months, DateTimeUnit.MONTH), thisOffset1) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
253+
val days = thisLdt2.until(otherLdt, DateTimeUnit.DAY) // `until` on dates never fails
254+
val (thisLdt3, thisOffset3) = timeZone.atZone(thisLdt2.plus(days, DateTimeUnit.DAY), thisOffset2) // won't throw: thisLdt + days <= otherLdt
255+
val nanoseconds = thisLdt3.toInstant(thisOffset3).until(other, DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
259256

260257
return buildDateTimePeriod(months, days.toInt(), nanoseconds)
261258
}
262259

263260
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long =
264261
when (unit) {
265262
is DateTimeUnit.DateBased ->
266-
toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit)
263+
toLocalDateTimeFailing(offsetIn(timeZone)).until(other.toLocalDateTimeFailing(other.offsetIn(timeZone)), unit)
267264
.toLong()
268265
is DateTimeUnit.TimeBased -> {
269266
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
@@ -94,7 +94,7 @@ public actual open class TimeZone internal constructor() {
9494
internal open fun localDateTimeToInstant(dateTime: LocalDateTime): Instant =
9595
atZone(dateTime).toInstant()
9696

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

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

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

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

124124
override fun instantToLocalDateTime(instant: Instant): LocalDateTime = instant.toLocalDateTime(offset)
125125
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)