Migrate Instant to kotlin.time

This commit is contained in:
Sergey Chernov 2026-02-18 21:13:21 +03:00
parent 79a541b148
commit 6d943c5e1e
5 changed files with 38 additions and 20 deletions

View File

@ -4,7 +4,7 @@
Before kotlin 2.0, there was an excellent library, kotlinx.datetime, which was widely used everywhere, also in Lyng and its dependencies. Before kotlin 2.0, there was an excellent library, kotlinx.datetime, which was widely used everywhere, also in Lyng and its dependencies.
When kotlin 2.0 was released, or soon after, JetBrains made an exptic decision to remove `Instant` and `Clock` from kotlinx.datetime and replace it with _yet experimental_ analogs in `kotlin.time`. When Kotlin 2.0 was released, or soon after, JetBrains made a perplexing decision to remove `Instant` and `Clock` from kotlinx.datetime and replace it with _yet experimental_ analogs in `kotlin.time`.
The problem is, these were not quite the same (these weren't `@Serializable`!), so people didn't migrate with ease. Okay, then JetBrains decided to not only deprecate it but also make them unusable on Apple targets. It sort of split auditories of many published libraries to those who hate JetBrains and Apple and continue to use 1.9-2.0 compatible versions that no longer work with Kotlin 2.2 on Apple targets (but work pretty well with earlier Kotlin or on other platforms). The problem is, these were not quite the same (these weren't `@Serializable`!), so people didn't migrate with ease. Okay, then JetBrains decided to not only deprecate it but also make them unusable on Apple targets. It sort of split auditories of many published libraries to those who hate JetBrains and Apple and continue to use 1.9-2.0 compatible versions that no longer work with Kotlin 2.2 on Apple targets (but work pretty well with earlier Kotlin or on other platforms).
@ -12,14 +12,14 @@ Later JetBrains added serializers for their new `Instant` and `Clock` types, but
## Solution ## Solution
We hereby publish a new version of Lyng, 1.0.8-SNAPSHOT, which uses `ktlin.time.Instant` and `kotlin.time.Clock` instead of `kotlinx.datetime.Instant` and `kotlinx.datetime.Clock; it is in other aspects compatible also with Lynon encoded binaries. Still you might need to migrate your code to use `kotlinx.datetime` types. We hereby publish a new version of Lyng, 1.0.8-SNAPSHOT, which uses `kotlin.time.Instant` and `kotlin.time.Clock` instead of `kotlinx.datetime.Instant` and `kotlinx.datetime.Clock`. It is in other aspects compatible also with Lynon encoded binaries. You might need to migrate your code to use `kotlin.time` types. (LocalDateTime/TimeZone still come from `kotlinx.datetime`.)
So, if you are getting errors with new version, plase do: So, if you are getting errors with new version, please do:
- upgrade to Kotlin 2.2 - upgrade to Kotlin 2.2
- upgrade to Lyng 1.0.8-SNAPSHOT - upgrade to Lyng 1.0.8-SNAPSHOT
- replace in your code imports (or other uses) of`kotlinx.datetime.Clock` to `kotlin.time.Clock` and `kotlinx.datetime.Instant` to `kotlin.time.Instant`. - replace in your code imports (or other uses) of `kotlinx.datetime.Clock` to `kotlin.time.Clock` and `kotlinx.datetime.Instant` to `kotlin.time.Instant`.
This should solve the problem and hopefully we'll see no more suh a brillant ideas from IDEA ideologspersons. This should solve the problem and hopefully we'll see no more such "brilliant" ideas from IDEA ideologspersons.
Sorry for inconvenicence and send a ray of hate to JetBrains ;) Sorry for inconvenience and send a ray of hate to JetBrains ;)

View File

@ -191,7 +191,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
fsGuard { fsGuard {
val self = this.thisObj as ObjPath val self = this.thisObj as ObjPath
val m = self.ensureMetadata() val m = self.ensureMetadata()
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull m.modifiedAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
} }
} }
// modifiedAtMillis(): Int? — milliseconds since epoch or null // modifiedAtMillis(): Int? — milliseconds since epoch or null

View File

@ -17,7 +17,16 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import kotlinx.datetime.* import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.UtcOffset
import kotlinx.datetime.asTimeZone
import kotlinx.datetime.isoDayNumber
import kotlinx.datetime.number
import kotlinx.datetime.plus
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
@ -28,12 +37,13 @@ import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
import kotlin.time.Instant
class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() { class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
override val objClass: ObjClass get() = type override val objClass: ObjClass get() = type
val localDateTime: LocalDateTime by lazy { val localDateTime: LocalDateTime by lazy {
instant.toLocalDateTime(timeZone) with(timeZone) { instant.toLocalDateTime() }
} }
override fun toString(): String { override fun toString(): String {
@ -177,11 +187,11 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.year.toObj() }) getter = { thisAs<ObjDateTime>().localDateTime.year.toObj() })
addPropertyDoc("month", "The month component (1..12).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("month", "The month component (1..12).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.monthNumber.toObj() }) getter = { thisAs<ObjDateTime>().localDateTime.month.number.toObj() })
addPropertyDoc("dayOfMonth", "The day of month component.", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("dayOfMonth", "The day of month component.", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() }) getter = { thisAs<ObjDateTime>().localDateTime.day.toObj() })
addPropertyDoc("day", "Alias to dayOfMonth.", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("day", "Alias to dayOfMonth.", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() }) getter = { thisAs<ObjDateTime>().localDateTime.day.toObj() })
addPropertyDoc("hour", "The hour component (0..23).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("hour", "The hour component (0..23).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.hour.toObj() }) getter = { thisAs<ObjDateTime>().localDateTime.hour.toObj() })
addPropertyDoc("minute", "The minute component (0..59).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("minute", "The minute component (0..59).", type("lyng.Int"), moduleName = "lyng.time",
@ -257,7 +267,7 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
returns = type("lyng.DateTime"), returns = type("lyng.DateTime"),
moduleName = "lyng.time") { moduleName = "lyng.time") {
val s = (args.firstAndOnly() as ObjString).value val s = (args.firstAndOnly() as ObjString).value
// kotlinx-datetime's Instant.parse handles RFC3339 // kotlin.time's Instant.parse handles RFC3339
// But we want to preserve the offset if present for DateTime. // But we want to preserve the offset if present for DateTime.
// However, Instant.parse("...") always gives an Instant. // However, Instant.parse("...") always gives an Instant.
// If we want the specific offset from the string, we might need a more complex parse. // If we want the specific offset from the string, we might need a more complex parse.

View File

@ -17,7 +17,13 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import kotlinx.datetime.* import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.UtcOffset
import kotlinx.datetime.asTimeZone
import kotlinx.datetime.number
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
@ -27,6 +33,7 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonSettings import net.sergeych.lynon.LynonSettings
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Instant
import kotlin.time.isDistantFuture import kotlin.time.isDistantFuture
import kotlin.time.isDistantPast import kotlin.time.isDistantPast
@ -196,8 +203,8 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
) { ) {
val t = thisAs<ObjInstant>().instant val t = thisAs<ObjInstant>().instant
val tz = TimeZone.UTC val tz = TimeZone.UTC
val dt = t.toLocalDateTime(tz) val dt = with(tz) { t.toLocalDateTime() }
val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0) val truncated = LocalDateTime(dt.year, dt.month.number, dt.day, dt.hour, dt.minute, 0, 0)
ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second) ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second)
} }
addFnDoc( addFnDoc(

View File

@ -25,6 +25,7 @@ import net.sergeych.lyng.obj.*
import net.sergeych.lynon.* import net.sergeych.lynon.*
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import kotlin.time.Instant
import kotlin.test.Ignore import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertContentEquals import kotlin.test.assertContentEquals
@ -368,11 +369,11 @@ class LynonTests {
roundTrip(ObjReal(1.22345)) roundTrip(ObjReal(1.22345))
roundTrip(ObjReal(-Math.PI)) roundTrip(ObjReal(-Math.PI))
val now = kotlinx.datetime.Instant.fromEpochMilliseconds(System.currentTimeMillis()) val now = Instant.fromEpochMilliseconds(System.currentTimeMillis())
roundTrip(ObjInstant(kotlinx.datetime.Instant.fromEpochSeconds(now.epochSeconds), LynonSettings.InstantTruncateMode.Second)) roundTrip(ObjInstant(Instant.fromEpochSeconds(now.epochSeconds), LynonSettings.InstantTruncateMode.Second))
roundTrip( roundTrip(
ObjInstant( ObjInstant(
kotlinx.datetime.Instant.fromEpochSeconds( Instant.fromEpochSeconds(
now.epochSeconds, now.epochSeconds,
now.nanosecondsOfSecond / 1_000_000 * 1_000_000 now.nanosecondsOfSecond / 1_000_000 * 1_000_000
), ),
@ -381,7 +382,7 @@ class LynonTests {
) )
roundTrip( roundTrip(
ObjInstant( ObjInstant(
kotlinx.datetime.Instant.fromEpochSeconds( Instant.fromEpochSeconds(
now.epochSeconds, now.epochSeconds,
now.nanosecondsOfSecond / 1_000 * 1_000 now.nanosecondsOfSecond / 1_000 * 1_000
), ),