Skip to content

Commit

Permalink
kn: Implement Instant using kotlinx.datetime.Instant (#1201)
Browse files Browse the repository at this point in the history
  • Loading branch information
lauzadis authored Jan 8, 2025
1 parent f5c5af0 commit 43e39b8
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 112 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ slf4j-version = "2.0.16"
slf4j-v1x-version = "1.7.36"
crt-kotlin-version = "0.8.10"
micrometer-version = "1.13.6"
kotlinx-datetime-version = "0.6.1"

# codegen
smithy-version = "1.51.0"
Expand Down Expand Up @@ -61,6 +62,7 @@ slf4j-api-v1x = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-v1x-versi
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j-version" }
crt-kotlin = { module = "aws.sdk.kotlin.crt:aws-crt-kotlin", version.ref = "crt-kotlin-version" }
micrometer-core = { module = "io.micrometer:micrometer-core", version.ref = "micrometer-version" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime-version" }

smithy-codegen-core = { module = "software.amazon.smithy:smithy-codegen-core", version.ref = "smithy-version" }
smithy-cli = { module = "software.amazon.smithy:smithy-cli", version.ref = "smithy-version" }
Expand Down
1 change: 1 addition & 0 deletions runtime/runtime-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ kotlin {
nativeMain {
dependencies {
api(libs.crt.kotlin)
implementation(libs.kotlinx.datetime)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ package aws.smithy.kotlin.runtime.time
*/
public enum class TimestampFormat {
/**
* 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
*/
ISO_8601,

Expand All @@ -28,7 +28,7 @@ public enum class TimestampFormat {
ISO_8601_CONDENSED_DATE,

/**
* ISO-8601/RFC5399 timestamp including fractional seconds at arbitrary (i.e., untruncated) precision
* ISO-8601/RFC3339 timestamp including fractional seconds at arbitrary (i.e., untruncated) precision
*/
ISO_8601_FULL,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/
package aws.smithy.kotlin.runtime.time

import aws.smithy.kotlin.runtime.IgnoreNative
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
Expand All @@ -18,7 +17,6 @@ import kotlin.time.Duration.Companion.seconds
// tests for conversion from a parsed representation into an Instant instance

class InstantTest {

/**
* Conversion from a string to epoch sec/ns
*/
Expand Down Expand Up @@ -58,11 +56,8 @@ class InstantTest {

// leap second - dropped to: 2020-12-31T23:59:59
FromTest("2020-12-31T23:59:60Z", 1609459199, 0),
// midnight - should be 11/5 12AM
FromTest("2020-11-04T24:00:00Z", 1604534400, 0),
)

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testFromIso8601() {
for ((idx, test) in iso8601Tests.withIndex()) {
Expand Down Expand Up @@ -101,7 +96,6 @@ class InstantTest {
TimestampFormat.ISO_8601_CONDENSED_DATE to Iso8601FmtTest::expectedIso8601CondDate,
)

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testFormatAsIso8601() {
for ((idx, test) in iso8601FmtTests.withIndex()) {
Expand All @@ -110,7 +104,7 @@ class InstantTest {
.fromEpochSeconds(test.sec, test.ns)
.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")
}
}
}
Expand All @@ -125,7 +119,6 @@ class InstantTest {
FromTest("Thu, 05 Nov 2020 19:22:37 -1245", 1604650057, 0),
)

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testFromRfc5322() {
for ((idx, test) in rfc5322Tests.withIndex()) {
Expand All @@ -143,7 +136,6 @@ class InstantTest {
FmtTest(1604650057, 0, "Fri, 06 Nov 2020 08:07:37 GMT"),
)

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testFormatAsRfc5322() {
for ((idx, test) in rfc5322FmtTests.withIndex()) {
Expand All @@ -162,7 +154,6 @@ class InstantTest {
FmtTest(1604604157, 345_006_000, "1604604157.345006"),
)

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testFormatAsEpochSeconds() {
for ((idx, test) in epochFmtTests.withIndex()) {
Expand All @@ -173,7 +164,6 @@ class InstantTest {
}
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testToEpochDouble() {
val sec = 1604604157L
Expand All @@ -184,7 +174,6 @@ class InstantTest {
assertTrue(kotlin.math.abs(0.012345 - fracSecs) < 0.00001)
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testGetCurrentTime() {
val currentTime = Instant.now()
Expand All @@ -194,7 +183,6 @@ class InstantTest {
assertTrue(currentTime.epochSeconds > pastInstant)
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testGetEpochMilliseconds() {
val instant = Instant.fromEpochSeconds(1602878160, 200_000)
Expand All @@ -206,7 +194,6 @@ class InstantTest {
assertEquals(expected2, instantWithMilli.epochMilliseconds)
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testFromEpochMilliseconds() {
val ts1 = 1602878160000L
Expand All @@ -218,54 +205,34 @@ class InstantTest {
assertEquals(expected2, Instant.fromEpochMilliseconds(ts2))
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testNegativeFromEpochSeconds() {
val timestamp = Instant.fromEpochSeconds(-806976000L)
assertEquals("1944-06-06T00:00:00Z", timestamp.toString())
}

// Select tests pulled from edge cases/tickets in the V2 Java SDK.
// Always good to learn from others...
class V2JavaSdkTests {
@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun v2JavaSdkTt0031561767() {
val input = "Fri, 16 May 2014 23:56:46 GMT"
val instant: Instant = Instant.fromRfc5322(input)
assertEquals(input, instant.format(TimestampFormat.RFC_5322))
}
@Test
fun testUntil() {
val untilTests = mapOf(
("2013-01-01T00:00:00+00:00" to "2014-01-01T00:00:00+00:00") to 365.days,
("2020-01-01T00:00:00+00:00" to "2021-01-01T00:00:00+00:00") to 366.days, // leap year!
("2023-10-06T00:00:00+00:00" to "2023-10-06T00:00:00+00:00") to Duration.ZERO,
("2023-10-06T00:00:00+00:00" to "2023-10-07T00:00:00+00:00") to 1.days,
("2023-10-06T00:00:00+00:00" to "2023-10-06T01:00:00+00:00") to 1.hours,
("2023-10-06T00:00:00+00:00" to "2023-10-06T00:01:00+00:00") to 1.minutes,
("2023-10-06T00:00:00+00:00" to "2023-10-06T00:00:01+00:00") to 1.seconds,
("2023-10-06T00:00:00+00:00" to "2023-10-06T12:12:12+00:00") to 12.hours + 12.minutes + 12.seconds,
)

/**
* Tests the Date marshalling and unmarshalling. Asserts that the value is
* same before and after marshalling/unmarshalling
*/
@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun v2JavaSdkUnixTimestampRoundtrip() {
// v2 sdk used currentTimeMillis(), instead we just hard code a value here
// otherwise that would be a JVM specific test since since we do not (yet) have
// a Kotlin MPP way of getting current timestamp. Also obviously not using epoch mill
// but instead just epoch sec. Spirit of the test is the same though
longArrayOf(1595016457, 1L, 0L)
.map { Instant.fromEpochSeconds(0, 0) }
.forEach { instant ->
val serverSpecificDateFormat: String = instant.format(TimestampFormat.EPOCH_SECONDS)
val parsed: Instant = parseEpoch(serverSpecificDateFormat)
assertEquals(instant.epochSeconds, parsed.epochSeconds)
}
}
for ((times, expectedDuration) in untilTests) {
val start = Instant.fromIso8601(times.first)
val end = Instant.fromIso8601(times.second)

// NOTE: There is additional set of edge case tests related to a past issue
// in DateUtilsTest.java in the v2 sdk. Specifically around
// issue 223: https://github.com/aws/aws-sdk-java/issues/233
//
// (1) - That issue is about round tripping values between SDK versions
// (2) - The input year in those tests is NOT valid and should never have
// been accepted by the parser.
assertEquals(expectedDuration, start.until(end))
assertEquals(end.until(start), -expectedDuration)
}
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testPlusMinusDuration() {
val start = Instant.fromEpochSeconds(1000, 1000)
Expand All @@ -275,7 +242,6 @@ class InstantTest {
assertEquals(Instant.fromEpochSeconds(990, 0), start - offset)
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
@Test
fun testRoundTripUtcOffset() {
// sanity check we only ever emit UTC timestamps (e.g. round trip a response with UTC offset)
Expand All @@ -293,27 +259,42 @@ class InstantTest {
assertEquals(test.second, actual, "test[$idx]: failed to format offset timestamp in UTC")
}
}
}

@IgnoreNative // FIXME Re-enable after Kotlin/Native implementation
// Select tests pulled from edge cases/tickets in the V2 Java SDK.
// Always good to learn from others...
class V2JavaSdkTests {
@Test
fun testUntil() {
val untilTests = mapOf(
("2013-01-01T00:00:00+00:00" to "2014-01-01T00:00:00+00:00") to 365.days,
("2020-01-01T00:00:00+00:00" to "2021-01-01T00:00:00+00:00") to 366.days, // leap year!
("2023-10-06T00:00:00+00:00" to "2023-10-06T00:00:00+00:00") to Duration.ZERO,
("2023-10-06T00:00:00+00:00" to "2023-10-07T00:00:00+00:00") to 1.days,
("2023-10-06T00:00:00+00:00" to "2023-10-06T01:00:00+00:00") to 1.hours,
("2023-10-06T00:00:00+00:00" to "2023-10-06T00:01:00+00:00") to 1.minutes,
("2023-10-06T00:00:00+00:00" to "2023-10-06T00:00:01+00:00") to 1.seconds,
("2023-10-06T00:00:00+00:00" to "2023-10-06T12:12:12+00:00") to 12.hours + 12.minutes + 12.seconds,
)

for ((times, expectedDuration) in untilTests) {
val start = Instant.fromIso8601(times.first)
val end = Instant.fromIso8601(times.second)
fun v2JavaSdkTt0031561767() {
val input = "Fri, 16 May 2014 23:56:46 GMT"
val instant: Instant = Instant.fromRfc5322(input)
assertEquals(input, instant.format(TimestampFormat.RFC_5322))
}

assertEquals(expectedDuration, start.until(end))
assertEquals(end.until(start), -expectedDuration)
}
/**
* Tests the Date marshalling and unmarshalling. Asserts that the value is
* same before and after marshalling/unmarshalling
*/
@Test
fun v2JavaSdkUnixTimestampRoundtrip() {
// v2 sdk used currentTimeMillis(), instead we just hard code a value here
// otherwise that would be a JVM specific test since since we do not (yet) have
// a Kotlin MPP way of getting current timestamp. Also obviously not using epoch mill
// but instead just epoch sec. Spirit of the test is the same though
longArrayOf(1595016457, 1L, 0L)
.map { Instant.fromEpochSeconds(0, 0) }
.forEach { instant ->
val serverSpecificDateFormat: String = instant.format(TimestampFormat.EPOCH_SECONDS)
val parsed: Instant = parseEpoch(serverSpecificDateFormat)
assertEquals(instant.epochSeconds, parsed.epochSeconds)
}
}

// NOTE: There is additional set of edge case tests related to a past issue
// in DateUtilsTest.java in the v2 sdk. Specifically around
// issue 223: https://github.com/aws/aws-sdk-java/issues/233
//
// (1) - That issue is about round tripping values between SDK versions
// (2) - The input year in those tests is NOT valid and should never have
// been accepted by the parser.
}
Loading

0 comments on commit 43e39b8

Please sign in to comment.