Skip to content

Commit 2d8ca25

Browse files
committed
Move all ZonedDateTime arithmetic outside of it
1 parent 4e3de8f commit 2d8ca25

File tree

5 files changed

+37
-67
lines changed

5 files changed

+37
-67
lines changed

core/native/src/Instant.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try
161161
*/
162162
private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime {
163163
val currentOffset = zone.offsetAt(this)
164-
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), zone, currentOffset)
164+
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), currentOffset)
165165
}
166166

167167
/** Check that [Instant] fits in [ZonedDateTime].
@@ -174,8 +174,8 @@ private fun Instant.check(zone: TimeZone): Instant = [email protected] {
174174
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
175175
with(period) {
176176
val withDate = toZonedDateTimeFailing(timeZone)
177-
.run { if (totalMonths != 0) plus(totalMonths, DateTimeUnit.MONTH) else this }
178-
.run { if (days != 0) plus(days, DateTimeUnit.DAY) else this }
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 }
179179
withDate.toInstant()
180180
.run { if (totalNanoseconds != 0L) plus(0, totalNanoseconds).check(timeZone) else this }
181181
}.check(timeZone)
@@ -197,7 +197,11 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
197197
is DateTimeUnit.DateBased -> {
198198
if (value < Int.MIN_VALUE || value > Int.MAX_VALUE)
199199
throw ArithmeticException("Can't add a Long date-based value, as it would cause an overflow")
200-
toZonedDateTimeFailing(timeZone).plus(value.toInt(), unit).toInstant()
200+
val toZonedDateTimeFailing = toZonedDateTimeFailing(timeZone)
201+
timeZone.atZone(
202+
toZonedDateTimeFailing.dateTime.plus(value.toInt(), unit),
203+
toZonedDateTimeFailing.offset
204+
).toInstant()
201205
}
202206
is DateTimeUnit.TimeBased ->
203207
check(timeZone).plus(value, unit).check(timeZone)
@@ -223,11 +227,20 @@ public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateT
223227
var thisLdt = toZonedDateTimeFailing(timeZone)
224228
val otherLdt = other.toZonedDateTimeFailing(timeZone)
225229

226-
val months = thisLdt.until(otherLdt, DateTimeUnit.MONTH).toInt() // `until` on dates never fails
227-
thisLdt = thisLdt.plus(months, DateTimeUnit.MONTH) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
228-
val days = thisLdt.until(otherLdt, DateTimeUnit.DAY).toInt() // `until` on dates never fails
229-
thisLdt = thisLdt.plus(days, DateTimeUnit.DAY) // won't throw: thisLdt + days <= otherLdt
230-
val nanoseconds = thisLdt.until(otherLdt, DateTimeUnit.NANOSECOND) // |otherLdt - thisLdt| < 24h
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
231244

232245
return buildDateTimePeriod(months, days, nanoseconds)
233246
}

core/native/src/TimeZone.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public actual class FixedOffsetTimeZone internal constructor(public actual val o
120120
override fun offsetAtImpl(instant: Instant): UtcOffset = offset
121121

122122
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
123-
ZonedDateTime(dateTime, this, offset)
123+
ZonedDateTime(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: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,67 +8,20 @@
88

99
package kotlinx.datetime
1010

11-
internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: TimeZone, val offset: UtcOffset) {
12-
/**
13-
* @throws IllegalArgumentException if the result exceeds the boundaries
14-
* @throws ArithmeticException if arithmetic overflow occurs
15-
*/
16-
internal fun plus(value: Int, unit: DateTimeUnit.DateBased): ZonedDateTime = dateTime.plus(value, unit).resolve()
17-
18-
// Never throws in practice
19-
private fun LocalDateTime.resolve(): ZonedDateTime =
20-
// workaround for https://github.com/Kotlin/kotlinx-datetime/issues/51
21-
if (this@resolve.toInstant(offset).toLocalDateTime(zone) == this@resolve) {
22-
// this LocalDateTime is valid in these timezone and offset.
23-
ZonedDateTime(this, zone, offset)
24-
} else {
25-
// this LDT does need proper resolving, as the instant that it would map to given the preferred offset
26-
// is is mapped to another LDT.
27-
zone.atZone(this, offset)
28-
}
29-
11+
internal class ZonedDateTime(val dateTime: LocalDateTime, val offset: UtcOffset) {
3012
override fun equals(other: Any?): Boolean =
3113
this === other || other is ZonedDateTime &&
32-
dateTime == other.dateTime && offset == other.offset && zone == other.zone
14+
dateTime == other.dateTime && offset == other.offset
3315

3416
override fun hashCode(): Int {
35-
return dateTime.hashCode() xor offset.hashCode() xor zone.hashCode().rotateLeft(3)
17+
return dateTime.hashCode() xor offset.hashCode()
3618
}
3719

3820
override fun toString(): String {
39-
var str = dateTime.toString() + offset.toString()
40-
if (zone !is FixedOffsetTimeZone || offset !== zone.offset) {
41-
str += "[$zone]"
42-
}
21+
val str = dateTime.toString() + offset.toString()
4322
return str
4423
}
4524
}
4625

4726
internal fun ZonedDateTime.toInstant(): Instant =
4827
Instant(dateTime.toEpochSecond(offset), dateTime.nanosecond)
49-
50-
51-
// org.threeten.bp.ZonedDateTime#until
52-
// This version is simplified and to be used ONLY in case you know the timezones are equal!
53-
/**
54-
* @throws ArithmeticException on arithmetic overflow
55-
* @throws DateTimeArithmeticException if setting [other] to the offset of [this] leads to exceeding boundaries of
56-
* [LocalDateTime].
57-
*/
58-
59-
internal fun ZonedDateTime.until(other: ZonedDateTime, unit: DateTimeUnit): Long =
60-
when (unit) {
61-
// if the time unit is date-based, the offsets are disregarded and only the dates and times are compared.
62-
is DateTimeUnit.DateBased -> dateTime.until(other.dateTime, unit).toLong()
63-
// if the time unit is not date-based, we need to make sure that [other] is at the same offset as [this].
64-
is DateTimeUnit.TimeBased -> {
65-
val offsetDiff = offset.totalSeconds - other.offset.totalSeconds
66-
val otherLdtAdjusted = try {
67-
other.dateTime.plusSeconds(offsetDiff)
68-
} catch (e: IllegalArgumentException) {
69-
throw DateTimeArithmeticException(
70-
"Unable to find difference between date-times, as one of them overflowed")
71-
}
72-
dateTime.until(otherLdtAdjusted, unit)
73-
}
74-
}

core/native/src/internal/RegionTimeZone.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id:
2020

2121
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
2222
when (val info = tzid.infoAtDatetime(dateTime)) {
23-
is OffsetInfo.Regular -> ZonedDateTime(dateTime, this, info.offset)
23+
is OffsetInfo.Regular -> ZonedDateTime(dateTime, info.offset)
2424
is OffsetInfo.Gap -> {
2525
try {
26-
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), this, info.offsetAfter)
26+
ZonedDateTime(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,8 +32,10 @@ internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id:
3232
}
3333
}
3434

35-
is OffsetInfo.Overlap -> ZonedDateTime(dateTime, this,
36-
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
35+
is OffsetInfo.Overlap -> ZonedDateTime(
36+
dateTime,
37+
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore
38+
)
3739
}
3840

3941
override fun offsetAtImpl(instant: Instant): UtcOffset = tzid.infoAtInstant(instant)

core/native/test/ThreeTenBpTimeZoneTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ 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(LocalDateTime(2007, 10, 28, 2, 30, 0, 0),
44-
zone, UtcOffset(seconds = 2 * 3600)), zone.atZone(t))
43+
assertEquals(ZonedDateTime(
44+
LocalDateTime(2007, 10, 28, 2, 30, 0, 0),
45+
UtcOffset(seconds = 2 * 3600)
46+
), zone.atZone(t))
4547
}
4648

4749
}

0 commit comments

Comments
 (0)