Skip to content

Commit 3f20fb3

Browse files
committed
Extract the shared logic of timezone handling to common Native code
1 parent 9533ba7 commit 3f20fb3

File tree

5 files changed

+70
-128
lines changed

5 files changed

+70
-128
lines changed

core/darwin/src/TimeZoneNative.kt

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,11 @@ import kotlinx.cinterop.*
1111
import kotlinx.datetime.internal.*
1212
import platform.Foundation.*
1313

14-
internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual override val id: String) : TimeZone() {
14+
internal actual class TimeZoneDatabase : TimeZone() {
1515
actual companion object {
16-
actual fun of(zoneId: String): RegionTimeZone = try {
17-
RegionTimeZone(tzdbOnFilesystem.rulesForId(zoneId), zoneId)
18-
} catch (e: Exception) {
19-
throw IllegalTimeZoneException("Invalid zone ID: $zoneId", e)
20-
}
16+
actual fun rulesForId(id: String): TimeZoneRules = tzdbOnFilesystem.rulesForId(id)
2117

22-
actual fun currentSystemDefault(): RegionTimeZone {
18+
actual fun currentSystemDefault(): Pair<String, TimeZoneRules?> {
2319
/* The framework has its own cache of the system timezone. Calls to
2420
[NSTimeZone systemTimeZone] do not reflect changes to the system timezone
2521
and instead just return the cached value. Thus, to acquire the current
@@ -70,41 +66,12 @@ internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual ove
7066
NSTimeZone.resetSystemTimeZone()
7167
val zone = NSTimeZone.systemTimeZone
7268
val zoneId = zone.name
73-
return RegionTimeZone(tzdbOnFilesystem.rulesForId(zoneId), zoneId)
69+
return zoneId to null
7470
}
7571

7672
actual val availableZoneIds: Set<String>
7773
get() = tzdbOnFilesystem.availableTimeZoneIds()
7874
}
79-
80-
actual override fun atStartOfDay(date: LocalDate): Instant = memScoped {
81-
val ldt = LocalDateTime(date, LocalTime.MIN)
82-
when (val info = tzid.infoAtDatetime(ldt)) {
83-
is OffsetInfo.Regular -> ldt.toInstant(info.offset)
84-
is OffsetInfo.Gap -> info.start
85-
is OffsetInfo.Overlap -> ldt.toInstant(info.offsetBefore)
86-
}
87-
}
88-
89-
actual override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
90-
when (val info = tzid.infoAtDatetime(dateTime)) {
91-
is OffsetInfo.Regular -> ZonedDateTime(dateTime, this, info.offset)
92-
is OffsetInfo.Gap -> {
93-
try {
94-
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), this, info.offsetAfter)
95-
} catch (e: IllegalArgumentException) {
96-
throw DateTimeArithmeticException(
97-
"Overflow whet correcting the date-time to not be in the transition gap",
98-
e
99-
)
100-
}
101-
}
102-
103-
is OffsetInfo.Overlap -> ZonedDateTime(dateTime, this,
104-
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
105-
}
106-
107-
actual override fun offsetAtImpl(instant: Instant): UtcOffset = tzid.infoAtInstant(instant)
10875
}
10976

11077
internal actual fun currentTime(): Instant = NSDate.date().toKotlinInstant()

core/linux/src/TimeZoneNative.kt

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,52 +10,19 @@ import kotlinx.cinterop.*
1010
import kotlinx.datetime.internal.*
1111
import platform.posix.*
1212

13-
internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual override val id: String) : TimeZone() {
13+
internal actual class TimeZoneDatabase {
1414
actual companion object {
15-
actual fun of(zoneId: String): RegionTimeZone = try {
16-
RegionTimeZone(tzdbOnFilesystem.rulesForId(zoneId), zoneId)
17-
} catch (e: Exception) {
18-
throw IllegalTimeZoneException("Invalid zone ID: $zoneId", e)
19-
}
15+
actual fun rulesForId(id: String): TimeZoneRules = tzdbOnFilesystem.rulesForId(id)
2016

21-
actual fun currentSystemDefault(): RegionTimeZone {
22-
val zoneId = tzdbOnFilesystem.currentSystemDefault()?.second
17+
actual fun currentSystemDefault(): Pair<String, TimeZoneRules?> {
18+
val zoneId = tzdbOnFilesystem.currentSystemDefault()?.second?.toString()
2319
?: throw IllegalStateException("Failed to get the system timezone")
24-
return of(zoneId.toString())
20+
return zoneId to null
2521
}
2622

2723
actual val availableZoneIds: Set<String>
2824
get() = tzdbOnFilesystem.availableTimeZoneIds()
2925
}
30-
31-
actual override fun atStartOfDay(date: LocalDate): Instant = memScoped {
32-
val ldt = LocalDateTime(date, LocalTime.MIN)
33-
when (val info = tzid.infoAtDatetime(ldt)) {
34-
is OffsetInfo.Regular -> ldt.toInstant(info.offset)
35-
is OffsetInfo.Gap -> info.start
36-
is OffsetInfo.Overlap -> ldt.toInstant(info.offsetBefore)
37-
}
38-
}
39-
40-
actual override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
41-
when (val info = tzid.infoAtDatetime(dateTime)) {
42-
is OffsetInfo.Regular -> ZonedDateTime(dateTime, this, info.offset)
43-
is OffsetInfo.Gap -> {
44-
try {
45-
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), this, info.offsetAfter)
46-
} catch (e: IllegalArgumentException) {
47-
throw DateTimeArithmeticException(
48-
"Overflow whet correcting the date-time to not be in the transition gap",
49-
e
50-
)
51-
}
52-
}
53-
54-
is OffsetInfo.Overlap -> ZonedDateTime(dateTime, this,
55-
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
56-
}
57-
58-
actual override fun offsetAtImpl(instant: Instant): UtcOffset = tzid.infoAtInstant(instant)
5926
}
6027

6128
@OptIn(UnsafeNumber::class)

core/native/src/TimeZone.kt

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@ public actual open class TimeZone internal constructor() {
1717

1818
public actual companion object {
1919

20-
public actual fun currentSystemDefault(): TimeZone =
20+
public actual fun currentSystemDefault(): TimeZone {
2121
// TODO: probably check if currentSystemDefault name is parseable as FixedOffsetTimeZone?
22-
RegionTimeZone.currentSystemDefault()
22+
val (name, rules) = TimeZoneDatabase.currentSystemDefault()
23+
return if (rules == null) {
24+
of(name)
25+
} else {
26+
RegionTimeZone(rules, name)
27+
}
28+
}
2329

2430
public actual val UTC: FixedOffsetTimeZone = UtcOffset.ZERO.asTimeZone()
2531

@@ -59,11 +65,15 @@ public actual open class TimeZone internal constructor() {
5965
} catch (e: DateTimeFormatException) {
6066
throw IllegalTimeZoneException(e)
6167
}
62-
return RegionTimeZone.of(zoneId)
68+
return try {
69+
RegionTimeZone(TimeZoneDatabase.rulesForId(zoneId), zoneId)
70+
} catch (e: Exception) {
71+
throw IllegalTimeZoneException("Invalid zone ID: $zoneId", e)
72+
}
6373
}
6474

6575
public actual val availableZoneIds: Set<String>
66-
get() = RegionTimeZone.availableZoneIds
76+
get() = TimeZoneDatabase.availableZoneIds
6777
}
6878

6979
public actual open val id: String
@@ -95,15 +105,10 @@ public actual open class TimeZone internal constructor() {
95105
override fun toString(): String = id
96106
}
97107

98-
internal expect class RegionTimeZone : TimeZone {
99-
override val id: String
100-
override fun atStartOfDay(date: LocalDate): Instant
101-
override fun offsetAtImpl(instant: Instant): UtcOffset
102-
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime
103-
108+
internal expect class TimeZoneDatabase {
104109
companion object {
105-
fun of(zoneId: String): RegionTimeZone
106-
fun currentSystemDefault(): RegionTimeZone
110+
fun rulesForId(id: String): TimeZoneRules
111+
fun currentSystemDefault(): Pair<String, TimeZoneRules?>
107112
val availableZoneIds: Set<String>
108113
}
109114
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2019-2024 JetBrains s.r.o. and contributors.
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+
6+
package kotlinx.datetime.internal
7+
8+
import kotlinx.datetime.*
9+
10+
internal class RegionTimeZone(private val tzid: TimeZoneRules, override val id: String) : TimeZone() {
11+
12+
override fun atStartOfDay(date: LocalDate): Instant {
13+
val ldt = LocalDateTime(date, LocalTime.MIN)
14+
return when (val info = tzid.infoAtDatetime(ldt)) {
15+
is OffsetInfo.Regular -> ldt.toInstant(info.offset)
16+
is OffsetInfo.Gap -> info.start
17+
is OffsetInfo.Overlap -> ldt.toInstant(info.offsetBefore)
18+
}
19+
}
20+
21+
override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
22+
when (val info = tzid.infoAtDatetime(dateTime)) {
23+
is OffsetInfo.Regular -> ZonedDateTime(dateTime, this, info.offset)
24+
is OffsetInfo.Gap -> {
25+
try {
26+
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), this, info.offsetAfter)
27+
} catch (e: IllegalArgumentException) {
28+
throw DateTimeArithmeticException(
29+
"Overflow whet correcting the date-time to not be in the transition gap",
30+
e
31+
)
32+
}
33+
}
34+
35+
is OffsetInfo.Overlap -> ZonedDateTime(dateTime, this,
36+
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
37+
}
38+
39+
override fun offsetAtImpl(instant: Instant): UtcOffset = tzid.infoAtInstant(instant)
40+
}

core/windows/src/TimeZoneNative.kt

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,22 @@
22
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
5-
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
5+
@file:OptIn(ExperimentalForeignApi::class)
66
package kotlinx.datetime
77

88
import kotlinx.cinterop.*
99
import kotlinx.datetime.internal.*
1010
import platform.posix.*
11-
import platform.windows.*
1211

13-
internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual override val id: String) : TimeZone() {
12+
internal actual class TimeZoneDatabase {
1413
actual companion object {
15-
actual fun of(zoneId: String): RegionTimeZone = try {
16-
RegionTimeZone(tzdbInRegistry.rulesForId(zoneId), zoneId)
17-
} catch (e: Exception) {
18-
throw IllegalTimeZoneException("Invalid zone ID: $zoneId", e)
19-
}
14+
actual fun rulesForId(id: String): TimeZoneRules = tzdbInRegistry.rulesForId(id)
2015

21-
actual fun currentSystemDefault(): RegionTimeZone {
22-
val (name, zoneRules) = tzdbInRegistry.currentSystemDefault()
23-
return RegionTimeZone(zoneRules, name)
24-
}
16+
actual fun currentSystemDefault(): Pair<String, TimeZoneRules?> = tzdbInRegistry.currentSystemDefault()
2517

2618
actual val availableZoneIds: Set<String>
2719
get() = tzdbInRegistry.availableTimeZoneIds()
2820
}
29-
30-
actual override fun atStartOfDay(date: LocalDate): Instant = memScoped {
31-
val ldt = LocalDateTime(date, LocalTime.MIN)
32-
when (val info = tzid.infoAtDatetime(ldt)) {
33-
is OffsetInfo.Regular -> ldt.toInstant(info.offset)
34-
is OffsetInfo.Gap -> info.start
35-
is OffsetInfo.Overlap -> ldt.toInstant(info.offsetBefore)
36-
}
37-
}
38-
39-
actual override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
40-
when (val info = tzid.infoAtDatetime(dateTime)) {
41-
is OffsetInfo.Regular -> ZonedDateTime(dateTime, this, info.offset)
42-
is OffsetInfo.Gap -> {
43-
try {
44-
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), this, info.offsetAfter)
45-
} catch (e: IllegalArgumentException) {
46-
throw DateTimeArithmeticException(
47-
"Overflow whet correcting the date-time to not be in the transition gap",
48-
e
49-
)
50-
}
51-
}
52-
53-
is OffsetInfo.Overlap -> ZonedDateTime(dateTime, this,
54-
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
55-
}
56-
57-
actual override fun offsetAtImpl(instant: Instant): UtcOffset = tzid.infoAtInstant(instant)
5821
}
5922

6023
private val tzdbInRegistry = TzdbInRegistry()

0 commit comments

Comments
 (0)