diff --git a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convert.kt b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convert.kt index c4b5da01b1..8ac000db22 100644 --- a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convert.kt +++ b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convert.kt @@ -15,6 +15,7 @@ import kotlinx.datetime.toJavaLocalTime import kotlinx.datetime.toKotlinInstant import kotlinx.datetime.toKotlinLocalDate import kotlinx.datetime.toKotlinLocalDateTime +import kotlinx.datetime.toKotlinLocalTime import kotlinx.datetime.toLocalDateTime import org.jetbrains.kotlinx.dataframe.AnyCol import org.jetbrains.kotlinx.dataframe.DataColumn @@ -40,6 +41,7 @@ import org.jetbrains.kotlinx.dataframe.impl.createStarProjectedType import org.jetbrains.kotlinx.dataframe.path import org.jetbrains.kotlinx.dataframe.type import java.math.BigDecimal +import java.math.BigInteger import java.net.URL import java.util.Locale import kotlin.math.roundToInt @@ -332,6 +334,8 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n BigDecimal::class -> convert { if (it) BigDecimal.ONE else BigDecimal.ZERO } + BigInteger::class -> convert { if (it) BigInteger.ONE else BigInteger.ZERO } + else -> null } @@ -343,10 +347,19 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n Short::class -> convert { it.toShort() } Long::class -> convert { it.toLong() } Boolean::class -> convert { it.toDouble() != 0.0 } + BigDecimal::class -> convert { it.toBigDecimal() } + BigInteger::class -> convert { it.toBigInteger() } + else -> null + } + + Char::class -> when (toClass) { + Int::class -> convert { it.code } else -> null } Int::class -> when (toClass) { + Char::class -> convert { it.toChar() } + Double::class -> convert { it.toDouble() } Float::class -> convert { it.toFloat() } @@ -359,6 +372,8 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n BigDecimal::class -> convert { it.toBigDecimal() } + BigInteger::class -> convert { it.toBigInteger() } + Boolean::class -> convert { it != 0 } LocalDateTime::class -> convert { it.toLong().toLocalDateTime(defaultTimeZone) } @@ -382,12 +397,90 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n else -> null } + Byte::class -> when (toClass) { + Double::class -> convert { it.toDouble() } + + Float::class -> convert { it.toFloat() } + + Int::class -> convert { it.toInt() } + + Short::class -> convert { it.toShort() } + + Long::class -> convert { it.toLong() } + + BigDecimal::class -> convert { it.toBigDecimal() } + + BigInteger::class -> convert { it.toBigInteger() } + + Boolean::class -> convert { it != 0.toByte() } + + LocalDateTime::class -> convert { it.toLong().toLocalDateTime(defaultTimeZone) } + + LocalDate::class -> convert { it.toLong().toLocalDate(defaultTimeZone) } + + LocalTime::class -> convert { it.toLong().toLocalTime(defaultTimeZone) } + + Instant::class -> convert { Instant.fromEpochMilliseconds(it.toLong()) } + + JavaLocalDateTime::class -> convert { + it.toLong().toLocalDateTime(defaultTimeZone).toJavaLocalDateTime() + } + + JavaLocalDate::class -> convert { it.toLong().toLocalDate(defaultTimeZone).toJavaLocalDate() } + + JavaLocalTime::class -> convert { it.toLong().toLocalTime(defaultTimeZone).toJavaLocalTime() } + + JavaInstant::class -> convert { JavaInstant.ofEpochMilli(it.toLong()) } + + else -> null + } + + Short::class -> when (toClass) { + Double::class -> convert { it.toDouble() } + + Float::class -> convert { it.toFloat() } + + Int::class -> convert { it.toInt() } + + Byte::class -> convert { it.toByte() } + + Long::class -> convert { it.toLong() } + + BigDecimal::class -> convert { it.toBigDecimal() } + + BigInteger::class -> convert { it.toBigInteger() } + + Boolean::class -> convert { it != 0.toShort() } + + LocalDateTime::class -> convert { it.toLong().toLocalDateTime(defaultTimeZone) } + + LocalDate::class -> convert { it.toLong().toLocalDate(defaultTimeZone) } + + LocalTime::class -> convert { it.toLong().toLocalTime(defaultTimeZone) } + + Instant::class -> convert { Instant.fromEpochMilliseconds(it.toLong()) } + + JavaLocalDateTime::class -> convert { + it.toLong().toLocalDateTime(defaultTimeZone).toJavaLocalDateTime() + } + + JavaLocalDate::class -> convert { it.toLong().toLocalDate(defaultTimeZone).toJavaLocalDate() } + + JavaLocalTime::class -> convert { it.toLong().toLocalTime(defaultTimeZone).toJavaLocalTime() } + + JavaInstant::class -> convert { JavaInstant.ofEpochMilli(it.toLong()) } + + else -> null + } + Double::class -> when (toClass) { Int::class -> convert { it.roundToInt() } Float::class -> convert { it.toFloat() } Long::class -> convert { it.roundToLong() } Short::class -> convert { it.roundToInt().toShort() } + Byte::class -> convert { it.roundToInt().toByte() } BigDecimal::class -> convert { it.toBigDecimal() } + BigInteger::class -> convert { it.toBigInteger() } Boolean::class -> convert { it != 0.0 } else -> null } @@ -405,12 +498,16 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n BigDecimal::class -> convert { it.toBigDecimal() } + BigInteger::class -> convert { it.toBigInteger() } + Boolean::class -> convert { it != 0L } LocalDateTime::class -> convert { it.toLocalDateTime(defaultTimeZone) } LocalDate::class -> convert { it.toLocalDate(defaultTimeZone) } + LocalTime::class -> convert { it.toLocalTime(defaultTimeZone) } + Instant::class -> convert { Instant.fromEpochMilliseconds(it) } JavaLocalDateTime::class -> convert { @@ -480,8 +577,10 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n Double::class -> convert { it.toDouble() } Long::class -> convert { it.roundToLong() } Int::class -> convert { it.roundToInt() } + Byte::class -> convert { it.roundToInt().toByte() } Short::class -> convert { it.roundToInt().toShort() } BigDecimal::class -> convert { it.toBigDecimal() } + BigInteger::class -> convert { it.toBigInteger() } Boolean::class -> convert { it != 0.0F } else -> null } @@ -489,12 +588,27 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n BigDecimal::class -> when (toClass) { Double::class -> convert { it.toDouble() } Int::class -> convert { it.toInt() } + Byte::class -> convert { it.toByte() } + Short::class -> convert { it.toShort() } Float::class -> convert { it.toFloat() } Long::class -> convert { it.toLong() } + BigInteger::class -> convert { it.toBigInteger() } Boolean::class -> convert { it != BigDecimal.ZERO } else -> null } + BigInteger::class -> when (toClass) { + Double::class -> convert { it.toDouble() } + Int::class -> convert { it.toInt() } + Byte::class -> convert { it.toByte() } + Short::class -> convert { it.toShort() } + Float::class -> convert { it.toFloat() } + Long::class -> convert { it.toLong() } + BigDecimal::class -> convert { it.toBigDecimal() } + Boolean::class -> convert { it != BigInteger.ZERO } + else -> null + } + LocalDateTime::class -> when (toClass) { LocalDate::class -> convert { it.date } LocalTime::class -> convert { it.time } @@ -565,6 +679,16 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n else -> null } + LocalTime::class -> when (toClass) { + JavaLocalTime::class -> convert { it.toJavaLocalTime() } + else -> null + } + + JavaLocalTime::class -> when (toClass) { + LocalTime::class -> convert { it.toKotlinLocalTime() } + else -> null + } + URL::class -> when (toClass) { IMG::class -> convert { IMG(it.toString()) } IFRAME::class -> convert { IFRAME(it.toString()) } @@ -588,3 +712,21 @@ internal fun Instant.toLocalDate(zone: TimeZone = defaultTimeZone) = toLocalDate internal fun Instant.toLocalTime(zone: TimeZone = defaultTimeZone) = toLocalDateTime(zone).time internal val defaultTimeZone = TimeZone.currentSystemDefault() + +internal fun Number.toBigDecimal(): BigDecimal = + when (this) { + is BigDecimal -> this + is BigInteger -> BigDecimal(this) + is Double -> BigDecimal.valueOf(this) + is Int -> BigDecimal(this) + is Long -> BigDecimal.valueOf(this) + else -> BigDecimal.valueOf(this.toDouble()) + } + +internal fun Number.toBigInteger(): BigInteger = + when (this) { + is BigInteger -> this + is BigDecimal -> this.toBigInteger() + is Long -> BigInteger.valueOf(this) + else -> BigInteger.valueOf(this.toLong()) + } diff --git a/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt b/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt index 31879e2638..188d13a17f 100644 --- a/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt +++ b/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt @@ -13,12 +13,31 @@ import org.jetbrains.kotlinx.dataframe.exceptions.CellConversionException import org.jetbrains.kotlinx.dataframe.exceptions.TypeConversionException import org.jetbrains.kotlinx.dataframe.exceptions.TypeConverterNotFoundException import org.jetbrains.kotlinx.dataframe.hasNulls +import org.jetbrains.kotlinx.dataframe.impl.api.toBigDecimal +import org.jetbrains.kotlinx.dataframe.impl.api.toBigInteger import org.junit.Test +import java.math.BigDecimal +import java.math.BigInteger +import kotlin.math.roundToInt +import kotlin.math.roundToLong +import kotlin.random.Random +import kotlin.reflect.full.starProjectedType import kotlin.reflect.typeOf import kotlin.time.Duration.Companion.hours +import java.time.LocalTime as JavaLocalTime class ConvertTests { + @Test + fun `convert LocalTime Kotlin to Java and back`() { + val time by columnOf(LocalTime(11, 22, 33)) + val converted = time.toDataFrame().convert { time }.to() + converted[time][0] shouldBe JavaLocalTime.of(11, 22, 33) + + val convertedBack = converted.convert(time).to() + convertedBack[time][0] shouldBe time[0] + } + @Test fun `convert nullable strings to time`() { val time by columnOf("11?22?33", null) @@ -172,4 +191,128 @@ class ConvertTests { val res = col.convertTo() res.print() } + + @Test + fun `convert Int and Char by code`() { + val col = columnOf(65, 66) + col.convertTo() shouldBe columnOf('A', 'B') + col.convertTo().convertTo() shouldBe col + } + + @Test + fun `convert all number types to each other`() { + val numberTypes: List = listOf( + Random.nextInt().toByte(), + Random.nextInt().toShort(), + Random.nextInt(), + Random.nextLong(), + Random.nextFloat(), + Random.nextDouble(), + BigInteger.valueOf(Random.nextLong()), + BigDecimal.valueOf(Random.nextDouble()), + ) + for (a in numberTypes) { + val aCol = columnOf(a) + for (b in numberTypes) { + val bCol = aCol.convertTo(b::class.starProjectedType) + when (a) { + is Byte -> + when (b) { + is Byte -> bCol.first() shouldBe a.toByte() + is Short -> bCol.first() shouldBe a.toShort() + is Int -> bCol.first() shouldBe a.toInt() + is Long -> bCol.first() shouldBe a.toLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is Short -> + when (b) { + is Byte -> bCol.first() shouldBe a.toByte() + is Short -> bCol.first() shouldBe a.toShort() + is Int -> bCol.first() shouldBe a.toInt() + is Long -> bCol.first() shouldBe a.toLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is Int -> + when (b) { + is Byte -> bCol.first() shouldBe a.toByte() + is Short -> bCol.first() shouldBe a.toShort() + is Int -> bCol.first() shouldBe a.toInt() + is Long -> bCol.first() shouldBe a.toLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is Long -> + when (b) { + is Byte -> bCol.first() shouldBe a.toByte() + is Short -> bCol.first() shouldBe a.toShort() + is Int -> bCol.first() shouldBe a.toInt() + is Long -> bCol.first() shouldBe a.toLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is Float -> + when (b) { + is Byte -> bCol.first() shouldBe a.roundToInt().toByte() + is Short -> bCol.first() shouldBe a.roundToInt().toShort() + is Int -> bCol.first() shouldBe a.roundToInt() + is Long -> bCol.first() shouldBe a.roundToLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is Double -> + when (b) { + is Byte -> bCol.first() shouldBe a.roundToInt().toByte() + is Short -> bCol.first() shouldBe a.roundToInt().toShort() + is Int -> bCol.first() shouldBe a.roundToInt() + is Long -> bCol.first() shouldBe a.roundToLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is BigInteger -> + when (b) { + is Byte -> bCol.first() shouldBe a.toByte() + is Short -> bCol.first() shouldBe a.toShort() + is Int -> bCol.first() shouldBe a.toInt() + is Long -> bCol.first() shouldBe a.toLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + + is BigDecimal -> + when (b) { + is Byte -> bCol.first() shouldBe a.toByte() + is Short -> bCol.first() shouldBe a.toShort() + is Int -> bCol.first() shouldBe a.toInt() + is Long -> bCol.first() shouldBe a.toLong() + is Float -> bCol.first() shouldBe a.toFloat() + is Double -> bCol.first() shouldBe a.toDouble() + is BigInteger -> bCol.first() shouldBe a.toBigInteger() + is BigDecimal -> bCol.first() shouldBe a.toBigDecimal() + } + } + } + } + } } diff --git a/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/Modify.kt b/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/Modify.kt index bfb7cc7ae0..a75290b8d8 100644 --- a/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/Modify.kt +++ b/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/api/Modify.kt @@ -216,6 +216,18 @@ class Modify : TestBase() { // SampleEnd } + @JvmInline + value class IntClass(val value: Int) + + @Test + @TransformDataFrameExpressions + fun convertToValueClass() { + // SampleStart + dataFrameOf("value")("1", "2") // note that values are strings; conversion is done automatically + .convert("value").to() + // SampleEnd + } + @Test @TransformDataFrameExpressions fun convertAsFrame() { diff --git a/docs/StardustDocs/snippets/org.jetbrains.kotlinx.dataframe.samples.api.Modify.convertToValueClass.html b/docs/StardustDocs/snippets/org.jetbrains.kotlinx.dataframe.samples.api.Modify.convertToValueClass.html index de631c3ccf..e6887a690d 100644 --- a/docs/StardustDocs/snippets/org.jetbrains.kotlinx.dataframe.samples.api.Modify.convertToValueClass.html +++ b/docs/StardustDocs/snippets/org.jetbrains.kotlinx.dataframe.samples.api.Modify.convertToValueClass.html @@ -59,6 +59,7 @@ table.dataframe td { vertical-align: top; + white-space: nowrap; } table.dataframe th.bottomBorder {