-
Couldn't load subscription status.
- Fork 1.4k
microsecond precision for Clock on JVM #4554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
I'll need to fix the test to only run on JDK > 8. |
|
@runtologist Looks like there is a compilation error on Dotty. I think merging upstream changes should fix. |
| // This test should only run on JRE >= 9, which is when microsecond precision was introduced. | ||
| // Versions of JREs < 9 started with s"1.${majorVersion}", then with JEP 223 they switched to semantic versioning. | ||
| assert(jreVersion)(isSome(startsWithString("1."))) || assert((b - a) % 1000)(not(equalTo(0L))) | ||
| }.provideLayer(Clock.live ++ System.live) @@ TestAspect.flaky |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use TestAspect.ifProp to clean this up slightly. Also if you use Live.live(clock.currentTime(unit) you can avoid having to provide any layers. Why is this test flaky?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TestAspect.ifProp is great, thanks. One of these small tools you rarely need, but that make life easier when you do.
This test tests the live clock. I think that is more obvious when providing a Layer once than when wrapping in Live.live everywhere clock is used. Do you prefer wrapping as more idiomatic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is flaky, because we might actually have measured exactly one millisecond. In that case we can simply retry.
|
|
||
| def currentTime(unit: TimeUnit): UIO[Long] | ||
|
|
||
| // Could be UIO. We keep IO to preserve binary compatibility. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this really be UIO? I know we changed this at what point because we observed that it really did throw DateTimeException when the system time zone wasn't in the list that was supported by the java-time implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous implementation used ZoneId.systemDefault in currentDateTime, which may throw a DateTimeException. The new implementation uses Instant.now, which does not throw according to the docs.
|
|
||
| // This could be a UIO. We keep IO to preserve binary compatibility. | ||
| // The implementation is only here to preserve binary compatibility. | ||
| def localDateTime: IO[DateTimeException, java.time.LocalDateTime] = currentDateTime.map(_.toLocalDateTime()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous implementation was based on currentDateTime, which could a DateTimeException. The new implementation directly uses LocalDateTime.now, which does not throw according to the docs.
| case TimeUnit.NANOSECONDS => | ||
| val micros = inst.toEpochMilli() * 1000000 + inst.getNano() | ||
| unit.convert(micros, TimeUnit.NANOSECONDS) | ||
| case TimeUnit.NANOSECONDS | TimeUnit.MICROSECONDS => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand this line. If we matched on NANOSECONDS above shouldn't we just be matching on MICROSECONDS here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. That's a leftover. Initially I only had the second case, but the decided to add the first one in order to keep the loss in range and precision as low as possible.
| def instant: UIO[java.time.Instant] = currentTime(TimeUnit.MILLISECONDS).map(java.time.Instant.ofEpochMilli(_)) | ||
|
|
||
| // The implementation is only here to preserve binary compatibility. | ||
| def instant: UIO[java.time.Instant] = currentTime(TimeUnit.MILLISECONDS).map(java.time.Instant.ofEpochMilli(_)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe keep these abstract and provide helper methods for default implementations like we do in other services? I think providing default implementations potentially results in counterintuitive behavior with like mock implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also prefer to do that, but it breaks binary compatibility. If we decide that's OK here, I'll remove the default implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Couple of specific comments but great idea!
Yes, I was waiting for #4551 |
|
@adamgfraser I think all review comments have been addressed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! We can update the type signatures and remove the default implementation when we move to 2.0.
/fixes #4550