Skip to content

Commit af003a3

Browse files
authored
Fix the documentation for platform-specific timezone access (#520)
Fixes #517
1 parent 7380325 commit af003a3

File tree

7 files changed

+122
-7
lines changed

7 files changed

+122
-7
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,9 @@ and after that add the following initialization code in your project:
475475
@JsNonModule
476476
external object JsJodaTimeZoneModule
477477

478-
private val jsJodaTz = JsJodaTimeZoneModule
478+
@OptIn(ExperimentalJsExport::class)
479+
@JsExport
480+
val jsJodaTz = JsJodaTimeZoneModule
479481
```
480482

481483
#### Note about time zones in Wasm/JS

core/common/src/TimeZone.kt

+57-6
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,36 @@ public expect open class TimeZone {
6363
*
6464
* If the current system time zone changes, this function can reflect this change on the next invocation.
6565
*
66-
* On Linux, this function queries the `/etc/localtime` symbolic link. If the link is missing, [UTC] is used.
67-
* If the link points to an invalid location, [IllegalTimeZoneException] is thrown.
66+
* It is recommended to call this function once at the start of an operation and reuse the result:
67+
* querying the system time zone may involve heavy operations like reading the system files,
68+
* and also, querying the system time zone multiple times in one operation may lead to inconsistent results
69+
* if the system time zone changes in the middle of the operation.
6870
*
69-
* Always returns the `UTC` timezone on the Wasm WASI platform due to the lack of support for retrieving system timezone information.
71+
* How exactly the time zone is acquired is system-dependent. The current implementation:
72+
* - JVM: `java.time.ZoneId.systemDefault()` is queried.
73+
* - Kotlin/Native:
74+
* - Darwin: first, `NSTimeZone.resetSystemTimeZone` is called to clear the cache of the system timezone.
75+
* Then, `NSTimeZone.systemTimeZone.name` is used to obtain the up-to-date timezone name.
76+
* - Linux: this function checks the `/etc/localtime` symbolic link.
77+
* If the link is missing, [UTC] is used.
78+
* If the file is not a link but a plain file,
79+
* the contents of `/etc/timezone` are additionally checked for the timezone name.
80+
* [IllegalTimeZoneException] is thrown if the timezone name cannot be determined
81+
* or is invalid.
82+
* - Windows: the `GetDynamicTimeZoneInformation` function is used,
83+
* with the native Windows timezone name being mapped to the corresponding IANA identifier.
84+
* [IllegalTimeZoneException] is thrown if this mapping fails. See [of] for details.
85+
* - JavaScript and Wasm/JS:
86+
* - If the `@js-joda/timezone` library is loaded,
87+
* `Intl.DateTimeFormat().resolvedOptions().timeZone` is used to obtain the timezone name.
88+
* See https://github.com/Kotlin/kotlinx-datetime/blob/master/README.md#note-about-time-zones-in-js
89+
* - Otherwise, a time zone with the identifier `"SYSTEM"` is returned,
90+
* and JS `Date`'s `getTimezoneOffset()` is used to obtain the offset for the given moment.
91+
* - Wasm/WASI: always returns the `UTC` timezone,
92+
* as the platform does not support retrieving system timezone information.
93+
*
94+
* Note that the implementation of this function for various platforms may change in the future,
95+
* in particular, the JavaScript and Wasm/JS platforms.
7096
*
7197
* @sample kotlinx.datetime.test.samples.TimeZoneSamples.currentSystemDefault
7298
*/
@@ -95,12 +121,37 @@ public expect open class TimeZone {
95121
* It is guaranteed that passing any value from [availableZoneIds] to this function will return
96122
* a valid time zone.
97123
*
124+
* How exactly the region-based time zone is acquired is system-dependent. The current implementation:
125+
* - JVM: `java.time.ZoneId.of(zoneId)` is used.
126+
* - Kotlin/Native:
127+
* - Darwin devices: the timezone database in `/var/db/timezone/zoneinfo` is used by default,
128+
* and if it is not a valid timezone database, the same search procedure as on Linux is used.
129+
* - Darwin simulators: the timezone database in `/usr/share/zoneinfo.default` is used by default,
130+
* and if it is not a valid timezone database, the same search procedure as on Linux is used.
131+
* - Linux: `/usr/share/zoneinfo`, `/usr/share/lib/zoneinfo`, and `/etc/zoneinfo`
132+
* are checked in turn for the timezone database.
133+
* If none of them is a valid timezone database, `/etc/localtime` is checked.
134+
* If it is a symlink of the form `.../zoneinfo/...`,
135+
* the target of the symlink with the last part stripped is used as the timezone database.
136+
* - Windows: the contents of the
137+
* `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`
138+
* registry key are queried to obtain the timezone database.
139+
* Then, the Windows-specific timezone name is mapped to the corresponding IANA identifier
140+
* using the information from the CLDR project:
141+
* https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml
142+
* - JavaScript and Wasm/JS:
143+
* if the `@js-joda/timezone` library is loaded,
144+
* it is used to obtain the timezone rules.
145+
* Otherwise, the [IllegalTimeZoneException] is thrown.
146+
* See https://github.com/Kotlin/kotlinx-datetime/blob/master/README.md#note-about-time-zones-in-js
147+
* - Wasm/WASI:
148+
* if the `kotlinx-datetime-zoneinfo` artifact is added to the project as a dependency,
149+
* it is used to obtain the timezone rules.
150+
* Otherwise, the [IllegalTimeZoneException] is thrown.
151+
*
98152
* @throws IllegalTimeZoneException if [zoneId] has an invalid format or a time-zone with the name [zoneId]
99153
* is not found.
100154
*
101-
* @throws IllegalTimeZoneException on the Wasm WASI platform for non-fixed-offset time zones,
102-
* unless a dependency on the `kotlinx-datetime-zoneinfo` artifact is added.
103-
*
104155
* @sample kotlinx.datetime.test.samples.TimeZoneSamples.constructorFunction
105156
*/
106157
public fun of(zoneId: String): TimeZone

js-with-timezones/build.gradle.kts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@file:OptIn(ExperimentalWasmDsl::class)
2+
3+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
4+
5+
plugins {
6+
id("kotlin-multiplatform")
7+
id("org.jetbrains.kotlinx.kover")
8+
}
9+
10+
kotlin {
11+
js {
12+
nodejs {
13+
}
14+
binaries.executable()
15+
}
16+
wasmJs {
17+
nodejs {
18+
}
19+
binaries.executable()
20+
}
21+
22+
sourceSets {
23+
commonMain {
24+
dependencies {
25+
api(project(":kotlinx-datetime"))
26+
}
27+
}
28+
jsMain {
29+
dependencies {
30+
implementation(npm("@js-joda/timezone", "2.3.0"))
31+
}
32+
}
33+
wasmJsMain {
34+
dependencies {
35+
implementation(npm("@js-joda/timezone", "2.3.0"))
36+
}
37+
}
38+
}
39+
}
40+
41+
// Ensure that loading the timezone code works even after minification
42+
tasks.named("check") {
43+
dependsOn(tasks.named("jsNodeProductionRun"), tasks.named("wasmJsNodeProductionRun"))
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import kotlinx.datetime.TimeZone
2+
3+
fun main() {
4+
println(TimeZone.of("Europe/Berlin"))
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@JsModule("@js-joda/timezone")
2+
@JsNonModule
3+
external object JsJodaTimeZoneModule
4+
5+
@OptIn(ExperimentalJsExport::class)
6+
@JsExport
7+
public val jsJodaTz = JsJodaTimeZoneModule
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@JsModule("@js-joda/timezone")
2+
external object JsJodaTimeZoneModule
3+
4+
private val jsJodaTz = JsJodaTimeZoneModule

settings.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ include(":serialization")
2424
project(":serialization").name = "kotlinx-datetime-serialization"
2525
include(":js-without-timezones")
2626
project(":js-without-timezones").name = "kotlinx-datetime-js-test-without-timezones"
27+
include(":js-with-timezones")
28+
project(":js-with-timezones").name = "kotlinx-datetime-js-test-with-timezones"
2729
include(":benchmarks")

0 commit comments

Comments
 (0)