Skip to content

Commit a12d050

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

File tree

7 files changed

+54
-46
lines changed

7 files changed

+54
-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: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -234,22 +234,31 @@ 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+
// org.threeten.bp.LocalDateTime#ofEpochSecond + org.threeten.bp.ZonedDateTime#create
244+
/**
245+
* @throws IllegalArgumentException if the [Instant] exceeds the boundaries of [LocalDateTime]
246+
*/
247+
private fun Instant.toZonedDateTime(zone: TimeZone): ZonedDateTime {
248+
val currentOffset = zone.offsetAt(this)
249+
return ZonedDateTime(toLocalDateTimeImpl(currentOffset), zone, currentOffset)
250+
}
251+
243252
/** Check that [Instant] fits in [ZonedDateTime].
244253
* This is done on the results of computations for consistency with other platforms.
245254
*/
246255
private fun Instant.check(zone: TimeZone): Instant = this@check.also {
247-
toZonedLocalDateTimeFailing(zone)
256+
toZonedDateTimeFailing(zone)
248257
}
249258

250259
public actual fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant = try {
251260
with(period) {
252-
val withDate = toZonedLocalDateTimeFailing(timeZone)
261+
val withDate = toZonedDateTimeFailing(timeZone)
253262
.run { if (totalMonths != 0) plus(totalMonths, DateTimeUnit.MONTH) else this }
254263
.run { if (days != 0) plus(days, DateTimeUnit.DAY) else this }
255264
withDate.toInstant()
@@ -272,7 +281,7 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZo
272281
is DateTimeUnit.DateBased -> {
273282
if (value < Int.MIN_VALUE || value > Int.MAX_VALUE)
274283
throw ArithmeticException("Can't add a Long date-based value, as it would cause an overflow")
275-
toZonedLocalDateTimeFailing(timeZone).plus(value.toInt(), unit).toInstant()
284+
toZonedDateTimeFailing(timeZone).plus(value.toInt(), unit).toInstant()
276285
}
277286
is DateTimeUnit.TimeBased ->
278287
check(timeZone).plus(value, unit).check(timeZone)
@@ -296,8 +305,8 @@ public actual fun Instant.plus(value: Long, unit: DateTimeUnit.TimeBased): Insta
296305

297306
@OptIn(ExperimentalTime::class)
298307
public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod {
299-
var thisLdt = toZonedLocalDateTimeFailing(timeZone)
300-
val otherLdt = other.toZonedLocalDateTimeFailing(timeZone)
308+
var thisLdt = toZonedDateTimeFailing(timeZone)
309+
val otherLdt = other.toZonedDateTimeFailing(timeZone)
301310

302311
val months = thisLdt.until(otherLdt, DateTimeUnit.MONTH).toInt() // `until` on dates never fails
303312
thisLdt = thisLdt.plus(months, DateTimeUnit.MONTH) // won't throw: thisLdt + months <= otherLdt, which is known to be valid
@@ -311,7 +320,7 @@ public actual fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateT
311320
public actual fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long =
312321
when (unit) {
313322
is DateTimeUnit.DateBased ->
314-
toZonedLocalDateTimeFailing(timeZone).dateTime.until(other.toZonedLocalDateTimeFailing(timeZone).dateTime, unit)
323+
toZonedDateTimeFailing(timeZone).dateTime.until(other.toZonedDateTimeFailing(timeZone).dateTime, unit)
315324
.toLong()
316325
is DateTimeUnit.TimeBased -> {
317326
check(timeZone); other.check(timeZone)

core/native/src/TimeZone.kt

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

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

73-
public actual fun LocalDateTime.toInstant(): Instant = atZone().toInstant()
78+
internal open fun localDateTimeToInstant(dateTime: LocalDateTime): Instant =
79+
atZone(dateTime).toInstant()
7480

75-
internal open fun atStartOfDay(date: LocalDate): Instant = value.atStartOfDay(date)
76-
77-
internal open fun LocalDateTime.atZone(preferred: UtcOffset? = null): ZonedDateTime =
78-
with(value) { atZone(preferred) }
81+
internal open fun atZone(dateTime: LocalDateTime, preferred: UtcOffset? = null): ZonedDateTime =
82+
value.atZone(dateTime, preferred)
7983

8084
override fun equals(other: Any?): Boolean =
8185
this === other || other is TimeZone && this.value == other.value
@@ -95,6 +99,9 @@ public actual class FixedOffsetTimeZone internal constructor(public actual val u
9599

96100
@Deprecated("Use utcOffset.totalSeconds", ReplaceWith("utcOffset.totalSeconds"))
97101
public actual val totalSeconds: Int get() = utcOffset.totalSeconds
102+
103+
override fun instantToLocalDateTime(instant: Instant): LocalDateTime = instant.toLocalDateTime(utcOffset)
104+
override fun localDateTimeToInstant(dateTime: LocalDateTime): Instant = dateTime.toInstant(utcOffset)
98105
}
99106

100107

@@ -223,7 +230,7 @@ public actual fun TimeZone.offsetAt(instant: Instant): UtcOffset =
223230
value.offsetAt(instant)
224231

225232
public actual fun Instant.toLocalDateTime(timeZone: TimeZone): LocalDateTime =
226-
with(timeZone) { toLocalDateTime() }
233+
timeZone.instantToLocalDateTime(this)
227234

228235
public actual fun Instant.toLocalDateTime(utcOffset: UtcOffset): LocalDateTime = try {
229236
toLocalDateTimeImpl(utcOffset)
@@ -241,10 +248,10 @@ internal fun Instant.toLocalDateTimeImpl(offset: UtcOffset): LocalDateTime {
241248
}
242249

243250
public actual fun LocalDateTime.toInstant(timeZone: TimeZone): Instant =
244-
with(timeZone) { toInstant() }
251+
timeZone.localDateTimeToInstant(this)
245252

246253
public actual fun LocalDateTime.toInstant(utcOffset: UtcOffset): Instant =
247254
Instant(this.toEpochSecond(utcOffset), this.nanosecond)
248255

249256
public actual fun LocalDate.atStartOfDayIn(timeZone: TimeZone): Instant =
250-
timeZone.atStartOfDay(this)
257+
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
@@ -126,15 +126,15 @@ class ThreeTenBpTimeZoneTest {
126126
val t1 = LocalDateTime(2020, 3, 29, 2, 14, 17, 201)
127127
val t2 = LocalDateTime(2020, 3, 29, 3, 14, 17, 201)
128128
val tz = TimeZone.of("Europe/Berlin")
129-
assertEquals(with(tz) { t1.atZone() }, with(tz) { t2.atZone() })
129+
assertEquals(tz.atZone(t1), tz.atZone(t2))
130130
}
131131

132132
@Test
133133
fun overlappingLocalTime() {
134134
val t = LocalDateTime(2007, 10, 28, 2, 30, 0, 0)
135135
val zone = TimeZone.of("Europe/Paris")
136136
assertEquals(ZonedDateTime(LocalDateTime(2007, 10, 28, 2, 30, 0, 0),
137-
zone, UtcOffset.ofSeconds(2 * 3600)), with(zone) { t.atZone() })
137+
zone, UtcOffset.ofSeconds(2 * 3600)), zone.atZone(t))
138138
}
139139

140140
}

0 commit comments

Comments
 (0)