-
Notifications
You must be signed in to change notification settings - Fork 30
kn: Implement Instant using kotlinx.datetime.Instant
#1201
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
Changes from 4 commits
5cdf2dd
43a9dfb
4e1aab0
72fa04f
79b5a9a
1171ecb
34bb00b
2ddf062
27f9966
e5e739f
b92d4a2
86c1d96
04cc6f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ kotlin { | |
| nativeMain { | ||
| dependencies { | ||
| api(libs.crt.kotlin) | ||
| implementation(libs.kotlinx.datetime) | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nothing else changed, it's just restructured |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,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 | ||
| */ | ||
|
|
@@ -107,7 +106,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") | ||
|
||
| } | ||
| } | ||
| } | ||
|
|
@@ -214,42 +213,26 @@ class InstantTest { | |
| 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 { | ||
| @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 | ||
| */ | ||
| @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) | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
|
|
@@ -278,26 +261,42 @@ class InstantTest { | |
| assertEquals(test.second, actual, "test[$idx]: failed to format offset timestamp in UTC") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 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. | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| /* | ||
| * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package aws.smithy.kotlin.runtime.time | ||
|
|
||
| import kotlinx.datetime.LocalDate | ||
| import kotlinx.datetime.UtcOffset | ||
| import kotlinx.datetime.format.DateTimeComponents | ||
| import kotlinx.datetime.format.DayOfWeekNames | ||
| import kotlinx.datetime.format.MonthNames | ||
| import kotlinx.datetime.format.alternativeParsing | ||
| import kotlinx.datetime.format.char | ||
| import kotlinx.datetime.format.optional | ||
|
|
||
| /** | ||
| * [DateTimeFormat<DateTimeComponents>] for use with [kotlinx.datetime.Instant] | ||
| */ | ||
| internal object DateTimeFormats { | ||
|
|
||
| /** | ||
| * ISO8601, full precision. Corresponds to [TimestampFormat.ISO_8601_FULL]. Truncate to microseconds for [TimestampFormat.ISO_8601]. | ||
| * e.g. "2020-11-05T19:22:37+00:00" | ||
| */ | ||
| val ISO_8601 = DateTimeComponents.Format { | ||
| // Two possible date formats: YYYY-MM-DD or YYYYMMDD | ||
| alternativeParsing({ | ||
| date( | ||
| LocalDate.Format { | ||
| year() | ||
| monthNumber() | ||
| dayOfMonth() | ||
| }, | ||
| ) | ||
| }) { | ||
| date( | ||
| LocalDate.Format { | ||
| year() | ||
| char('-') | ||
| monthNumber() | ||
| char('-') | ||
| dayOfMonth() | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| char('T') | ||
|
|
||
| // Two possible time formats: HH:MM:SS or HHMMSS | ||
| alternativeParsing({ | ||
| hour() | ||
| minute() | ||
| second() | ||
| }) { | ||
| hour() | ||
| char(':') | ||
| minute() | ||
| char(':') | ||
| second() | ||
| } | ||
|
|
||
| // Fractional seconds | ||
| optional { | ||
| char('.') | ||
| secondFraction(1, 9) | ||
| } | ||
|
|
||
| // Offsets | ||
| alternativeParsing({ | ||
| offsetHours() | ||
| }) { | ||
| offset(UtcOffset.Formats.ISO) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * ISO8601 condensed. Corresponds to [TimestampFormat.ISO_8601_CONDENSED]. | ||
| */ | ||
| val ISO_8601_CONDENSED = DateTimeComponents.Format { | ||
| year() | ||
| monthNumber() | ||
| dayOfMonth() | ||
|
|
||
| char('T') | ||
| hour() | ||
| minute() | ||
| second() | ||
| char('Z') | ||
| } | ||
|
|
||
| /** | ||
| * ISO8601 condensed, date only. Corresponds to [TimestampFormat.ISO_8601_CONDENSED_DATE] | ||
| */ | ||
| val ISO_8601_CONDENSED_DATE = DateTimeComponents.Format { | ||
| year() | ||
| monthNumber() | ||
| dayOfMonth() | ||
| } | ||
|
|
||
| /** | ||
| * [RFC-5322/2822/822 IMF timestamp](https://tools.ietf.org/html/rfc5322). Corresponds to [TimestampFormat.RFC_5322]. | ||
| * e.g. "Thu, 05 Nov 2020 19:22:37 +0000" | ||
| */ | ||
| val RFC_5322 = DateTimeComponents.Format { | ||
| dayOfWeek(DayOfWeekNames.ENGLISH_ABBREVIATED) | ||
| chars(", ") | ||
|
|
||
| dayOfMonth() | ||
| char(' ') | ||
| monthName(MonthNames.ENGLISH_ABBREVIATED) | ||
| char(' ') | ||
| year() | ||
| char(' ') | ||
|
|
||
| hour() | ||
| char(':') | ||
| minute() | ||
| char(':') | ||
| second() | ||
| char(' ') | ||
|
|
||
| optional("GMT") { | ||
| offset(UtcOffset.Formats.FOUR_DIGITS) | ||
| } | ||
| } | ||
| } |
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.
Comment: Oh jeez, good catch. 🤦♂️