Skip to content
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

Wasm/WASI target implementation #366

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

Wasm/WASI target implementation #366

wants to merge 7 commits into from

Conversation

igoriakovlev
Copy link
Contributor

This is Wasm/WASI target implementation. It based on native version with hardcoded tzdb.

  1. Extracted native implementation into intermediate sourceset.
  2. Added WASI implementation
  3. Added timezones-full database project

Before building timezones project you should run generateWasmWasiZoneInfo task to download and convert timezones db (nodejs and npm should be installed in the system). We decided to put the result of this task into the repository but for review it is not generated yet.

sourceSets {
commonMain {
dependencies {
compileOnly(project(":kotlinx-datetime"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applying timezones without datetime lead to broken compilation (or exception on usage times zone when PL is enabled). This helps us to avoid version collisions on gradle side and circullar dependency while testing.


@OptIn(InternalDateTimeApi::class)
private fun zoneDataByName(name: String): ByteArray =
timeZonesProvider?.zoneDataByName(name) ?: error("TimeZones are not supported")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to decide how to lead user to use timezones dependency in this case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be an IllegalTimeZoneException (which is documented in the KDoc of TimeZone.of), with the text like "WASI does not support time zones automatically; please add a dependency on kotlinx-datetime-timezones-full"

Also, there should be a clear difference between a zone not being found because it doesn't exist and it not being found because the dependency wasn't provided, though with the current pure-Kotlin TimeZone.kt, it won't work, so we'll need to reorganize the code a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would like to add it in common TimeZone.of KDoc or in make a separate KDoc for platform-specific TimeZone.of?

*/
@InternalDateTimeApi
public fun initializeTimeZonesProvider(provider: TimeZonesProvider) {
check(timeZonesProvider != provider) { "TimeZone database redeclaration" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to decide what to do if more that one timezone library were added

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this issue can't occur currently, when there's only one artifact with the time zones, right? If we decide to publish partial timezone data, we could do it in a way that resolves conflicts on the build system level. For example, kotlinx-datetime-timezone-basic + kotlinx-datetime-timezones-full, with full depending on basic. So I'm not sure if we will ever encounter such conflicts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea how to to resolve such conflicts for different flavors of timezones libraries without a special gradle plugin. We, with gradle plugin team, do not see the way for now, how to get one dependency with gradle resolution, the only way I see now is to allow redeclaration in runtime with a special "priority" level. But how to manage this level's needs a consideration.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a scheme:

kotlinx-datetime-timezones-full -> kotlinx-datetime-timezones-basic -> kotlinx-datetime

A -> B means "A depends on B". kotlinx-datetime-timezones-full only provides new data, the bulk of the data is in kotlinx-datetime-timezones-basic. Then, kotlinx-datetime-timezones-basic could have another thing like initializeFullTimeZonesProvider, which kotlinx-datetime-timezones-full would call. Do you see any problems with this approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for now we are not decided yet what could -basic be but a first thoughts was about to remove some information from timezones (like make only future implementation or +-5 years). But not amount timezones. So the information in timezones then will be not additive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, we'll be able to work around this somehow. This is a technical thing invisible to the users, so we can safely do anything we want here.

@igoriakovlev
Copy link
Contributor Author

The build fails because of none of TZ binaries in the PR.

Copy link
Contributor

@dkhalanskyjb dkhalanskyjb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, not to forget: when we decide on the versions, artifact names, etc., we'll need to update the README.

core/build.gradle.kts Outdated Show resolved Hide resolved
core/native/src/internal/ThreadLocal.kt Outdated Show resolved Hide resolved
buildSrc/src/main/kotlin/zoneInfosResourcesGenerator.kt Outdated Show resolved Hide resolved
core/nativeAndWasmWasi/src/internal/ThreadLocal.kt Outdated Show resolved Hide resolved
}

internal actual fun currentSystemDefaultZone(): Pair<String, TimeZoneRules?> =
throw UnsupportedOperationException("WASI platform does not support system timezone obtaining")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we expect this exception to realistically occur, we must document it in TimeZone.currentSystemDefault()'s KDoc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would like to add it in common TimeZone KDoc or in make a separate KDoc for platform-specific currentSystemDefaultZone?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The common one, because when writing TimeZone.currentSystemDefault() in common code, you should still be aware that this may fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After an internal discussion, we decided that it makes more sense to return the UTC time zone by default. The reason is, because TimeZone.currentSystemDefault() is not easily mockable (see #17), sometimes, there are calls to currentSystemDefault() deep inside the existing code, and to have them throw exceptions now would be problematic. If/when we replace currentSystemDefault() with a version that supports dependency injection better, that version will throw on WASI.

This should still be documented, and it should be in the common code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, you suggest to set currentSystemDefault into the UTC and document it, right?
Should we also need to put UTC into availableTimeZoneIds?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

timezones/full/build.gradle.kts Outdated Show resolved Hide resolved
timezones/full/build.gradle.kts Outdated Show resolved Hide resolved
timezones/full/build.gradle.kts Outdated Show resolved Hide resolved
org.gradle.java.installations.fromEnv=JDK_8

group=org.jetbrains.kotlinx
version=0.6.0-RC.2
versionSuffix=SNAPSHOT

timezonesMajorVersion=1.0
tzdbVersion=2024a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note (we'll have to discuss this internally): SemVer doesn't allow letters in the initial components, so 1.0.2024a is an invalid version number. We could stick to this anyway, or we could use the less natural 1.0.2024-a (note the dash).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end, this isn't a SemVer anyway, so dashes aren't needed, 2024a is ok.

import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolverPlugin

plugins {
kotlin("multiplatform")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good call to have timezonesMajorVersion so we could describe the compatibility between kotlinx-datetime and the timezones package. It's easy to imagine people sticking to stable versions of the library but still upgrading the time zones.

But the Kotlin version is also part of the compatibility: there are no compatibility guarantees when someone uses the library compiled with the compiler version newer than the one used by the project itself, so to upgrade the timezone database correctly, you will need to upgrade Kotlin, which I think is strange.

I propose this scheme: kotlinx-datetime-timezones-full is compiled with the oldest compiler that works, and when some incompatibility arises between the compiler used for kotlinx-datetime and the one for kotlinx-datetime-timezones-full, only then to upgrade the compiler here, while simultaneously upgrading the timezonesMajorVersion.

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only thing here is that we have no possibility (in current approach) to stick a range of major versions of timezones to specific range of datetime versions in gradle. So for now it could be only documented as compatibility table.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a discussion, here's the plan we arrived at:

  • Initially, while WASI is allowed to remain binary-incompatible and the library itself is unstable, we want to version our timezone database artifacts like 2024a-spi.0.6.0, where 2024a is the timezone database version and 0.6.0 is the library version. The compatibility table isn't needed, because it's self-evident if the artifacts are compatible.
  • When both the library and WASI reach stability, we'll have 2028c-spi.1, 2029d-spi.2, etc., where 1 and 2 is the version of the protocol by which the timezone artifact registers itself in the datetime library. Yes, these versions will need to be described in a compatibility table.
  • When we are completely satisfied with the protocol, we may drop the versioning entirely and only have things like 2036f as our timezone database versions.

org.gradle.java.installations.fromEnv=JDK_8

group=org.jetbrains.kotlinx
version=0.6.0-RC.2
versionSuffix=SNAPSHOT

timezonesMajorVersion=1.0
tzdbVersion=2024a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be out of this PR's scope, but it's possible to automatically detect that a new version of tzdb was published: https://github.com/ThreeTen/threetenbp/blob/main/.github/workflows/tzdbupdate.yml If you feel like implementing this, it would be nice; if not, we'll just do it later ourselves.

gradle.properties Outdated Show resolved Hide resolved
@dkhalanskyjb dkhalanskyjb added this to the 0.7.0 milestone Apr 12, 2024
@@ -16,6 +16,8 @@ rootProject.name = "Kotlin-DateTime-library"

include(":core")
project(":core").name = "kotlinx-datetime"
include(":timezones/full")
project(":timezones/full").name = "kotlinx-datetime-timezones-full"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The artifact name we decided on is kotlinx-datetime-zoneinfo. At least for now, without full.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if we decide to make an intermediate timezones db?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we decide to do it, we'll know from the reported use cases what exactly people want, and then we'll choose the names appropriately.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-zoneinfo, not -timezones.

@igoriakovlev
Copy link
Contributor Author

I have rebased onto the master and merged all the AMEND commits by @dkhalanskyjb request.

sourceSets {
val wasmWasiMain by getting {
dependencies {
implementation("kotlinx-datetime-zoneinfo", "2024a-spi.0.6.0-RC.2")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
implementation("kotlinx-datetime-zoneinfo", "2024a-spi.0.6.0-RC.2")
implementation("kotlinx-datetime-zoneinfo", "2024a-spi.0.6.0")

val diff = instant1.periodUntil(instant2, TimeZone.currentSystemDefault())
val instant3 = instant1.plus(diff, TimeZone.currentSystemDefault())
val diff = instant1.periodUntil(instant2, TimeZone.UTC)
val instant3 = instant1.plus(diff, TimeZone.UTC)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needed, right?

core/common/src/TimeZone.kt Outdated Show resolved Hide resolved
core/common/src/TimeZone.kt Outdated Show resolved Hide resolved
igoriakovlev and others added 3 commits May 23, 2024 18:28
Co-authored-by: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com>
Co-authored-by: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants