From 6c5491245c4e7664cca33bf883c938bbdedfba71 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:33:54 +0300 Subject: [PATCH 1/3] Improve the precision of `ClockLive#currentTime` --- core/shared/src/main/scala/zio/Clock.scala | 28 ++++------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/core/shared/src/main/scala/zio/Clock.scala b/core/shared/src/main/scala/zio/Clock.scala index 9560fa2c4338..cd2f1474fe5f 100644 --- a/core/shared/src/main/scala/zio/Clock.scala +++ b/core/shared/src/main/scala/zio/Clock.scala @@ -107,17 +107,8 @@ object Clock extends ClockPlatformSpecific with Serializable { override val unsafe: UnsafeAPI = new UnsafeAPI { - override def currentTime(unit: TimeUnit)(implicit unsafe: Unsafe): Long = { - val inst = instant() - unit match { - case TimeUnit.NANOSECONDS => - inst.getEpochSecond * 1000000000 + inst.getNano - case TimeUnit.MICROSECONDS => - inst.getEpochSecond * 1000000 + inst.getNano / 1000 - case TimeUnit.MILLISECONDS => inst.toEpochMilli - case _ => unit.convert(inst.toEpochMilli, TimeUnit.MILLISECONDS) - } - } + override def currentTime(unit: TimeUnit)(implicit unsafe: Unsafe): Long = + currentTime(unit.toChronoUnit) override def currentTime(unit: ChronoUnit)(implicit unsafe: Unsafe): Long = unit.between(Instant.EPOCH, instant()) @@ -186,19 +177,8 @@ object Clock extends ClockPlatformSpecific with Serializable { override val unsafe: UnsafeAPI = new UnsafeAPI { - override def currentTime(unit: TimeUnit)(implicit unsafe: Unsafe): Long = { - val inst = instant() - // A nicer solution without loss of precision or range would be - // unit.toChronoUnit.between(Instant.EPOCH, inst) - // However, ChronoUnit is not available on all platforms - unit match { - case TimeUnit.NANOSECONDS => - inst.getEpochSecond() * 1000000000 + inst.getNano() - case TimeUnit.MICROSECONDS => - inst.getEpochSecond() * 1000000 + inst.getNano() / 1000 - case _ => unit.convert(inst.toEpochMilli(), TimeUnit.MILLISECONDS) - } - } + override def currentTime(unit: TimeUnit)(implicit unsafe: Unsafe): Long = + currentTime(unit.toChronoUnit) override def currentTime(unit: ChronoUnit)(implicit unsafe: Unsafe): Long = unit.between(Instant.EPOCH, instant()) From ec5d6b1c69f570e57d1903b617f6ddefd484e613 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:22:58 +0300 Subject: [PATCH 2/3] Implement `toChronoUnit` manually --- core/shared/src/main/scala/zio/Clock.scala | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/zio/Clock.scala b/core/shared/src/main/scala/zio/Clock.scala index cd2f1474fe5f..50ad27c75d3d 100644 --- a/core/shared/src/main/scala/zio/Clock.scala +++ b/core/shared/src/main/scala/zio/Clock.scala @@ -22,6 +22,7 @@ import java.lang.{System => JSystem} import java.time.temporal.ChronoUnit import java.time.{Instant, LocalDateTime, OffsetDateTime, ZoneId} import java.util.concurrent.TimeUnit +import scala.annotation.switch trait Clock extends Serializable { self => @@ -108,7 +109,7 @@ object Clock extends ClockPlatformSpecific with Serializable { override val unsafe: UnsafeAPI = new UnsafeAPI { override def currentTime(unit: TimeUnit)(implicit unsafe: Unsafe): Long = - currentTime(unit.toChronoUnit) + currentTime(toChronoUnit(unit)) override def currentTime(unit: ChronoUnit)(implicit unsafe: Unsafe): Long = unit.between(Instant.EPOCH, instant()) @@ -178,7 +179,7 @@ object Clock extends ClockPlatformSpecific with Serializable { override val unsafe: UnsafeAPI = new UnsafeAPI { override def currentTime(unit: TimeUnit)(implicit unsafe: Unsafe): Long = - currentTime(unit.toChronoUnit) + currentTime(toChronoUnit(unit)) override def currentTime(unit: ChronoUnit)(implicit unsafe: Unsafe): Long = unit.between(Instant.EPOCH, instant()) @@ -242,4 +243,14 @@ object Clock extends ClockPlatformSpecific with Serializable { def sleep(duration: => Duration)(implicit trace: Trace): UIO[Unit] = ZIO.clockWith(_.sleep(duration)) + private def toChronoUnit(unit: TimeUnit): ChronoUnit = + (unit: @switch) match { + case TimeUnit.NANOSECONDS => ChronoUnit.NANOS + case TimeUnit.MICROSECONDS => ChronoUnit.MICROS + case TimeUnit.MILLISECONDS => ChronoUnit.MILLIS + case TimeUnit.SECONDS => ChronoUnit.SECONDS + case TimeUnit.MINUTES => ChronoUnit.MINUTES + case TimeUnit.HOURS => ChronoUnit.HOURS + case TimeUnit.DAYS => ChronoUnit.DAYS + } } From b5d7c9e4601a3ca39ce2300baa23a467f8f5f088 Mon Sep 17 00:00:00 2001 From: kyri-petrou <67301607+kyri-petrou@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:47:04 +0300 Subject: [PATCH 3/3] Add platform-specific syntax --- .../scala/zio/ClockSyntaxPlatformSpecific.scala | 17 +++++++++++++++++ .../scala/zio/ClockSyntaxPlatformSpecific.scala | 9 +++++++++ core/shared/src/main/scala/zio/Clock.scala | 13 +------------ 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 core/js-native/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala create mode 100644 core/jvm/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala diff --git a/core/js-native/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala b/core/js-native/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala new file mode 100644 index 000000000000..64ef073aa9d7 --- /dev/null +++ b/core/js-native/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala @@ -0,0 +1,17 @@ +package zio + +import java.time.temporal.ChronoUnit +import java.util.concurrent.TimeUnit + +private[zio] trait ClockSyntaxPlatformSpecific { + final protected def toChronoUnit(unit: TimeUnit): ChronoUnit = + unit match { + case TimeUnit.NANOSECONDS => ChronoUnit.NANOS + case TimeUnit.MICROSECONDS => ChronoUnit.MICROS + case TimeUnit.MILLISECONDS => ChronoUnit.MILLIS + case TimeUnit.SECONDS => ChronoUnit.SECONDS + case TimeUnit.MINUTES => ChronoUnit.MINUTES + case TimeUnit.HOURS => ChronoUnit.HOURS + case TimeUnit.DAYS => ChronoUnit.DAYS + } +} diff --git a/core/jvm/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala b/core/jvm/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala new file mode 100644 index 000000000000..8ae781fc92c4 --- /dev/null +++ b/core/jvm/src/main/scala/zio/ClockSyntaxPlatformSpecific.scala @@ -0,0 +1,9 @@ +package zio + +import java.time.temporal.ChronoUnit +import java.util.concurrent.TimeUnit + +private[zio] trait ClockSyntaxPlatformSpecific { + @inline final protected def toChronoUnit(unit: TimeUnit): ChronoUnit = + unit.toChronoUnit +} diff --git a/core/shared/src/main/scala/zio/Clock.scala b/core/shared/src/main/scala/zio/Clock.scala index 50ad27c75d3d..5cf39beb6ac7 100644 --- a/core/shared/src/main/scala/zio/Clock.scala +++ b/core/shared/src/main/scala/zio/Clock.scala @@ -22,7 +22,6 @@ import java.lang.{System => JSystem} import java.time.temporal.ChronoUnit import java.time.{Instant, LocalDateTime, OffsetDateTime, ZoneId} import java.util.concurrent.TimeUnit -import scala.annotation.switch trait Clock extends Serializable { self => @@ -77,7 +76,7 @@ trait Clock extends Serializable { self => } } -object Clock extends ClockPlatformSpecific with Serializable { +object Clock extends ClockPlatformSpecific with ClockSyntaxPlatformSpecific with Serializable { val tag: Tag[Clock] = Tag[Clock] @@ -243,14 +242,4 @@ object Clock extends ClockPlatformSpecific with Serializable { def sleep(duration: => Duration)(implicit trace: Trace): UIO[Unit] = ZIO.clockWith(_.sleep(duration)) - private def toChronoUnit(unit: TimeUnit): ChronoUnit = - (unit: @switch) match { - case TimeUnit.NANOSECONDS => ChronoUnit.NANOS - case TimeUnit.MICROSECONDS => ChronoUnit.MICROS - case TimeUnit.MILLISECONDS => ChronoUnit.MILLIS - case TimeUnit.SECONDS => ChronoUnit.SECONDS - case TimeUnit.MINUTES => ChronoUnit.MINUTES - case TimeUnit.HOURS => ChronoUnit.HOURS - case TimeUnit.DAYS => ChronoUnit.DAYS - } }