Skip to content

Commit b4bdbcf

Browse files
committed
added missing Duration conversions from String and JavaDuration (by Claude)
1 parent 3909f59 commit b4bdbcf

3 files changed

Lines changed: 208 additions & 0 deletions

File tree

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,15 @@ import kotlin.reflect.KProperty
7878
import kotlin.reflect.KType
7979
import kotlin.reflect.full.isSubtypeOf
8080
import kotlin.reflect.typeOf
81+
import kotlin.time.Duration
8182
import kotlin.time.Instant as StdlibInstant
8283
import kotlinx.datetime.Instant as DeprecatedInstant
84+
import java.time.Instant as JavaInstant
85+
import java.time.Duration as JavaDuration
86+
import java.time.LocalDate as JavaLocalDate
87+
import java.time.LocalTime as JavaLocalTime
88+
import java.time.LocalDateTime as JavaLocalDateTime
89+
8390

8491
/**
8592
* See also [parse] — a specialized form of the [convert] operation that parses [String] columns
@@ -2917,6 +2924,132 @@ public fun <T> Convert<T, *>.toDateTimeComponents(): DataFrame<T> = asColumn { i
29172924

29182925
// endregion
29192926

2927+
// region toDuration
2928+
2929+
/**
2930+
* Converts values in this [String] column to [Duration].
2931+
*
2932+
* Parses each string using [Duration.parse].
2933+
* Fails with an exception if a value cannot be parsed.
2934+
*
2935+
* @return A new [DataColumn] with the [Duration] values.
2936+
*/
2937+
@JvmName("convertToDurationFromString")
2938+
public fun DataColumn<String>.convertToDuration(): DataColumn<Duration> = convertTo()
2939+
2940+
/**
2941+
* Converts values in this [String] column to [Duration]. Preserves null values.
2942+
*
2943+
* Parses each string using [Duration.parse].
2944+
* Fails with an exception if a value cannot be parsed.
2945+
*
2946+
* @return A new [DataColumn] with the [Duration] nullable values.
2947+
*/
2948+
@JvmName("convertToDurationFromStringNullable")
2949+
public fun DataColumn<String?>.convertToDuration(): DataColumn<Duration?> = convertTo()
2950+
2951+
/**
2952+
* Converts values in this [JavaDuration] column to [Duration].
2953+
*
2954+
* @return A new [DataColumn] with the [Duration] values.
2955+
*/
2956+
@JvmName("convertToDurationFromJavaDuration")
2957+
public fun DataColumn<JavaDuration>.convertToDuration(): DataColumn<Duration> = convertTo()
2958+
2959+
/**
2960+
* Converts values in this [JavaDuration] column to [Duration]. Preserves null values.
2961+
*
2962+
* @return A new [DataColumn] with the [Duration] nullable values.
2963+
*/
2964+
@JvmName("convertToDurationFromJavaDurationNullable")
2965+
public fun DataColumn<JavaDuration?>.convertToDuration(): DataColumn<Duration?> = convertTo()
2966+
2967+
/**
2968+
* Converts values in the [String] columns previously selected with [convert] to [Duration],
2969+
* preserving their original names and positions within the [DataFrame].
2970+
* Preserves null values.
2971+
*
2972+
* Parses each string using [Duration.parse].
2973+
* Fails with an exception if a value cannot be parsed.
2974+
*
2975+
* For more information: {@include [DocumentationUrls.Convert]}
2976+
*
2977+
* ### Examples:
2978+
* ```kotlin
2979+
* df.convert { duration }.toDuration()
2980+
* ```
2981+
*
2982+
* @return A new [DataFrame] with the values converted to [Duration].
2983+
*/
2984+
@JvmName("toDurationFromStringNullable")
2985+
@Refine
2986+
@Converter(Duration::class, nullable = true)
2987+
@Interpretable("ToSpecificType")
2988+
public fun <T> Convert<T, String?>.toDuration(): DataFrame<T> = asColumn { it.convertToDuration() }
2989+
2990+
/**
2991+
* Converts values in the [String] columns previously selected with [convert] to [Duration],
2992+
* preserving their original names and positions within the [DataFrame].
2993+
*
2994+
* Parses each string using [Duration.parse].
2995+
* Fails with an exception if a value cannot be parsed.
2996+
*
2997+
* For more information: {@include [DocumentationUrls.Convert]}
2998+
*
2999+
* ### Examples:
3000+
* ```kotlin
3001+
* df.convert { duration }.toDuration()
3002+
* ```
3003+
*
3004+
* @return A new [DataFrame] with the values converted to [Duration].
3005+
*/
3006+
@JvmName("toDurationFromString")
3007+
@Refine
3008+
@Converter(Duration::class, nullable = false)
3009+
@Interpretable("ToSpecificType")
3010+
public fun <T> Convert<T, String>.toDuration(): DataFrame<T> = asColumn { it.convertToDuration() }
3011+
3012+
/**
3013+
* Converts values in the [JavaDuration] columns previously selected with [convert] to [Duration],
3014+
* preserving their original names and positions within the [DataFrame].
3015+
* Preserves null values.
3016+
*
3017+
* For more information: {@include [DocumentationUrls.Convert]}
3018+
*
3019+
* ### Examples:
3020+
* ```kotlin
3021+
* df.convert { duration }.toDuration()
3022+
* ```
3023+
*
3024+
* @return A new [DataFrame] with the values converted to [Duration].
3025+
*/
3026+
@JvmName("toDurationFromJavaDurationNullable")
3027+
@Refine
3028+
@Converter(Duration::class, nullable = true)
3029+
@Interpretable("ToSpecificType")
3030+
public fun <T> Convert<T, JavaDuration?>.toDuration(): DataFrame<T> = asColumn { it.convertToDuration() }
3031+
3032+
/**
3033+
* Converts values in the [JavaDuration] columns previously selected with [convert] to [Duration],
3034+
* preserving their original names and positions within the [DataFrame].
3035+
*
3036+
* For more information: {@include [DocumentationUrls.Convert]}
3037+
*
3038+
* ### Examples:
3039+
* ```kotlin
3040+
* df.convert { duration }.toDuration()
3041+
* ```
3042+
*
3043+
* @return A new [DataFrame] with the values converted to [Duration].
3044+
*/
3045+
@JvmName("toDurationFromJavaDuration")
3046+
@Refine
3047+
@Converter(Duration::class, nullable = false)
3048+
@Interpretable("ToSpecificType")
3049+
public fun <T> Convert<T, JavaDuration>.toDuration(): DataFrame<T> = asColumn { it.convertToDuration() }
3050+
3051+
// endregion
3052+
29203053
/**
29213054
* Converts values in the columns previously selected with [convert] to the [Int],
29223055
* preserving their original names and positions within the [DataFrame].

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convert.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ import kotlin.reflect.full.primaryConstructor
5858
import kotlin.reflect.full.withNullability
5959
import kotlin.reflect.jvm.jvmErasure
6060
import kotlin.reflect.typeOf
61+
import kotlin.time.Duration
62+
import kotlin.time.toJavaDuration
6163
import kotlin.time.toJavaInstant
64+
import kotlin.time.toKotlinDuration
6265
import kotlin.time.toKotlinInstant
6366
import kotlin.toBigDecimal
67+
import java.time.Duration as JavaDuration
6468
import java.time.Instant as JavaInstant
6569
import java.time.LocalDate as JavaLocalDate
6670
import java.time.LocalDateTime as JavaLocalDateTime
@@ -264,6 +268,17 @@ internal fun createConverter(from: KType, to: KType, options: ParserOptions? = n
264268
return when {
265269
fromClass == toClass -> TypeConverterIdentity
266270

271+
// kotlin.time.Duration is a value class,
272+
// so it must be handled before the generic toClass.isValue / fromClass.isValue branches.
273+
toClass == Duration::class -> when (fromClass) {
274+
String::class -> Parsers.getAsConverterOrNull(to, options)!!
275+
JavaDuration::class -> convert<JavaDuration> { it.toKotlinDuration() }
276+
else -> null
277+
}
278+
279+
fromClass == Duration::class && toClass == JavaDuration::class ->
280+
convert<Duration> { it.toJavaDuration() }
281+
267282
toClass.isValue -> {
268283
val constructor =
269284
toClass.primaryConstructor ?: error("Value type $toClass doesn't have primary constructor")

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import kotlin.math.roundToLong
2626
import kotlin.random.Random
2727
import kotlin.reflect.full.starProjectedType
2828
import kotlin.reflect.typeOf
29+
import kotlin.time.Duration
2930
import kotlin.time.Duration.Companion.hours
31+
import kotlin.time.Duration.Companion.minutes
32+
import kotlin.time.toKotlinDuration
33+
import java.time.Duration as JavaDuration
3034
import java.time.LocalTime as JavaLocalTime
3135

3236
class ConvertTests {
@@ -342,6 +346,62 @@ class ConvertTests {
342346
}
343347
}
344348

349+
@Test
350+
fun `convertToDuration from String`() {
351+
val col = columnOf("1h", "30m")
352+
col.convertToDuration() shouldBe columnOf(1.hours, 30.minutes)
353+
}
354+
355+
@Test
356+
fun `convertToDuration from nullable String`() {
357+
val col = columnOf("1h", null)
358+
col.convertToDuration() shouldBe columnOf(1.hours, null)
359+
}
360+
361+
@Test
362+
fun `convertToDuration from JavaDuration`() {
363+
val javaDuration = JavaDuration.ofHours(1)
364+
val col = columnOf(javaDuration)
365+
col.convertToDuration() shouldBe columnOf(javaDuration.toKotlinDuration())
366+
}
367+
368+
@Test
369+
fun `convertToDuration from nullable JavaDuration`() {
370+
val javaDuration = JavaDuration.ofMinutes(30)
371+
val col = columnOf(javaDuration, null)
372+
col.convertToDuration() shouldBe columnOf(javaDuration.toKotlinDuration(), null)
373+
}
374+
375+
@Test
376+
fun `toDuration from String column`() {
377+
val duration by columnOf("1h", "30m")
378+
val df = duration.toDataFrame()
379+
df.convert { duration }.toDuration()[{ "duration"<Duration>() }][0] shouldBe 1.hours
380+
}
381+
382+
@Test
383+
fun `toDuration from nullable String column`() {
384+
val duration by columnOf("1h", null)
385+
val df = duration.toDataFrame()
386+
df.convert { duration }.toDuration()[{ "duration"<Duration?>() }].hasNulls shouldBe true
387+
}
388+
389+
@Test
390+
fun `toDuration from JavaDuration column`() {
391+
val javaDuration = JavaDuration.ofHours(2)
392+
val duration by columnOf(javaDuration)
393+
val df = duration.toDataFrame()
394+
df.convert { duration }.toDuration()[{ "duration"<Duration>() }][0] shouldBe javaDuration.toKotlinDuration()
395+
}
396+
397+
@Test
398+
fun `toDuration from nullable JavaDuration column`() {
399+
val javaDuration = JavaDuration.ofMinutes(45)
400+
val duration by columnOf(javaDuration, null)
401+
val df = duration.toDataFrame()
402+
df.convert { duration }.toDuration()[{ "duration"<Duration?>() }][0] shouldBe javaDuration.toKotlinDuration()
403+
}
404+
345405
private interface Marker
346406

347407
private val ColumnsContainer<Marker>.a get() = this["a"] as DataColumn<String>

0 commit comments

Comments
 (0)