diff --git a/docs/time.md b/docs/time.md index 212b114..1860ad3 100644 --- a/docs/time.md +++ b/docs/time.md @@ -58,13 +58,66 @@ The resolution of system clock could be more precise and double precision real n >>> void +## Getting the max precision + +Normally, subtracting instants gives precision to microseconds, which is well inside the jitter +the language VM adds. Still `Instant()` captures most precise system timer at hand and provide +inner value of 12 bytes, up to nanoseconds (hopefully). To access it use: + + import lyng.time + + // capture time + val now = Instant() + + // this is Int value, number of whole epoch + // milliseconds to the moment, it fits 8 bytes Int well + val seconds = now.epochWholeSeconds + assert(seconds is Int) + + // and this is Int value of nanoseconds _since_ the epochMillis, + // it effectively add 4 more mytes int: + val nanos = now.nanosecondsOfSecond + assert(nanos is Int) + assert( nanos in 0..999_999_999 ) + + // we can construct epochSeconds from these parts: + assertEquals( now.epochSeconds, nanos * 1e-9 + seconds ) + >>> void + +## Formatting instants + +You can freely use `Instant` in string formatting. It supports usual sprintf-style formats: + + import lyng.time + val now = Instant() + + // will be something like "now: 12:10:05" + val currentTimeOnly24 = "now: %tT"(now) + + // we can extract epoch second with formatting too, + // this was since early C time + + // get epoch while seconds from formatting + val unixEpoch = "Now is %ts since unix epoch"(now) + + // and it is the same as now.epochSeconds, int part: + assertEquals( unixEpoch, "Now is %d since unix epoch"(now.epochSeconds.toInt()) ) + >>> void + +See the [complete list of available formats](https://github.com/sergeych/mp_stools?tab=readme-ov-file#datetime-formatting) and the [formatting reference](https://github.com/sergeych/mp_stools?tab=readme-ov-file#printf--sprintf): it all works in Lyng as `"format"(args...)`! + ## Instant members -| member | description | -|-----------------------|---------------------------------------------------------| -| epochSeconds: Real | positive or negative offset in seconds since Unix epoch | -| isDistantFuture: Bool | true if it `Instant.distantFuture` | -| isDistantPast: Bool | true if it `Instant.distantPast` | +| member | description | +|--------------------------|---------------------------------------------------------| +| epochSeconds: Real | positive or negative offset in seconds since Unix epoch | +| epochWholeSeconds: Int | same, but in _whole seconds_. Slightly faster | +| nanosecondsOfSecond: Int | offset from epochWholeSeconds in nanos (1) | +| isDistantFuture: Bool | true if it `Instant.distantFuture` | +| isDistantPast: Bool | true if it `Instant.distantPast` | + +(1) +: The value of nanoseconds is to be added to `epochWholeSeconds` to get exact time point. It is in 0..999_999_999 range. The precise time instant value therefore needs as for now 12 bytes integer; we might use bigint later (it is planned to be added) ## Class members @@ -102,6 +155,7 @@ The bigger time units like months or years are calendar-dependent and can't be u Each duration instance can be converted to number of any of these time units, as `Real` number, if `d` is a `Duration` instance: +- `d.microseconds` - `d.milliseconds` - `d.seconds` - `d.minutes` @@ -112,6 +166,8 @@ for example import lyng.time assertEquals( 60, 1.minute.seconds ) + assertEquals( 10.milliseconds, 0.01.seconds ) + >>> void # Utility functions diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 67a191f..ac2ac7b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.5.2" clikt = "5.0.3" -kotlin = "2.2.0" +kotlin = "2.1.21" android-minSdk = "24" android-compileSdk = "34" kotlinx-coroutines = "1.10.1" diff --git a/lyng/build.gradle.kts b/lyng/build.gradle.kts index 841fc83..90e0bbe 100644 --- a/lyng/build.gradle.kts +++ b/lyng/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "2.2.0" + kotlin("multiplatform") version "2.1.21" } group = "net.sergeych" diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt index 23d0c81..3cac0a0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt @@ -33,10 +33,6 @@ open class ObjClass( return instance } -// fun defaultInstance(): Obj = object : Obj() { -// override val objClass: ObjClass = this@ObjClass -// } - fun createField( name: String, initialValue: Obj, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjDuration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjDuration.kt index 6ccb202..9975601 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjDuration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjDuration.kt @@ -56,6 +56,11 @@ class ObjDuration(val duration: Duration) : Obj() { addFn("milliseconds") { thisAs().duration.toDouble(DurationUnit.MILLISECONDS).toObj() } + addFn("microseconds") { + thisAs().duration.toDouble(DurationUnit.MICROSECONDS).toObj() + } + // extensions + ObjInt.type.addFn("seconds") { ObjDuration(thisAs().value.seconds) } @@ -63,7 +68,6 @@ class ObjDuration(val duration: Duration) : Obj() { ObjInt.type.addFn("second") { ObjDuration(thisAs().value.seconds) } - ObjInt.type.addFn("milliseconds") { ObjDuration(thisAs().value.milliseconds) } @@ -71,7 +75,6 @@ class ObjDuration(val duration: Duration) : Obj() { ObjInt.type.addFn("millisecond") { ObjDuration(thisAs().value.milliseconds) } - ObjReal.type.addFn("seconds") { ObjDuration(thisAs().value.seconds) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInstant.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInstant.kt index 7030648..fdc7498 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInstant.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInstant.kt @@ -34,6 +34,10 @@ class ObjInstant(val instant: Instant) : Obj() { return super.compareTo(scope, other) } + override suspend fun toKotlin(scope: Scope): Any { + return instant + } + companion object { val distantFuture by lazy { ObjInstant(Instant.DISTANT_FUTURE) @@ -51,7 +55,7 @@ class ObjInstant(val instant: Instant) : Obj() { when (a0) { null -> { val t = Clock.System.now() - Instant.fromEpochSeconds(t.epochSeconds, (t.nanosecondsOfSecond / 1_000_000).toLong()*1_000_000) + Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond) } is ObjInt -> Instant.fromEpochSeconds(a0.value) is ObjReal -> { @@ -78,6 +82,14 @@ class ObjInstant(val instant: Instant) : Obj() { addFn("isDistantPast") { thisAs().instant.isDistantPast.toObj() } + addFn("epochWholeSeconds") { + ObjInt(thisAs().instant.epochSeconds) + } + addFn("nanosecondsOfSecond") { + ObjInt(thisAs().instant.nanosecondsOfSecond.toLong()) + } + // class members + addClassConst("distantFuture", distantFuture) addClassConst("distantPast", distantPast) // addFn("epochMilliseconds") { diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index ba8368c..d237cc1 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2560,4 +2560,18 @@ class ScriptTest { ) } + @Test + fun testInstantFormatting() = runTest { + eval( + """ + import lyng.time + val now = Instant() + val unixEpoch = "%ts"(now) + println("current seconds is %s"(unixEpoch)) + println("current time is %tT"(now)) + assertEquals( unixEpoch.toInt(), now.epochSeconds.toInt() ) + """.trimIndent() + ) + } + } \ No newline at end of file diff --git a/lynglib/src/jvmTest/kotlin/OtherTests.kt b/lynglib/src/jvmTest/kotlin/OtherTests.kt index 890120d..7fff3af 100644 --- a/lynglib/src/jvmTest/kotlin/OtherTests.kt +++ b/lynglib/src/jvmTest/kotlin/OtherTests.kt @@ -2,6 +2,7 @@ import junit.framework.TestCase.assertEquals import kotlinx.coroutines.runBlocking import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.Source +import net.sergeych.lyng.eval import net.sergeych.lyng.pacman.InlineSourcesImportProvider import net.sergeych.lyng.toSource import kotlin.test.Test @@ -37,4 +38,16 @@ class OtherTests { assertEquals("foo1 / bar1", scope.eval(src).toString()) } + @Test + fun testInstantTruncation() = runBlocking { + eval(""" + import lyng.time + val t1 = Instant() + val t2 = Instant() +// assert( t1 != t2 ) + println(t1 - t2) + """.trimIndent()) + Unit + } + } \ No newline at end of file