Skip to content

Conversation

lauzadis
Copy link
Contributor

Issue #

Description of changes

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@lauzadis lauzadis changed the title Initial commit / tests are passing kn: Instant Dec 20, 2024
@lauzadis lauzadis marked this pull request as ready for review December 20, 2024 21:32
@lauzadis lauzadis requested a review from a team as a code owner December 20, 2024 21:32
@lauzadis lauzadis changed the title kn: Instant kn: Implement Instant using kotlinx.datetime.Instant Dec 20, 2024
Comment on lines -12 to +15
* ISO-8601/RFC5399 timestamp including fractional seconds at microsecond precision (e.g.,
* ISO-8601/RFC3339 timestamp including fractional seconds at microsecond precision (e.g.,
* "2022-04-25T16:44:13.667307Z")
*
* Prefers RFC5399 when formatting
* Prefers RFC3339 when formatting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: Oh jeez, good catch. 🤦‍♂️

Comment on lines 70 to 71
// Handle leap seconds (23:59:59)
parsed.second = parsed.second?.coerceAtMost(59)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: This looks like it's discarding leap seconds not handling them. 60 is a valid leap second.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost certainly, yes. Treating x:y:60 and x:y:59 as the same instant is incorrect. The problem gets worse when subsecond precision is considered and timestamps increment like :59.998, :59.999, :59.000, :59.001.

We should verify with other SDK implementations but I believe our existing test (and therefore likely our existing JVM implementation) is flawed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We spoke offline and agreed to match Java's Instant behavior here, which effectively truncates the leap second 23:59:60 to 23:59:59.

Comment on lines 73 to 79
// Handle leap hours (24:00:00)
return if (parsed.hour == 24) {
parsed.hour = 0
Instant(parsed.toInstantUsingOffset() + 1.days)
} else {
Instant(parsed.toInstantUsingOffset())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: What's a leap hour?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I don't think that's a thing with RFC 3339:

From § 5.6:

   date-month      = 2DIGIT  ; 01-12
   date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                             ; month/year
   time-hour       = 2DIGIT  ; 00-23
   time-minute     = 2DIGIT  ; 00-59
   time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second
                             ; rules```

Note that time-hour ranges from 0 to 23, not 24.

From § 5.7:

Although ISO 8601 permits the hour to be "24", this profile of ISO 8601 only allows values between "00" and "23" for the hour in order to reduce confusion.

Comment on lines 116 to 121
override fun hashCode(): Int {
var result = delegate.hashCode()
result = 31 * result + epochSeconds.hashCode()
result = 31 * result + nanosecondsOfSecond
return result
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: epochSeconds and nanosecondsOfSecond are derived from delegate. They shouldn't be part of the hash code calculation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Because of some of the refactoring, it's a bit tricky for me to see what's functionally changed in these tests. Besides the addition of .fromEpochSeconds(${test.sec}, ${test.ns}) to an assert message, what's changed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing else changed, it's just restructured

.format(format)
val expected = getter(test)
assertEquals(expected, actual, "test[$idx]: failed to correctly format Instant as $format")
assertEquals(expected, actual, "test[$idx]: failed to correctly format Instant.fromEpochSeconds(${test.sec}, ${test.ns}) as $format")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Stray space in message (") as")

Comment on lines 23 to 21
private fun KtInstant.truncateToMicros(): KtInstant = KtInstant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond / 1_000 * 1_000)
private fun KtInstant.truncateToMicros(): KtInstant = KtInstant.fromEpochSeconds(epochSeconds, nanosecondsOfSecond / 1000 * 1000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why did this change? I don't particularly need separators in relatively small numbers like this but, to me, they're harmless in cases like this and very helpful in cases of larger numbers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason besides change of personal preference in this case, I'll add it back

Comment on lines 116 to 121
override fun hashCode(): Int {
var result = delegate.hashCode()
result = 31 * result + epochSeconds.hashCode()
result = 31 * result + nanosecondsOfSecond
return result
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctness: This removes too much from the hash calculation. We don't need the entropy from epochSeconds or nanosecondsOfSecond since they're derived values but we do need the entropy from delegate itself. Otherwise, instances will use the default hashcode implementation which is based on instance memory location and will diverge even for objects which are structurally equal.

@lauzadis lauzadis added the no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly. label Jan 8, 2025

This comment has been minimized.

1 similar comment
Copy link

github-actions bot commented Jan 8, 2025

Affected Artifacts

Changed in size
Artifact Pull Request (bytes) Latest Release (bytes) Delta (bytes) Delta (percentage)
http-test-jvm.jar 61,633 58,718 2,915 4.96%
crt-util-jvm.jar 21,443 20,954 489 2.33%
runtime-core-jvm.jar 813,449 812,469 980 0.12%
aws-signing-tests-jvm.jar 456,614 456,568 46 0.01%
test-suite-jvm.jar 96,907 97,180 -273 -0.28%

@lauzadis lauzadis merged commit 43e39b8 into kn-main Jan 8, 2025
21 of 22 checks passed
@lauzadis lauzadis deleted the kn-time branch January 30, 2025 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-changelog Indicates that a changelog entry isn't required for a pull request. Use sparingly.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants