Skip to content

Commit

Permalink
TypeBinder timezone should be configurable
Browse files Browse the repository at this point in the history
(cherry picked from commit 8938a51)
  • Loading branch information
xuwei-k committed Jun 22, 2018
1 parent ec3bd23 commit 556b571
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package scalikejdbc

import java.time.ZoneId

/** A hold of a specific ZoneId instance to be passed as an implicit parameter for TypeBinder. */
case class OverwrittenZoneId(value: ZoneId) extends AnyVal
28 changes: 22 additions & 6 deletions scalikejdbc-core/src/main/scala/scalikejdbc/TypeBinder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package scalikejdbc

import java.sql.ResultSet
import java.time._
import scalikejdbc.UnixTimeInMillisConverterImplicits._

/**
* Type binder for java.sql.ResultSet.
Expand Down Expand Up @@ -84,11 +85,16 @@ object TypeBinder extends LowPriorityTypeBinderImplicits {
implicit val javaUtilCalendar: TypeBinder[java.util.Calendar] = Binders.javaUtilCalendar

implicit val javaTimeInstant: TypeBinder[Instant] = Binders.javaTimeInstant
implicit val javaTimeZonedDateTime: TypeBinder[ZonedDateTime] = Binders.javaTimeZonedDateTime
implicit val javaTimeOffsetDateTime: TypeBinder[OffsetDateTime] = Binders.javaTimeOffsetDateTime
implicit val javaTimeLocalDate: TypeBinder[LocalDate] = Binders.javaTimeLocalDate
implicit val javaTimeLocalTime: TypeBinder[LocalTime] = Binders.javaTimeLocalTime
implicit val javaTimeLocalDateTime: TypeBinder[LocalDateTime] = Binders.javaTimeLocalDateTime
implicit def javaTimeZonedDateTimeTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[ZonedDateTime] =
Binders.sqlTimestamp.map(Binders.nullThrough(_.toZonedDateTimeWithZoneId(z.value)))
implicit def javaTimeOffsetDateTimeTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[OffsetDateTime] =
Binders.sqlTimestamp.map(Binders.nullThrough(_.toOffsetDateTimeWithZoneId(z.value)))
implicit def javaTimeLocalDateTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[LocalDate] =
Binders.sqlDate.map(Binders.nullThrough(_.toLocalDateWithZoneId(z.value)))
implicit def javaTimeLocalTimeTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[LocalTime] =
Binders.sqlTime.map(Binders.nullThrough(_.toLocalTimeWithZoneId(z.value)))
implicit def javaTimeLocalDateTimeTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[LocalDateTime] =
Binders.sqlTimestamp.map(Binders.nullThrough(_.toLocalDateTimeWithZoneId(z.value)))

implicit val url: TypeBinder[java.net.URL] = Binders.url

Expand All @@ -98,7 +104,7 @@ object TypeBinder extends LowPriorityTypeBinderImplicits {

}

trait LowPriorityTypeBinderImplicits {
trait LowPriorityTypeBinderImplicits extends LowPriorityTypeBinderImplicits2 {

This comment has been minimized.

Copy link
@xuwei-k

xuwei-k Jun 23, 2018

Author Member

Should we revert this commit?🤔 lightbend-labs/mima#225


implicit def option[A](implicit ev: TypeBinder[A]): TypeBinder[Option[A]] = new TypeBinder[Option[A]] {
def apply(rs: ResultSet, columnIndex: Int): Option[A] = wrap(ev(rs, columnIndex))
Expand All @@ -108,3 +114,13 @@ trait LowPriorityTypeBinderImplicits {
}

}

sealed abstract class LowPriorityTypeBinderImplicits2 {

implicit val javaTimeZonedDateTime: TypeBinder[ZonedDateTime] = Binders.javaTimeZonedDateTime
implicit val javaTimeOffsetDateTime: TypeBinder[OffsetDateTime] = Binders.javaTimeOffsetDateTime
implicit val javaTimeLocalDate: TypeBinder[LocalDate] = Binders.javaTimeLocalDate
implicit val javaTimeLocalTime: TypeBinder[LocalTime] = Binders.javaTimeLocalTime
implicit val javaTimeLocalDateTime: TypeBinder[LocalDateTime] = Binders.javaTimeLocalDateTime

}
84 changes: 84 additions & 0 deletions scalikejdbc-core/src/test/scala/scalikejdbc/TypeBinderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,88 @@ class TypeBinderSpec extends FlatSpec with Matchers with MockitoSugar with UnixT
implicitly[TypeBinder[Boolean]].apply(rs, 1) should be(false)
}

it should "configurable timezone" in {
val current = System.currentTimeMillis
val date = new java.util.Date(current)
val rs: ResultSet = mock[ResultSet]

when(rs.getTimestamp("time")).thenReturn(new java.sql.Timestamp(current))
when(rs.getDate("date")).thenReturn(new java.sql.Date(current))
when(rs.getTime("time")).thenReturn(new java.sql.Time(current))

val defaultZone = ZoneId.systemDefault
val anohterZone = {
val hawaii = ZoneId.of("US/Hawaii")
if (defaultZone == hawaii) {
ZoneId.of("Asia/Tokyo")
} else {
hawaii
}
}

case class Values(
zonedDateTime: ZonedDateTime,
offsetDateTime: OffsetDateTime,
localDate: LocalDate,
localTime: LocalTime,
localDateTime: LocalDateTime) {
def notEqualAll(that: Values) = {
this.zonedDateTime should not be that.zonedDateTime
this.offsetDateTime should not be that.offsetDateTime
this.localTime should not be that.localTime
this.localDateTime should not be that.localDateTime
}
}

val valuesDefault = locally {
val values = Values(
implicitly[TypeBinder[ZonedDateTime]].apply(rs, "time"),
implicitly[TypeBinder[OffsetDateTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDate]].apply(rs, "date"),
implicitly[TypeBinder[LocalTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDateTime]].apply(rs, "time"))

values.zonedDateTime shouldBe date.toZonedDateTime
values.offsetDateTime shouldBe date.toOffsetDateTime
values.localDate shouldBe date.toLocalDate
values.localTime shouldBe date.toLocalTime
values.localDateTime shouldBe date.toLocalDateTime

values
}

val valuesAnother = locally {
implicit val overwrittenZone: OverwrittenZoneId = OverwrittenZoneId(anohterZone)

val values = Values(
implicitly[TypeBinder[ZonedDateTime]].apply(rs, "time"),
implicitly[TypeBinder[OffsetDateTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDate]].apply(rs, "date"),
implicitly[TypeBinder[LocalTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDateTime]].apply(rs, "time"))

values.zonedDateTime shouldBe date.toZonedDateTimeWithZoneId(anohterZone)
values.offsetDateTime shouldBe date.toOffsetDateTimeWithZoneId(anohterZone)
values.localDate shouldBe date.toLocalDateWithZoneId(anohterZone)
values.localTime shouldBe date.toLocalTimeWithZoneId(anohterZone)
values.localDateTime shouldBe date.toLocalDateTimeWithZoneId(anohterZone)

values
}

valuesDefault notEqualAll valuesAnother

val valuesExplicitDefault = locally {
implicit val overwrittenZone: OverwrittenZoneId = OverwrittenZoneId(ZoneId.systemDefault)

Values(
implicitly[TypeBinder[ZonedDateTime]].apply(rs, "time"),
implicitly[TypeBinder[OffsetDateTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDate]].apply(rs, "date"),
implicitly[TypeBinder[LocalTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDateTime]].apply(rs, "time"))
}

valuesDefault shouldBe valuesExplicitDefault
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,23 @@ import org.joda.time.{ DateTime => JodaDateTime }
import org.joda.time.{ LocalDate => JodaLocalDate }
import org.joda.time.{ LocalTime => JodaLocalTime }
import org.joda.time.{ LocalDateTime => JodaLocalDateTime }
import JodaUnixTimeInMillisConverterImplicits._

/**
* Type binder for java.sql.ResultSet.
*/
object JodaTypeBinder {
object JodaTypeBinder extends JodaTypeBinderInstances1 {
implicit def jodaDateTimeTypeBinderTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[JodaDateTime] =
Binders.utilDate.map(Binders.nullThrough(_.toJodaDateTimeWithZoneId(z.value)))
implicit def jodaLocalDateTypeBinderTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[JodaLocalDate] =
Binders.sqlDate.map(Binders.nullThrough(_.toJodaLocalDateWithZoneId(z.value)))
implicit def jodaLocalTimeTypeBinderTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[JodaLocalTime] =
Binders.sqlTime.map(Binders.nullThrough(_.toJodaLocalTimeWithZoneId(z.value)))
implicit def jodaLocalDateTimeTypeBinderTypeBinder(implicit z: OverwrittenZoneId): TypeBinder[JodaLocalDateTime] =
Binders.utilDate.map(Binders.nullThrough(_.toJodaLocalDateTimeWithZoneId(z.value)))
}

sealed abstract class JodaTypeBinderInstances1 {

implicit val jodaDateTimeTypeBinder: TypeBinder[JodaDateTime] = JodaBinders.jodaDateTime
implicit val jodaLocalDateTypeBinder: TypeBinder[JodaLocalDate] = JodaBinders.jodaLocalDate
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package scalikejdbc
package jodatime

import java.time.ZoneId
import JodaUnixTimeInMillisConverter.zoneIdToJodaTimeZone

/**
* Unix Time Converter to several types.
*
Expand All @@ -12,16 +15,29 @@ class JodaUnixTimeInMillisConverter(val millis: Long) extends AnyVal {

def toJodaDateTimeWithTimeZone(timezone: org.joda.time.DateTimeZone): org.joda.time.DateTime = new org.joda.time.DateTime(millis, timezone)

def toJodaDateTimeWithZoneId(zoneId: ZoneId): org.joda.time.DateTime = new org.joda.time.DateTime(millis, zoneIdToJodaTimeZone(zoneId))

def toJodaLocalDateTime: org.joda.time.LocalDateTime = new org.joda.time.LocalDateTime(millis)

def toJodaLocalDateTimeWithTimeZone(timezone: org.joda.time.DateTimeZone): org.joda.time.LocalDateTime = new org.joda.time.LocalDateTime(millis, timezone)

def toJodaLocalDateTimeWithZoneId(zoneId: ZoneId): org.joda.time.LocalDateTime = new org.joda.time.LocalDateTime(millis, zoneIdToJodaTimeZone(zoneId))

def toJodaLocalDate: org.joda.time.LocalDate = new org.joda.time.LocalDate(millis)

def toJodaLocalDateWithTimeZone(timezone: org.joda.time.DateTimeZone): org.joda.time.LocalDate = new org.joda.time.LocalDate(millis, timezone)

def toJodaLocalDateWithZoneId(zoneId: ZoneId): org.joda.time.LocalDate = new org.joda.time.LocalDate(millis, zoneIdToJodaTimeZone(zoneId))

def toJodaLocalTime: org.joda.time.LocalTime = new org.joda.time.LocalTime(millis)

def toJodaLocalTimeWithTimeZone(timezone: org.joda.time.DateTimeZone): org.joda.time.LocalTime = new org.joda.time.LocalTime(millis, timezone)

def toJodaLocalTimeWithZoneId(zoneId: ZoneId): org.joda.time.LocalTime = new org.joda.time.LocalTime(millis, zoneIdToJodaTimeZone(zoneId))

}

object JodaUnixTimeInMillisConverter {
def zoneIdToJodaTimeZone(zoneId: ZoneId): org.joda.time.DateTimeZone =
org.joda.time.DateTimeZone.forTimeZone(java.util.TimeZone.getTimeZone(zoneId))
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ package jodatime
import org.scalatest._
import org.scalatest.mockito.MockitoSugar
import java.sql.ResultSet
import java.time.ZoneId
import org.joda.time._
import scalikejdbc.jodatime.JodaTypeBinder._
import JodaUnixTimeInMillisConverterImplicits._
import org.mockito.Mockito

class JodaTypeBinderSpec extends FlatSpec with Matchers with MockitoSugar with UnixTimeInMillisConverterImplicits {
class JodaTypeBinderSpec extends FlatSpec with Matchers with MockitoSugar {

behavior of "TypeBinder"

Expand All @@ -19,4 +22,82 @@ class JodaTypeBinderSpec extends FlatSpec with Matchers with MockitoSugar with U
implicitly[TypeBinder[Option[LocalTime]]].apply(rs, 1) should be(None)
}

it should "configurable timezone" in {
val current = System.currentTimeMillis
val date = new java.util.Date(current)
val rs: ResultSet = mock[ResultSet]

Mockito.when(rs.getTimestamp("time")).thenReturn(new java.sql.Timestamp(current))
Mockito.when(rs.getDate("date")).thenReturn(new java.sql.Date(current))
Mockito.when(rs.getTime("time")).thenReturn(new java.sql.Time(current))

val defaultZone = ZoneId.systemDefault
val anohterZone = {
val hawaii = ZoneId.of("US/Hawaii")
if (defaultZone == hawaii) {
ZoneId.of("Asia/Tokyo")
} else {
hawaii
}
}

case class Values(
dateTime: DateTime,
localDate: LocalDate,
localTime: LocalTime,
localDateTime: LocalDateTime) {
def notEqualAll(that: Values) = {
this.dateTime should not be that.dateTime
this.localTime should not be that.localTime
this.localDateTime should not be that.localDateTime
}
}

val valuesDefault = locally {
val values = Values(
implicitly[TypeBinder[DateTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDate]].apply(rs, "date"),
implicitly[TypeBinder[LocalTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDateTime]].apply(rs, "time"))

values.dateTime shouldBe date.toJodaDateTime
values.localDate shouldBe date.toJodaLocalDate
values.localTime shouldBe date.toJodaLocalTime
values.localDateTime shouldBe date.toJodaLocalDateTime

values
}

val valuesAnother = locally {
implicit val overwrittenZone: OverwrittenZoneId = OverwrittenZoneId(anohterZone)

val values = Values(
implicitly[TypeBinder[DateTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDate]].apply(rs, "date"),
implicitly[TypeBinder[LocalTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDateTime]].apply(rs, "time"))

values.dateTime shouldBe date.toJodaDateTimeWithZoneId(anohterZone)
values.localDate shouldBe date.toJodaLocalDateWithZoneId(anohterZone)
values.localTime shouldBe date.toJodaLocalTimeWithZoneId(anohterZone)
values.localDateTime shouldBe date.toJodaLocalDateTimeWithZoneId(anohterZone)

values
}

valuesDefault notEqualAll valuesAnother

val valuesExplicitDefault = locally {
implicit val overwrittenZone: OverwrittenZoneId = OverwrittenZoneId(ZoneId.systemDefault)

Values(
implicitly[TypeBinder[DateTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDate]].apply(rs, "date"),
implicitly[TypeBinder[LocalTime]].apply(rs, "time"),
implicitly[TypeBinder[LocalDateTime]].apply(rs, "time"))
}

valuesDefault shouldBe valuesExplicitDefault
}

}

0 comments on commit 556b571

Please sign in to comment.