Skip to content

Commit f7d5e45

Browse files
committed
Refactor native TimeZone to avoid member-extension internal functions
Override some methods in FixedOffsetTimeZone to provide a simpler implementation
1 parent 8cd4ab7 commit f7d5e45

File tree

7 files changed

+53
-46
lines changed

7 files changed

+53
-46
lines changed

core/darwin/src/TimeZoneNative.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,30 +132,30 @@ internal actual class PlatformTimeZoneImpl(private val value: NSTimeZone, overri
132132
return Instant(midnight.timeIntervalSince1970.toLong(), 0)
133133
}
134134

135-
override fun LocalDateTime.atZone(preferred: UtcOffset?): ZonedDateTime {
136-
val epochSeconds = toEpochSecond(UtcOffset.ZERO)
135+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime {
136+
val epochSeconds = dateTime.toEpochSecond(UtcOffset.ZERO)
137137
var offset = preferred?.totalSeconds ?: Int.MAX_VALUE
138138
val transitionDuration = run {
139139
/* a date in an unspecified timezone, defined by the number of seconds since
140140
the start of the epoch in *that* unspecified timezone */
141141
val date = dateWithTimeIntervalSince1970Saturating(epochSeconds)
142142
val newDate = systemDateByLocalDate(value, date)
143-
?: throw RuntimeException("Unable to acquire the offset at ${this@atZone} for zone ${this@PlatformTimeZoneImpl}")
143+
?: throw RuntimeException("Unable to acquire the offset at $dateTime for zone ${this@PlatformTimeZoneImpl}")
144144
// we now know the offset of that timezone at this time.
145145
offset = value.secondsFromGMTForDate(newDate).toInt()
146146
/* `dateFromComponents` automatically corrects the date to avoid gaps. We
147147
need to learn which adjustments it performed. */
148148
(newDate.timeIntervalSince1970.toLong() +
149149
offset.toLong() - date.timeIntervalSince1970.toLong()).toInt()
150150
}
151-
val dateTime = try {
152-
this@atZone.plusSeconds(transitionDuration)
151+
val correctedDateTime = try {
152+
dateTime.plusSeconds(transitionDuration)
153153
} catch (e: IllegalArgumentException) {
154154
throw DateTimeArithmeticException("Overflow whet correcting the date-time to not be in the transition gap", e)
155155
} catch (e: ArithmeticException) {
156156
throw RuntimeException("Anomalously long timezone transition gap reported", e)
157157
}
158-
return ZonedDateTime(dateTime, TimeZone(this@PlatformTimeZoneImpl), UtcOffset.ofSeconds(offset))
158+
return ZonedDateTime(correctedDateTime, TimeZone(this), UtcOffset.ofSeconds(offset))
159159
}
160160

161161
override fun offsetAt(instant: Instant): UtcOffset {

core/native/cinterop_actuals/TimeZoneNative.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,22 @@ internal actual class PlatformTimeZoneImpl(private val tzid: TZID, override val
5555
Instant(midnightInstantSeconds, 0)
5656
}
5757

58-
override fun LocalDateTime.atZone(preferred: UtcOffset?): ZonedDateTime = memScoped {
59-
val epochSeconds = toEpochSecond(UtcOffset.ZERO)
58+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime = memScoped {
59+
val epochSeconds = dateTime.toEpochSecond(UtcOffset.ZERO)
6060
val offset = alloc<IntVar>()
6161
offset.value = preferred?.totalSeconds ?: Int.MAX_VALUE
6262
val transitionDuration = offset_at_datetime(tzid, epochSeconds, offset.ptr)
6363
if (offset.value == Int.MAX_VALUE) {
64-
throw RuntimeException("Unable to acquire the offset at ${this@atZone} for zone ${this@PlatformTimeZoneImpl}")
64+
throw RuntimeException("Unable to acquire the offset at $dateTime for zone ${this@PlatformTimeZoneImpl}")
6565
}
66-
val dateTime = try {
67-
this@atZone.plusSeconds(transitionDuration)
66+
val correctedDateTime = try {
67+
dateTime.plusSeconds(transitionDuration)
6868
} catch (e: IllegalArgumentException) {
6969
throw DateTimeArithmeticException("Overflow whet correcting the date-time to not be in the transition gap", e)
7070
} catch (e: ArithmeticException) {
7171
throw RuntimeException("Anomalously long timezone transition gap reported", e)
7272
}
73-
ZonedDateTime(dateTime, TimeZone(this@PlatformTimeZoneImpl), UtcOffset.ofSeconds(offset.value))
73+
ZonedDateTime(correctedDateTime, TimeZone(this@PlatformTimeZoneImpl), UtcOffset.ofSeconds(offset.value))
7474
}
7575

7676
override fun offsetAt(instant: Instant): UtcOffset {

core/native/src/Instant.kt

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,22 +234,30 @@ public actual class Instant internal constructor(public actual val epochSeconds:
234234

235235
}
236236

237-
private fun Instant.toZonedLocalDateTimeFailing(zone: TimeZone): ZonedDateTime = try {
238-
toZonedLocalDateTime(zone)
237+
private fun Instant.toZonedDateTimeFailing(zone: TimeZone): ZonedDateTime = try {
238+
toZonedDateTime(zone)
239239
} catch (e: IllegalArgumentException) {
240240
throw DateTimeArithmeticException("Can not convert instant $this to LocalDateTime to perform computations", e)
241241
}
242242

243+
/**
244+
* @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime]
245+
*/
246+
private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime {
247+
val currentOffset = zone.offsetAt(this)
248+
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), zone, currentOffset)
249+
}
250+
243251
/** Check that [Instant] fits in [ZonedDateTime].
244252
* This is done on the results of computations for consistency with other platforms.
245253
*/
246254
private fun Instant.check(zone: TimeZone): Instant = this@check.also {
247-
toZonedLocalDateTimeFailing(zone)
255+
toZonedDateTimeFailing(zone)
248256
}
249257

250258
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
251259
with(period) {
252-
val withDate = toZonedLocalDateTimeFailing(timeZone)
260+
val withDate = toZonedDateTimeFailing(timeZone)
253261
.run { if (totalMonths != 0) plus(totalMonths, DateTimeUnit.MONTH) else this }
254262
.run { if (days != 0) plus(days, DateTimeUnit.DAY) else this }
255263
withDate.toInstant()
@@ -272,7 +280,7 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
272280
is DateTimeUnit.DateBased -> {
273281
if (value < Int.MIN_VALUE || value > Int.MAX_VALUE)
274282
throw ArithmeticException("Can't add a Long date-based value, as it would cause an overflow")
275-
toZonedLocalDateTimeFailing(timeZone).plus(value.toInt(), unit).toInstant()
283+
toZonedDateTimeFailing(timeZone).plus(value.toInt(), unit).toInstant()
276284
}
277285
is DateTimeUnit.TimeBased ->
278286
check(timeZone).plus(value, unit).check(timeZone)
@@ -296,8 +304,8 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
296304

297305
@OptIn(ExperimentalTime::class)
298306
public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod {
299-
var thisLdt = toZonedLocalDateTimeFailing(timeZone)
300-
val otherLdt = other.toZonedLocalDateTimeFailing(timeZone)
307+
var thisLdt = toZonedDateTimeFailing(timeZone)
308+
val otherLdt = other.toZonedDateTimeFailing(timeZone)
301309

302310
val months = thisLdt.until(otherLdt, DateTimeUnit.MONTH).toInt() // `until` on dates never fails
303311
thisLdt = thisLdt.plus(months, DateTimeUnit.MONTH) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
@@ -311,7 +319,7 @@ public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateT
311319
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long =
312320
when (unit) {
313321
is DateTimeUnit.DateBased ->
314-
toZonedLocalDateTimeFailing(timeZone).dateTime.until(other.toZonedLocalDateTimeFailing(timeZone).dateTime, unit)
322+
toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit)
315323
.toLong()
316324
is DateTimeUnit.TimeBased -> {
317325
check(timeZone); other.check(timeZone)

core/native/src/TimeZone.kt

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,22 @@ public actual open class TimeZone internal constructor(internal val value: TimeZ
6161
public actual val id: String
6262
get() = value.id
6363

64-
public actual fun Instant.toLocalDateTime(): LocalDateTime = try {
65-
toZonedLocalDateTime(this@TimeZone).dateTime
64+
public actual fun Instant.toLocalDateTime(): LocalDateTime = instantToLocalDateTime(this)
65+
public actual fun LocalDateTime.toInstant(): Instant = localDateTimeToInstant(this)
66+
67+
internal open fun atStartOfDay(date: LocalDate): Instant = value.atStartOfDay(date)
68+
69+
internal open fun instantToLocalDateTime(instant: Instant): LocalDateTime = try {
70+
instant.toLocalDateTimeImpl(offsetAt(instant))
6671
} catch (e: IllegalArgumentException) {
67-
throw DateTimeArithmeticException("Instant ${this@toLocalDateTime} is not representable as LocalDateTime", e)
72+
throw DateTimeArithmeticException("Instant $instant is not representable as LocalDateTime.", e)
6873
}
6974

70-
public actual fun LocalDateTime.toInstant(): Instant = atZone().toInstant()
75+
internal open fun localDateTimeToInstant(dateTime: LocalDateTime): Instant =
76+
atZone(dateTime).toInstant()
7177

72-
internal open fun atStartOfDay(date: LocalDate): Instant = value.atStartOfDay(date)
73-
74-
internal open fun LocalDateTime.atZone(preferred: UtcOffset? = null): ZonedDateTime =
75-
with(value) { atZone(preferred) }
78+
internal open fun atZone(dateTime: LocalDateTime, preferred: UtcOffset? = null): ZonedDateTime =
79+
value.atZone(dateTime, preferred)
7680

7781
override fun equals(other: Any?): Boolean =
7882
this === other || other is TimeZone && this.value == other.value
@@ -90,14 +94,17 @@ public actual class FixedOffsetTimeZone internal constructor(public actual val u
9094

9195
@Deprecated("Use utcOffset.totalSeconds", ReplaceWith("utcOffset.totalSeconds"))
9296
public actual val totalSeconds: Int get() = utcOffset.totalSeconds
97+
98+
override fun instantToLocalDateTime(instant: Instant): LocalDateTime = instant.toLocalDateTime(utcOffset)
99+
override fun localDateTimeToInstant(dateTime: LocalDateTime): Instant = dateTime.toInstant(utcOffset)
93100
}
94101

95102

96103
public actual fun TimeZone.offsetAt(instant: Instant): UtcOffset =
97104
value.offsetAt(instant)
98105

99106
public actual fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime =
100-
with(timeZone) { toLocalDateTime() }
107+
timeZone.instantToLocalDateTime(this)
101108

102109
public actual fun Instant.toLocalDateTime(utcOffset: UtcOffset): LocalDateTime = try {
103110
toLocalDateTimeImpl(utcOffset)
@@ -115,10 +122,10 @@ internal fun Instant.toLocalDateTimeImpl(offset: UtcOffset): LocalDateTime {
115122
}
116123

117124
public actual fun LocalDateTime.toInstant(timeZone: TimeZone): Instant =
118-
with(timeZone) { toInstant() }
125+
timeZone.localDateTimeToInstant(this)
119126

120127
public actual fun LocalDateTime.toInstant(utcOffset: UtcOffset): Instant =
121128
Instant(this.toEpochSecond(utcOffset), this.nanosecond)
122129

123130
public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant =
124-
timeZone.atStartOfDay(this)
131+
timeZone.atStartOfDay(this)

core/native/src/TimeZoneImpl.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ package kotlinx.datetime
77
internal interface TimeZoneImpl {
88
val id: String
99
fun atStartOfDay(date: LocalDate): Instant
10-
fun LocalDateTime.atZone(preferred: UtcOffset?): ZonedDateTime
10+
fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime
1111
fun offsetAt(instant: Instant): UtcOffset
1212
}
1313

@@ -22,10 +22,10 @@ internal expect class PlatformTimeZoneImpl: TimeZoneImpl {
2222
internal class ZoneOffsetImpl(val utcOffset: UtcOffset, override val id: String): TimeZoneImpl {
2323

2424
override fun atStartOfDay(date: LocalDate): Instant =
25-
LocalDateTime(date, LocalTime.MIN).atZone(null).toInstant()
25+
LocalDateTime(date, LocalTime.MIN).toInstant(utcOffset)
2626

27-
override fun LocalDateTime.atZone(preferred: UtcOffset?): ZonedDateTime {
28-
return ZonedDateTime(this@atZone, utcOffset.asTimeZone(), utcOffset)
27+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime {
28+
return ZonedDateTime(dateTime, utcOffset.asTimeZone(), utcOffset)
2929
}
3030

3131
override fun offsetAt(instant: Instant): UtcOffset = utcOffset

core/native/src/ZonedDateTime.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: Time
2424
} else {
2525
// this LDT does need proper resolving, as the instant that it would map to given the preferred offset
2626
// is is mapped to another LDT.
27-
with(zone) { atZone(offset) }
27+
zone.atZone(this, offset)
2828
}
2929

3030
override fun equals(other: Any?): Boolean =
@@ -48,14 +48,6 @@ internal class ZonedDateTime(val dateTime: LocalDateTime, private val zone: Time
4848
internal fun ZonedDateTime.toInstant(): Instant =
4949
Instant(dateTime.toEpochSecond(offset), dateTime.nanosecond)
5050

51-
// org.threeten.bp.LocalDateTime#ofEpochSecond + org.threeten.bp.ZonedDateTime#create
52-
/**
53-
* @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime]
54-
*/
55-
internal fun Instant.toZonedLocalDateTime(zone: TimeZone): ZonedDateTime {
56-
val currentOffset = zone.value.offsetAt(this)
57-
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), zone, currentOffset)
58-
}
5951

6052
// org.threeten.bp.ZonedDateTime#until
6153
// This version is simplified and to be used ONLY in case you know the timezones are equal!

core/native/test/ThreeTenBpTimeZoneTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ class ThreeTenBpTimeZoneTest {
4545
val t1 = LocalDateTime(2020, 3, 29, 2, 14, 17, 201)
4646
val t2 = LocalDateTime(2020, 3, 29, 3, 14, 17, 201)
4747
val tz = TimeZone.of("Europe/Berlin")
48-
assertEquals(with(tz) { t1.atZone() }, with(tz) { t2.atZone() })
48+
assertEquals(tz.atZone(t1), tz.atZone(t2))
4949
}
5050

5151
@Test
5252
fun overlappingLocalTime() {
5353
val t = LocalDateTime(2007, 10, 28, 2, 30, 0, 0)
5454
val zone = TimeZone.of("Europe/Paris")
5555
assertEquals(ZonedDateTime(LocalDateTime(2007, 10, 28, 2, 30, 0, 0),
56-
zone, UtcOffset.ofSeconds(2 * 3600)), with(zone) { t.atZone() })
56+
zone, UtcOffset.ofSeconds(2 * 3600)), zone.atZone(t))
5757
}
5858

5959
}

0 commit comments

Comments
 (0)