diff --git a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/TimeStringUtility.kt b/airbyte-cdk/bulk/core/load/src/main/kotlin/io/airbyte/cdk/load/util/TimeStringUtility.kt similarity index 86% rename from airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/TimeStringUtility.kt rename to airbyte-cdk/bulk/core/load/src/main/kotlin/io/airbyte/cdk/load/util/TimeStringUtility.kt index 2c41fa720a624..910d1ff2dd05f 100644 --- a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/TimeStringUtility.kt +++ b/airbyte-cdk/bulk/core/load/src/main/kotlin/io/airbyte/cdk/load/util/TimeStringUtility.kt @@ -2,7 +2,7 @@ * Copyright (c) 2024 Airbyte, Inc., all rights reserved. */ -package io.airbyte.cdk.load.data.iceberg.parquet +package io.airbyte.cdk.load.util import io.airbyte.cdk.load.data.TimeStringToInteger import java.time.LocalDate @@ -13,12 +13,17 @@ import java.time.OffsetTime import java.time.ZoneOffset import java.time.ZonedDateTime +/** Collection of time/date string to time/date object conversion utilities. */ object TimeStringUtility { fun toLocalDate(dateString: String): LocalDate { return LocalDate.parse(dateString, TimeStringToInteger.DATE_TIME_FORMATTER) } + fun toLocalDateTime(dateString: String): LocalDateTime { + return LocalDateTime.parse(dateString, TimeStringToInteger.DATE_TIME_FORMATTER) + } + fun toOffset(timeString: String): LocalTime { return try { toMicrosOfDayWithTimezone(timeString) diff --git a/airbyte-cdk/bulk/core/load/src/test/kotlin/io/airbyte/cdk/load/util/TimeStringUtilityTest.kt b/airbyte-cdk/bulk/core/load/src/test/kotlin/io/airbyte/cdk/load/util/TimeStringUtilityTest.kt new file mode 100644 index 0000000000000..28e39a4219e82 --- /dev/null +++ b/airbyte-cdk/bulk/core/load/src/test/kotlin/io/airbyte/cdk/load/util/TimeStringUtilityTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2024 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.cdk.load.util + +import io.airbyte.cdk.load.data.TimeStringToInteger +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.OffsetTime +import java.time.ZoneOffset +import java.time.ZonedDateTime +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test + +internal class TimeStringUtilityTest { + + @Test + fun testToLocalDate() { + val localDateString = "2024-11-18" + val localDate = TimeStringUtility.toLocalDate(localDateString) + assertEquals( + LocalDate.parse(localDateString, TimeStringToInteger.DATE_TIME_FORMATTER), + localDate + ) + } + + @Test + fun testToLocalDateInvalidDateString() { + val invalidDateStr = "invalid-date" + assertThrows(java.time.format.DateTimeParseException::class.java) { + TimeStringUtility.toLocalDate(invalidDateStr) + } + } + + @Test + fun testToLocalDateTime() { + val localDateTimeString = "2024-11-18T12:34:56Z" + val localDateTime = TimeStringUtility.toLocalDateTime(localDateTimeString) + assertEquals( + LocalDateTime.parse(localDateTimeString, TimeStringToInteger.DATE_TIME_FORMATTER), + localDateTime + ) + } + + @Test + fun testToOffsetWithTimezone() { + val offsetWithTimezoneString = "12:34:56Z" + val offsetWithTimezone = TimeStringUtility.toOffset(offsetWithTimezoneString) + assertEquals( + OffsetTime.parse(offsetWithTimezoneString, TimeStringToInteger.TIME_FORMATTER) + .toLocalTime(), + offsetWithTimezone + ) + } + + @Test + fun testToOffsetWithoutTimezone() { + val offsetWithoutTimezoneString = "12:34:56" + val offsetWithoutTimezone = TimeStringUtility.toOffset(offsetWithoutTimezoneString) + assertEquals( + LocalTime.parse(offsetWithoutTimezoneString, TimeStringToInteger.TIME_FORMATTER), + offsetWithoutTimezone + ) + } + + @Test + fun testToOffsetDateTimeWithTimezone() { + val offsetWithTimezoneString = "2024-11-18T12:34:56Z" + val offsetWithTimezone = TimeStringUtility.toOffsetDateTime(offsetWithTimezoneString) + assertEquals( + ZonedDateTime.parse(offsetWithTimezoneString, TimeStringToInteger.DATE_TIME_FORMATTER) + .toOffsetDateTime(), + offsetWithTimezone + ) + } + + @Test + fun testToOffsetDateTimeWithoutTimezone() { + val offsetWithoutTimezoneString = "2024-11-18T12:34:56" + val offsetWithoutTimezone = TimeStringUtility.toOffsetDateTime(offsetWithoutTimezoneString) + assertEquals( + LocalDateTime.parse( + offsetWithoutTimezoneString, + TimeStringToInteger.DATE_TIME_FORMATTER + ) + .atOffset(ZoneOffset.UTC), + offsetWithoutTimezone + ) + } + + @Test + fun testToOffsetDateTimeInvalidFormat() { + val invalidDateTime = "invalid-datetime" + assertThrows(java.time.format.DateTimeParseException::class.java) { + TimeStringUtility.toOffsetDateTime(invalidDateTime) + } + } +} diff --git a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/AirbyteValueToIcebergRecord.kt b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/AirbyteValueToIcebergRecord.kt index 8d710c117eb8f..4fe37446a559d 100644 --- a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/AirbyteValueToIcebergRecord.kt +++ b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/main/kotlin/io/airbyte/cdk/load/data/iceberg/parquet/AirbyteValueToIcebergRecord.kt @@ -15,6 +15,7 @@ import io.airbyte.cdk.load.data.StringValue import io.airbyte.cdk.load.data.TimeValue import io.airbyte.cdk.load.data.TimestampValue import io.airbyte.cdk.load.data.UnknownValue +import io.airbyte.cdk.load.util.TimeStringUtility import org.apache.iceberg.Schema import org.apache.iceberg.data.GenericRecord import org.apache.iceberg.types.Type diff --git a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/testFixtures/kotlin/io/airbyte/cdk/load/data/icerberg/parquet/TimeStringUtilityTest.kt b/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/testFixtures/kotlin/io/airbyte/cdk/load/data/icerberg/parquet/TimeStringUtilityTest.kt deleted file mode 100644 index 72ae318a1697f..0000000000000 --- a/airbyte-cdk/bulk/toolkits/load-iceberg-parquet/src/testFixtures/kotlin/io/airbyte/cdk/load/data/icerberg/parquet/TimeStringUtilityTest.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2024 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.cdk.load.data.icerberg.parquet - -import io.airbyte.cdk.load.data.iceberg.parquet.TimeStringUtility -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalTime -import java.time.OffsetDateTime -import java.time.ZoneOffset -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.Test - -class TimeStringUtilityTest { - - @Test - fun `toLocalDate should parse a valid date string`() { - val dateStr = "2024-12-16T00:00:00" - val date = TimeStringUtility.toLocalDate(dateStr) - assertEquals(LocalDate.of(2024, 12, 16), date) - } - - @Test - fun `toLocalDate should throw exception for invalid date string`() { - val invalidDateStr = "invalid-date" - assertThrows(java.time.format.DateTimeParseException::class.java) { - TimeStringUtility.toLocalDate(invalidDateStr) - } - } - - @Test - fun `toOffset should parse time with timezone`() { - val timeStrWithOffset = "12:34:56+02:00" - val localTime = TimeStringUtility.toOffset(timeStrWithOffset) - assertEquals(LocalTime.of(12, 34, 56), localTime) - } - - @Test - fun `toOffset should parse time without timezone`() { - val timeStrWithoutOffset = "12:34:56" - val localTime = TimeStringUtility.toOffset(timeStrWithoutOffset) - assertEquals(LocalTime.of(12, 34, 56), localTime) - } - - @Test - fun `toOffsetDateTime should parse datetime with timezone`() { - val dateTimeWithTz = "2024-12-16T12:34:56-05:00" - val odt = TimeStringUtility.toOffsetDateTime(dateTimeWithTz) - assertEquals(OffsetDateTime.of(2024, 12, 16, 12, 34, 56, 0, ZoneOffset.of("-05:00")), odt) - } - - @Test - fun `toOffsetDateTime should parse datetime without timezone as UTC`() { - val dateTimeWithoutTz = "2024-12-16T12:34:56" - val odt = TimeStringUtility.toOffsetDateTime(dateTimeWithoutTz) - assertEquals( - OffsetDateTime.of(LocalDateTime.of(2024, 12, 16, 12, 34, 56), ZoneOffset.UTC), - odt - ) - } - - @Test - fun `toOffsetDateTime should throw exception for invalid format`() { - val invalidDateTime = "invalid-datetime" - assertThrows(java.time.format.DateTimeParseException::class.java) { - TimeStringUtility.toOffsetDateTime(invalidDateTime) - } - } -}