Skip to content

Commit 9416b4f

Browse files
committed
Fix #294 : Support kotlin.time.Duration
1 parent a03dbff commit 9416b4f

File tree

12 files changed

+109
-6
lines changed

12 files changed

+109
-6
lines changed

demo/js-frontend-app/src/jsMain/resources/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</head>
1111
<body>
1212
<script type="text/javascript"
13-
src="https://cdn.jsdelivr.net/gh/JetBrains/[email protected].1/js-package/distr/lets-plot.min.js"></script>
13+
src="https://cdn.jsdelivr.net/gh/JetBrains/[email protected].2/js-package/distr/lets-plot.min.js"></script>
1414
<script src="js-frontend-app.js"></script>
1515
<div>
1616
<h2>Lets-Plot Kotlin/JS Demo.</h2>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2025. JetBrains s.r.o.
3+
* Use of this source code is governed by the MIT license that can be found in the LICENSE file.
4+
*/
5+
6+
package frontendContextDemo.scripts
7+
8+
import frontendContextDemo.ScriptInBatikContext
9+
import org.jetbrains.letsPlot.geom.geomBar
10+
import org.jetbrains.letsPlot.geom.geomStep
11+
import org.jetbrains.letsPlot.ggplot
12+
import org.jetbrains.letsPlot.label.ggtitle
13+
import org.jetbrains.letsPlot.scale.scaleYTime
14+
import org.jetbrains.letsPlot.tooltips.layerTooltips
15+
import kotlin.time.Duration.Companion.hours
16+
import kotlin.time.Duration.Companion.minutes
17+
18+
object DurationDemo {
19+
@JvmStatic
20+
fun main(args: Array<String>) {
21+
ScriptInBatikContext.eval("Duration support") {
22+
kotlinDurationPlot()
23+
javaDurationPlot()
24+
}
25+
}
26+
27+
private fun kotlinDurationPlot() {
28+
// Example using kotlin.time.Duration
29+
val tasks = listOf("Task A", "Task B", "Task C", "Task D", "Task E")
30+
val durations = listOf(
31+
2.hours + 30.minutes,
32+
1.hours + 45.minutes,
33+
3.hours + 15.minutes,
34+
45.minutes,
35+
2.hours
36+
)
37+
38+
val data = mapOf(
39+
"task" to tasks,
40+
"duration" to durations
41+
)
42+
43+
val p = ggplot(data) +
44+
geomBar(stat = org.jetbrains.letsPlot.Stat.identity) {
45+
x = "task"
46+
y = "duration"
47+
} +
48+
scaleYTime() +
49+
ggtitle("Task Durations (kotlin.time.Duration)")
50+
51+
p.show()
52+
}
53+
54+
private fun javaDurationPlot() {
55+
// Example using java.time.Duration
56+
val executionTimes = (0..10).map { it * 0.5 }
57+
val durations = executionTimes.map {
58+
java.time.Duration.ofMinutes((it * 60).toLong()).plusSeconds((it * 30 % 60).toLong())
59+
}
60+
61+
val data = mapOf(
62+
"time" to executionTimes,
63+
"duration" to durations
64+
)
65+
66+
val p = ggplot(data) +
67+
geomStep(
68+
tooltips = layerTooltips().line("T=^y")
69+
) {
70+
x = "time"
71+
y = "duration"
72+
} +
73+
scaleYTime() +
74+
ggtitle("Process Duration Over Time (java.time.Duration)")
75+
76+
p.show()
77+
}
78+
}

future_changes.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ This release is 100% compatible with Lets-Plot [v 4.8.1](https://github.com/JetB
44
GeoTools [v 33.2](https://github.com/geotools/geotools/releases/tag/33.2)
55

66
### Added
7-
7+
8+
- Support for `kotlin.time.Duration` and `java.time.Duration` [[LPK-294](https://github.com/JetBrains/lets-plot-kotlin/issues/294)].
89

910
### Changed
1011

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ ksp.version=1.9.25-1.0.20
3636
jupyterApi.version=0.12.0-313
3737

3838
# Also update the JS version in <home>/demo/js-frontend-app/src/jsMain/resources/index.html
39-
letsPlot.version=4.8.1
39+
letsPlot.version=4.8.2
4040

4141
# https://geotoolsnews.blogspot.com/
4242
geotools.version=33.2

plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/intern/ToSpecConverters.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,13 +448,15 @@ private fun inferSeriesDType(data: Any?): String {
448448
value is kotlinx.datetime.LocalTime -> SeriesAnnotation.Types.TIME
449449
value is kotlinx.datetime.LocalDateTime -> SeriesAnnotation.Types.DATE_TIME
450450

451+
value is kotlin.time.Duration -> SeriesAnnotation.Types.INTEGER
452+
451453
else -> SeriesAnnotation.Types.UNKNOWN
452454
}
453455
}
454456
}
455457

456458
/**
457-
* Not private to allow access from tests
459+
* Not private to allow the access to in tests.
458460
*/
459461
internal fun detectTimeZones(data: Any?): Map<String, String> {
460462
return if (data is Map<*, *>) {
@@ -469,6 +471,7 @@ internal fun detectTimeZones(data: Any?): Map<String, String> {
469471
}
470472

471473
private fun detectSeriesTimeZoneID(data: Any?): String? {
474+
@Suppress("NAME_SHADOWING")
472475
val data = if (isListy(data)) {
473476
asList(data!!).filterNotNull()
474477
} else {

plot-api/src/commonMain/kotlin/org/jetbrains/letsPlot/intern/standardizing/Standardizing.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ internal object Standardizing {
3636
is kotlinx.datetime.LocalDateTime -> value.toInstant(kotlinx.datetime.TimeZone.UTC)
3737
.toEpochMilliseconds().toDouble()
3838

39+
is kotlin.time.Duration -> value.inWholeMilliseconds.toDouble()
40+
3941
// Lets-Plot DateTime API
4042
is Instant -> throw IllegalArgumentException("Use java.util.Instant or kotlinx.datetime.Instant instead of org.jetbrains.letsPlot.commons.intern.datetime.Instant")
4143
is DateTime -> throw IllegalArgumentException("Use java.util.LocalDateTime or kotlinx.datetime.LocalDateTime instead of org.jetbrains.letsPlot.commons.intern.datetime.DateTime")

plot-api/src/commonTest/kotlin/org/jetbrains/letsPlot/intern/SeriesAnnotationDataTypeTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class SeriesAnnotation_TypeAndTZTest {
4242
"kotlinx.LocalDate" to KLocalDate(2023, 1, 1), // 2023-01-01
4343
"kotlinx.LocalTime" to KLocalTime(12, 30, 45), // 12:30:45
4444
"kotlinx.LocalDateTime" to KLocalDateTime(2023, 1, 1, 12, 30, 45), // 2023-01-01T12:30:45
45+
46+
"kotlin.time.Duration" to kotlin.time.Duration.parse("2h30m45s")
4547
) + SeriesAnnotationDataTypeTestJvmValues.getTestValues()
4648

4749
).mapValues { (_, value) -> listOf(value) }
@@ -68,6 +70,8 @@ class SeriesAnnotation_TypeAndTZTest {
6870
mapOf("column" to "kotlinx.LocalTime", "type" to "time"),
6971
mapOf("column" to "kotlinx.LocalDateTime", "type" to "datetime"),
7072

73+
mapOf("column" to "kotlin.time.Duration", "type" to "int"),
74+
7175
) + SeriesAnnotationDataTypeTestJvmValues.getExpectedValues()
7276

7377

plot-api/src/commonTest/kotlin/org/jetbrains/letsPlot/intern/standardizing/StandardizingTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,23 @@ class StandardizingTest {
5757
val kLocalTime = KLocalTime(12, 30, 45)
5858
val kLocalDateTime = KLocalDateTime(2023, 1, 1, 12, 30, 45)
5959

60+
val kDuration = kotlin.time.Duration.parse("2h30m45s")
61+
val expectedDurationMillis = kDuration.inWholeMilliseconds
62+
6063
val values = listOf<Any>(
6164
kInstant,
6265
kLocalDate,
6366
kLocalTime,
6467
kLocalDateTime,
68+
kDuration,
6569
)
6670

6771
val expectedValues = values.map {
6872
when (it) {
6973
is KLocalDate -> expectedLocalDateTimestamp
7074
is KLocalTime -> expectedLocalTimeTimestamp
7175
is KLocalDateTime -> expectedTimestamp // Same as ZonedDateTime because here we use UTC
76+
is kotlin.time.Duration -> expectedDurationMillis
7277
else -> expectedTimestamp
7378
}.toDouble()
7479
} + StandardizingTestJvmValues.getExpectedValues()

plot-api/src/jvmMain/kotlin/org/jetbrains/letsPlot/intern/standardizing/JvmStandardizing.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ actual object JvmStandardizing {
2828
is LocalDate -> true
2929
is LocalTime -> true
3030
is LocalDateTime -> true
31+
is Duration -> true
3132
else -> false
3233
}
3334
}
@@ -41,6 +42,7 @@ actual object JvmStandardizing {
4142
is LocalDate -> SeriesAnnotation.Types.DATE
4243
is LocalTime -> SeriesAnnotation.Types.TIME
4344
is LocalDateTime -> SeriesAnnotation.Types.DATE_TIME
45+
is Duration -> SeriesAnnotation.Types.INTEGER
4446
else -> SeriesAnnotation.Types.UNKNOWN
4547
}
4648
}
@@ -66,6 +68,7 @@ actual object JvmStandardizing {
6668
is LocalDate -> o.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()
6769
is LocalTime -> LocalDateTime.of(LocalDate.EPOCH, o).toInstant(ZoneOffset.UTC).toEpochMilli()
6870
is LocalDateTime -> o.toInstant(ZoneOffset.UTC).toEpochMilli()
71+
is Duration -> o.toMillis()
6972
else -> {
7073
throw IllegalArgumentException(
7174
"Can't standardize value \"$o\" of type ${o::class.qualifiedName} as string or number."
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
lets_plot.version=4.8.1
2-
lets_plot_kotlin_api.version=4.12.0
1+
lets_plot.version=4.8.2
2+
lets_plot_kotlin_api.version=4.12.1-SNAPSHOT

0 commit comments

Comments
 (0)