Skip to content

Commit 20030bc

Browse files
committed
Clearly define the internal API for platform definitions
1 parent 68a1c95 commit 20030bc

File tree

10 files changed

+120
-131
lines changed

10 files changed

+120
-131
lines changed

core/darwin/src/TimeZoneNative.kt

-79
This file was deleted.
+68-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,75 @@
11
/*
2-
* Copyright 2019-2024 JetBrains s.r.o. and contributors.
2+
* Copyright 2019-2020 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

6+
@file:OptIn(ExperimentalForeignApi::class)
67
package kotlinx.datetime.internal
78

9+
import kotlinx.cinterop.*
10+
import kotlinx.datetime.Instant
11+
import kotlinx.datetime.toKotlinInstant
12+
import kotlinx.datetime.internal.*
13+
import platform.Foundation.*
14+
15+
internal actual val systemTzdb: TimeZoneDatabase get() = tzdbOnFilesystem
16+
17+
internal actual fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?> {
18+
/* The framework has its own cache of the system timezone. Calls to
19+
[NSTimeZone systemTimeZone] do not reflect changes to the system timezone
20+
and instead just return the cached value. Thus, to acquire the current
21+
system timezone, first, the cache should be cleared.
22+
23+
This solution is not without flaws, however. In particular, resetting the
24+
system timezone also resets the default timezone ([NSTimeZone default]) if
25+
it's the same as the cached system timezone:
26+
27+
NSTimeZone.defaultTimeZone = [NSTimeZone
28+
timeZoneWithName: [[NSTimeZone systemTimeZone] name]];
29+
NSLog(@"%@", NSTimeZone.defaultTimeZone.name);
30+
NSLog(@"Change the system time zone, then press Enter");
31+
getchar();
32+
[NSTimeZone resetSystemTimeZone];
33+
NSLog(@"%@", NSTimeZone.defaultTimeZone.name); // will also change
34+
35+
This is a fairly marginal problem:
36+
* It is only a problem when the developer deliberately sets the default
37+
timezone to the region that just happens to be the one that the user
38+
is in, and then the user moves to another region, and the app also
39+
uses the system timezone.
40+
* Since iOS 11, the significance of the default timezone has been
41+
de-emphasized. In particular, it is not included in the API for
42+
Swift: https://forums.swift.org/t/autoupdating-type-properties/4608/4
43+
44+
Another possible solution could involve using [NSTimeZone localTimeZone].
45+
This is documented to reflect the current, uncached system timezone on
46+
iOS 11 and later:
47+
https://developer.apple.com/documentation/foundation/nstimezone/1387209-localtimezone
48+
However:
49+
* Before iOS 11, this was the same as the default timezone and did not
50+
reflect the system timezone.
51+
* Worse, on a Mac (10.15.5), I failed to get it to work as documented.
52+
NSLog(@"%@", NSTimeZone.localTimeZone.name);
53+
NSLog(@"Change the system time zone, then press Enter");
54+
getchar();
55+
// [NSTimeZone resetSystemTimeZone]; // uncomment to make it work
56+
NSLog(@"%@", NSTimeZone.localTimeZone.name);
57+
The printed strings are the same even if I wait for good 10 minutes
58+
before pressing Enter, unless the line with "reset" is uncommented--
59+
then the timezone is updated, as it should be. So, for some reason,
60+
NSTimeZone.localTimeZone, too, is cached.
61+
With no iOS device to test this on, it doesn't seem worth the effort
62+
to avoid just resetting the system timezone due to one edge case
63+
that's hard to avoid.
64+
*/
65+
NSTimeZone.resetSystemTimeZone()
66+
val zone = NSTimeZone.systemTimeZone
67+
val zoneId = zone.name
68+
return zoneId to null
69+
}
70+
71+
internal actual fun currentTime(): Instant = NSDate.date().toKotlinInstant()
72+
73+
private val tzdbOnFilesystem = TzdbOnFilesystem(Path.fromString(defaultTzdbPath()))
74+
875
internal expect fun defaultTzdbPath(): String

core/linux/src/TimeZoneNative.kt renamed to core/linux/src/internal/TimeZoneNative.kt

+8-15
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,18 @@
44
*/
55

66
@file:OptIn(ExperimentalForeignApi::class)
7-
package kotlinx.datetime
7+
package kotlinx.datetime.internal
88

99
import kotlinx.cinterop.*
10-
import kotlinx.datetime.internal.*
10+
import kotlinx.datetime.Instant
1111
import platform.posix.*
1212

13-
internal actual class TimeZoneDatabase {
14-
actual companion object {
15-
actual fun rulesForId(id: String): TimeZoneRules = tzdbOnFilesystem.rulesForId(id)
13+
internal actual val systemTzdb: TimeZoneDatabase get() = tzdbOnFilesystem
1614

17-
actual fun currentSystemDefault(): Pair<String, TimeZoneRules?> {
18-
val zoneId = tzdbOnFilesystem.currentSystemDefault()?.second?.toString()
19-
?: throw IllegalStateException("Failed to get the system timezone")
20-
return zoneId to null
21-
}
22-
23-
actual val availableZoneIds: Set<String>
24-
get() = tzdbOnFilesystem.availableTimeZoneIds()
25-
}
15+
internal actual fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?> {
16+
val zoneId = tzdbOnFilesystem.currentSystemDefault()?.second?.toString()
17+
?: throw IllegalStateException("Failed to get the system timezone")
18+
return zoneId to null
2619
}
2720

2821
@OptIn(UnsafeNumber::class)
@@ -43,4 +36,4 @@ internal actual fun currentTime(): Instant = memScoped {
4336
}
4437
}
4538

46-
private val tzdbOnFilesystem = TzdbOnFilesystem(Path.fromString("/usr/share/zoneinfo"))
39+
private val tzdbOnFilesystem: TzdbOnFilesystem = TzdbOnFilesystem(Path.fromString("/usr/share/zoneinfo"))

core/native/src/Instant.kt

-2
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,6 @@ private const val MAX_SECOND = 31494816403199L // +1000000-12-31T23:59:59
127127

128128
private fun isValidInstantSecond(second: Long) = second >= MIN_SECOND && second <= MAX_SECOND
129129

130-
internal expect fun currentTime(): Instant
131-
132130
@Serializable(with = InstantIso8601Serializer::class)
133131
public actual class Instant internal constructor(public actual val epochSeconds: Long, public actual val nanosecondsOfSecond: Int) : Comparable<Instant> {
134132

core/native/src/TimeZone.kt

+3-12
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public actual open class TimeZone internal constructor() {
1919

2020
public actual fun currentSystemDefault(): TimeZone {
2121
// TODO: probably check if currentSystemDefault name is parseable as FixedOffsetTimeZone?
22-
val (name, rules) = TimeZoneDatabase.currentSystemDefault()
22+
val (name, rules) = currentSystemDefaultZone()
2323
return if (rules == null) {
2424
of(name)
2525
} else {
@@ -66,14 +66,14 @@ public actual open class TimeZone internal constructor() {
6666
throw IllegalTimeZoneException(e)
6767
}
6868
return try {
69-
RegionTimeZone(TimeZoneDatabase.rulesForId(zoneId), zoneId)
69+
RegionTimeZone(systemTzdb.rulesForId(zoneId), zoneId)
7070
} catch (e: Exception) {
7171
throw IllegalTimeZoneException("Invalid zone ID: $zoneId", e)
7272
}
7373
}
7474

7575
public actual val availableZoneIds: Set<String>
76-
get() = TimeZoneDatabase.availableZoneIds
76+
get() = systemTzdb.availableTimeZoneIds()
7777
}
7878

7979
public actual open val id: String
@@ -105,15 +105,6 @@ public actual open class TimeZone internal constructor() {
105105
override fun toString(): String = id
106106
}
107107

108-
internal expect class TimeZoneDatabase {
109-
companion object {
110-
fun rulesForId(id: String): TimeZoneRules
111-
fun currentSystemDefault(): Pair<String, TimeZoneRules?>
112-
val availableZoneIds: Set<String>
113-
}
114-
}
115-
116-
117108
@Serializable(with = FixedOffsetTimeZoneSerializer::class)
118109
public actual class FixedOffsetTimeZone internal constructor(public actual val offset: UtcOffset, override val id: String) : TimeZone() {
119110

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
internal interface TimeZoneDatabase {
9+
fun rulesForId(id: String): TimeZoneRules
10+
fun availableTimeZoneIds(): Set<String>
11+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 expect val systemTzdb: TimeZoneDatabase
11+
12+
internal expect fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?>
13+
14+
internal expect fun currentTime(): Instant

core/nix/src/internal/TzdbOnFilesystem.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
package kotlinx.datetime.internal
77

8-
internal class TzdbOnFilesystem(defaultTzdbPath: Path) {
8+
internal class TzdbOnFilesystem(defaultTzdbPath: Path): TimeZoneDatabase {
99

10-
internal fun rulesForId(id: String): TimeZoneRules =
10+
override fun rulesForId(id: String): TimeZoneRules =
1111
readTzFile(tzdbPath.resolve(Path.fromString(id)).readBytes()).toTimeZoneRules()
1212

13-
internal fun availableTimeZoneIds(): Set<String> = buildSet {
13+
override fun availableTimeZoneIds(): Set<String> = buildSet {
1414
tzdbPath.traverseDirectory(exclude = tzdbUnneededFiles) { add(it.toString()) }
1515
}
1616

Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
/*
2-
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
2+
* Copyright 2019-2024 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+
56
@file:OptIn(ExperimentalForeignApi::class)
6-
package kotlinx.datetime
7+
package kotlinx.datetime.internal
78

89
import kotlinx.cinterop.*
9-
import kotlinx.datetime.internal.*
10+
import kotlinx.datetime.*
1011
import platform.posix.*
1112

12-
internal actual class TimeZoneDatabase {
13-
actual companion object {
14-
actual fun rulesForId(id: String): TimeZoneRules = tzdbInRegistry.rulesForId(id)
15-
16-
actual fun currentSystemDefault(): Pair<String, TimeZoneRules?> = tzdbInRegistry.currentSystemDefault()
17-
18-
actual val availableZoneIds: Set<String>
19-
get() = tzdbInRegistry.availableTimeZoneIds()
20-
}
21-
}
13+
internal actual val systemTzdb: TimeZoneDatabase get() = tzdbInRegistry
2214

23-
private val tzdbInRegistry = TzdbInRegistry()
15+
internal actual fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?> = tzdbInRegistry.currentSystemDefault()
2416

2517
internal actual fun currentTime(): Instant = memScoped {
2618
val tm = alloc<timespec>()
@@ -33,3 +25,5 @@ internal actual fun currentTime(): Instant = memScoped {
3325
throw IllegalStateException("The readings from the system clock (${tm.tv_sec} seconds, ${tm.tv_nsec} nanoseconds) are not representable as an Instant")
3426
}
3527
}
28+
29+
private val tzdbInRegistry = TzdbInRegistry()

core/windows/src/internal/TzdbInRegistry.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
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.internal
77

88
import kotlinx.datetime.*
99
import kotlinx.cinterop.*
10-
import kotlinx.datetime.internal.*
10+
import platform.posix.*
1111
import platform.windows.*
1212

13-
internal class TzdbInRegistry {
13+
internal class TzdbInRegistry: TimeZoneDatabase {
1414

1515
// TODO: starting version 1703 of Windows 10, the ICU library is also bundled, with more accurate/ timezone information.
1616
// When Kotlin/Native drops support for Windows 7, we should investigate moving to the ICU.
@@ -45,13 +45,13 @@ internal class TzdbInRegistry {
4545
}
4646
}
4747

48-
internal fun rulesForId(id: String): TimeZoneRules {
48+
override fun rulesForId(id: String): TimeZoneRules {
4949
val standardName = standardToWindows[id] ?: throw IllegalTimeZoneException("Unknown time zone $id")
5050
return windowsToRules[standardName]
5151
?: throw IllegalTimeZoneException("The rules for time zone $id are absent in the Windows registry")
5252
}
5353

54-
internal fun availableTimeZoneIds(): Set<String> = standardToWindows.filter {
54+
override fun availableTimeZoneIds(): Set<String> = standardToWindows.filter {
5555
windowsToRules.containsKey(it.value)
5656
}.keys
5757

0 commit comments

Comments
 (0)