Skip to content

Implement the timezone database on Windows in Kotlin #291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "date-cpp-library/date"]
path = thirdparty/date
url = https://github.com/HowardHinnant/date
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,15 +381,6 @@ Add a dependency to the `<dependencies>` element. Note that you need to use the

## Building

Before building, ensure that you have [thirdparty/date](thirdparty/date) submodule initialized and updated.
IDEA does that automatically when cloning the repository, and if you cloned it in the command line, you may need
to run additionally:

```kotlin
git submodule init
git submodule update
```

The project requires JDK 8 to build classes and to run tests.
Gradle will try to find it among the installed JDKs or [provision](https://docs.gradle.org/current/userguide/toolchains.html#sec:provisioning) it automatically if it couldn't be found.
The path to JDK 8 can be additionally specified with the environment variable `JDK_8`.
Expand Down
59 changes: 21 additions & 38 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import kotlinx.team.infra.mavenPublicationsPom
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.net.URL
import java.util.Locale
import javax.xml.parsers.DocumentBuilderFactory
import java.io.ByteArrayOutputStream
import java.io.PrintWriter
Expand Down Expand Up @@ -75,7 +74,9 @@ kotlin {
}
}
// Tier 3
target("mingwX64")
common("windows") {
target("mingwX64")
}
}

jvm {
Expand Down Expand Up @@ -113,7 +114,7 @@ kotlin {
sourceSets.all {
val suffixIndex = name.indexOfLast { it.isUpperCase() }
val targetName = name.substring(0, suffixIndex)
val suffix = name.substring(suffixIndex).lowercase().takeIf { it != "main" }
val suffix = name.substring(suffixIndex).toLowerCase().takeIf { it != "main" }
// println("SOURCE_SET: $name")
kotlin.srcDir("$targetName/${suffix ?: "src"}")
resources.srcDir("$targetName/${suffix?.let { it + "Resources" } ?: "resources"}")
Expand All @@ -129,24 +130,11 @@ kotlin {
when {
konanTarget.family == org.jetbrains.kotlin.konan.target.Family.MINGW -> {
compilations["main"].cinterops {
create("date") {
val cinteropDir = "$projectDir/native/cinterop"
val dateLibDir = "${project(":").projectDir}/thirdparty/date"
headers("$cinteropDir/public/cdate.h")
defFile("native/cinterop/date.def")
extraOpts("-Xsource-compiler-option", "-I$cinteropDir/public")
extraOpts("-Xsource-compiler-option", "-DONLY_C_LOCALE=1")
// needed to be able to use std::shared_mutex to implement caching.
extraOpts("-Xsource-compiler-option", "-std=c++17")
// the date library headers, needed for some pure calculations.
extraOpts("-Xsource-compiler-option", "-I$dateLibDir/include")
// the main source for the platform bindings.
extraOpts("-Xcompile-source", "$cinteropDir/cpp/windows.cpp")
create("declarations") {
defFile("$projectDir/windows/cinterop/definitions.def")
headers("$projectDir/windows/cinterop/definitions.h")
}
}
compilations["main"].defaultSourceSet {
kotlin.srcDir("native/cinterop_actuals")
}
}
konanTarget.family == org.jetbrains.kotlin.konan.target.Family.LINUX -> {
// do nothing special
Expand All @@ -159,8 +147,6 @@ kotlin {
}
}
}


sourceSets {
commonMain {
dependencies {
Expand Down Expand Up @@ -297,8 +283,8 @@ tasks {

val downloadWindowsZonesMapping by tasks.registering {
description = "Updates the mapping between Windows-specific and usual names for timezones"
val output = "$projectDir/native/cinterop/public/windows_zones.hpp"
val initialFileContents = File(output).readBytes()
val output = "$projectDir/windows/src/WindowsZoneNames.kt"
val initialFileContents = try { File(output).readBytes() } catch(e: Throwable) { ByteArray(0) }
outputs.file(output)
doLast {
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
Expand All @@ -312,11 +298,13 @@ val downloadWindowsZonesMapping by tasks.registering {
xmlDoc.documentElement.normalize()
val mapZones = xmlDoc.getElementsByTagName("mapZone")
val mapping = linkedMapOf<String, String>()
mapping["UTC"] = "UTC"
for (i in 0 until mapZones.length) {
val mapZone = mapZones.item(i)
val windowsName = mapZone.attributes.getNamedItem("other").nodeValue
val usualNames = mapZone.attributes.getNamedItem("type").nodeValue
for (usualName in usualNames.split(' ')) {
if (usualName == "") continue
val oldWindowsName = mapping[usualName] // don't do it in `put` to preserve the order in the map
if (oldWindowsName == null) {
mapping[usualName] = windowsName
Expand All @@ -329,31 +317,26 @@ val downloadWindowsZonesMapping by tasks.registering {
val bos = ByteArrayOutputStream()
PrintWriter(bos).use { out ->
out.println("""// generated with gradle task `$name`""")
out.println("""#include <unordered_map>""")
out.println("""#include <string>""")
out.println("""static const std::unordered_map<std::string, std::string> standard_to_windows = {""")
out.println("""package kotlinx.datetime""")
out.println("""import kotlin.native.concurrent.SharedImmutable""")
out.println("""@SharedImmutable""")
out.println("""internal val standardToWindows: Map<String, String> = mutableMapOf(""")
for ((usualName, windowsName) in sortedMapping) {
out.println("\t{ \"$usualName\", \"$windowsName\" },")
out.println(" \"$usualName\" to \"$windowsName\",")
}
out.println("};")
out.println("""static const std::unordered_map<std::string, std::string> windows_to_standard = {""")
out.println(")")
out.println("""@SharedImmutable""")
out.println("""internal val windowsToStandard: Map<String, String> = mutableMapOf(""")
val reverseMap = sortedMapOf<String, String>()
for ((usualName, windowsName) in mapping) {
if (reverseMap[windowsName] == null) {
reverseMap[windowsName] = usualName
}
}
for ((windowsName, usualName) in reverseMap) {
out.println("\t{ \"$windowsName\", \"$usualName\" },")
}
out.println("};")
out.println("""static const std::unordered_map<std::string, size_t> zone_ids = {""")
var i = 0
for ((usualName, windowsName) in sortedMapping) {
out.println("\t{ \"$usualName\", $i },")
++i
out.println(" \"$windowsName\" to \"$usualName\",")
}
out.println("};")
out.println(")")
}
val newFileContents = bos.toByteArray()
if (!(initialFileContents contentEquals newFileContents)) {
Expand Down
12 changes: 6 additions & 6 deletions core/common/test/TimeZoneTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ class TimeZoneTest {
@Test
fun available() {
val allTzIds = TimeZone.availableZoneIds
assertContains(allTzIds, "Europe/Berlin")
assertContains(allTzIds, "Europe/Moscow")
assertContains(allTzIds, "America/New_York")
assertContains(allTzIds, "Europe/Berlin", "Europe/Berlin not in $allTzIds")
assertContains(allTzIds, "Europe/Moscow", "Europe/Moscow not in $allTzIds")
assertContains(allTzIds, "America/New_York", "America/New_York not in $allTzIds")

assertNotEquals(0, allTzIds.size)
assertTrue(TimeZone.currentSystemDefault().id in allTzIds)
assertTrue("UTC" in allTzIds)
assertTrue(TimeZone.currentSystemDefault().id in allTzIds,
"The current system timezone ${TimeZone.currentSystemDefault().id} is not in $allTzIds")
assertTrue("UTC" in allTzIds, "The UTC timezone not in $allTzIds")
}

@Test
Expand Down
Loading