Skip to content

Commit

Permalink
GEOMESA-3389 Fix conversion of attributes to java.sql dates (location…
Browse files Browse the repository at this point in the history
  • Loading branch information
elahrvivaz authored Aug 28, 2024
1 parent a4390ee commit 42c4418
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

package org.locationtech.geomesa.utils.geotools.converters

import org.geotools.data.util.TemporalConverterFactory
import org.geotools.util.factory.Hints
import org.geotools.util.{Converter, ConverterFactory}
import org.locationtech.geomesa.utils.geotools.converters.JavaTimeConverterFactory.TwoStageTemporalConverter
import org.locationtech.geomesa.utils.text.DateParsing

import java.time._
Expand All @@ -18,20 +20,31 @@ import java.util.Date

class JavaTimeConverterFactory extends ConverterFactory {
def createConverter(source: Class[_], target: Class[_], hints: Hints): Converter = {
if (classOf[Date].isAssignableFrom(source) && classOf[String].isAssignableFrom(target)) {
if (classOf[Date].isAssignableFrom(source) && classOf[String] == target) {
JavaTimeConverterFactory.DateToStringConverter
} else if (classOf[Date].isAssignableFrom(target)) {
if (classOf[String].isAssignableFrom(source)) {
JavaTimeConverterFactory.StringToDateConverter
} else if (classOf[ZonedDateTime].isAssignableFrom(source) || classOf[Instant].isAssignableFrom(source)
|| classOf[OffsetDateTime].isAssignableFrom(source)) {
JavaTimeConverterFactory.TemporalToDateConverter
} else if (classOf[LocalDateTime].isAssignableFrom(source)) {
JavaTimeConverterFactory.LocalDateTimeToDateConverter
} else if (classOf[LocalDate].isAssignableFrom(source)) {
JavaTimeConverterFactory.LocalDateToDateConverter
val toDate =
if (classOf[String].isAssignableFrom(source)) {
JavaTimeConverterFactory.StringToDateConverter
} else if (classOf[ZonedDateTime].isAssignableFrom(source) || classOf[Instant].isAssignableFrom(source)
|| classOf[OffsetDateTime].isAssignableFrom(source)) {
JavaTimeConverterFactory.TemporalToDateConverter
} else if (classOf[LocalDateTime].isAssignableFrom(source)) {
JavaTimeConverterFactory.LocalDateTimeToDateConverter
} else if (classOf[LocalDate].isAssignableFrom(source)) {
JavaTimeConverterFactory.LocalDateToDateConverter
} else {
null
}
if (toDate == null || classOf[Date] == target) {
toDate
} else {
null
val toTarget = new TemporalConverterFactory().createConverter(classOf[Date], target, hints)
if (toTarget == null) {
null
} else {
new TwoStageTemporalConverter(toDate, toTarget)
}
}
} else {
null
Expand All @@ -41,32 +54,43 @@ class JavaTimeConverterFactory extends ConverterFactory {

object JavaTimeConverterFactory {

private val DateToStringConverter = new Converter {
private trait DateConverter extends Converter {

override def convert[T](source: AnyRef, target: Class[T]): T = convert(source).asInstanceOf[T]

def convert(source: AnyRef): Date
}

private object DateToStringConverter extends Converter {
override def convert[T](source: AnyRef, target: Class[T]): T =
DateParsing.formatDate(source.asInstanceOf[Date]).asInstanceOf[T]
}

private val StringToDateConverter = new Converter {
override def convert[T](source: AnyRef, target: Class[T]): T =
DateParsing.parseDate(source.asInstanceOf[String]).asInstanceOf[T]
private object StringToDateConverter extends DateConverter {
override def convert(source: AnyRef): Date = DateParsing.parseDate(source.asInstanceOf[String])
}

private val TemporalToDateConverter = new Converter {
override def convert[T](source: AnyRef, target: Class[T]): T = {
private object TemporalToDateConverter extends DateConverter {
override def convert(source: AnyRef): Date = {
val accessor = source.asInstanceOf[TemporalAccessor]
val millis = accessor.getLong(ChronoField.INSTANT_SECONDS) * 1000 +
accessor.getLong(ChronoField.MILLI_OF_SECOND)
new Date(millis).asInstanceOf[T]
new Date(millis)
}
}

private val LocalDateToDateConverter = new Converter {
override def convert[T](source: AnyRef, target: Class[T]): T =
TemporalToDateConverter.convert(source.asInstanceOf[LocalDate].atStartOfDay(ZoneOffset.UTC), target)
private object LocalDateToDateConverter extends DateConverter {
override def convert(source: AnyRef): Date =
TemporalToDateConverter.convert(source.asInstanceOf[LocalDate].atStartOfDay(ZoneOffset.UTC))
}

private object LocalDateTimeToDateConverter extends DateConverter {
override def convert(source: AnyRef): Date =
TemporalToDateConverter.convert(source.asInstanceOf[LocalDateTime].atZone(ZoneOffset.UTC))
}

private val LocalDateTimeToDateConverter = new Converter {
private class TwoStageTemporalConverter(toDate: DateConverter, toTarget: Converter) extends Converter {
override def convert[T](source: AnyRef, target: Class[T]): T =
TemporalToDateConverter.convert(source.asInstanceOf[LocalDateTime].atZone(ZoneOffset.UTC), target)
toTarget.convert(toDate.convert(source), target)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.junit.runner.RunWith
import org.specs2.mutable.Specification
import org.specs2.runner.JUnitRunner

import java.sql.Timestamp
import java.time._
import java.time.format.DateTimeFormatter
import java.util.{Date, UUID}
Expand Down Expand Up @@ -275,5 +276,20 @@ class ConverterFactoriesTest extends Specification {
factory.createConverter(time.getClass, classOf[Date], null).convert(time, classOf[Date]) mustEqual date
}
}

"convert java time classes to sql dates" >> {
val times = Seq(
Instant.ofEpochMilli(date.getTime),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(date.getTime), ZoneOffset.UTC),
LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime), ZoneOffset.UTC),
OffsetDateTime.ofInstant(Instant.ofEpochMilli(date.getTime), ZoneOffset.UTC),
LocalDate.from(ZonedDateTime.ofInstant(Instant.ofEpochMilli(date.getTime), ZoneOffset.UTC))
)
foreach(times) { time =>
val result = factory.createConverter(time.getClass, classOf[Timestamp], null).convert(time, classOf[Timestamp])
result must beAnInstanceOf[Timestamp]
date mustEqual result // compare backwards (expected vs actual) so that equals check works
}
}
}
}

0 comments on commit 42c4418

Please sign in to comment.