From b33e26501e0218279417bfd27e0e5b160db2c9aa Mon Sep 17 00:00:00 2001 From: "Andrey.Tarashevskiy" Date: Thu, 1 Jul 2021 01:56:32 +0300 Subject: [PATCH] LocalDateTime regression #1028 --- .../sql/java-time/JavaDateColumnType.kt | 6 ++-- .../org/jetbrains/exposed/JavaTimeTests.kt | 31 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateColumnType.kt b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateColumnType.kt index 01ee21f3f3..f12761883f 100644 --- a/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateColumnType.kt +++ b/exposed-java-time/src/main/kotlin/org/jetbrains/exposed/sql/java-time/JavaDateColumnType.kt @@ -115,8 +115,10 @@ class JavaLocalDateTimeColumnType : ColumnType(), IDateColumnType { override fun notNullValueToDB(value: Any): Any = when { value is LocalDateTime && currentDialect is SQLiteDialect -> SQLITE_AND_ORACLE_DATE_TIME_STRING_FORMATTER.format(value.atZone(ZoneId.systemDefault())) - value is LocalDateTime -> - java.sql.Timestamp(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()) + value is LocalDateTime -> { + val instant = value.atZone(ZoneId.systemDefault()).toInstant() + java.sql.Timestamp(instant.toEpochMilli()).apply { nanos = instant.nano } + } else -> value } diff --git a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt index 54574ce7c8..7a447c530f 100644 --- a/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt +++ b/exposed-java-time/src/test/kotlin/org/jetbrains/exposed/JavaTimeTests.kt @@ -7,7 +7,7 @@ import org.jetbrains.exposed.sql.tests.DatabaseTestsBase import org.jetbrains.exposed.sql.tests.TestDB import org.jetbrains.exposed.sql.tests.currentDialectTest import org.jetbrains.exposed.sql.tests.shared.assertEquals -import org.jetbrains.exposed.sql.vendors.MysqlDialect +import org.jetbrains.exposed.sql.vendors.* import org.junit.Test import java.time.* import java.time.temporal.Temporal @@ -71,6 +71,22 @@ open class JavaTimeBaseTest : DatabaseTestsBase() { } } } + + @Test + fun `test storing LocalDateTime with nanos`() { + val TestDate = object : IntIdTable("TestLocalDateTime") { + val time = datetime("time") + } + withTables(TestDate) { + val dateTimeWithNanos = LocalDateTime.now().withNano(123) + TestDate.insert { + it[time] = dateTimeWithNanos + } + + val dateTimeFromDB = TestDate.selectAll().single()[TestDate.time] + assertEqualDateTime(dateTimeWithNanos, dateTimeFromDB) + } + } } fun assertEqualDateTime(d1: T?, d2: T?) { @@ -87,14 +103,21 @@ fun assertEqualDateTime(d1: T?, d2: T?) { d1 is LocalTime && d2 is LocalTime && d2.nano == 0 -> assertEquals(d1.withNano(0), d2, "Failed on ${currentDialectTest.name}") d1 is LocalTime && d2 is LocalTime -> assertEquals(d1, d2, "Failed on ${currentDialectTest.name}") d1 is LocalDateTime && d2 is LocalDateTime -> { - val d1Millis = Instant.from(d1.atZone(ZoneId.systemDefault())).toEpochMilli() - val d2Millis = Instant.from(d2.atZone(ZoneId.systemDefault())).toEpochMilli() - assertEquals(d1Millis, d2Millis, "Failed on ${currentDialectTest.name}") + val d1Nanos = currentDialectTest.extractNanos(d1) + val d2Nanos = currentDialectTest.extractNanos(d1) + assertEquals(d1.second + d1Nanos, d2.second + d2Nanos, "Failed on ${currentDialectTest.name}") } else -> assertEquals(d1, d2, "Failed on ${currentDialectTest.name}") } } +private fun DatabaseDialect.extractNanos(dt: LocalDateTime) = when (this) { + is MysqlDialect -> dt.nano.toString().take(6).toInt() // 1000000 ns + is SQLiteDialect -> 0 + is PostgreSQLDialect -> dt.nano.toString().take(1).toInt() // 1 ms + else -> dt.nano +} + fun equalDateTime(d1: Temporal?, d2: Temporal?) = try { assertEqualDateTime(d1, d2) true