From 42f3553135d201699ca4f32fcc58d2424470f6be Mon Sep 17 00:00:00 2001 From: Alan Devlin Date: Sun, 7 Jul 2019 04:11:14 +0100 Subject: [PATCH] Fixed #1012 - added zoneId to Clock (#1116) * Fixed #1012 - added zoneId to Clock * Fixes #1012 change zoneId for OffsetDateTime * #1012 respond to comments and abandon zoneId due to scalajs unavailability * #1012 minor formatting * need to retrigger build due to flaky test * revert change to retrigger build but no changes since last push --- core/shared/src/main/scala-2.12+/clock.scala | 8 ++++ .../src/main/scala/zio/clock/Clock.scala | 9 +++++ .../main/scala/zio/testkit/TestClock.scala | 24 ++++++++++-- .../test/scala/zio/testkit/ClockSpec.scala | 38 +++++++++++++++---- 4 files changed, 69 insertions(+), 10 deletions(-) diff --git a/core/shared/src/main/scala-2.12+/clock.scala b/core/shared/src/main/scala-2.12+/clock.scala index 9d31fa0745ee..f14d745eaef0 100644 --- a/core/shared/src/main/scala-2.12+/clock.scala +++ b/core/shared/src/main/scala-2.12+/clock.scala @@ -19,6 +19,7 @@ package zio import zio.duration.Duration import java.util.concurrent.TimeUnit +import java.time.OffsetDateTime package object clock extends Clock.Service[Clock] { final val clockService: ZIO[Clock, Nothing, Clock.Service[Any]] = @@ -30,6 +31,12 @@ package object clock extends Clock.Service[Clock] { final def currentTime(unit: TimeUnit): ZIO[Clock, Nothing, Long] = ZIO.accessM(_.clock currentTime unit) + /** + * Get the current time, represented in the current timezone. + */ + def currentDateTime: ZIO[Clock, Nothing, OffsetDateTime] = + ZIO.accessM(_.clock.currentDateTime) + /** * Returns the system nano time, which is not relative to any date. */ @@ -41,4 +48,5 @@ package object clock extends Clock.Service[Clock] { */ final def sleep(duration: Duration): ZIO[Clock, Nothing, Unit] = ZIO.accessM(_.clock sleep duration) + } diff --git a/core/shared/src/main/scala/zio/clock/Clock.scala b/core/shared/src/main/scala/zio/clock/Clock.scala index a493a97ae1e8..342a350f55d3 100644 --- a/core/shared/src/main/scala/zio/clock/Clock.scala +++ b/core/shared/src/main/scala/zio/clock/Clock.scala @@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit import zio.duration.Duration import zio.scheduler.SchedulerLive import zio.{ IO, UIO, ZIO } +import java.time.{ Instant, OffsetDateTime, ZoneId } trait Clock extends Serializable { val clock: Clock.Service[Any] @@ -29,6 +30,7 @@ trait Clock extends Serializable { object Clock extends Serializable { trait Service[R] extends Serializable { def currentTime(unit: TimeUnit): ZIO[R, Nothing, Long] + def currentDateTime: ZIO[R, Nothing, OffsetDateTime] val nanoTime: ZIO[R, Nothing, Long] def sleep(duration: Duration): ZIO[R, Nothing, Unit] } @@ -50,6 +52,13 @@ object Clock extends Serializable { Left(ZIO.effectTotal(canceler())) } ) + + def currentDateTime: ZIO[Any, Nothing, OffsetDateTime] = + for { + millis <- currentTime(TimeUnit.MILLISECONDS) + zone <- ZIO.effectTotal(ZoneId.systemDefault) + } yield OffsetDateTime.ofInstant(Instant.ofEpochMilli(millis), zone) + } } object Live extends Live diff --git a/testkit/jvm/src/main/scala/zio/testkit/TestClock.scala b/testkit/jvm/src/main/scala/zio/testkit/TestClock.scala index 45566d3a7273..3eef55eb1297 100644 --- a/testkit/jvm/src/main/scala/zio/testkit/TestClock.scala +++ b/testkit/jvm/src/main/scala/zio/testkit/TestClock.scala @@ -17,17 +17,22 @@ package zio.testkit import java.util.concurrent.TimeUnit +import java.time.ZoneId import zio._ import zio.duration.Duration import zio.clock.Clock import zio.testkit.TestClock.Data +import java.time.{ Instant, OffsetDateTime } case class TestClock(ref: Ref[TestClock.Data]) extends Clock.Service[Any] { final def currentTime(unit: TimeUnit): UIO[Long] = ref.get.map(data => unit.convert(data.currentTimeMillis, TimeUnit.MILLISECONDS)) + final def currentDateTime: UIO[OffsetDateTime] = + ref.get.map(data => TestClock.offset(data.currentTimeMillis, data.timeZone)) + final val nanoTime: IO[Nothing, Long] = ref.get.map(_.nanoTime) @@ -38,13 +43,26 @@ case class TestClock(ref: Ref[TestClock.Data]) extends Clock.Service[Any] { final def adjust(duration: Duration): UIO[Unit] = ref.update { data => - Data(data.nanoTime + duration.toNanos, data.currentTimeMillis + duration.toMillis, data.sleeps0) + Data( + data.nanoTime + duration.toNanos, + data.currentTimeMillis + duration.toMillis, + data.sleeps0, + data.timeZone + ) }.unit } object TestClock { - val Zero = Data(0, 0, Nil) + val Zero = Data(0, 0, Nil, ZoneId.of("UTC")) + + def offset(millis: Long, timeZone: ZoneId): OffsetDateTime = + OffsetDateTime.ofInstant(Instant.ofEpochMilli(millis), timeZone) - case class Data(nanoTime: Long, currentTimeMillis: Long, sleeps0: List[Duration]) + case class Data( + nanoTime: Long, + currentTimeMillis: Long, + sleeps0: List[Duration], + timeZone: ZoneId + ) } diff --git a/testkit/jvm/src/test/scala/zio/testkit/ClockSpec.scala b/testkit/jvm/src/test/scala/zio/testkit/ClockSpec.scala index 1fbcc12f72a4..7b923d31902c 100644 --- a/testkit/jvm/src/test/scala/zio/testkit/ClockSpec.scala +++ b/testkit/jvm/src/test/scala/zio/testkit/ClockSpec.scala @@ -11,10 +11,12 @@ class ClockSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRun Sleep does sleep instantly $e1 Sleep passes nanotime correctly $e2 Sleep passes currentTime correctly $e3 - Sleep correctly records sleeps $e4 - Adjust correctly advances nanotime $e5 - Adjust correctly advances currentTime $e6 - Adjust does not produce sleeps $e7 + Sleep passes currentDateTime correctly $e4 + Sleep correctly records sleeps $e5 + Adjust correctly advances nanotime $e6 + Adjust correctly advances currentTime $e7 + Adjust correctly advances currentDateTime $e8 + Adjust does not produce sleeps $e9 """ def e1 = @@ -49,6 +51,17 @@ class ClockSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRun ) def e4 = + unsafeRun( + for { + ref <- Ref.make(TestClock.Zero) + testClock = TestClock(ref) + time1 <- testClock.currentDateTime + _ <- testClock.sleep(1.millis) + time2 <- testClock.currentDateTime + } yield (time2.toInstant.toEpochMilli - time1.toInstant.toEpochMilli) must_== 1L + ) + + def e5 = unsafeRun( for { ref <- Ref.make(TestClock.Zero) @@ -58,7 +71,7 @@ class ClockSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRun } yield sleeps must_== List(1.milliseconds) ) - def e5 = + def e6 = unsafeRun( for { ref <- Ref.make(TestClock.Zero) @@ -69,7 +82,7 @@ class ClockSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRun } yield (time2 - time1) must_== 1000000L ) - def e6 = + def e7 = unsafeRun( for { ref <- Ref.make(TestClock.Zero) @@ -80,7 +93,18 @@ class ClockSpec(implicit ee: org.specs2.concurrent.ExecutionEnv) extends TestRun } yield (time2 - time1) must_== 1L ) - def e7 = + def e8 = + unsafeRun( + for { + ref <- Ref.make(TestClock.Zero) + testClock = TestClock(ref) + time1 <- testClock.currentDateTime + _ <- testClock.adjust(1.millis) + time2 <- testClock.currentDateTime + } yield (time2.toInstant.toEpochMilli - time1.toInstant.toEpochMilli) must_== 1L + ) + + def e9 = unsafeRun( for { ref <- Ref.make(TestClock.Zero)