├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ └── ci.yml ├── project ├── build.properties └── plugins.sbt ├── .scala-steward.conf ├── .gitignore ├── src ├── test │ ├── resources │ │ └── test-mysql-conf │ │ │ └── my.cnf │ └── scala │ │ └── com │ │ └── github │ │ └── tototoshi │ │ └── slick │ │ ├── converter │ │ └── JodaSqlTypeConverterSpec.scala │ │ └── JodaSupportSpec.scala └── main │ └── scala │ └── com │ └── github │ └── tototoshi │ └── slick │ ├── JodaGetResult.scala │ ├── JodaSetParameter.scala │ ├── converter │ ├── SqlTypeConverter.scala │ └── JodaSqlTypeCoverter.scala │ ├── Converters.scala │ ├── JodaSupport.scala │ └── JodaTypeMapper.scala ├── LICENSE.txt └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @tototoshi 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.7 2 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | updates.includeScala = "no" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bsp/ 2 | target/ 3 | *.db 4 | .testcontainers-*/ 5 | -------------------------------------------------------------------------------- /src/test/resources/test-mysql-conf/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | explicit_defaults_for_timestamp = 1 3 | character-set-server=utf8 4 | collation-server=utf8_unicode_ci 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | scalacOptions += "-deprecation" 2 | 3 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3") 4 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.12.2") 5 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | schedule: 6 | - cron: '0 0 * * 3' 7 | workflow_dispatch: 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 30 12 | strategy: 13 | matrix: 14 | include: 15 | - java: 8 16 | - java: 11 17 | fail-fast: false 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions/setup-java@v4 21 | with: 22 | java-version: ${{matrix.java}} 23 | distribution: adopt 24 | - uses: coursier/cache-action@v6 25 | - uses: sbt/setup-sbt@v1 26 | - run: sbt -v "+test" 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Toshiyuki Takahashi 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/JodaGetResult.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | package com.github.tototoshi.slick 30 | 31 | import com.github.tototoshi.slick.converter.SqlTypeConverter 32 | import slick.jdbc.{ PositionedResult, GetResult } 33 | 34 | trait JodaGetResult[A, B] { 35 | self: SqlTypeConverter[A, B] => 36 | 37 | def next(rs: PositionedResult): A 38 | 39 | def nextOption(rs: PositionedResult): Option[A] 40 | 41 | object getResult extends GetResult[B] { 42 | def apply(rs: PositionedResult) = fromSqlType(next(rs)) 43 | } 44 | 45 | object getOptionResult extends GetResult[Option[B]] { 46 | def apply(rs: PositionedResult) = nextOption(rs).map(fromSqlType) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/JodaSetParameter.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | package com.github.tototoshi.slick 30 | 31 | import com.github.tototoshi.slick.converter.SqlTypeConverter 32 | import slick.jdbc.{ PositionedParameters, SetParameter } 33 | 34 | trait JodaSetParameter[A, B] { 35 | self: SqlTypeConverter[A, B] => 36 | 37 | def set(rs: PositionedParameters, d: A): Unit 38 | 39 | def setOption(rs: PositionedParameters, d: Option[A]): Unit 40 | 41 | object setJodaParameter extends SetParameter[B] { 42 | def apply(d: B, p: PositionedParameters): Unit = { 43 | set(p, toSqlType(d)) 44 | } 45 | } 46 | 47 | object setJodaOptionParameter extends SetParameter[Option[B]] { 48 | def apply(d: Option[B], p: PositionedParameters): Unit = { 49 | setOption(p, d.map(toSqlType)) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/converter/SqlTypeConverter.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | package com.github.tototoshi.slick.converter 29 | 30 | import scala.language.reflectiveCalls 31 | 32 | trait SqlTypeConverter[A, B] extends FromTypeConverter[A, B] 33 | with ToTypeConverter[A, B] 34 | 35 | trait FromTypeConverter[A, B] { 36 | def fromSqlType(a: A): B 37 | } 38 | 39 | trait ToTypeConverter[A, B] { 40 | 41 | def toSqlType(b: B): A 42 | 43 | def millisToSqlType(d: { def getTime(): Long }): java.sql.Date = { 44 | import java.util.Calendar 45 | val cal = Calendar.getInstance() 46 | cal.setTimeInMillis(d.getTime()) 47 | cal.set(Calendar.HOUR_OF_DAY, 0) 48 | cal.set(Calendar.MINUTE, 0) 49 | cal.set(Calendar.SECOND, 0) 50 | cal.set(Calendar.MILLISECOND, 0) 51 | new java.sql.Date(cal.getTimeInMillis) 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/converter/JodaSqlTypeCoverter.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | package com.github.tototoshi.slick.converter 29 | 30 | import org.joda.time._ 31 | import java.sql.{ Timestamp, Time } 32 | 33 | trait JodaDateTimeZoneSqlStringConverter 34 | extends SqlTypeConverter[String, DateTimeZone] { 35 | 36 | def toSqlType(z: DateTimeZone): String = 37 | if (z == null) null else z.getID 38 | 39 | def fromSqlType(z: String): DateTimeZone = 40 | if (z == null) null else DateTimeZone.forID(z) 41 | } 42 | 43 | trait JodaLocalDateSqlDateConverter 44 | extends SqlTypeConverter[java.sql.Date, LocalDate] { 45 | 46 | def toSqlType(d: LocalDate): java.sql.Date = 47 | if (d == null) null else millisToSqlType(d.toDate) 48 | 49 | def fromSqlType(d: java.sql.Date): LocalDate = 50 | if (d == null) null else new LocalDate(d.getTime) 51 | 52 | } 53 | 54 | trait JodaDateTimeSqlTimestampConverter 55 | extends SqlTypeConverter[Timestamp, DateTime] { 56 | 57 | def fromSqlType(t: java.sql.Timestamp): DateTime = 58 | if (t == null) null else new DateTime(t.getTime) 59 | 60 | def toSqlType(t: DateTime): java.sql.Timestamp = 61 | if (t == null) null else new java.sql.Timestamp(t.getMillis) 62 | 63 | } 64 | 65 | trait JodaInstantSqlTimestampConverter 66 | extends SqlTypeConverter[Timestamp, Instant] { 67 | 68 | def fromSqlType(t: java.sql.Timestamp): Instant = 69 | if (t == null) null else new Instant(t.getTime) 70 | 71 | def toSqlType(t: Instant): java.sql.Timestamp = 72 | if (t == null) null else new java.sql.Timestamp(t.getMillis) 73 | 74 | } 75 | 76 | trait JodaLocalDateTimeSqlTimestampConverter 77 | extends SqlTypeConverter[Timestamp, LocalDateTime] { 78 | 79 | def fromSqlType(t: java.sql.Timestamp): LocalDateTime = 80 | if (t == null) null else new LocalDateTime(t.getTime) 81 | 82 | def toSqlType(t: LocalDateTime): java.sql.Timestamp = 83 | if (t == null) null else new java.sql.Timestamp(t.toDate.getTime) 84 | 85 | } 86 | 87 | trait JodaLocalTimeSqlTimeConverter 88 | extends SqlTypeConverter[Time, LocalTime] { 89 | 90 | def fromSqlType(t: java.sql.Time): LocalTime = 91 | if (t == null) null else new LocalTime(t.getTime) 92 | 93 | def toSqlType(t: LocalTime): java.sql.Time = { 94 | if (t == null) null else new java.sql.Time(t.toDateTimeToday.getMillis) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/Converters.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | package com.github.tototoshi.slick 30 | 31 | import org.joda.time.{ LocalDateTime, LocalTime, LocalDate, DateTime, DateTimeZone } 32 | import com.github.tototoshi.slick.converter._ 33 | 34 | object Converters { 35 | 36 | implicit class WrappedDateTimeZone(z: DateTimeZone) extends JodaDateTimeZoneSqlStringConverter { 37 | def toSqlString(): String = toSqlType(z) 38 | } 39 | 40 | implicit class WrappedDateTime(d: DateTime) extends JodaDateTimeSqlTimestampConverter { 41 | def toSqlTimestamp(): java.sql.Timestamp = toSqlType(d) 42 | } 43 | 44 | implicit class WrappedDateTimeOption(d: Option[DateTime]) extends JodaDateTimeSqlTimestampConverter { 45 | def toSqlTimestampOption(): Option[java.sql.Timestamp] = d.map(toSqlType) 46 | } 47 | 48 | implicit class WrappedLocalDateTime(d: LocalDateTime) extends JodaLocalDateTimeSqlTimestampConverter { 49 | def toSqlTimestamp(): java.sql.Timestamp = toSqlType(d) 50 | } 51 | 52 | implicit class WrappedLocalDateTimeOption(d: Option[LocalDateTime]) extends JodaLocalDateTimeSqlTimestampConverter { 53 | def toSqlTimestampOption(): Option[java.sql.Timestamp] = d.map(toSqlType) 54 | } 55 | 56 | implicit class WrappedLocalDate(d: LocalDate) extends JodaLocalDateSqlDateConverter { 57 | def toSqlDate(): java.sql.Date = toSqlType(d) 58 | } 59 | 60 | implicit class WrappedLocalDateOption(d: Option[LocalDate]) extends JodaLocalDateSqlDateConverter { 61 | def toSqlDateOption(): Option[java.sql.Date] = d.map(toSqlType) 62 | } 63 | 64 | implicit class WrappedLocalTime(t: LocalTime) extends JodaLocalTimeSqlTimeConverter { 65 | def toSqlTime(): java.sql.Time = toSqlType(t) 66 | } 67 | 68 | implicit class WrappedLocalTimeOption(t: Option[LocalTime]) extends JodaLocalTimeSqlTimeConverter { 69 | def toSqlTimeOption(): Option[java.sql.Time] = t.map(toSqlType) 70 | } 71 | 72 | implicit class WrappedSqlTimestamp(t: java.sql.Timestamp) extends JodaDateTimeSqlTimestampConverter { 73 | def toDateTime(): DateTime = fromSqlType(t) 74 | } 75 | 76 | implicit class WrappedSqlTimestampOption(t: Option[java.sql.Timestamp]) extends JodaDateTimeSqlTimestampConverter { 77 | def toDateTimeOption(): Option[DateTime] = t.map(fromSqlType) 78 | } 79 | 80 | implicit class WrappedSqlTimestampForLocalDateTime(t: java.sql.Timestamp) extends JodaLocalDateTimeSqlTimestampConverter { 81 | def toLocalDateTime(): LocalDateTime = fromSqlType(t) 82 | } 83 | 84 | implicit class WrappedSqlTimestampOptionForLocalDateTime(t: Option[java.sql.Timestamp]) extends JodaLocalDateTimeSqlTimestampConverter { 85 | def toLocalDateTimeOption(): Option[LocalDateTime] = t.map(fromSqlType) 86 | } 87 | 88 | implicit class WrappedSqlTime(t: java.sql.Time) extends JodaLocalTimeSqlTimeConverter { 89 | def toLocalTime(): LocalTime = fromSqlType(t) 90 | } 91 | 92 | implicit class WrappedSqlTimeOption(t: Option[java.sql.Time]) extends JodaLocalTimeSqlTimeConverter { 93 | def toLocalTimeOption(): Option[LocalTime] = t.map(fromSqlType) 94 | } 95 | 96 | implicit class WrappedSqlDate(d: java.sql.Date) extends JodaLocalDateSqlDateConverter { 97 | def toLocalDate(): LocalDate = fromSqlType(d) 98 | } 99 | 100 | implicit class WrappedSqlDateOption(d: Option[java.sql.Date]) extends JodaLocalDateSqlDateConverter { 101 | def toLocalDateOption(): Option[LocalDate] = d.map(fromSqlType) 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/test/scala/com/github/tototoshi/slick/converter/JodaSqlTypeConverterSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.tototoshi.slick.converter 2 | 3 | import org.joda.time._ 4 | import java.sql.Time 5 | import org.scalatest.BeforeAndAfter 6 | import org.scalatest._ 7 | import java.util.{ TimeZone, Locale } 8 | import org.scalatest.funspec.AnyFunSpec 9 | import org.scalatest.matchers.should.Matchers 10 | 11 | class JodaSqlTypeConverterSpec extends AnyFunSpec with Matchers with BeforeAndAfter { 12 | 13 | private object fixture { 14 | val dateTimeZoneConverter = new JodaDateTimeZoneSqlStringConverter {} 15 | val localDateConverter = new JodaLocalDateSqlDateConverter {} 16 | val dateTimeConverter = new JodaDateTimeSqlTimestampConverter {} 17 | val instantConverter = new JodaInstantSqlTimestampConverter {} 18 | val localDateTimeConverter = new JodaLocalDateTimeSqlTimestampConverter {} 19 | val localTimeConverter = new JodaLocalTimeSqlTimeConverter {} 20 | } 21 | 22 | before { 23 | Locale.setDefault(Locale.JAPAN) 24 | val tz = TimeZone.getTimeZone("Asia/Tokyo") 25 | TimeZone.setDefault(tz) 26 | DateTimeZone.setDefault(DateTimeZone.forID(tz.getID)) 27 | } 28 | 29 | describe("JodaDateTimeZoneSqlStringConverter") { 30 | 31 | it("should convert DateTimeZone to String") { 32 | fixture.dateTimeZoneConverter.toSqlType(null) should be(null) 33 | val timezone = DateTimeZone.forID("Asia/Tokyo") 34 | fixture.dateTimeZoneConverter.toSqlType(timezone) should be("Asia/Tokyo") 35 | } 36 | 37 | it("should convert String to DateTimeZone") { 38 | fixture.dateTimeZoneConverter.fromSqlType(null) should be(null) 39 | fixture.dateTimeZoneConverter.fromSqlType("Asia/Tokyo") should be(DateTimeZone.forID("Asia/Tokyo")) 40 | } 41 | 42 | } 43 | 44 | describe("JodaLocalDateSqlDateConverter") { 45 | 46 | it("should convert LocalDate to java.sql.Date") { 47 | fixture.localDateConverter.toSqlType(null) should be(null) 48 | val date = new LocalDate(2013, 3, 23) 49 | fixture.localDateConverter.toSqlType(date).getTime should be(1363964400000L) 50 | } 51 | 52 | it("should convert java.sql.Date to LocalDate") { 53 | fixture.localDateConverter.fromSqlType(null) should be(null) 54 | fixture.localDateConverter.fromSqlType(new java.sql.Date(1364000000000L)) should be(new LocalDate(2013, 3, 23)) 55 | } 56 | 57 | } 58 | 59 | describe("JodaDateTimeSqlTimestampConverter") { 60 | 61 | it("should convert DateTime to java.sql.Timestamp") { 62 | fixture.dateTimeConverter.toSqlType(null) should be(null) 63 | val current = DateTime.now 64 | val timestamp = current.getMillis 65 | fixture.dateTimeConverter.toSqlType(current).getTime should be(timestamp) 66 | } 67 | 68 | it("should convert java.sql.Timestamp to DateTime") { 69 | fixture.dateTimeConverter.fromSqlType(null) should be(null) 70 | val current = DateTime.now 71 | val timestamp = current.getMillis 72 | fixture.dateTimeConverter.fromSqlType(new java.sql.Timestamp(timestamp)) should be(current) 73 | } 74 | 75 | } 76 | 77 | describe("JodaInstantSqlTimestampConverter") { 78 | 79 | it("should convert Instant to java.sql.Timestamp") { 80 | fixture.instantConverter.toSqlType(null) should be(null) 81 | val current = Instant.now 82 | val timestamp = current.getMillis 83 | fixture.instantConverter.toSqlType(current).getTime should be(timestamp) 84 | } 85 | 86 | it("should convert java.sql.Timestamp to Instant") { 87 | fixture.instantConverter.fromSqlType(null) should be(null) 88 | val current = Instant.now 89 | val timestamp = current.getMillis 90 | fixture.instantConverter.fromSqlType(new java.sql.Timestamp(timestamp)) should be(current) 91 | } 92 | 93 | } 94 | 95 | describe("JodaLocalDateTimeSqlDateConverter") { 96 | 97 | it("should convert LocalDateTime to java.sql.Timestamp") { 98 | fixture.localDateTimeConverter.toSqlType(null) should be(null) 99 | val current = LocalDateTime.now 100 | val timestamp = current.toDate.getTime 101 | fixture.localDateTimeConverter.toSqlType(current).getTime should be(timestamp) 102 | } 103 | 104 | it("should convert java.sql.Timestamp to LocalDateTime") { 105 | fixture.localDateTimeConverter.fromSqlType(null) should be(null) 106 | val current = LocalDateTime.now 107 | val timestamp = current.toDate.getTime 108 | fixture.localDateTimeConverter.fromSqlType(new java.sql.Timestamp(timestamp)) should be(current) 109 | } 110 | 111 | } 112 | 113 | describe("JodaLocalTimeSqlTimeConverter") { 114 | 115 | it("should convert LocalTime to java.sql.Time") { 116 | fixture.localTimeConverter.fromSqlType(null) should be(null) 117 | val current = LocalTime.now 118 | fixture.localTimeConverter.toSqlType(current) should be(new Time(current.toDateTimeToday.getMillis)) 119 | } 120 | 121 | it("should convert java.sql.Time to LocalTime") { 122 | fixture.localTimeConverter.fromSqlType(null) should be(null) 123 | val current = LocalTime.now 124 | val timestamp = current.toDateTimeToday.getMillis 125 | fixture.localTimeConverter.fromSqlType(new java.sql.Time(timestamp)) should be(current) 126 | } 127 | 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/JodaSupport.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | package com.github.tototoshi.slick 29 | 30 | import org.joda.time.{ DateTime, DateTimeZone, Instant, LocalDate, LocalDateTime, LocalTime } 31 | import java.util.Calendar 32 | import org.joda.time.DateTime 33 | import slick.jdbc._ 34 | 35 | /** 36 | * @param setTimeZone `Calendar` parameter for [[https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-java.util.Calendar-]] 37 | */ 38 | class GenericJodaSupport(val driver: JdbcProfile, setTimeZone: DateTime => Option[Calendar]) { 39 | 40 | def this(driver: JdbcProfile) = { 41 | this(driver, GenericJodaSupport.defaultSetTimeZoneFunction) 42 | } 43 | 44 | protected val dateTimeZoneMapperDelegate: JodaDateTimeZoneMapper = new JodaDateTimeZoneMapper(driver) 45 | protected val localDateMapperDelegate: JodaLocalDateMapper = new JodaLocalDateMapper(driver) 46 | protected val dateTimeMapperDelegate: JodaDateTimeMapper = new JodaDateTimeMapper(driver, setTimeZone) 47 | protected val instantMapperDelegate: JodaInstantMapper = new JodaInstantMapper(driver) 48 | protected val localDateTimeMapperDelegate: JodaLocalDateTimeMapper = new JodaLocalDateTimeMapper(driver) 49 | protected val localTimeMapperDelegate: JodaLocalTimeMapper = new JodaLocalTimeMapper(driver) 50 | 51 | implicit val dateTimeZoneTypeMapper: JdbcProfile#DriverJdbcType[DateTimeZone] = dateTimeZoneMapperDelegate.TypeMapper 52 | implicit val getDateTimeZoneResult: GetResult[DateTimeZone] = dateTimeZoneMapperDelegate.JodaGetResult.getResult 53 | implicit val getDateTimeZoneOptionResult: GetResult[Option[DateTimeZone]] = dateTimeZoneMapperDelegate.JodaGetResult.getOptionResult 54 | implicit val setDateTimeZoneParameter: SetParameter[DateTimeZone] = dateTimeZoneMapperDelegate.JodaSetParameter.setJodaParameter 55 | implicit val setDateTimeZoneOptionParameter: SetParameter[Option[DateTimeZone]] = dateTimeZoneMapperDelegate.JodaSetParameter.setJodaOptionParameter 56 | 57 | implicit val localDateTypeMapper: JdbcProfile#DriverJdbcType[LocalDate] = localDateMapperDelegate.TypeMapper 58 | implicit val getLocalDateResult: GetResult[LocalDate] = localDateMapperDelegate.JodaGetResult.getResult 59 | implicit val getLocalDateOptionResult: GetResult[Option[LocalDate]] = localDateMapperDelegate.JodaGetResult.getOptionResult 60 | implicit val setLocalDateParameter: SetParameter[LocalDate] = localDateMapperDelegate.JodaSetParameter.setJodaParameter 61 | implicit val setLocalDateOptionParameter: SetParameter[Option[LocalDate]] = localDateMapperDelegate.JodaSetParameter.setJodaOptionParameter 62 | 63 | implicit val datetimeTypeMapper: JdbcProfile#DriverJdbcType[DateTime] = dateTimeMapperDelegate.TypeMapper 64 | implicit val getDatetimeResult: GetResult[DateTime] = dateTimeMapperDelegate.JodaGetResult.getResult 65 | implicit val getDatetimeOptionResult: GetResult[Option[DateTime]] = dateTimeMapperDelegate.JodaGetResult.getOptionResult 66 | implicit val setDatetimeParameter: SetParameter[DateTime] = dateTimeMapperDelegate.JodaSetParameter.setJodaParameter 67 | implicit val setDatetimeOptionParameter: SetParameter[Option[DateTime]] = dateTimeMapperDelegate.JodaSetParameter.setJodaOptionParameter 68 | 69 | implicit val instantTypeMapper: JdbcProfile#DriverJdbcType[Instant] = instantMapperDelegate.TypeMapper 70 | implicit val getInstantResult: GetResult[Instant] = instantMapperDelegate.JodaGetResult.getResult 71 | implicit val getInstantOptionResult: GetResult[Option[Instant]] = instantMapperDelegate.JodaGetResult.getOptionResult 72 | implicit val setInstantParameter: SetParameter[Instant] = instantMapperDelegate.JodaSetParameter.setJodaParameter 73 | implicit val setInstantOptionParameter: SetParameter[Option[Instant]] = instantMapperDelegate.JodaSetParameter.setJodaOptionParameter 74 | 75 | implicit val localDatetimeTypeMapper: JdbcProfile#DriverJdbcType[LocalDateTime] = localDateTimeMapperDelegate.TypeMapper 76 | implicit val getLocalDatetimeResult: GetResult[LocalDateTime] = localDateTimeMapperDelegate.JodaGetResult.getResult 77 | implicit val getLocalDatetimeOptionResult: GetResult[Option[LocalDateTime]] = localDateTimeMapperDelegate.JodaGetResult.getOptionResult 78 | implicit val setLocalDatetimeParameter: SetParameter[LocalDateTime] = localDateTimeMapperDelegate.JodaSetParameter.setJodaParameter 79 | implicit val setLocalDatetimeOptionParameter: SetParameter[Option[LocalDateTime]] = localDateTimeMapperDelegate.JodaSetParameter.setJodaOptionParameter 80 | 81 | implicit val localTimeTypeMapper: JdbcProfile#DriverJdbcType[LocalTime] = localTimeMapperDelegate.TypeMapper 82 | implicit val getLocalTimeResult: GetResult[LocalTime] = localTimeMapperDelegate.JodaGetResult.getResult 83 | implicit val getLocalTimeOptionResult: GetResult[Option[LocalTime]] = localTimeMapperDelegate.JodaGetResult.getOptionResult 84 | implicit val setLocalTimeParameter: SetParameter[LocalTime] = localTimeMapperDelegate.JodaSetParameter.setJodaParameter 85 | implicit val setLocalTimeOptionParameter: SetParameter[Option[LocalTime]] = localTimeMapperDelegate.JodaSetParameter.setJodaOptionParameter 86 | 87 | } 88 | 89 | object GenericJodaSupport { 90 | val defaultSetTimeZoneFunction: DateTime => Option[Calendar] = datetime => 91 | Some(Calendar.getInstance(datetime.getZone.toTimeZone)) 92 | } 93 | 94 | object H2JodaSupport extends GenericJodaSupport(H2Profile) 95 | object PostgresJodaSupport extends GenericJodaSupport(PostgresProfile) 96 | object MySQLJodaSupport extends GenericJodaSupport(MySQLProfile) 97 | object HsqldbJodaSupport extends GenericJodaSupport(HsqldbProfile) 98 | object SQLiteJodaSupport extends GenericJodaSupport(SQLiteProfile) 99 | object OracleJodaSupport extends GenericJodaSupport(OracleProfile) 100 | object SQLServerJodaSupport extends GenericJodaSupport(SQLServerProfile) 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slick-joda-mapper 2 | 3 | ![CI](https://github.com/tototoshi/slick-joda-mapper/workflows/CI/badge.svg) 4 | 5 | Enables you to use joda-time with Slick. 6 | You can persist `DateTime`, `Instant`, `LocalDateTime`, `LocalDate`, `LocalTime`, `DateTimeZone` with Slick. 7 | 8 | # Usage 9 | 10 | ## For Slick 3.x 11 | 12 | | Slick version | slick-joda-mapper version | 13 | | ------------- | ------------------------- | 14 | | 3.5.x | 2.9.1 | 15 | | 3.4.x | 2.8.0 | 16 | | 3.3.x | 2.7.1 | 17 | | 3.2.x | 2.3.0 | 18 | | 3.1.x | 2.2.0 | 19 | | 3.0.x | 2.0.0 | 20 | 21 | ```scala 22 | libraryDependencies ++= Seq( 23 | "com.typesafe.slick" %% "slick" % slickVersion, 24 | "com.github.tototoshi" %% "slick-joda-mapper" % slickJodaMapperVersion, 25 | "joda-time" % "joda-time" % "2.12.5", 26 | "org.joda" % "joda-convert" % "2.2.3" 27 | ) 28 | ``` 29 | 30 | Import the appropriate `xJodaSupport` class suitable for the database you use (`H2JodaSupport`, `PostgresJodaSupport`, `MySQLJodaSupport`, etc.). 31 | For example, import Slick's `H2Driver` API and `H2JodaSupport` if you are using the H2 database: 32 | 33 | ```scala 34 | import slick.driver.H2Driver.api._ 35 | import com.github.tototoshi.slick.H2JodaSupport._ 36 | ``` 37 | 38 | As another example, if you are using the Postgres database: 39 | 40 | ```scala 41 | import slick.driver.PostgresDriver.api._ 42 | import com.github.tototoshi.slick.PostgresJodaSupport._ 43 | ``` 44 | 45 | Different drivers **can't** be mixed, You **can't** do the following. 46 | 47 | ```scala 48 | import scala.slick.driver.H2Driver.api._ 49 | import com.github.tototoshi.slick.JdbcJodaSupport._ 50 | ``` 51 | 52 | Write your own `JdbcSupport` when you want to write db agnostic model class. 53 | 54 | ```scala 55 | object PortableJodaSupport extends com.github.tototoshi.slick.GenericJodaSupport(yourAbstractDriver) 56 | 57 | import PortableJodaSupport._ 58 | ``` 59 | 60 | ## For Slick 2.x 61 | 62 | ```scala 63 | libraryDependencies ++= Seq( 64 | "com.typesafe.slick" %% "slick" % "2.1.0", 65 | "joda-time" % "joda-time" % "2.4", 66 | "org.joda" % "joda-convert" % "1.6", 67 | "com.github.tototoshi" %% "slick-joda-mapper" % "1.2.0" 68 | ) 69 | ``` 70 | 71 | Import xJodaSupport(H2JodaSupport, PostgresJodaSupport, MySQLJodaSupport...) class suitable for the database you use. 72 | For example, import `H2JodaSupport` if you are using H2Driver. 73 | 74 | ```scala 75 | import scala.slick.driver.H2Driver.simple._ 76 | import com.github.tototoshi.slick.H2JodaSupport._ 77 | ``` 78 | 79 | Different drivers **can't** be mixed, You **can't** do the following. 80 | 81 | ```scala 82 | import scala.slick.driver.H2Driver.simple._ 83 | import com.github.tototoshi.slick.JdbcJodaSupport._ 84 | ``` 85 | 86 | Write your own `JdbcSupport` when you want to write db agnostic model class. 87 | 88 | ```scala 89 | object PortableJodaSupport extends com.github.tototoshi.slick.GenericJodaSupport(yourAbstractDriver) 90 | 91 | // with play-slick 92 | object PortableJodaSupport extends com.github.tototoshi.slick.GenericJodaSupport(play.api.db.slick.Config.driver) 93 | 94 | import PortableJodaSupport._ 95 | ``` 96 | 97 | ### Code generation 98 | 99 | Write a custom code generator that replaces `java.sql.Timestamp` with whatever Joda classes you prefer. 100 | 101 | This example maps `java.sql.Timestamp` to `org.joda.time.DateTime` using the Postgres support. When you modify it to suit your needs, make sure that the imports refer to the correct `JdbcSupport` class for your database and Joda classes. 102 | 103 | ```scala 104 | import scala.slick.{model => m} 105 | import scala.slick.codegen.SourceCodeGenerator 106 | 107 | class CustomSourceCodeGenerator(model: m.Model) extends SourceCodeGenerator(model) { 108 | 109 | // add some custom imports 110 | // TODO: fix these imports to refer to your JdbcSupport and your Joda imports 111 | override def code = "import com.github.tototoshi.slick.PostgresJodaSupport._\n" + "import org.joda.time.DateTime\n" + super.code 112 | 113 | override def Table = new Table(_) { 114 | override def Column = new Column(_) { 115 | 116 | // munge rawType -> SQL column type HERE (scaladoc in Slick 2.1.0 is outdated or incorrect, GeneratorHelpers#mapJdbcTypeString does not exist) 117 | // you can filter on model.name for the column name or model.tpe for the column type 118 | // your IDE won't like the String here but don't worry, the return type the compiler expects here is String 119 | override def rawType = model.tpe match { 120 | case "java.sql.Timestamp" => "DateTime" // kill j.s.Timestamp 121 | case _ => { 122 | // println(s"${model.table.table}#${model.name} tpe=${model.tpe} rawType=${super.rawType}") 123 | super.rawType 124 | } 125 | } 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | Then write a simple app harness to run code generation: 132 | 133 | ```scala 134 | 135 | import scala.slick.driver.JdbcProfile 136 | 137 | object CodeGen extends App { 138 | 139 | // http://slick.typesafe.com/doc/2.1.0/code-generation.html 140 | 141 | val slickDriver = "scala.slick.driver.PostgresDriver" // TODO: replace this with your Slick driver 142 | val jdbcDriver = "org.postgresql.Driver" // TODO: replace this with your JDBC driver 143 | val url = "jdbc:postgresql://127.0.0.1:5432/foo" // TODO: replace this with your database's JDBC URL 144 | val outputFolder = "src/main/scala" // TODO: or whatever output folder you're in the mood for 145 | val pkg = "foo" // TODO: your package name 146 | val user = "postgres" // TODO: database username - optional, use forURL supports both with and without credentials 147 | val password = "" // TODO: database password - optional, use forURL supports both with and without credentials 148 | 149 | val driver: JdbcProfile = scala.slick.driver.PostgresDriver // TODO: replace this with your Slick driver 150 | 151 | val db = { 152 | // UNCOMMENT this if your database doesn't need credentials 153 | // driver.simple.Database.forURL(url, jdbcDriver) 154 | driver.simple.Database.forURL(url, driver = jdbcDriver, user = user, password = password) 155 | } 156 | 157 | db.withSession { implicit session => 158 | new CustomSourceCodeGenerator(driver.createModel()).writeToFile(slickDriver, outputFolder, pkg) 159 | } 160 | 161 | } 162 | 163 | ``` 164 | 165 | You can run and keep adjusting your source code generation using `sbt run`. If you get compile errors on the generated `Tables.scala`, just delete it and try again. 166 | 167 | ## For Slick 1.x 168 | 169 | ```scala 170 | libraryDependencies += "com.github.tototoshi" %% "slick-joda-mapper" % "0.4.1" 171 | ``` 172 | 173 | ```scala 174 | import com.github.tototoshi.slick.JodaSupport._ 175 | ``` 176 | 177 | # Example 178 | 179 | https://github.com/tototoshi/slick-joda-mapper/blob/master/src/test/scala/com/github/tototoshi/slick/JodaSupportSpec.scala 180 | 181 | # Changelog 182 | 183 | ## 2.9.0 184 | 185 | - Fixed slick version for Scala 3 186 | 187 | ## 2.9.0 188 | 189 | - Updated slick to 3.5.0 190 | 191 | ## 2.8.0 192 | 193 | - Updated slick to 3.4.1 194 | 195 | ## 2.7.1 196 | 197 | - Updated joda-time 198 | 199 | ## 2.7.0 200 | 201 | - Added a workaround for a problem related to timezone 202 | 203 | ## 2.6.0 204 | 205 | - Update dependencies 206 | - Add Scala 3 to cross build 207 | 208 | ## 2.5.0 209 | 210 | - Update dependencies 211 | - Migrate from travis-ci to GitHub Actions 212 | - Add explicit type annotations to implicit val 213 | - etc 214 | 215 | ## 2.4.2 216 | 217 | - Update joda-time to 2.10.3 218 | 219 | ## 2.4.1 220 | 221 | - Support Scala 2.13.0 222 | - Support Slick 3.3.1 223 | 224 | ## 2.4.0 225 | 226 | - Support Slick 3.3.0 227 | 228 | ## 2.3.0 229 | 230 | - Support Slick 3.2.0 231 | 232 | ## 2.2.0 233 | 234 | - Use JdbcProfile since JdbcDriver is deprecated. 235 | 236 | ## 2.1.0 237 | 238 | - Removed deprecated Access support 239 | 240 | ## 2.0.0 241 | 242 | - Support Slick 3.0.0. 243 | 244 | ## 1.2.0 245 | 246 | - Support Slick 2.1.0. 247 | 248 | ## 1.1.0 249 | 250 | - Added DateTimeZone support. 251 | 252 | ## 1.0.1 253 | 254 | - Added JdbcJodaSupport. 255 | -------------------------------------------------------------------------------- /src/main/scala/com/github/tototoshi/slick/JodaTypeMapper.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | package com.github.tototoshi.slick 30 | 31 | import slick.jdbc.JdbcProfile 32 | import org.joda.time._ 33 | import com.github.tototoshi.slick.converter._ 34 | import java.sql._ 35 | import java.util.Calendar 36 | import slick.jdbc.{ PositionedResult, PositionedParameters } 37 | 38 | class JodaDateTimeZoneMapper(val driver: JdbcProfile) { 39 | 40 | object TypeMapper extends driver.DriverJdbcType[DateTimeZone] 41 | with JodaDateTimeZoneSqlStringConverter { 42 | def sqlType = java.sql.Types.VARCHAR 43 | override def setValue(v: DateTimeZone, p: PreparedStatement, idx: Int): Unit = 44 | p.setString(idx, toSqlType(v)) 45 | override def getValue(r: ResultSet, idx: Int): DateTimeZone = 46 | fromSqlType(r.getString(idx)) 47 | override def updateValue(v: DateTimeZone, r: ResultSet, idx: Int): Unit = 48 | r.updateString(idx, toSqlType(v)) 49 | override def valueToSQLLiteral(value: DateTimeZone) = toSqlType(value) 50 | } 51 | 52 | object JodaGetResult extends JodaGetResult[String, DateTimeZone] with JodaDateTimeZoneSqlStringConverter { 53 | def next(rs: PositionedResult): String = rs.nextString() 54 | def nextOption(rs: PositionedResult): Option[String] = rs.nextStringOption() 55 | } 56 | 57 | object JodaSetParameter extends JodaSetParameter[String, DateTimeZone] with JodaDateTimeZoneSqlStringConverter { 58 | def set(rs: PositionedParameters, z: String): Unit = rs.setString(z) 59 | def setOption(rs: PositionedParameters, z: Option[String]): Unit = rs.setStringOption(z) 60 | } 61 | 62 | } 63 | 64 | class JodaLocalDateMapper(val driver: JdbcProfile) { 65 | 66 | object TypeMapper extends driver.DriverJdbcType[LocalDate] 67 | with JodaLocalDateSqlDateConverter { 68 | def sqlType = java.sql.Types.DATE 69 | override def setValue(v: LocalDate, p: PreparedStatement, idx: Int): Unit = 70 | p.setDate(idx, toSqlType(v)) 71 | override def getValue(r: ResultSet, idx: Int): LocalDate = 72 | fromSqlType(r.getDate(idx)) 73 | override def updateValue(v: LocalDate, r: ResultSet, idx: Int): Unit = 74 | r.updateDate(idx, toSqlType(v)) 75 | override def valueToSQLLiteral(value: LocalDate) = "{d '" + toSqlType(value).toString + "'}" 76 | } 77 | 78 | object JodaGetResult extends JodaGetResult[java.sql.Date, LocalDate] with JodaLocalDateSqlDateConverter { 79 | def next(rs: PositionedResult): Date = rs.nextDate() 80 | def nextOption(rs: PositionedResult): Option[Date] = rs.nextDateOption() 81 | } 82 | 83 | object JodaSetParameter extends JodaSetParameter[java.sql.Date, LocalDate] with JodaLocalDateSqlDateConverter { 84 | def set(rs: PositionedParameters, d: Date): Unit = rs.setDate(d) 85 | def setOption(rs: PositionedParameters, d: Option[Date]): Unit = rs.setDateOption(d) 86 | } 87 | 88 | } 89 | 90 | class JodaDateTimeMapper(val driver: JdbcProfile, setTimeZone: DateTime => Option[Calendar]) { 91 | 92 | object TypeMapper extends driver.DriverJdbcType[DateTime] 93 | with JodaDateTimeSqlTimestampConverter { 94 | 95 | def sqlType = java.sql.Types.TIMESTAMP 96 | override def sqlTypeName(sym: scala.Option[slick.ast.FieldSymbol]): String = 97 | driver.columnTypes.timestampJdbcType.sqlTypeName(sym) 98 | override def setValue(v: DateTime, p: PreparedStatement, idx: Int): Unit = { 99 | setTimeZone(v) match { 100 | case Some(calendar) => 101 | p.setTimestamp(idx, toSqlType(v), calendar) 102 | case None => 103 | p.setTimestamp(idx, toSqlType(v)) 104 | } 105 | } 106 | override def getValue(r: ResultSet, idx: Int): DateTime = 107 | fromSqlType(r.getTimestamp(idx)) 108 | override def updateValue(v: DateTime, r: ResultSet, idx: Int): Unit = 109 | r.updateTimestamp(idx, toSqlType(v)) 110 | override def valueToSQLLiteral(value: DateTime) = 111 | driver.columnTypes.timestampJdbcType.valueToSQLLiteral(new Timestamp(value.getMillis())) 112 | } 113 | 114 | object JodaGetResult extends JodaGetResult[Timestamp, DateTime] with JodaDateTimeSqlTimestampConverter { 115 | def next(rs: PositionedResult): Timestamp = rs.nextTimestamp() 116 | def nextOption(rs: PositionedResult): Option[Timestamp] = rs.nextTimestampOption() 117 | } 118 | 119 | object JodaSetParameter extends JodaSetParameter[Timestamp, DateTime] with JodaDateTimeSqlTimestampConverter { 120 | def set(rs: PositionedParameters, d: Timestamp): Unit = rs.setTimestamp(d) 121 | def setOption(rs: PositionedParameters, d: Option[Timestamp]): Unit = rs.setTimestampOption(d) 122 | } 123 | 124 | } 125 | 126 | class JodaInstantMapper(val driver: JdbcProfile) { 127 | 128 | object TypeMapper extends driver.DriverJdbcType[Instant] 129 | with JodaInstantSqlTimestampConverter { 130 | def sqlType = java.sql.Types.TIMESTAMP 131 | override def sqlTypeName(sym: scala.Option[slick.ast.FieldSymbol]): String = 132 | driver.columnTypes.timestampJdbcType.sqlTypeName(sym) 133 | override def setValue(v: Instant, p: PreparedStatement, idx: Int): Unit = 134 | p.setTimestamp(idx, toSqlType(v)) 135 | override def getValue(r: ResultSet, idx: Int): Instant = 136 | fromSqlType(r.getTimestamp(idx)) 137 | override def updateValue(v: Instant, r: ResultSet, idx: Int): Unit = 138 | r.updateTimestamp(idx, toSqlType(v)) 139 | override def valueToSQLLiteral(value: Instant) = 140 | driver.columnTypes.timestampJdbcType.valueToSQLLiteral(new Timestamp(value.getMillis())) 141 | } 142 | 143 | object JodaGetResult extends JodaGetResult[Timestamp, Instant] with JodaInstantSqlTimestampConverter { 144 | def next(rs: PositionedResult): Timestamp = rs.nextTimestamp() 145 | def nextOption(rs: PositionedResult): Option[Timestamp] = rs.nextTimestampOption() 146 | } 147 | 148 | object JodaSetParameter extends JodaSetParameter[Timestamp, Instant] with JodaInstantSqlTimestampConverter { 149 | def set(rs: PositionedParameters, d: Timestamp): Unit = rs.setTimestamp(d) 150 | def setOption(rs: PositionedParameters, d: Option[Timestamp]): Unit = rs.setTimestampOption(d) 151 | } 152 | 153 | } 154 | 155 | class JodaLocalDateTimeMapper(val driver: JdbcProfile) { 156 | 157 | object TypeMapper extends driver.DriverJdbcType[LocalDateTime] 158 | with JodaLocalDateTimeSqlTimestampConverter { 159 | def sqlType = java.sql.Types.TIMESTAMP 160 | override def sqlTypeName(sym: scala.Option[slick.ast.FieldSymbol]): String = 161 | driver.columnTypes.timestampJdbcType.sqlTypeName(sym) 162 | override def setValue(v: LocalDateTime, p: PreparedStatement, idx: Int): Unit = 163 | p.setTimestamp(idx, toSqlType(v)) 164 | override def getValue(r: ResultSet, idx: Int): LocalDateTime = 165 | fromSqlType(r.getTimestamp(idx)) 166 | override def updateValue(v: LocalDateTime, r: ResultSet, idx: Int): Unit = 167 | r.updateTimestamp(idx, toSqlType(v)) 168 | override def valueToSQLLiteral(value: LocalDateTime) = 169 | driver.columnTypes.timestampJdbcType.valueToSQLLiteral(new Timestamp(value.toDateTime.getMillis())) 170 | } 171 | 172 | object JodaGetResult extends JodaGetResult[Timestamp, LocalDateTime] with JodaLocalDateTimeSqlTimestampConverter { 173 | def next(rs: PositionedResult): Timestamp = rs.nextTimestamp() 174 | def nextOption(rs: PositionedResult): Option[Timestamp] = rs.nextTimestampOption() 175 | } 176 | 177 | object JodaSetParameter extends JodaSetParameter[Timestamp, LocalDateTime] with JodaLocalDateTimeSqlTimestampConverter { 178 | def set(rs: PositionedParameters, d: Timestamp): Unit = rs.setTimestamp(d) 179 | def setOption(rs: PositionedParameters, d: Option[Timestamp]): Unit = rs.setTimestampOption(d) 180 | } 181 | 182 | } 183 | 184 | class JodaLocalTimeMapper(val driver: JdbcProfile) { 185 | 186 | object TypeMapper extends driver.DriverJdbcType[LocalTime] 187 | with JodaLocalTimeSqlTimeConverter { 188 | def sqlType = java.sql.Types.TIME 189 | override def setValue(v: LocalTime, p: PreparedStatement, idx: Int): Unit = 190 | p.setTime(idx, toSqlType(v)) 191 | override def getValue(r: ResultSet, idx: Int): LocalTime = 192 | fromSqlType(r.getTime(idx)) 193 | override def updateValue(v: LocalTime, r: ResultSet, idx: Int): Unit = 194 | r.updateTime(idx, toSqlType(v)) 195 | override def valueToSQLLiteral(value: LocalTime) = "{t '" + toSqlType(value).toString + "'}" 196 | } 197 | 198 | object JodaGetResult extends JodaGetResult[Time, LocalTime] with JodaLocalTimeSqlTimeConverter { 199 | def next(rs: PositionedResult): Time = rs.nextTime() 200 | def nextOption(rs: PositionedResult): Option[Time] = rs.nextTimeOption() 201 | } 202 | 203 | object JodaSetParameter extends JodaSetParameter[Time, LocalTime] with JodaLocalTimeSqlTimeConverter { 204 | def set(rs: PositionedParameters, d: Time): Unit = rs.setTime(d) 205 | def setOption(rs: PositionedParameters, d: Option[Time]): Unit = rs.setTimeOption(d) 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/test/scala/com/github/tototoshi/slick/JodaSupportSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Toshiyuki Takahashi 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * * Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | package com.github.tototoshi.slick 29 | 30 | import org.scalatest.BeforeAndAfterEach 31 | import org.joda.time._ 32 | 33 | import scala.concurrent.{ Await, Future } 34 | import scala.concurrent.duration._ 35 | import slick.jdbc.{ GetResult, H2Profile, JdbcProfile, MySQLProfile, PostgresProfile } 36 | import java.util.{ Locale, TimeZone } 37 | 38 | import com.dimafeng.testcontainers.{ Container, ForAllTestContainer, JdbcDatabaseContainer, MySQLContainer, PostgreSQLContainer } 39 | import org.testcontainers.utility.DockerImageName 40 | import org.scalatest.funspec.AnyFunSpec 41 | import org.scalatest.matchers.should.Matchers 42 | 43 | abstract class JodaSupportSpec extends AnyFunSpec 44 | with Matchers 45 | with BeforeAndAfterEach { 46 | 47 | val driver: JdbcProfile 48 | val jodaSupport: GenericJodaSupport 49 | def jdbcUrl: String 50 | def jdbcDriver: String 51 | def jdbcUser: String 52 | def jdbcPassword: String 53 | def isWithoutCalender: Boolean 54 | 55 | import driver.api._ 56 | import jodaSupport._ 57 | import slick.sql.SqlProfile.ColumnOption.SqlType 58 | 59 | case class Jodas( 60 | dateTimeZone: DateTimeZone, 61 | localDate: LocalDate, 62 | dateTime: DateTime, 63 | instant: Instant, 64 | localDateTime: LocalDateTime, 65 | localTime: LocalTime, 66 | optDateTimeZone: Option[DateTimeZone], 67 | optLocalDate: Option[LocalDate], 68 | optDateTime: Option[DateTime], 69 | optInstant: Option[Instant], 70 | optLocalDateTime: Option[LocalDateTime], 71 | optLocalTime: Option[LocalTime]) 72 | 73 | class JodaTest(tag: Tag) extends Table[Jodas](tag, "joda_test") { 74 | def dateTimeZone = column[DateTimeZone]("date_time_zone") 75 | def localDate = column[LocalDate]("local_date") 76 | def dateTime = column[DateTime]("date_time") 77 | def instant = column[Instant]("instant", SqlType("timestamp not null default CURRENT_TIMESTAMP")) 78 | def localDateTime = column[LocalDateTime]("local_date_time", SqlType("timestamp not null default CURRENT_TIMESTAMP")) 79 | def localTime = column[LocalTime]("local_time") 80 | def optDateTimeZone = column[Option[DateTimeZone]]("opt_date_time_zone") 81 | def optLocalDate = column[Option[LocalDate]]("opt_local_date") 82 | def optDateTime = column[Option[DateTime]]("opt_date_time") 83 | def optInstant = column[Option[Instant]]("opt_instant") 84 | def optLocalDateTime = column[Option[LocalDateTime]]("opt_local_date_time") 85 | def optLocalTime = column[Option[LocalTime]]("opt_local_time") 86 | def * = (dateTimeZone, localDate, dateTime, instant, localDateTime, localTime, optDateTimeZone, optLocalDate, optDateTime, optInstant, optLocalDateTime, optLocalTime).mapTo[Jodas] 87 | } 88 | 89 | lazy val db = Database.forURL(url = jdbcUrl, user = jdbcUser, password = jdbcPassword, driver = jdbcDriver) 90 | 91 | val jodaTest = TableQuery[JodaTest] 92 | 93 | private[this] val timeout = 10.seconds 94 | 95 | private[this] implicit class FutureOps[A](future: Future[A]) { 96 | def await(): A = Await.result(future, timeout) 97 | } 98 | 99 | override def beforeEach(): Unit = { 100 | super.beforeEach() 101 | Locale.setDefault(Locale.JAPAN) 102 | val tz = TimeZone.getTimeZone("Asia/Tokyo") 103 | TimeZone.setDefault(tz) 104 | DateTimeZone.setDefault(DateTimeZone.forID(tz.getID)) 105 | db.run(DBIO.seq(jodaTest.schema.create)).await() 106 | } 107 | 108 | override def afterEach(): Unit = { 109 | db.run(DBIO.seq(jodaTest.schema.drop)).await() 110 | super.afterEach() 111 | } 112 | 113 | def insertTestData(): DBIOAction[Unit, NoStream, Effect.Write] = { 114 | DBIO.seq( 115 | jodaTest += Jodas( 116 | DateTimeZone.forID("Asia/Tokyo"), 117 | new LocalDate(2012, 12, 4), 118 | new DateTime(2012, 12, 4, 0, 0, 0, 0), 119 | new DateTime(2012, 12, 4, 0, 0, 0, 0).toInstant, 120 | new LocalDateTime(2012, 12, 4, 0, 0, 0, 0), 121 | new LocalTime(0), 122 | Some(DateTimeZone.forID("Asia/Tokyo")), 123 | Some(new LocalDate(2012, 12, 4)), 124 | Some(new DateTime(2012, 12, 4, 0, 0, 0, 0)), 125 | Some(new DateTime(2012, 12, 4, 0, 0, 0, 0).toInstant), 126 | Some(new LocalDateTime(2012, 12, 4, 0, 0, 0, 0)), 127 | Some(new LocalTime(0)) 128 | ), 129 | jodaTest += 130 | Jodas( 131 | DateTimeZone.forID("Europe/London"), 132 | new LocalDate(2012, 12, 5), 133 | new DateTime(2012, 12, 5, 0, 0, 0, 0), 134 | new DateTime(2012, 12, 5, 0, 0, 0, 0).toInstant, 135 | new LocalDateTime(2012, 12, 5, 0, 0, 0, 0), 136 | new LocalTime(0), 137 | Some(DateTimeZone.forID("Europe/London")), 138 | Some(new LocalDate(2012, 12, 5)), 139 | None, 140 | None, 141 | None, 142 | Some(new LocalTime(0)) 143 | ), 144 | jodaTest += 145 | Jodas( 146 | DateTimeZone.forID("America/New_York"), 147 | new LocalDate(2012, 12, 6), 148 | new DateTime(2012, 12, 6, 0, 0, 0, 0), 149 | new DateTime(2012, 12, 6, 0, 0, 0, 0).toInstant, 150 | new LocalDateTime(2012, 12, 6, 0, 0, 0, 0), 151 | new LocalTime(0), 152 | Some(DateTimeZone.forID("America/New_York")), 153 | Some(new LocalDate(2012, 12, 6)), 154 | None, 155 | None, 156 | None, 157 | Some(new LocalTime(0)) 158 | ) 159 | ) 160 | } 161 | 162 | describe("JodaSupport") { 163 | 164 | it("should enable us to use joda-time with slick") { 165 | db.run(insertTestData()).await() 166 | db.run(jodaTest.result).await() should have size 3 167 | } 168 | 169 | it("should enable us to use joda-time with string interpolation API") { 170 | db.run(insertTestData()).await() 171 | db.run(sql"SELECT opt_date_time_zone FROM joda_test WHERE date_time_zone = 'Asia/Tokyo'" 172 | .as[Option[DateTimeZone]].head).await() should be(Some(DateTimeZone.forID("Asia/Tokyo"))) 173 | db.run(sql"SELECT opt_local_date FROM joda_test WHERE local_date = ${new LocalDate(2012, 12, 4)}" 174 | .as[Option[LocalDate]].head).await() should be(Some(new LocalDate(2012, 12, 4))) 175 | db.run(sql"SELECT opt_date_time FROM joda_test WHERE date_time = ${new DateTime(2012, 12, 4, 0, 0, 0, 0)}" 176 | .as[Option[DateTime]].head).await() should be(Some(new DateTime(2012, 12, 4, 0, 0, 0, 0))) 177 | db.run(sql"SELECT opt_instant FROM joda_test WHERE instant = ${new DateTime(2012, 12, 4, 0, 0, 0, 0)}" 178 | .as[Option[Instant]].head).await() should be(Some(new DateTime(2012, 12, 4, 0, 0, 0, 0).toInstant)) 179 | db.run(sql"SELECT opt_local_date_time FROM joda_test WHERE local_date_time = ${new LocalDateTime(2012, 12, 4, 0, 0, 0, 0)}" 180 | .as[Option[LocalDateTime]].head).await() should be(Some(new LocalDateTime(2012, 12, 4, 0, 0, 0, 0))) 181 | db.run(sql"SELECT opt_local_time FROM joda_test WHERE local_time = ${new LocalTime(0)}" 182 | .as[Option[LocalTime]].head).await() should be(Some(new LocalTime(0))) 183 | db.run(sql"SELECT local_date FROM joda_test WHERE opt_local_date = ${Some(new LocalDate(2012, 12, 5))}" 184 | .as[LocalDate].head).await() should be(new LocalDate(2012, 12, 5)) 185 | db.run(sql"SELECT date_time FROM joda_test WHERE opt_date_time = ${Some(new DateTime(2012, 12, 4, 0, 0, 0, 0))}" 186 | .as[DateTime].head).await() should be(new DateTime(2012, 12, 4, 0, 0, 0, 0)) 187 | db.run(sql"SELECT instant FROM joda_test WHERE opt_instant = ${Some(new DateTime(2012, 12, 4, 0, 0, 0, 0).toInstant)}" 188 | .as[Instant].head).await() should be(new DateTime(2012, 12, 4, 0, 0, 0, 0).toInstant) 189 | db.run(sql"SELECT local_date_time FROM joda_test WHERE opt_local_date_time = ${Some(new LocalDateTime(2012, 12, 4, 0, 0, 0, 0))}" 190 | .as[LocalDateTime].head).await() should be(new LocalDateTime(2012, 12, 4, 0, 0, 0, 0)) 191 | db.run(sql"SELECT local_time FROM joda_test WHERE opt_local_time = ${Some(new LocalTime(0))}" 192 | .as[LocalTime].head).await() should be(new LocalTime(0)) 193 | 194 | implicit val getResult: GetResult[(DateTimeZone, LocalDate, DateTime, Instant, LocalDateTime, LocalTime)] = GetResult(r => (r.<<, r.<<, r.<<, r.<<, r.<<, r.<<)) 195 | implicit val getResult2: GetResult[Jodas] = GetResult(r => Jodas(r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<, r.<<)) 196 | 197 | db.run(sql"SELECT date_time_zone, local_date, date_time, instant, local_date_time, local_time FROM joda_test".as[(DateTimeZone, LocalDate, DateTime, Instant, LocalDateTime, LocalTime)]).await() should have size 3 198 | db.run(sql"SELECT date_time_zone, local_date, date_time, instant, local_date_time, local_time, opt_date_time_zone, opt_local_date, opt_date_time, opt_instant, opt_local_date_time, opt_local_time FROM joda_test".as[Jodas]).await() should have size 3 199 | } 200 | } 201 | 202 | it("can be used with comparative operators") { 203 | db.run(insertTestData()).await() 204 | val q1 = jodaTest.filter(_.localDate > new LocalDate(2012, 12, 5)) 205 | db.run(q1.result).await() should have size 1 206 | } 207 | 208 | it("should be able to filter with the specified date") { 209 | db.run(insertTestData()).await() 210 | val q1 = for { 211 | jt <- jodaTest 212 | if jt.localDate === new LocalDate(2012, 12, 5) 213 | } yield jt 214 | 215 | val res1 = db.run(q1.result).await() 216 | res1 should have size 1 217 | res1.headOption.map(_.localDate) should be(Some(new LocalDate(2012, 12, 5))) 218 | 219 | val q2 = for { 220 | jt <- jodaTest 221 | if jt.localDate =!= new LocalDate(2012, 12, 5) 222 | } yield jt 223 | val res2 = db.run(q2.result).await() 224 | res2 should have size 2 225 | res2.lift(1).map(_.localDate) should not be Some(new LocalDate(2012, 12, 5)) 226 | res2.lift(2).map(_.localDate) should not be Some(new LocalDate(2012, 12, 5)) 227 | } 228 | 229 | private val systemTimeZone = java.util.TimeZone.getDefault().getID 230 | Seq("Asia/Tokyo", "UTC", "Europe/London", "America/New_York").foreach { tz => 231 | val timeZone = DateTimeZone.forID(tz) 232 | it(s"should be the same in/out joda-time zoned $tz") { 233 | val otherZonedDateTime = new DateTime(2012, 12, 7, 0, 0, 0, 0, timeZone) 234 | val data = Jodas( 235 | DateTimeZone.forID("Asia/Tokyo"), 236 | new LocalDate(2012, 12, 7), 237 | otherZonedDateTime, 238 | new DateTime(2012, 12, 7, 0, 0, 0, 0, DateTimeZone.UTC).toInstant, 239 | new LocalDateTime(2012, 12, 7, 0, 0, 0, 0), 240 | new LocalTime(1000), 241 | Some(DateTimeZone.forID("America/New_York")), 242 | Some(new LocalDate(2012, 12, 6)), 243 | None, 244 | Some(new Instant(1000)), 245 | None, 246 | Some(new LocalTime(1000)) 247 | ) 248 | db.run(jodaTest += data).await() 249 | 250 | val actual = db.run(jodaTest.result).await() 251 | actual should have size 1 252 | 253 | actual.foreach { d => 254 | if (isWithoutCalender) 255 | d.dateTime.getMillis should be(data.dateTime.getMillis) 256 | else if (systemTimeZone == timeZone.getID) 257 | d.dateTime.getMillis should be(data.dateTime.getMillis) 258 | else 259 | pending 260 | } 261 | } 262 | } 263 | } 264 | 265 | class H2JodaSupportSpec extends JodaSupportSpec { 266 | override val driver: JdbcProfile = H2Profile 267 | override val jodaSupport: GenericJodaSupport = H2JodaSupport 268 | override def jdbcUrl = "jdbc:h2:mem:testh2;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE" 269 | override def jdbcDriver = "org.h2.Driver" 270 | override def jdbcUser = "sa" 271 | override def jdbcPassword: String = null 272 | override val isWithoutCalender = false 273 | } 274 | 275 | abstract class TestContainerSpec extends JodaSupportSpec with ForAllTestContainer { 276 | override def container: JdbcDatabaseContainer with Container 277 | override def jdbcUrl = container.jdbcUrl 278 | override def jdbcUser = container.username 279 | override def jdbcPassword = container.password 280 | } 281 | 282 | object MySQLJodaSupportSpec { 283 | val mySQLDockerImageName = "mysql:5.7.41" 284 | } 285 | 286 | class MySQLJodaSupportSpec extends TestContainerSpec { 287 | override val container: JdbcDatabaseContainer with Container = MySQLContainer( 288 | configurationOverride = "test-mysql-conf", 289 | mysqlImageVersion = DockerImageName.parse(MySQLJodaSupportSpec.mySQLDockerImageName) 290 | ) 291 | override def jdbcDriver = "com.mysql.jdbc.Driver" 292 | override val driver: JdbcProfile = MySQLProfile 293 | override val jodaSupport: GenericJodaSupport = MySQLJodaSupport 294 | override val isWithoutCalender = false 295 | } 296 | 297 | class MySQLJodaSupportWithoutCalenderSpec extends TestContainerSpec { 298 | override val container: JdbcDatabaseContainer with Container = MySQLContainer(mysqlImageVersion = DockerImageName.parse(MySQLJodaSupportSpec.mySQLDockerImageName)) 299 | override def jdbcDriver = "com.mysql.jdbc.Driver" 300 | override val driver: JdbcProfile = MySQLProfile 301 | override val jodaSupport: GenericJodaSupport = new GenericJodaSupport(MySQLProfile, _ => None) 302 | override val isWithoutCalender = true 303 | } 304 | 305 | class PostgresJodaSupportSpec extends TestContainerSpec { 306 | override val container: JdbcDatabaseContainer with Container = PostgreSQLContainer() 307 | override def jdbcDriver = "org.postgresql.Driver" 308 | override val driver: JdbcProfile = PostgresProfile 309 | override val jodaSupport: GenericJodaSupport = PostgresJodaSupport 310 | override val isWithoutCalender = false 311 | } 312 | --------------------------------------------------------------------------------