Skip to content

Commit b6ea691

Browse files
committed
Move kotlinx.datetime handling to common Standardizing, update tests.
1 parent d3d6f4b commit b6ea691

File tree

7 files changed

+204
-119
lines changed

7 files changed

+204
-119
lines changed

plot-api/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ plugins {
1010
}
1111

1212
val letsPlotVersion = extra["letsPlot.version"] as String
13-
//val kotlinxDatetimeVersion = extra["kotlinx.datetime.version"] as String
13+
val kotlinxDatetimeVersion = extra["kotlinx.datetime.version"] as String
1414
val kotlinLoggingVersion = extra["kotlinLogging.version"] as String
1515
val assertjVersion = extra["assertj.version"] as String
1616

@@ -26,7 +26,8 @@ kotlin {
2626
api("org.jetbrains.lets-plot:plot-base:$letsPlotVersion")
2727
api("org.jetbrains.lets-plot:plot-builder:$letsPlotVersion")
2828
api("org.jetbrains.lets-plot:plot-stem:$letsPlotVersion")
29-
}
29+
30+
compileOnly("org.jetbrains.kotlinx:kotlinx-datetime:${kotlinxDatetimeVersion}") }
3031
}
3132
commonTest {
3233
dependencies {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ private fun inferSeriesType(data: Any?): String {
441441
is Float -> SeriesAnnotation.Types.FLOATING
442442
is String -> SeriesAnnotation.Types.STRING
443443
is Boolean -> SeriesAnnotation.Types.BOOLEAN
444+
is kotlinx.datetime.Instant -> SeriesAnnotation.Types.DATE_TIME
445+
is kotlinx.datetime.LocalDate -> SeriesAnnotation.Types.DATE_TIME
446+
is kotlinx.datetime.LocalTime -> SeriesAnnotation.Types.DATE_TIME
447+
is kotlinx.datetime.LocalDateTime -> SeriesAnnotation.Types.DATE_TIME
444448
else -> SeriesAnnotation.Types.UNKNOWN
445449
}
446450
}

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

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,8 @@ internal object SeriesStandardizing {
4646

4747
fun toList(rawValue: Any, messageKey: String? = null): List<Any?> {
4848
return standardizeList(asList(rawValue, messageKey))
49-
/* return when (rawValue) {
50-
is List<*> -> standardizeList(rawValue)
51-
is Iterable<*> -> standardizeIterable(rawValue).toList()
52-
is Sequence<*> -> standardizeIterable(rawValue.asIterable()).toList()
53-
is Array<*> -> standardizeList(rawValue.asList())
54-
is ByteArray -> standardizeList(rawValue.asList())
55-
is ShortArray -> standardizeList(rawValue.asList())
56-
is IntArray -> standardizeList(rawValue.asList())
57-
is LongArray -> standardizeList(rawValue.asList())
58-
is FloatArray -> standardizeList(rawValue.asList())
59-
is DoubleArray -> standardizeList(rawValue.asList())
60-
is CharArray -> standardizeList(rawValue.asList())
61-
is Pair<*, *> -> standardizeList(rawValue.toList())
62-
else -> {
63-
val keyInfo = messageKey?.let { "[$messageKey]" } ?: ""
64-
throw IllegalArgumentException("Can't transform ${rawValue::class.simpleName} to list$keyInfo.")
65-
}
66-
}*/
6749
}
6850

69-
// fun toListOrPass(rawValue: Any): Any {
70-
// if (isListy(rawValue)) {
71-
// return toList("<key not provided>", rawValue)
72-
// }
73-
// return rawValue
74-
// }
75-
7651
private fun needToStandardizeValues(series: Iterable<*>): Boolean {
7752
return series.any {
7853
it != null &&
@@ -87,35 +62,8 @@ internal object SeriesStandardizing {
8762
}
8863

8964
private fun standardizeIterable(series: Iterable<*>): Iterable<*> {
90-
// fun toDouble(n: Number): Double? {
91-
// return when (n) {
92-
// is Float -> if (n.isFinite()) n.toDouble() else null
93-
// is Double -> if (n.isFinite()) n else null
94-
// else -> n.toDouble()
95-
// }
96-
// }
9765
return if (needToStandardizeValues(series)) {
9866
series.map {
99-
// when (it) {
100-
// null -> it
101-
// is String -> it
102-
// is Number -> toDouble(it)
103-
// is Char -> it.toString()
104-
// is jetbrains.datalore.base.values.Color -> it.toHexColor()
105-
// else -> {
106-
// if (JvmStandardizing.isJvm(it)) {
107-
// JvmStandardizing.standardize(it)
108-
// } else {
109-
// throw IllegalArgumentException(
110-
// "Can't standardize the value \"$it\" of type ${
111-
// ReflectionPatch.qualifiedName(
112-
// it
113-
// )
114-
// } as a string, number or date-time."
115-
// )
116-
// }
117-
// }
118-
// }
11967
Standardizing.standardizeValue(it)
12068
}
12169
} else {

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,16 @@
55

66
package org.jetbrains.letsPlot.intern.standardizing
77

8+
import kotlinx.datetime.atStartOfDayIn
9+
import kotlinx.datetime.toInstant
10+
import org.jetbrains.letsPlot.commons.intern.datetime.Date
11+
import org.jetbrains.letsPlot.commons.intern.datetime.DateTime
812
import org.jetbrains.letsPlot.commons.intern.datetime.Instant
13+
import org.jetbrains.letsPlot.commons.intern.datetime.Time
914

1015
internal object Standardizing {
16+
// private val UTC_LP by lazy { TimeZone("UTC") }
17+
1118
fun standardizeValue(value: Any?): Any? {
1219
return when (value) {
1320
null -> value
@@ -17,20 +24,33 @@ internal object Standardizing {
1724
is org.jetbrains.letsPlot.commons.values.Color -> value.toHexColor()
1825
is Map<*, *> -> MapStandardizing.standardize(value)
1926
is Enum<*> -> value.name
20-
is Instant -> value.toEpochMilliseconds()
27+
// Kotlinx DateTime API
28+
is kotlinx.datetime.Instant -> value.toEpochMilliseconds().toDouble()
29+
is kotlinx.datetime.LocalDate -> value.atStartOfDayIn(kotlinx.datetime.TimeZone.UTC)
30+
.toEpochMilliseconds().toDouble()
31+
32+
is kotlinx.datetime.LocalTime -> {
33+
((value.hour * 3600 + value.minute * 60 + value.second) * 1000L + value.nanosecond / 1_000_000)
34+
.toDouble()
35+
}
36+
37+
is kotlinx.datetime.LocalDateTime -> value.toInstant(kotlinx.datetime.TimeZone.UTC)
38+
.toEpochMilliseconds().toDouble()
39+
40+
// Lets-Plot DateTime API
41+
is Instant -> throw IllegalArgumentException("Use java.util.Instant or kotlinx.datetime.Instant instead of org.jetbrains.letsPlot.commons.intern.datetime.Instant")
42+
is DateTime -> throw IllegalArgumentException("Use java.util.LocalDateTime or kotlinx.datetime.LocalDateTime instead of org.jetbrains.letsPlot.commons.intern.datetime.DateTime")
43+
is Date -> throw IllegalArgumentException("Use java.util.LocalDate or kotlinx.datetime.LocalDate instead of org.jetbrains.letsPlot.commons.intern.datetime.Date")
44+
is Time -> throw IllegalArgumentException("Use java.util.LocalTime or kotlinx.datetime.LocalTime instead of org.jetbrains.letsPlot.commons.intern.datetime.Time")
45+
2146
else -> {
2247
if (JvmStandardizing.isJvm(value)) {
2348
JvmStandardizing.standardize(value)
2449
} else if (SeriesStandardizing.isListy(value)) {
2550
val l = SeriesStandardizing.toList(value)
2651
l.map { standardizeValue(it) }
2752
} else {
28-
// throw IllegalArgumentException(
29-
// "Can't standardize the value \"$value\" of type ${
30-
// ReflectionPatch.qualifiedName(value)
31-
// } as a string, number or date-time."
32-
// )
33-
// Just ignore: this might be some ligit object like `org.jetbrains.letsPlot.MappingMeta` for example.
53+
// This might be some ligit object like `org.jetbrains.letsPlot.MappingMeta` for example.
3454
value
3555
}
3656
}

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

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import java.time.*
99
import java.util.*
1010

1111
private val AWT_PRESENT: Boolean = try {
12+
@Suppress("KotlinConstantConditions")
1213
java.awt.Color.WHITE == java.awt.Color.WHITE
13-
} catch (e: NoClassDefFoundError) {
14+
} catch (_: NoClassDefFoundError) {
1415
false // Android
1516
}
1617

@@ -23,10 +24,7 @@ actual object JvmStandardizing {
2324
is LocalDate -> true
2425
is LocalTime -> true
2526
is LocalDateTime -> true
26-
else -> when {
27-
isKotlinxDateTime(o) -> true
28-
else -> false
29-
}
27+
else -> false
3028
}
3129
}
3230

@@ -46,34 +44,16 @@ actual object JvmStandardizing {
4644
}
4745

4846
return when (o) {
49-
is Date -> o.time
50-
is Instant -> o.toEpochMilli()
51-
is ZonedDateTime -> o.toInstant().toEpochMilli()
52-
is LocalDate -> noTimeZoneError(o)
53-
is LocalTime -> noTimeZoneError(o)
54-
is LocalDateTime -> noTimeZoneError(o)
55-
// is java.awt.Color -> "#%02x%02x%02x".format(o.red, o.green, o.blue)
56-
else -> when {
57-
isKotlinxDateTime(o) -> when (o) {
58-
is kotlinx.datetime.Instant -> o.toEpochMilliseconds()
59-
is kotlinx.datetime.LocalDate -> noTimeZoneError(o)
60-
is kotlinx.datetime.LocalDateTime -> noTimeZoneError(o)
61-
else -> unsupportedTypeError(o)
62-
}
63-
64-
else -> unsupportedTypeError(o)
65-
}
47+
is Date -> o.time.toDouble()
48+
is Instant -> o.toEpochMilli().toDouble()
49+
is ZonedDateTime -> o.toInstant().toEpochMilli().toDouble()
50+
is LocalDate -> o.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli().toDouble()
51+
is LocalTime -> LocalDateTime.of(LocalDate.EPOCH, o).toInstant(ZoneOffset.UTC).toEpochMilli().toDouble()
52+
is LocalDateTime -> o.toInstant(ZoneOffset.UTC).toEpochMilli().toDouble()
53+
else -> unsupportedTypeError(o)
6654
}
6755
}
6856

69-
private fun isKotlinxDateTime(o: Any) = o.javaClass.name.startsWith("kotlinx.datetime.")
70-
71-
private fun noTimeZoneError(time: Any): Nothing {
72-
throw IllegalArgumentException(
73-
"Can't convert ${time::class.qualifiedName} to the number of milliseconds from the epoch of 1970-01-01T00:00:00Z."
74-
)
75-
}
76-
7757
private fun unsupportedTypeError(o: Any) {
7858
throw IllegalArgumentException("Can't standardize value \"$o\" of type ${o::class.qualifiedName} as string or number.")
7959
}

plot-api/src/jvmTest/kotlin/org/jetbrains/letsPlot/SeriesAnnotationTest.kt

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class SeriesAnnotationTest {
2727
/**
2828
If levels are specified, the label should not be added to the mapping annotations.
2929
It's a bug, but Why not fix - if both levels and label are specified, the levels get ignored.
30-
*/
30+
*/
3131
@Test
3232
fun `do not add label if factor level is specified`() {
3333
val data = mapOf(
@@ -55,8 +55,18 @@ class SeriesAnnotationTest {
5555
"float-column" to listOf(1.0f, 2.0f, 3.0f),
5656
"string-column" to listOf("a", "b", "c"),
5757
"boolean-column" to listOf(true, false, true),
58+
5859
"java-instant-column" to listOf(Instant.parse("2021-01-01T00:00:00Z")),
59-
"kotlin-instant-column" to listOf(kotlinx.datetime.Instant.parse("2021-01-01T00:00:00Z"))
60+
"java-date-column" to listOf(java.util.Date.from(java.time.Instant.parse("2021-01-01T00:00:00Z"))),
61+
"java-zoned-datetime-column" to listOf(java.time.ZonedDateTime.parse("2021-01-01T00:00:00Z")),
62+
"java-local-date-column" to listOf(java.time.LocalDate.parse("2021-01-01")),
63+
"java-local-time-column" to listOf(java.time.LocalTime.parse("12:34:56")),
64+
"java-local-datetime-column" to listOf(java.time.LocalDateTime.parse("2021-01-01T12:34:56")),
65+
66+
"kotlin-instant-column" to listOf(kotlinx.datetime.Instant.parse("2021-01-01T00:00:00Z")),
67+
"kotlin-local-date-column" to listOf(kotlinx.datetime.LocalDate.parse("2021-01-01")),
68+
"kotlin-local-time-column" to listOf(kotlinx.datetime.LocalTime.parse("12:34:56")),
69+
"kotlin-local-datetime-column" to listOf(kotlinx.datetime.LocalDateTime.parse("2021-01-01T12:34:56")),
6070
)
6171

6272
val p = ggplot(data) + geomPoint()
@@ -72,8 +82,18 @@ class SeriesAnnotationTest {
7282
seriesAnnotation(column = "float-column", type = Types.FLOATING),
7383
seriesAnnotation(column = "string-column", type = Types.STRING),
7484
seriesAnnotation(column = "boolean-column", type = Types.BOOLEAN),
85+
7586
seriesAnnotation(column = "java-instant-column", type = Types.DATE_TIME),
76-
seriesAnnotation(column = "kotlin-instant-column", type = Types.DATE_TIME)
87+
seriesAnnotation(column = "java-date-column", type = Types.DATE_TIME),
88+
seriesAnnotation(column = "java-zoned-datetime-column", type = Types.DATE_TIME),
89+
seriesAnnotation(column = "java-local-date-column", type = Types.DATE_TIME),
90+
seriesAnnotation(column = "java-local-time-column", type = Types.DATE_TIME),
91+
seriesAnnotation(column = "java-local-datetime-column", type = Types.DATE_TIME),
92+
93+
seriesAnnotation(column = "kotlin-instant-column", type = Types.DATE_TIME),
94+
seriesAnnotation(column = "kotlin-local-date-column", type = Types.DATE_TIME),
95+
seriesAnnotation(column = "kotlin-local-time-column", type = Types.DATE_TIME),
96+
seriesAnnotation(column = "kotlin-local-datetime-column", type = Types.DATE_TIME),
7797
)
7898
)
7999
)
@@ -193,7 +213,9 @@ class SeriesAnnotationTest {
193213
assertThat(it.getList(DATA_META, MappingAnnotation.TAG)).isNull()
194214
}
195215

196-
(ggplot(data) { x = asDiscrete("v", order = 1, orderBy = "bar", levels = listOf("b", "a")) } + geomPoint()).toSpec().let {
216+
(ggplot(data) {
217+
x = asDiscrete("v", order = 1, orderBy = "bar", levels = listOf("b", "a"))
218+
} + geomPoint()).toSpec().let {
197219
assertThat(it.getList(DATA_META, SeriesAnnotation.TAG)).containsExactly(
198220
seriesAnnotation(column = "v", type = Types.STRING, factorLevels = listOf("b", "a"), order = 1),
199221
seriesAnnotation(column = "bar", type = Types.STRING)
@@ -220,21 +242,27 @@ class SeriesAnnotationTest {
220242
assertThat(it.getList(DATA_META, MappingAnnotation.TAG)).isNull()
221243
}
222244

223-
(ggplot(mapOf("v" to listOf(a, b))) { x = asDiscrete("v", levels = listOf(b, a), order = 1) } + geomPoint()).toSpec().let {
245+
(ggplot(mapOf("v" to listOf(a, b))) {
246+
x = asDiscrete("v", levels = listOf(b, a), order = 1)
247+
} + geomPoint()).toSpec().let {
224248
assertThat(it.getList(DATA_META, SeriesAnnotation.TAG)).containsExactly(
225249
seriesAnnotation(column = "v", type = Types.DATE_TIME, factorLevels = listOf(b, a), order = 1)
226250
)
227251
assertThat(it.getList(DATA_META, MappingAnnotation.TAG)).isNull()
228252
}
229253

230-
(ggplot(mapOf("v" to listOf(a, b))) { x = asDiscrete("v", levels = listOf(b, a), orderBy = "v") } + geomPoint()).toSpec().let {
254+
(ggplot(mapOf("v" to listOf(a, b))) {
255+
x = asDiscrete("v", levels = listOf(b, a), orderBy = "v")
256+
} + geomPoint()).toSpec().let {
231257
assertThat(it.getList(DATA_META, SeriesAnnotation.TAG)).containsExactly(
232258
seriesAnnotation(column = "v", type = Types.DATE_TIME, factorLevels = listOf(b, a))
233259
)
234260
assertThat(it.getList(DATA_META, MappingAnnotation.TAG)).isNull()
235261
}
236262

237-
(ggplot(mapOf("v" to listOf(a, b))) { x = asDiscrete("v", levels = listOf(b, a), order = 1, orderBy = "v") } + geomPoint()).toSpec().let {
263+
(ggplot(mapOf("v" to listOf(a, b))) {
264+
x = asDiscrete("v", levels = listOf(b, a), order = 1, orderBy = "v")
265+
} + geomPoint()).toSpec().let {
238266
assertThat(it.getList(DATA_META, SeriesAnnotation.TAG)).containsExactly(
239267
seriesAnnotation(column = "v", type = Types.DATE_TIME, factorLevels = listOf(b, a), order = 1)
240268
)

0 commit comments

Comments
 (0)