diff --git a/jdbc/src/main/scala/zio/sql/jdbc.scala b/jdbc/src/main/scala/zio/sql/jdbc.scala index 73be39c16..71f902015 100644 --- a/jdbc/src/main/scala/zio/sql/jdbc.scala +++ b/jdbc/src/main/scala/zio/sql/jdbc.scala @@ -1,9 +1,8 @@ package zio.sql import java.sql._ - import java.io.IOException - +import java.time.{ ZoneId, ZoneOffset } import zio.{ Chunk, Has, IO, Managed, ZIO, ZLayer, ZManaged } import zio.blocking.Blocking import zio.stream.{ Stream, ZStream } @@ -193,7 +192,14 @@ trait Jdbc extends zio.sql.Sql { tryDecode[java.util.UUID]( java.util.UUID.fromString(column.fold(resultSet.getString(_), resultSet.getString(_))) ) - case TZonedDateTime => ??? + case TZonedDateTime => + tryDecode[java.time.ZonedDateTime]( + java.time.ZonedDateTime + .ofInstant( + column.fold(resultSet.getTimestamp(_), resultSet.getTimestamp(_)).toInstant, + ZoneId.of(ZoneOffset.UTC.getId) + ) + ) case TDialectSpecific(_) => ??? case t @ Nullable() => extractColumn(column, resultSet, t.typeTag, false).map(Option(_)) } diff --git a/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala b/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala index f17311a80..86ab98620 100644 --- a/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala +++ b/postgres/src/main/scala/zio/sql/postgresql/PostgresModule.scala @@ -1,7 +1,6 @@ package zio.sql.postgresql -import java.time.LocalDate - +import java.time.{ LocalDate, ZonedDateTime } import zio.sql.Jdbc /** @@ -32,6 +31,7 @@ trait PostgresModule extends Jdbc { self => val Degrees = FunctionDef[Double, Double](FunctionName("degrees")) val Div = FunctionDef[(Double, Double), Double](FunctionName("div")) val Factorial = FunctionDef[Int, Int](FunctionName("factorial")) + val ToTimestamp = FunctionDef[Long, ZonedDateTime](FunctionName("to_timestamp")) } override def renderRead(read: self.Read[_]): String = { diff --git a/postgres/src/test/resources/shop_schema.sql b/postgres/src/test/resources/shop_schema.sql index 4d439d58f..6c1064e3e 100644 --- a/postgres/src/test/resources/shop_schema.sql +++ b/postgres/src/test/resources/shop_schema.sql @@ -4,7 +4,9 @@ create table customers first_name varchar not null, last_name varchar not null, verified boolean not null, - dob date not null + dob date not null, + created_timestamp_string varchar not null, + created_timestamp timestamp with time zone default now() ); create table orders @@ -37,15 +39,14 @@ create table order_details unit_price money not null ); - insert into customers - (id, first_name, last_name, verified, dob) + (id, first_name, last_name, verified, dob, created_timestamp_string, created_timestamp) values - ('60b01fc9-c902-4468-8d49-3c0f989def37', 'Ronald', 'Russell', true, '1983-01-05'), - ('f76c9ace-be07-4bf3-bd4c-4a9c62882e64', 'Terrence', 'Noel', true, '1999-11-02'), - ('784426a5-b90a-4759-afbb-571b7a0ba35e', 'Mila', 'Paterso', true, '1990-11-16'), - ('df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', 'Alana', 'Murray', true, '1995-11-12'), - ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23'); + ('60b01fc9-c902-4468-8d49-3c0f989def37', 'Ronald', 'Russell', true, '1983-01-05', '2020-11-21T19:10:25+00:00', '2020-11-21 19:10:25+00'), + ('f76c9ace-be07-4bf3-bd4c-4a9c62882e64', 'Terrence', 'Noel', true, '1999-11-02', '2020-11-21T15:10:25-04:00', '2020-11-21 15:10:25-04'), + ('784426a5-b90a-4759-afbb-571b7a0ba35e', 'Mila', 'Paterso', true, '1990-11-16', '2020-11-22T02:10:25+07:00', '2020-11-22 02:10:25+07'), + ('df8215a2-d5fd-4c6c-9984-801a1b3a2a0b', 'Alana', 'Murray', true, '1995-11-12', '2020-11-21T12:10:25-07:00', '2020-11-21 12:10:25-07'), + ('636ae137-5b1a-4c8c-b11f-c47c624d9cdc', 'Jose', 'Wiggins', false, '1987-03-23', '2020-11-21T19:10:25+00:00', '2020-11-21 19:10:25+00'); insert into products (id, name, description, image_url) diff --git a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala index 4af8c9f4a..fcbf84bff 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/FunctionDefSpec.scala @@ -1,8 +1,7 @@ package zio.sql.postgresql -import java.time.LocalDate +import java.time.{ LocalDate, ZoneId, ZoneOffset, ZonedDateTime } import java.util.UUID - import zio.Cause import zio.random.Random import zio.test.Assertion._ @@ -687,6 +686,33 @@ object FunctionDefSpec extends PostgresRunnableSpec with ShopSchema { assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) }, + testM("to_timestamp") { + val query = select(ToTimestamp(1284352323L)) from customers + val expected = ZonedDateTime.of(2010, 9, 13, 4, 32, 3, 0, ZoneId.of(ZoneOffset.UTC.getId)) + val testResult = execute(query).to[ZonedDateTime, ZonedDateTime](identity) + + val expectedRoundTripTimestamp = ZonedDateTime.of(2020, 11, 21, 19, 10, 25, 0, ZoneId.of(ZoneOffset.UTC.getId)) + val roundTripQuery = + select(createdString ++ createdTimestamp) from customers + val roundTripResults = execute(roundTripQuery).to[String, ZonedDateTime, (String, ZonedDateTime, ZonedDateTime)] { + case row => + (row._1, ZonedDateTime.parse(row._1), row._2) + } + val roundTripExpected = List( + ("2020-11-21T19:10:25+00:00", ZonedDateTime.parse("2020-11-21T19:10:25+00:00"), expectedRoundTripTimestamp), + ("2020-11-21T15:10:25-04:00", ZonedDateTime.parse("2020-11-21T15:10:25-04:00"), expectedRoundTripTimestamp), + ("2020-11-22T02:10:25+07:00", ZonedDateTime.parse("2020-11-22T02:10:25+07:00"), expectedRoundTripTimestamp), + ("2020-11-21T12:10:25-07:00", ZonedDateTime.parse("2020-11-21T12:10:25-07:00"), expectedRoundTripTimestamp) + ) + + val assertion = for { + single <- testResult.runCollect + roundTrip <- roundTripResults.runCollect + } yield assert(single.head)(equalTo(expected)) && + assert(roundTrip)(hasSameElementsDistinct(roundTripExpected)) + + assertion.mapErrorCause(cause => Cause.stackless(cause.untraced)) + }, testM("replace") { val lastNameReplaced = Replace(lName, "'ll'", "'_'") as "lastNameReplaced" val computedReplace = Replace("'special ::ąę::'", "'ąę'", "'__'") as "computedReplace" diff --git a/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala b/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala index 1fb348f4c..54c4b4ddd 100644 --- a/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala +++ b/postgres/src/test/scala/zio/sql/postgresql/ShopSchema.scala @@ -6,11 +6,15 @@ trait ShopSchema extends Jdbc { self => import self.ColumnSet._ object Customers { + //https://github.com/zio/zio-sql/issues/320 Once Insert is supported, we can remove created_timestamp_string val customers = - (uuid("id") ++ localDate("dob") ++ string("first_name") ++ string("last_name") ++ boolean("verified")) + (uuid("id") ++ localDate("dob") ++ string("first_name") ++ string("last_name") ++ boolean( + "verified" + ) ++ string("created_timestamp_string") ++ zonedDateTime("created_timestamp")) .table("customers") - val customerId :*: dob :*: fName :*: lName :*: verified :*: _ = customers.columns + val customerId :*: dob :*: fName :*: lName :*: verified :*: createdString :*: createdTimestamp :*: _ = + customers.columns } object Orders { val orders = (uuid("id") ++ uuid("customer_id") ++ localDate("order_date")).table("orders")